Compare commits
81 Commits
zig-0.14
...
0.16-redux
| Author | SHA1 | Date | |
|---|---|---|---|
| 578911ba67 | |||
| 56ad79ba94 | |||
| 2cb0863cc1 | |||
| 2c47c7492e | |||
| 4b2b7021c7 | |||
| a1b9828578 | |||
| 8e4661c4c6 | |||
| 54aaf30ea5 | |||
| df22cf6529 | |||
| 6b5c830234 | |||
| 116234cf9c | |||
| 4601e8f323 | |||
| 50cae8b63d | |||
| 8b8c9a772f | |||
| f563648b20 | |||
| 8d4f3d72f8 | |||
| 0b6129f1ae | |||
| 7308faad36 | |||
| c9499251f8 | |||
| d470ca98e3 | |||
| a606f5e11a | |||
| a4e23a840d | |||
| 3a10572953 | |||
| edfe919c1b | |||
| 4515610082 | |||
| beca6a2ae6 | |||
| 760e11df0b | |||
| 5f629df47c | |||
| b0160e005b | |||
| b7b99325da | |||
| b22e4d003d | |||
| a41c37fef4 | |||
| 2d079d77f7 | |||
| 48f4235875 | |||
| 567dea8a0b | |||
| f78e5c7386 | |||
| 81e975c0d9 | |||
| fd274a8072 | |||
| 50ae79637e | |||
| ee41dc7278 | |||
| c34acebf51 | |||
| bdbda29d39 | |||
| ca4e867ddc | |||
| b066835066 | |||
| 2363bd7d10 | |||
| e619005b77 | |||
| 28b44891b3 | |||
| 4829c802a3 | |||
| 570db9632a | |||
| 0076294675 | |||
| fd9e3d595b | |||
| b8189490eb | |||
| 6adc1d5c0c | |||
| 5ec12b5786 | |||
| b892adacd7 | |||
| 2760ad6ccb | |||
| 61311433b9 | |||
| 053d64a954 | |||
| 0e0222cd02 | |||
| 9c0dfbadc2 | |||
| db2fb4b9f2 | |||
| 067eaa87c2 | |||
| b64a3ec44a | |||
| 704215e1a9 | |||
| bcfd983f8d | |||
| 75502da1d0 | |||
| a316ba569f | |||
| a0f3f45885 | |||
| f771ef7623 | |||
| 0d2576f5ee | |||
| a76803aad1 | |||
| 1ff1e91d5e | |||
| 2bcbc16613 | |||
| 3c98cf2cdb | |||
| 2c392cf250 | |||
| 5d4e7b1435 | |||
| 7aed59b5b1 | |||
| f3fb8a128f | |||
| 23bb19644b | |||
| ed14f13d9a | |||
| 428f938c3a |
@@ -0,0 +1,31 @@
|
||||
name: Release Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- name: Install deps
|
||||
run: sudo apt update && sudo apt install -y liblzma-dev liblzo2-dev
|
||||
- name: Build normal version
|
||||
run: zig build --release=fast -Duse_zig_decomp=true -Dversion=${{ github.ref_name }}
|
||||
- name: Move zig build out
|
||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-zig-libs
|
||||
- name: Rebuild with C libraries
|
||||
run: zig build --release=fast -Dversion="${{ github.ref_name }}"
|
||||
- name: Move C build out
|
||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: true
|
||||
files: |
|
||||
unsquashfs-x86_64-zig-libs
|
||||
unsquashfs-x86_64-c-libs
|
||||
@@ -1,2 +1,5 @@
|
||||
testing/
|
||||
|
||||
.zig-cache/
|
||||
zig-out/
|
||||
zig-pkg/
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Project-local debug tasks
|
||||
//
|
||||
// For more documentation on how to configure debug tasks,
|
||||
// see: https://zed.dev/docs/debugger
|
||||
[
|
||||
{
|
||||
"label": "Build & Run",
|
||||
|
||||
"adapter": "CodeLLDB",
|
||||
"request": "launch",
|
||||
|
||||
"build": {
|
||||
"command": "zig",
|
||||
"args": ["build", "-Ddebug=true"],
|
||||
},
|
||||
|
||||
"program": "zig-out/bin/unsquashfs",
|
||||
"args": [
|
||||
"--force",
|
||||
"-d",
|
||||
"testing/TestExtractUnsquashfs",
|
||||
"testing/LinuxPATest.sfs",
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Caleb Gardner
|
||||
Copyright (c) 2026 Caleb Gardner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# zig-squashfs
|
||||
|
||||
This is my experiments to learn Zig. Might amount to something. Might not.
|
||||
|
||||
A library and application to decompress or view squashfs archives.
|
||||
|
||||
## Current State
|
||||
|
||||
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
||||
|
||||
## Build options
|
||||
|
||||
> `-Duse_c_libs=true`
|
||||
|
||||
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
||||
|
||||
> `-Dallow_lzo=true`
|
||||
|
||||
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
||||
|
||||
> `-Ddebug=true`
|
||||
|
||||
Sets various build options that make debugging easier. Specifically, debug optimization is forced, valgrind support is enabled, error tracing is enabled, stipping is disabled, and copmilation uses LLVM (this is due to some linking issues when on Debug optimization and is required for debugging tools such as `lldb`. In the future this may be removed from the debug flag).
|
||||
|
||||
> `-Dversion=0.0.0`
|
||||
|
||||
Sets the version of `unsquashfs` shown when `--version` is passed.
|
||||
|
||||
## Capabilities
|
||||
|
||||
Most features are present except for the following:
|
||||
|
||||
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
|
||||
|
||||
## Performance
|
||||
|
||||
This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) and with `--release=fast`.
|
||||
|
||||
Currently, my only performance checks are checking execution time, nothing deeper.
|
||||
|
||||
* Under ideal circumstances, my library is ~70% slower (.12s vs .20s).
|
||||
* Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances.
|
||||
* Performance improvements/regressions will be common. I'm still learning Zig.
|
||||
|
||||
Example Times:
|
||||
|
||||
* *unsquashfs, multi-threaded*: .12s
|
||||
* *unsquashfs, single-threaded*: .13s
|
||||
* *C-libs, single-threaded*: .45s
|
||||
* *C-libs, multi-threaded*: .20s
|
||||
* *Zig-libs, single-threaded*: 5.78s
|
||||
* *Zig-libs, multi-threaded*: 1.08s
|
||||
|
||||
## Build considerations
|
||||
|
||||
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
|
||||
|
||||
```zig
|
||||
pub const lzo_bytep = @compileError("unable to translate C expr: unexpected token ''");
|
||||
// /usr/include/lzo/lzoconf.h:148:9
|
||||
pub const lzo_charp = @compileError("unable to translate C expr: unexpected token ''");
|
||||
// /usr/include/lzo/lzoconf.h:149:9
|
||||
pub const lzo_voidp = @compileError("unable to translate C expr: unexpected token ''");
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```zig
|
||||
pub const lzo_bytep = *u8;
|
||||
// /usr/include/lzo/lzoconf.h:148:9
|
||||
pub const lzo_charp = @compileError("unable to translate C expr: unexpected token ''");
|
||||
// /usr/include/lzo/lzoconf.h:149:9
|
||||
pub const lzo_voidp = ?*anyopaque;
|
||||
```
|
||||
@@ -0,0 +1,125 @@
|
||||
const std = @import("std");
|
||||
const Build = std.Build;
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse false;
|
||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo decompression support.") orelse false;
|
||||
const dynamic = b.option(bool, "dynamic", "Dynamic link C decompression libraries.") orelse false;
|
||||
const debug = b.option(bool, "debug", "Enable options to make debugging easier.") orelse false;
|
||||
const version_string = b.option([]const u8, "version", "Version of the library/binary") orelse "0.0.0-testing";
|
||||
|
||||
const version: std.SemanticVersion = try .parse(version_string);
|
||||
|
||||
const zig_squashfs_options = b.addOptions();
|
||||
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
|
||||
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const c = b.addTranslateC(.{
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/c.h"),
|
||||
});
|
||||
if (allow_lzo)
|
||||
c.defineCMacro("ALLOW_LZO", null);
|
||||
if (dynamic) {
|
||||
c.linkSystemLibrary("z", .{});
|
||||
c.linkSystemLibrary("lzma", .{});
|
||||
c.linkSystemLibrary("lz4", .{});
|
||||
c.linkSystemLibrary("zstd", .{});
|
||||
if (allow_lzo)
|
||||
c.linkSystemLibrary("minilzo", .{});
|
||||
}
|
||||
|
||||
const deps = try getDependencies(b, optimize, target, allow_lzo);
|
||||
|
||||
const lib = b.addLibrary(.{
|
||||
.name = "squashfs",
|
||||
.use_llvm = debug,
|
||||
.version = version,
|
||||
.root_module = b.createModule(.{
|
||||
.imports = &.{
|
||||
.{ .name = "options", .module = zig_squashfs_options.createModule() },
|
||||
.{ .name = "c", .module = c.createModule() },
|
||||
},
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.valgrind = debug,
|
||||
}),
|
||||
});
|
||||
|
||||
for (deps) |d|
|
||||
lib.root_module.linkLibrary(d);
|
||||
|
||||
b.installArtifact(lib);
|
||||
|
||||
const exe_config = b.addOptions();
|
||||
exe_config.addOption(std.SemanticVersion,"version", version);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "unsquashfs",
|
||||
.use_llvm = debug,
|
||||
.version = version,
|
||||
.root_module = b.createModule(.{
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||
.valgrind = debug,
|
||||
.imports = &.{
|
||||
.{ .name = "config", .module = exe_config.createModule() },
|
||||
.{ .name = "squashfs", .module = lib.root_module }
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const lib_test = b.addTest(.{
|
||||
.name = "squashfs-test",
|
||||
.root_module = lib.root_module,
|
||||
});
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&lib_test.step);
|
||||
|
||||
// zls check step
|
||||
const lib_check = b.addLibrary(.{
|
||||
.name = "squashfs-check",
|
||||
.root_module = lib.root_module,
|
||||
});
|
||||
const exe_check = b.addLibrary(.{
|
||||
.name = "unsquashfs-check",
|
||||
.root_module = exe.root_module,
|
||||
});
|
||||
|
||||
const check = b.step("check", "Check if squashfs compiles");
|
||||
check.dependOn(&lib_check.step);
|
||||
check.dependOn(&exe_check.step);
|
||||
}
|
||||
|
||||
fn getDependencies(b: *Build, optimize: std.builtin.OptimizeMode, target: Build.ResolvedTarget, allow_lzo: bool) ![]*Build.Step.Compile {
|
||||
const alloc = b.allocator;
|
||||
|
||||
var list: std.ArrayList(*Build.Step.Compile) = .empty;
|
||||
|
||||
const zlib_ng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target });
|
||||
try list.append(alloc, zlib_ng.artifact("zng"));
|
||||
|
||||
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
|
||||
try list.append(alloc, xz.artifact("lzma"));
|
||||
|
||||
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
|
||||
try list.append(alloc, lz4.artifact("lz4"));
|
||||
|
||||
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
|
||||
try list.append(alloc, zstd.artifact("zstd"));
|
||||
|
||||
if (allow_lzo) {
|
||||
const minilzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
|
||||
try list.append(alloc, minilzo.artifact("minilzo"));
|
||||
}
|
||||
|
||||
return list.toOwnedSlice(b.allocator);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
.{
|
||||
.name = .squashfs,
|
||||
.version = "0.0.6",
|
||||
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.dependencies = .{
|
||||
.zlib_ng = .{
|
||||
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133",
|
||||
.hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa",
|
||||
},
|
||||
.zstd = .{
|
||||
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3",
|
||||
.hash = "zstd-1.5.7-1-KEItkAMwAAD6OKY3m0OOmXG7aL-aLUfrDqbP5J5oYapU",
|
||||
},
|
||||
.lz4 = .{
|
||||
.url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102",
|
||||
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
|
||||
},
|
||||
.minilzo = .{
|
||||
.url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90",
|
||||
.hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP",
|
||||
},
|
||||
.xz = .{
|
||||
.url = "git+https://github.com/akunaakwei/zig-xz.git#e2d389262c8291907e3e4c6fb119819141c16c0f",
|
||||
.hash = "xz-5.8.2-6v47_JYeAABSL-jonprpL5-E_YaaGc4B5xrbe93WsJ3G",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
},
|
||||
}
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
zig test \
|
||||
-lc \
|
||||
-lz \
|
||||
-llzma \
|
||||
-lminilzo \
|
||||
-llz4 \
|
||||
-lzstd \
|
||||
src/test.zig
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = Io.File;
|
||||
const MemoryMap = File.MemoryMap;
|
||||
|
||||
const Decomp = @import("decomp.zig");
|
||||
const DecompCache = @import("decomp_cache.zig");
|
||||
const Extract = @import("extract.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const Inode = @import("inode.zig");
|
||||
const SfsFile = @import("file.zig");
|
||||
|
||||
const Archive = @This();
|
||||
|
||||
const CACHE_MEM_MAX = 1024 * 1024 * 1024;
|
||||
|
||||
super: Superblock,
|
||||
|
||||
cache: DecompCache,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, io: Io, fil: File) !Archive {
|
||||
return initAdvanced(alloc, io, fil, 0, 0);
|
||||
}
|
||||
pub fn initAdvanced(alloc: std.mem.Allocator, io: Io, fil: File, offset: u64, cache_memory_max: u64) !Archive {
|
||||
var rdr = fil.reader(io, &[0]u8{});
|
||||
try rdr.seekTo(offset);
|
||||
var super: Superblock = undefined;
|
||||
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
|
||||
try super.validate();
|
||||
|
||||
const map = try fil.createMemoryMap(io, .{
|
||||
.offset = offset,
|
||||
.len = super.size,
|
||||
.protection = .{ .read = true },
|
||||
});
|
||||
|
||||
return .{
|
||||
.super = super,
|
||||
|
||||
.cache = try .init(
|
||||
alloc,
|
||||
map,
|
||||
super.compression,
|
||||
if (cache_memory_max != 0)
|
||||
cache_memory_max
|
||||
else
|
||||
@min(CACHE_MEM_MAX, (try std.process.totalSystemMemory()) / 2),
|
||||
),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *Archive, io: Io) void {
|
||||
self.cache.deinit(io);
|
||||
}
|
||||
|
||||
pub fn root(self: *Archive, alloc: std.mem.Allocator, io: Io) !SfsFile {
|
||||
const inode: Inode = try .initRef(
|
||||
alloc,
|
||||
io,
|
||||
&self.cache,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
self.super.root_ref,
|
||||
);
|
||||
return .init(alloc, self, inode, "");
|
||||
}
|
||||
pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile {
|
||||
const path = std.mem.trim(u8, filepath, "/");
|
||||
|
||||
var root_file = try self.root(alloc, io);
|
||||
|
||||
if (path.len == 0 or path[0] == '.') return root_file;
|
||||
|
||||
defer root_file.deinit();
|
||||
return root_file.open(alloc, io, filepath);
|
||||
}
|
||||
|
||||
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {
|
||||
const root_inode: Inode = try .initRef(
|
||||
alloc,
|
||||
io,
|
||||
&self.cache,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
self.super.root_ref,
|
||||
);
|
||||
return Extract.extract(alloc, io, root_inode, &self.cache, self.super, ext_loc, options);
|
||||
}
|
||||
|
||||
// Superblock
|
||||
|
||||
pub const Superblock = extern struct {
|
||||
magic: u32,
|
||||
inode_count: u32,
|
||||
mod_time: u32,
|
||||
block_size: u32,
|
||||
frag_count: u32,
|
||||
compression: Decomp.Enum,
|
||||
block_log: u16,
|
||||
flags: packed struct(u16) {
|
||||
inode_uncompressed: bool,
|
||||
data_uncompressed: bool,
|
||||
check: bool,
|
||||
frag_uncompressed: bool,
|
||||
frag_never: bool,
|
||||
frag_always: bool,
|
||||
de_dupe: bool,
|
||||
exportable: bool,
|
||||
xattr_uncompressed: bool,
|
||||
xattr_never: bool,
|
||||
compression_options: bool,
|
||||
id_uncompressed: bool,
|
||||
_: u4,
|
||||
},
|
||||
id_count: u16,
|
||||
ver_maj: u16,
|
||||
ver_min: u16,
|
||||
root_ref: Inode.Ref,
|
||||
size: u64,
|
||||
id_start: u64,
|
||||
xattr_start: u64,
|
||||
inode_start: u64,
|
||||
dir_start: u64,
|
||||
frag_start: u64,
|
||||
export_start: u64,
|
||||
|
||||
pub fn validate(self: Superblock) !void {
|
||||
if (self.magic != std.mem.readInt(u32, "hsqs", .little))
|
||||
return error.BadMagic;
|
||||
if (self.ver_maj != 4 or self.ver_min != 0)
|
||||
return error.InvalidVersion;
|
||||
if (self.block_log != std.math.log2(self.block_size))
|
||||
return error.BadBlockLog;
|
||||
if (self.flags.check)
|
||||
return error.BadCheckFlag;
|
||||
}
|
||||
};
|
||||
|
||||
// Test
|
||||
|
||||
const TestArchive = "testing/LinuxPATest.sfs";
|
||||
|
||||
test "Basics" {
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer archive_file.close(io);
|
||||
var arc: Archive = try .init(alloc, io, archive_file);
|
||||
defer arc.deinit(io);
|
||||
|
||||
var root_file = try arc.root(alloc, io);
|
||||
defer root_file.deinit();
|
||||
}
|
||||
|
||||
const TestFile = "Start.exe";
|
||||
const TestFileExtractLocation = "testing/Start.exe";
|
||||
|
||||
test "SingleFileExtraction" {
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer archive_file.close(io);
|
||||
var arc: Archive = try .init(alloc, io, archive_file);
|
||||
defer arc.deinit(io);
|
||||
|
||||
var ext_file = try arc.open(alloc, io, TestFile);
|
||||
defer ext_file.deinit();
|
||||
|
||||
try ext_file.extract(alloc, io, TestFileExtractLocation, .default);
|
||||
}
|
||||
|
||||
const TestFullExtractLocation = "testing/TestExtract";
|
||||
|
||||
test "FullExtraction" {
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer archive_file.close(io);
|
||||
var arc: Archive = try .init(alloc, io, archive_file);
|
||||
defer arc.deinit(io);
|
||||
|
||||
try arc.extract(alloc, io, TestFullExtractLocation, .default);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Writer = Io.Writer;
|
||||
const File = Io.File;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const config = @import("config");
|
||||
const squashfs = @import("squashfs");
|
||||
|
||||
//TODO: Add more options
|
||||
const help_mgs =
|
||||
\\
|
||||
\\Usage: unsquashfs [options] <archive>
|
||||
\\
|
||||
\\Options:
|
||||
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
||||
\\
|
||||
\\ -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.
|
||||
\\ -v Verbose
|
||||
\\
|
||||
\\ --force Force extraction. If the destination already exists, it will be deleted.
|
||||
\\
|
||||
\\ --help Display this messages
|
||||
\\ --version Display the version
|
||||
\\
|
||||
;
|
||||
|
||||
const errors = error{InvalidArguments};
|
||||
|
||||
var archive: []const u8 = "";
|
||||
var extLoc: []const u8 = "squashfs-root";
|
||||
var offset: u64 = 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(init: std.process.Init) !void {
|
||||
const alloc = init.gpa;
|
||||
const io = init.io;
|
||||
|
||||
var stdout = File.stdout();
|
||||
var out = stdout.writer(io, &[0]u8{});
|
||||
defer out.interface.flush() catch {};
|
||||
try handleArgs(init.minimal.args, &out.interface);
|
||||
if (archive.len == 0) {
|
||||
try out.interface.print("You must provide a squashfs archive\n", .{});
|
||||
try out.interface.print(help_mgs, .{});
|
||||
return;
|
||||
}
|
||||
var fil: File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
||||
defer fil.close(io);
|
||||
var arc: squashfs.Archive = try .initAdvanced(alloc, io, fil, offset, 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||
defer arc.deinit(io);
|
||||
const options: squashfs.ExtractionOptions = .{
|
||||
.verbose = verbose,
|
||||
.verbose_writer = if (verbose) &out.interface else null,
|
||||
.ignore_xattr = ignore_xattrs,
|
||||
.ignore_permissions = ignore_permissions,
|
||||
};
|
||||
if (force)
|
||||
try Io.Dir.cwd().deleteTree(io, extLoc);
|
||||
try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
||||
}
|
||||
|
||||
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
||||
var arg_iter = args.iterate();
|
||||
defer arg_iter.deinit();
|
||||
_ = arg_iter.skip(); // args[0] is the application launch command.
|
||||
while (arg_iter.next()) |arg| {
|
||||
if (std.mem.eql(u8, arg, "-o")) {
|
||||
const nxt = arg_iter.next();
|
||||
if (nxt == null or nxt.?.len == 0) {
|
||||
try out.print("-o must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
offset = std.fmt.parseInt(u64, nxt.?, 10) catch {
|
||||
try out.print("-o must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
};
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, arg, "-d")) {
|
||||
const nxt = arg_iter.next();
|
||||
if (nxt == null or nxt.?.len == 0) {
|
||||
try out.print("-d must be followed by a location\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
extLoc = nxt.?;
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
||||
const nxt = arg_iter.next();
|
||||
if (nxt == null or nxt.?.len == 0) {
|
||||
try out.print("-p must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
threads = std.fmt.parseInt(u32, nxt.?, 10) catch {
|
||||
try out.print("-p must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
};
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, arg, "-v")) {
|
||||
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")) {
|
||||
try out.print("zig-unsquashfs v", .{});
|
||||
try config.version.format(out);
|
||||
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
|
||||
std.process.exit(0);
|
||||
return;
|
||||
} else if (std.mem.eql(u8, arg, "--help")) {
|
||||
try out.print(help_mgs, .{});
|
||||
std.process.exit(0);
|
||||
return;
|
||||
}
|
||||
if (archive.len > 0) {
|
||||
try out.print("you can only provide one file at a time\n", .{});
|
||||
try out.print(help_mgs, .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
archive = arg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <zlib.h>
|
||||
#include <lzma.h>
|
||||
#include <lz4.h>
|
||||
#include <zstd.h>
|
||||
#ifdef ALLOW_LZO
|
||||
#include <lzo/minilzo.h>
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
const std = @import("std");
|
||||
|
||||
const c = @import("c");
|
||||
|
||||
const Error = @import("decomp.zig").Error;
|
||||
|
||||
pub fn zlibDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var strem: c.z_stream = .{
|
||||
.next_in = in.ptr,
|
||||
.avail_in = @truncate(in.len),
|
||||
.next_out = out.ptr,
|
||||
.avail_out = @truncate(out.len),
|
||||
};
|
||||
var res = c.inflateInit(&strem);
|
||||
if (res != c.Z_OK) return Error.ReadFailed;
|
||||
defer _ = c.inflateEnd(&strem);
|
||||
|
||||
res = c.inflate(&strem, c.Z_FULL_FLUSH);
|
||||
if (res != c.Z_OK) return Error.ReadFailed;
|
||||
|
||||
return strem.total_out;
|
||||
}
|
||||
pub fn lzmaDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var strem: c.lzma_stream = .{
|
||||
.next_in = in.ptr,
|
||||
.avail_in = in.len,
|
||||
.next_out = out.ptr,
|
||||
.avail_out = out.len,
|
||||
};
|
||||
var res = c.lzma_auto_decoder(&strem, out.len * 2, 0);
|
||||
if (res != c.LZMA_OK) return Error.ReadFailed;
|
||||
defer c.lzma_end(&strem);
|
||||
|
||||
while (res == c.LZMA_OK)
|
||||
res = c.lzma_code(&strem, c.LZMA_RUN);
|
||||
if (res != c.LZMA_FINISH) return Error.ReadFailed;
|
||||
|
||||
return strem.total_out;
|
||||
}
|
||||
pub fn lzoDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var out_len = out.len;
|
||||
const res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null);
|
||||
if (res != c.LZO_E_OK) return Error.ReadFailed;
|
||||
return out_len;
|
||||
}
|
||||
pub fn lz4Decompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
const res = c.LZ4_decompress_safe(
|
||||
in.ptr,
|
||||
out.ptr,
|
||||
@bitCast(@as(u32, @truncate(in.len))),
|
||||
@bitCast(@as(u32, @truncate(out.len))),
|
||||
);
|
||||
if (res < 0) return Error.ReadFailed;
|
||||
return @abs(res);
|
||||
}
|
||||
pub fn zstdDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
|
||||
if (c.ZSTD_isError(res) != 0)
|
||||
return Error.ReadFailed;
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
|
||||
const DecompCache = @import("../decomp_cache.zig");
|
||||
const DataBlock = @import("../inode.zig").DataBlock;
|
||||
|
||||
const Extractor = @This();
|
||||
|
||||
cache: *DecompCache,
|
||||
block_size: u32,
|
||||
|
||||
start: u64,
|
||||
size: u64,
|
||||
blocks: []DataBlock,
|
||||
|
||||
frag_data: ?[]u8 = null,
|
||||
frag_offset: u32 = 0,
|
||||
|
||||
pub fn init(cache: *DecompCache, block_size: u32, size: u64, start: u64, blocks: []DataBlock) Extractor {
|
||||
return .{
|
||||
.cache = cache,
|
||||
.block_size = block_size,
|
||||
|
||||
.start = start,
|
||||
.size = size,
|
||||
.blocks = blocks,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addFragment(self: *Extractor, data: []u8, offset: u32) void {
|
||||
self.frag_data = data;
|
||||
self.frag_offset = offset;
|
||||
}
|
||||
|
||||
pub fn asyncExtract(self: Extractor, io: Io, fil: Io.File) Error!void {
|
||||
try fil.writePositionalAll(io, &.{&.{0}}, self.size - 1);
|
||||
|
||||
var map = try fil.createMemoryMap(io, .{ .len = self.size, .protection = .{ .write = true } });
|
||||
defer map.destroy(io);
|
||||
|
||||
var group: Io.Group = .init;
|
||||
defer group.cancel(io);
|
||||
|
||||
var ret_err: ?Error = null;
|
||||
|
||||
var offset = self.start;
|
||||
for (0..self.blocks.len) |i| {
|
||||
group.async(io, blockThread, .{ self, io, map, offset, i, &ret_err });
|
||||
|
||||
offset += self.blocks[i].size;
|
||||
}
|
||||
if (self.frag_data != null)
|
||||
group.async(io, fragThread, .{ self, map });
|
||||
|
||||
group.await(io) catch |err| return ret_err orelse err;
|
||||
|
||||
try map.write(io);
|
||||
}
|
||||
|
||||
fn blockThread(self: Extractor, io: Io, map: Io.File.MemoryMap, read_offset: u64, idx: usize, ret_err: *?Error) error{Canceled}!void {
|
||||
const write_pos = idx * self.block_size;
|
||||
const size = if (self.frag_data == null and idx == self.block_size.len - 1)
|
||||
self.size % self.block_size
|
||||
else
|
||||
self.block_size;
|
||||
const block = self.blocks[idx];
|
||||
|
||||
if (block.size == 0) {
|
||||
@memset(map.memory[write_pos..][0..size], 0);
|
||||
return;
|
||||
}
|
||||
if (block.uncompressed) {
|
||||
@memcpy(map[write_pos..][0..size], self.cache.map.memory[read_offset..][0..size]);
|
||||
return;
|
||||
}
|
||||
const data = self.cache.get(io, read_offset, block.size, size) catch |err| switch (err) {
|
||||
error.Canceled => {
|
||||
io.recancel();
|
||||
return error.Canceled;
|
||||
},
|
||||
else => |e| {
|
||||
ret_err.* = e;
|
||||
return error.Canceled;
|
||||
},
|
||||
};
|
||||
defer self.cache.finished(io, read_offset);
|
||||
if (data.len != size) {
|
||||
std.debug.print("Size of decompression at {} is {} and should be {}\n", .{ read_offset, data.len, size });
|
||||
return Error.BadDecompressionSize;
|
||||
}
|
||||
@memcpy(map[write_pos..][0..size], data);
|
||||
}
|
||||
fn fragThread(self: Extractor, map: Io.File.MemoryMap) error{Canceled}!void {
|
||||
const write_pos = self.blocks.len * self.block_size;
|
||||
const size = self.size % self.block_size;
|
||||
|
||||
@memcpy(map.memory[write_pos..][0..size], self.frag_data.?[self.frag_offset..][0..size]);
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
pub const Error = error{BadDecompressionSize} || Io.File.WritePositionalError || Io.File.MemoryMap.CreateError;
|
||||
@@ -0,0 +1,151 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
|
||||
const DecompCache = @import("../decomp_cache.zig");
|
||||
const DataBlock = @import("../inode.zig").DataBlock;
|
||||
|
||||
const Reader = @This();
|
||||
|
||||
io: Io,
|
||||
|
||||
cache: *DecompCache,
|
||||
block_size: u32,
|
||||
|
||||
size: u64,
|
||||
blocks: []DataBlock,
|
||||
|
||||
frag_data: ?[]u8 = null,
|
||||
frag_offset: u32 = 0,
|
||||
|
||||
cur_offset: u64 = 0,
|
||||
next_offset: u64,
|
||||
|
||||
idx: u32 = 0,
|
||||
cur_block_sparse: bool = false,
|
||||
|
||||
interface: Io.Reader = .{
|
||||
.buffer = &[0]u8{},
|
||||
.end = 0,
|
||||
.seek = 0,
|
||||
.vtable = &.{
|
||||
.stream = stream,
|
||||
.discard = discard,
|
||||
.readVec = readVec,
|
||||
},
|
||||
},
|
||||
|
||||
pub fn init(io: Io, cache: *DecompCache, block_size: u32, size: u64, start: u64, blocks: []DataBlock) Reader {
|
||||
return .{
|
||||
.io = io,
|
||||
|
||||
.cache = cache,
|
||||
.block_size = block_size,
|
||||
|
||||
.size = size,
|
||||
.blocks = blocks,
|
||||
|
||||
.next_offset = start,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: Reader) void {
|
||||
self.cache.finished(self.io);
|
||||
}
|
||||
|
||||
pub fn addFragment(self: *Reader, data: []u8, offset: u32) void {
|
||||
self.frag_data = data;
|
||||
self.frag_offset = offset;
|
||||
}
|
||||
|
||||
fn advance(self: *Reader) Io.Reader.Error!void {
|
||||
errdefer self.interface.end = 0;
|
||||
self.interface.seek = 0;
|
||||
|
||||
if (self.idx > self.blocks.len) return error.EndOfStream;
|
||||
defer self.idx += 1;
|
||||
self.cache.finished(self.io, self.cur_offset);
|
||||
|
||||
if (self.idx == self.blocks.len) {
|
||||
if (self.frag_data == null) return error.EndOfStream;
|
||||
self.cur_offset = 0;
|
||||
|
||||
const size = self.size % self.block_size;
|
||||
self.interface.buffer = self.frag_data.?[self.frag_offset..][0..size];
|
||||
self.interface.end = size;
|
||||
return;
|
||||
}
|
||||
|
||||
const block = self.blocks[self.idx];
|
||||
|
||||
const size = if (self.idx == self.blocks.len - 1 and self.frag_data == null)
|
||||
self.size % self.block_size
|
||||
else
|
||||
self.block_size;
|
||||
|
||||
if (block.size == 0) {
|
||||
self.interface.buffer = &[0]u8{};
|
||||
self.cur_block_sparse = true;
|
||||
self.interface.end = size;
|
||||
return;
|
||||
} else {
|
||||
self.cur_block_sparse = false;
|
||||
}
|
||||
|
||||
self.cur_offset = self.next_offset;
|
||||
self.next_offset = self.cur_offset + block.size;
|
||||
|
||||
if (block.uncompressed) {
|
||||
self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..size];
|
||||
self.interface.end = size;
|
||||
return;
|
||||
}
|
||||
const data = self.cache.get(self.io, self.cur_offset, block.size, size);
|
||||
if (data.len != size) {
|
||||
std.debug.print("Size of decompression at {} is {} and should be {}\n", .{ self.cur_offset, data.len, size });
|
||||
return Io.Reader.Error.ReadFailed;
|
||||
}
|
||||
self.interface.buffer = data;
|
||||
self.interface.end = size;
|
||||
}
|
||||
|
||||
fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
||||
const self: *Reader = @fieldParentPtr("interface", r);
|
||||
if (r.seek >= r.end) {
|
||||
try self.advance();
|
||||
}
|
||||
const to_write = @min(@intFromEnum(limit), r.end - r.seek);
|
||||
const wrote = try if (self.cur_block_sparse)
|
||||
w.splatByte(0, to_write)
|
||||
else
|
||||
w.write(r.buffer[r.seek..][0..to_write]);
|
||||
r.seek += wrote;
|
||||
return wrote;
|
||||
}
|
||||
fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
|
||||
if (r.seek >= r.end) {
|
||||
const self: *Reader = @fieldParentPtr("interface", r);
|
||||
try self.advance();
|
||||
}
|
||||
const to_discard = @min(@intFromEnum(limit), r.end - r.seek);
|
||||
r.seek += to_discard;
|
||||
return to_discard;
|
||||
}
|
||||
fn readVec(r: *Io.Reader, vec: [][]u8) Io.Reader.Error!usize {
|
||||
const self: *Reader = @fieldParentPtr("interface", r);
|
||||
if (r.seek >= r.end) {
|
||||
try self.advance();
|
||||
}
|
||||
var total: usize = 0;
|
||||
for (vec) |v| {
|
||||
const to_copy = @min(v.len, r.end - r.seek);
|
||||
if (self.cur_block_sparse) {
|
||||
@memset(v[0..to_copy], 0);
|
||||
} else {
|
||||
@memcpy(v[0..to_copy], r.buffer[r.seek..][0..to_copy]);
|
||||
}
|
||||
total += to_copy;
|
||||
r.seek += to_copy;
|
||||
|
||||
if (r.seek >= r.end) break;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
const std = @import("std");
|
||||
|
||||
const options = @import("options");
|
||||
|
||||
const c_decomp = @import("c_decomp.zig");
|
||||
const zig_decomp = @import("zig_decomp.zig");
|
||||
|
||||
pub const Error = error{} || std.Io.Reader.UnlimitedAllocError;
|
||||
|
||||
pub const Enum = enum(u16) {
|
||||
zlib = 1,
|
||||
lzma,
|
||||
lzo,
|
||||
xz,
|
||||
lz4,
|
||||
zstd,
|
||||
};
|
||||
|
||||
pub const Fn = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize;
|
||||
|
||||
pub fn DecompFn(comp: Enum) !Fn {
|
||||
return if (options.use_zig_decomp)
|
||||
switch (comp) {
|
||||
.zlib => zig_decomp.zlibDecompress,
|
||||
.lzma => zig_decomp.lzmaDecompress,
|
||||
.xz => zig_decomp.xzDecompress,
|
||||
.zstd => zig_decomp.zstdDecompress,
|
||||
.lz4 => error.Lz4Unsupported,
|
||||
.lzo => error.LzoUnsupported,
|
||||
}
|
||||
else switch (comp) {
|
||||
.zlib => c_decomp.zlibDecompress,
|
||||
.lzma, .xz => c_decomp.lzmaDecompress,
|
||||
.zstd => c_decomp.zstdDecompress,
|
||||
.lz4 => c_decomp.lz4Decompress,
|
||||
.lzo => if (options.allow_lzo)
|
||||
c_decomp.zstdDecompress
|
||||
else
|
||||
error.LzoUnsupported,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = Io.File;
|
||||
const MemoryMap = File.MemoryMap;
|
||||
const Atomic = std.atomic.Value;
|
||||
|
||||
const Decomp = @import("decomp.zig");
|
||||
|
||||
const DecompCache = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
map: MemoryMap,
|
||||
decomp_fn: Decomp.Fn,
|
||||
|
||||
cache: std.AutoHashMap(u64, Cache),
|
||||
mut: std.Io.RwLock = .init,
|
||||
cond: std.Io.Condition = .init,
|
||||
|
||||
max_mem: u64,
|
||||
cur_mem: u64 = 0,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, map: MemoryMap, compression: Decomp.Enum, max_mem: u64) !DecompCache {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.map = map,
|
||||
.decomp_fn = try Decomp.DecompFn(compression),
|
||||
|
||||
.cache = .init(alloc),
|
||||
|
||||
.max_mem = max_mem,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *DecompCache, io: Io) void {
|
||||
self.mut.lockUncancelable(io);
|
||||
|
||||
var iter = self.cache.valueIterator();
|
||||
while (iter.next()) |v|
|
||||
self.alloc.free(v.data);
|
||||
self.cache.deinit();
|
||||
}
|
||||
|
||||
pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_size: u32) ![]u8 {
|
||||
{
|
||||
try self.mut.lockShared(io);
|
||||
defer self.mut.unlockShared(io);
|
||||
|
||||
const cache = self.cache.getPtr(offset);
|
||||
if (cache != null) {
|
||||
_ = cache.?.usage.fetchAdd(1, .acquire);
|
||||
return cache.?.data;
|
||||
}
|
||||
}
|
||||
try self.mut.lock(io);
|
||||
defer self.mut.unlock(io);
|
||||
|
||||
const cache = try self.cache.getOrPut(offset);
|
||||
if (cache.found_existing) {
|
||||
_ = cache.value_ptr.usage.fetchAdd(1, .acquire);
|
||||
return cache.value_ptr.data;
|
||||
}
|
||||
errdefer self.cache.removeByPtr(cache.key_ptr);
|
||||
|
||||
try self.ensureSpace(io, max_size);
|
||||
|
||||
var out = try self.alloc.alloc(u8, max_size);
|
||||
errdefer self.alloc.free(out);
|
||||
|
||||
const decomp_size = try self.decomp_fn(self.alloc, self.map.memory[offset..][0..compressed_size], out);
|
||||
if (decomp_size != max_size) {
|
||||
if (!self.alloc.resize(out, decomp_size)) {
|
||||
const new_out = try self.alloc.alloc(u8, decomp_size);
|
||||
@memcpy(new_out, out[0..decomp_size]);
|
||||
out = new_out;
|
||||
} else {
|
||||
out.len = decomp_size;
|
||||
}
|
||||
}
|
||||
self.cur_mem += decomp_size;
|
||||
|
||||
cache.value_ptr.data = out;
|
||||
_ = cache.value_ptr.usage.fetchAdd(1, .acquire);
|
||||
return out;
|
||||
}
|
||||
pub fn finished(self: *DecompCache, io: Io, offset: u64) void {
|
||||
const cache = self.cache.getPtr(offset);
|
||||
if (cache == null) {
|
||||
std.debug.print("Finished using cache, but cache does not exist: {}\n", .{offset});
|
||||
return;
|
||||
}
|
||||
const use = cache.?.usage.fetchSub(1, .acquire);
|
||||
if (use == 0)
|
||||
self.cond.broadcast(io);
|
||||
}
|
||||
|
||||
fn ensureSpace(self: *DecompCache, io: Io, size: u64) !void {
|
||||
while (self.cur_mem + size > self.max_mem) {
|
||||
var iter = self.cache.valueIterator();
|
||||
while (iter.next()) |cache| {
|
||||
if (cache.usage.load(.unordered) == 0) {
|
||||
self.alloc.free(cache.data);
|
||||
self.cur_mem -= cache.data.len;
|
||||
|
||||
if (self.cur_mem + size <= self.max_mem) return;
|
||||
}
|
||||
}
|
||||
if (self.cur_mem + size <= self.max_mem) return;
|
||||
try self.cond.wait(io, &self.mut.mutex);
|
||||
}
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
const Cache = struct {
|
||||
data: []u8,
|
||||
usage: Atomic(u32),
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Reader = Io.Reader;
|
||||
|
||||
const Inode = @import("inode.zig");
|
||||
|
||||
const Directory = @This();
|
||||
|
||||
entries: []Entry,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u32) Error!Directory {
|
||||
if (size <= 3) return .{ .entries = &[0]Entry{} };
|
||||
|
||||
var entries: std.ArrayList(Entry) = try .initCapacity(alloc, 50);
|
||||
errdefer {
|
||||
for (entries.items) |ent|
|
||||
ent.deinit(alloc);
|
||||
entries.deinit(alloc);
|
||||
}
|
||||
|
||||
var read: u32 = 3;
|
||||
while (read < size) {
|
||||
var hdr: Header = undefined;
|
||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||
read += @sizeOf(Header);
|
||||
|
||||
try entries.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||
for (0..hdr.count + 1) |_| {
|
||||
var raw: RawEntry = undefined;
|
||||
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
||||
|
||||
const name = try alloc.alloc(u8, raw.name_size + 1);
|
||||
errdefer alloc.free(name);
|
||||
try rdr.readSliceEndian(u8, name, .little);
|
||||
|
||||
entries.appendAssumeCapacity(.{
|
||||
.inode_num = if (raw.inode_num_offset > 0)
|
||||
hdr.inode_num + @abs(raw.inode_num_offset)
|
||||
else
|
||||
hdr.inode_num - @abs(raw.inode_num_offset),
|
||||
.block_start = hdr.block_start,
|
||||
.block_offset = raw.block_offset,
|
||||
.type = raw.type,
|
||||
.name = name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .entries = try entries.toOwnedSlice(alloc) };
|
||||
}
|
||||
pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
|
||||
for (self.entries) |entry|
|
||||
entry.deinit(alloc);
|
||||
alloc.free(self.entries);
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
pub const Error = Reader.Error || std.mem.Allocator.Error;
|
||||
|
||||
pub const Entry = struct {
|
||||
inode_num: u32,
|
||||
block_start: u32,
|
||||
block_offset: u16,
|
||||
type: Inode.Enum,
|
||||
name: []const u8,
|
||||
|
||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.name);
|
||||
}
|
||||
};
|
||||
|
||||
const Header = extern struct {
|
||||
count: u32,
|
||||
block_start: u32,
|
||||
inode_num: u32,
|
||||
};
|
||||
const RawEntry = extern struct {
|
||||
block_offset: u16,
|
||||
inode_num_offset: i16,
|
||||
type: Inode.Enum,
|
||||
name_size: u16,
|
||||
};
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
|
||||
const Atomic = std.atomic.Value;
|
||||
|
||||
const DecompCache = @import("decomp_cache.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const Inode = @import("inode.zig");
|
||||
const Superblock = @import("archive.zig").Superblock;
|
||||
const Directory = @import("directory.zig");
|
||||
const DataExtractor = @import("data/extractor.zig");
|
||||
const DataReader = @import("data/reader.zig");
|
||||
|
||||
pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCache, super: Superblock, ext_loc: []const u8, options: ExtractionOptions) !void {
|
||||
const path = std.mem.trim(u8, ext_loc, "/");
|
||||
|
||||
var buf: [50]ReturnUnion = undefined;
|
||||
var sel: Io.Select(ReturnUnion) = .init(io, &buf);
|
||||
defer sel.cancelDiscard();
|
||||
|
||||
var ret_loop = io.async(returnLoop, .{ alloc, &sel, options });
|
||||
|
||||
try extractReal(alloc, io, cache, super, &sel, path, inode, null, false);
|
||||
|
||||
ret_loop.await(io) catch |err| {
|
||||
// TODO: Drain sel
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
fn extractReal(
|
||||
alloc: std.mem.Allocator,
|
||||
io: Io,
|
||||
cache: *DecompCache,
|
||||
super: Superblock,
|
||||
sel: *Io.Select(ReturnUnion),
|
||||
path: []const u8,
|
||||
inode: Inode,
|
||||
parent: ?*Atomic(usize),
|
||||
origin: bool,
|
||||
) Error!void {
|
||||
try io.checkCancel();
|
||||
|
||||
switch (inode.data) {
|
||||
.dir, .ext_dir => sel.async(
|
||||
.dir_ret,
|
||||
extractDir,
|
||||
.{ alloc, io, cache, super, sel, path, inode, parent, origin },
|
||||
),
|
||||
else => return error.Canceled,
|
||||
}
|
||||
}
|
||||
|
||||
fn extractDir(
|
||||
alloc: std.mem.Allocator,
|
||||
io: Io,
|
||||
cache: *DecompCache,
|
||||
super: Superblock,
|
||||
sel: *Io.Select(ReturnUnion),
|
||||
path: []const u8,
|
||||
inode: Inode,
|
||||
parent: ?*Atomic(usize),
|
||||
origin: bool,
|
||||
) Error!DirReturn {
|
||||
defer {
|
||||
if (parent != null)
|
||||
_ = parent.?.fetchSub(1, .acquire);
|
||||
if (!origin) inode.deinit(alloc);
|
||||
}
|
||||
errdefer if (!origin) alloc.free(path);
|
||||
|
||||
const dir = inode.directory(alloc, io, cache, super.dir_start) catch |err| switch (err) {
|
||||
error.NotDirectory => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer dir.deinit(alloc);
|
||||
|
||||
const sub_files = try alloc.create(Atomic(usize));
|
||||
sub_files.* = .init(dir.entries.len);
|
||||
|
||||
const ret: DirReturn = .{
|
||||
.path = path,
|
||||
.sub_files = sub_files,
|
||||
.origin = origin,
|
||||
|
||||
.uid_idx = inode.hdr.uid_idx,
|
||||
.gid_idx = inode.hdr.gid_idx,
|
||||
.mod_time = inode.hdr.mod_time,
|
||||
.permissions = inode.hdr.permission,
|
||||
|
||||
.xattr_idx = switch (inode.data) {
|
||||
.ext_dir => |d| if (d.xattr_idx != 0xFFFFFFFF) d.xattr_idx else null,
|
||||
else => null,
|
||||
},
|
||||
};
|
||||
|
||||
for (dir.entries) |entry| {
|
||||
const new_inode: Inode = try .initDirEntry(alloc, io, cache, super.inode_start, super.block_size, entry);
|
||||
errdefer new_inode.deinit(alloc);
|
||||
|
||||
const new_path = try std.mem.concat(alloc, u8, &.{ path, "/", entry.name });
|
||||
|
||||
try extractReal(
|
||||
alloc,
|
||||
io,
|
||||
cache,
|
||||
super,
|
||||
sel,
|
||||
new_path,
|
||||
new_inode,
|
||||
sub_files,
|
||||
false,
|
||||
);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
fn extractFile(
|
||||
alloc: std.mem.Allocator,
|
||||
io: Io,
|
||||
cache: *DecompCache,
|
||||
block_size: u32,
|
||||
path: []const u8,
|
||||
inode: Inode,
|
||||
parent: ?*Atomic(usize),
|
||||
origin: bool,
|
||||
) Error!FileReturn {
|
||||
defer {
|
||||
if (parent != null)
|
||||
_ = parent.?.fetchSub(1, .acquire);
|
||||
if (!origin) inode.deinit(alloc);
|
||||
}
|
||||
errdefer if (!origin) alloc.free(path);
|
||||
|
||||
const atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{});
|
||||
defer atomic.deinit(io);
|
||||
|
||||
var ret: FileReturn = .{
|
||||
.path = path,
|
||||
.origin = origin,
|
||||
|
||||
.uid_idx = inode.hdr.uid_idx,
|
||||
.gid_idx = inode.hdr.gid_idx,
|
||||
.permissions = inode.hdr.permission,
|
||||
.mod_time = inode.hdr.mod_time,
|
||||
};
|
||||
|
||||
var data: DataExtractor = switch (inode.data) {
|
||||
.file => |f| blk: {
|
||||
var data: DataExtractor = .init(cache, block_size, f.size, f.data_start, f.blocks);
|
||||
if (f.frag_idx != 0xFFFFFFFF) {
|
||||
// TODO
|
||||
}
|
||||
break :blk data;
|
||||
},
|
||||
.ext_file => |f| blk: {
|
||||
var data: DataExtractor = .init(cache, block_size, f.size, f.data_start, f.blocks);
|
||||
if (f.frag_idx != 0xFFFFFFFF) {
|
||||
//TODO
|
||||
}
|
||||
break :blk data;
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
try atomic.link(io);
|
||||
// return .{
|
||||
// .path = path,
|
||||
// };
|
||||
return error.Canceled;
|
||||
}
|
||||
|
||||
// Loop
|
||||
|
||||
fn returnLoop(alloc: std.mem.Allocator, sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void {
|
||||
while (true) {
|
||||
const finished = try sel.await();
|
||||
|
||||
switch (finished) {
|
||||
.dir_ret => |d| {
|
||||
const ret = try d;
|
||||
if (ret.sub_files.load(.unordered) != 0) {
|
||||
sel.queue.putOne(sel.io, .{ .dir_ret = ret }) catch |err| {
|
||||
if (!ret.origin) alloc.free(ret.path);
|
||||
return err;
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if (!ret.origin) alloc.free(ret.path);
|
||||
alloc.destroy(ret.sub_files);
|
||||
|
||||
if (!options.ignore_permissions and !options.ignore_xattr) {
|
||||
// TODO: set permissions & xattr.
|
||||
}
|
||||
},
|
||||
.file_ret => |f| {
|
||||
const ret = try f;
|
||||
if (!ret.origin) alloc.free(ret.path);
|
||||
|
||||
if (!options.ignore_permissions and !options.ignore_xattr) {
|
||||
// TODO: set permissions & xattr.
|
||||
}
|
||||
},
|
||||
.void_ret => |v| try v,
|
||||
}
|
||||
|
||||
if (sel.group.token.load(.unordered) == null) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility types
|
||||
|
||||
const ReturnUnion = union(enum) {
|
||||
file_ret: Error!FileReturn,
|
||||
dir_ret: Error!DirReturn,
|
||||
void_ret: Error!void,
|
||||
};
|
||||
|
||||
const Error = error{Canceled} || Directory.Error;
|
||||
|
||||
const FileReturn = struct {
|
||||
path: []const u8,
|
||||
origin: bool,
|
||||
|
||||
uid_idx: u32,
|
||||
gid_idx: u32,
|
||||
mod_time: u32,
|
||||
permissions: u16,
|
||||
|
||||
xattr_idx: ?u32 = null,
|
||||
};
|
||||
const DirReturn = struct {
|
||||
path: []const u8,
|
||||
sub_files: *Atomic(usize),
|
||||
origin: bool,
|
||||
|
||||
uid_idx: u32,
|
||||
gid_idx: u32,
|
||||
mod_time: u32,
|
||||
permissions: u16,
|
||||
|
||||
xattr_idx: ?u32 = null,
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
|
||||
const Archive = @import("archive.zig");
|
||||
const Directory = @import("directory.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const Inode = @import("inode.zig");
|
||||
|
||||
const SfsFile = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
archive: *Archive,
|
||||
|
||||
inode: Inode,
|
||||
name: []const u8,
|
||||
|
||||
/// The given allocator must have been used to create the Inode and name.
|
||||
pub fn init(alloc: std.mem.Allocator, archive: *Archive, inode: Inode, name: []const u8) SfsFile {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.archive = archive,
|
||||
|
||||
.inode = inode,
|
||||
.name = name,
|
||||
};
|
||||
}
|
||||
pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry: Directory.Entry) !SfsFile {
|
||||
const new_name = try alloc.alloc(u8, entry.name.len);
|
||||
defer alloc.free(new_name);
|
||||
@memcpy(new_name, entry.name);
|
||||
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.archive = archive,
|
||||
|
||||
.inode = try .initDirEntry(
|
||||
alloc,
|
||||
io,
|
||||
&archive.cache,
|
||||
archive.super.inode_start,
|
||||
archive.super.block_size,
|
||||
entry,
|
||||
),
|
||||
.name = new_name,
|
||||
};
|
||||
}
|
||||
/// Creates a new copy of the given SfsFile using the given allocator
|
||||
pub fn copy(self: SfsFile, alloc: std.mem.Allocator) !SfsFile {
|
||||
const new_name = try alloc(u8, self.name.len);
|
||||
errdefer alloc.free(new_name);
|
||||
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.archive = self.archive,
|
||||
|
||||
.inode = try self.inode.copy(alloc),
|
||||
.name = new_name,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: SfsFile) void {
|
||||
self.inode.deinit(self.alloc);
|
||||
self.alloc.free(self.name);
|
||||
}
|
||||
|
||||
/// Attempts to open the filepath if the SfsFile is a directory.
|
||||
/// If the given path refers to itself (such as "" or "."), a copied SfsFile is returned.
|
||||
pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile {
|
||||
const path = std.mem.trim(u8, filepath, "/");
|
||||
|
||||
const first_element: []const u8 = std.mem.sliceTo(path, '/');
|
||||
|
||||
const dir: Directory = try self.inode.directory(alloc, io, &self.archive.cache, self.archive.super.dir_start);
|
||||
defer dir.deinit(alloc);
|
||||
|
||||
var cur_slice = dir.entries;
|
||||
var idx: usize = undefined;
|
||||
while (cur_slice.len > 0) {
|
||||
idx = cur_slice.len / 2;
|
||||
switch (std.mem.order(u8, first_element, cur_slice[idx].name)) {
|
||||
.eq => break,
|
||||
.lt => cur_slice = cur_slice[0..idx],
|
||||
.gt => cur_slice = cur_slice[idx..],
|
||||
}
|
||||
} else {
|
||||
return error.NotFound;
|
||||
}
|
||||
if (first_element.len == path.len) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
|
||||
if (cur_slice[idx].type != .dir) return error.NotFound;
|
||||
const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
|
||||
defer tmp_file.deinit();
|
||||
|
||||
return tmp_file.open(alloc, io, path[first_element.len..]);
|
||||
}
|
||||
|
||||
pub fn extract(self: SfsFile, alloc: std.mem.Allocator, io: Io, ext_dir: []const u8, options: ExtractionOptions) !void {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
_ = io;
|
||||
_ = ext_dir;
|
||||
_ = options;
|
||||
return error.TODO;
|
||||
}
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Reader = Io.Reader;
|
||||
|
||||
const DecompCache = @import("decomp_cache.zig");
|
||||
const Directory = @import("directory.zig");
|
||||
const MetadataReader = @import("meta_rdr.zig");
|
||||
|
||||
const Inode = @This();
|
||||
|
||||
hdr: Header,
|
||||
data: Data,
|
||||
|
||||
/// Read an inode given an inode Ref.
|
||||
pub fn initRef(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, ref: Ref) !Inode {
|
||||
var meta: MetadataReader = .init(io, cache, inode_start + ref.block_start);
|
||||
defer meta.deinit(io);
|
||||
try meta.interface.discardAll(ref.block_offset);
|
||||
|
||||
return .init(alloc, &meta.interface, block_size);
|
||||
}
|
||||
pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, entry: Directory.Entry) !Inode {
|
||||
var meta: MetadataReader = .init(io, cache, inode_start + entry.block_start);
|
||||
defer meta.deinit(io);
|
||||
try meta.interface.discardAll(entry.block_offset);
|
||||
|
||||
return .init(alloc, &meta.interface, block_size);
|
||||
}
|
||||
/// Read the inode from the given Reader.
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
||||
var hdr: Header = undefined;
|
||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||
const data: Data = switch (hdr.type) {
|
||||
.dir => .{ .dir = try .init(rdr) },
|
||||
.file => .{ .file = try .init(alloc, rdr, block_size) },
|
||||
.symlink => .{ .symlink = try .init(alloc, rdr) },
|
||||
.block_dev => .{ .block_dev = try .init(rdr) },
|
||||
.char_dev => .{ .char_dev = try .init(rdr) },
|
||||
.fifo => .{ .fifo = try .init(rdr) },
|
||||
.socket => .{ .socket = try .init(rdr) },
|
||||
.ext_dir => .{ .ext_dir = try .init(rdr) },
|
||||
.ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) },
|
||||
.ext_symlink => .{ .ext_symlink = try .init(alloc, rdr) },
|
||||
.ext_block_dev => .{ .ext_block_dev = try .init(rdr) },
|
||||
.ext_char_dev => .{ .ext_char_dev = try .init(rdr) },
|
||||
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
|
||||
.ext_socket => .{ .ext_socket = try .init(rdr) },
|
||||
};
|
||||
return .{
|
||||
.hdr = hdr,
|
||||
.data = data,
|
||||
};
|
||||
}
|
||||
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
|
||||
var new_inode = self;
|
||||
switch (new_inode.data) {
|
||||
.file => |*f| {
|
||||
if (f.blocks.len > 0) {
|
||||
f.blocks = try alloc.alloc(DataBlock, f.blocks.len);
|
||||
@memcpy(f.blocks, self.data.file.blocks);
|
||||
}
|
||||
},
|
||||
.ext_file => |*f| {
|
||||
if (f.blocks.len > 0) {
|
||||
f.blocks = try alloc.alloc(DataBlock, f.blocks.len);
|
||||
@memcpy(f.blocks, self.data.ext_file.blocks);
|
||||
}
|
||||
},
|
||||
.symlink => |*s| {
|
||||
s.target = try alloc.alloc(u8, s.target.len);
|
||||
@memcpy(s.target, self.data.symlink.target);
|
||||
},
|
||||
.ext_symlink => |*s| {
|
||||
s.target = try alloc.alloc(u8, s.target.len);
|
||||
@memcpy(s.target, self.data.ext_symlink.target);
|
||||
},
|
||||
}
|
||||
return new_inode;
|
||||
}
|
||||
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||
switch (self.data) {
|
||||
.file => |f| f.deinit(alloc),
|
||||
.ext_file => |f| f.deinit(alloc),
|
||||
.symlink => |s| s.deinit(alloc),
|
||||
.ext_symlink => |s| s.deinit(alloc),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
pub fn directory(self: Inode, alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64) !Directory {
|
||||
return switch (self.data) {
|
||||
.dir => |d| readDirectory(alloc, io, cache, dir_start, d),
|
||||
.ext_dir => |d| readDirectory(alloc, io, cache, dir_start, d),
|
||||
else => error.NotDirectory,
|
||||
};
|
||||
}
|
||||
fn readDirectory(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64, d: anytype) !Directory {
|
||||
var meta: MetadataReader = .init(io, cache, dir_start + d.block_start);
|
||||
defer meta.deinit(io);
|
||||
try meta.interface.discardAll(d.block_offset);
|
||||
|
||||
return .init(alloc, &meta.interface, d.size);
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
pub const Ref = packed struct(u64) {
|
||||
block_offset: u16,
|
||||
block_start: u32,
|
||||
_: u16,
|
||||
};
|
||||
|
||||
pub const Enum = enum(u16) {
|
||||
dir = 1,
|
||||
file,
|
||||
symlink,
|
||||
block_dev,
|
||||
char_dev,
|
||||
fifo,
|
||||
socket,
|
||||
ext_dir,
|
||||
ext_file,
|
||||
ext_symlink,
|
||||
ext_block_dev,
|
||||
ext_char_dev,
|
||||
ext_fifo,
|
||||
ext_socket,
|
||||
};
|
||||
|
||||
pub const Header = extern struct {
|
||||
type: Enum,
|
||||
permission: u16,
|
||||
uid_idx: u16,
|
||||
gid_idx: u16,
|
||||
mod_time: u32,
|
||||
num: u32,
|
||||
};
|
||||
|
||||
pub const Data = union(Enum) {
|
||||
dir: Dir,
|
||||
file: File,
|
||||
symlink: Symlink,
|
||||
block_dev: Device,
|
||||
char_dev: Device,
|
||||
fifo: IPC,
|
||||
socket: IPC,
|
||||
ext_dir: ExtDir,
|
||||
ext_file: ExtFile,
|
||||
ext_symlink: ExtSymlink,
|
||||
ext_block_dev: ExtDevice,
|
||||
ext_char_dev: ExtDevice,
|
||||
ext_fifo: ExtIPC,
|
||||
ext_socket: ExtIPC,
|
||||
};
|
||||
|
||||
pub const DataBlock = packed struct(u32) {
|
||||
size: u24,
|
||||
uncompressed: bool,
|
||||
_: u7,
|
||||
};
|
||||
|
||||
const Dir = extern struct {
|
||||
block_start: u32,
|
||||
hard_links: u32,
|
||||
size: u16,
|
||||
block_offset: u16,
|
||||
parent: u32,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn init(rdr: *Reader) !Self {
|
||||
var dir: Self = undefined;
|
||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
||||
return dir;
|
||||
}
|
||||
};
|
||||
const ExtDir = extern struct {
|
||||
hard_links: u32,
|
||||
size: u32,
|
||||
block_start: u32,
|
||||
parent: u32,
|
||||
idx_count: u16,
|
||||
block_offset: u16,
|
||||
xattr_idx: u32,
|
||||
// []DirIndex
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn init(rdr: *Reader) !Self {
|
||||
var dir: Self = undefined;
|
||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
||||
return dir;
|
||||
}
|
||||
};
|
||||
|
||||
const File = struct {
|
||||
data_start: u32,
|
||||
frag_idx: u32,
|
||||
frag_offset: u32,
|
||||
size: u32,
|
||||
blocks: []DataBlock,
|
||||
|
||||
const Raw = extern struct {
|
||||
data_start: u32,
|
||||
frag_idx: u32,
|
||||
frag_offset: u32,
|
||||
size: u32,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self {
|
||||
var raw: Raw = undefined;
|
||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
||||
|
||||
var blocks_num = raw.size / block_size;
|
||||
if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0)
|
||||
blocks_num += 1;
|
||||
|
||||
const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num);
|
||||
errdefer alloc.free(blocks);
|
||||
|
||||
try rdr.readSliceEndian(DataBlock, blocks, .little);
|
||||
return .{
|
||||
.data_start = raw.data_start,
|
||||
.frag_idx = raw.frag_idx,
|
||||
.frag_offset = raw.frag_offset,
|
||||
.size = raw.size,
|
||||
.blocks = blocks,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.blocks);
|
||||
}
|
||||
};
|
||||
const ExtFile = struct {
|
||||
data_start: u64,
|
||||
size: u64,
|
||||
sparse: u64,
|
||||
hard_links: u32,
|
||||
frag_idx: u32,
|
||||
frag_offset: u32,
|
||||
xattr_idx: u32,
|
||||
blocks: []DataBlock,
|
||||
|
||||
const Raw = extern struct {
|
||||
data_start: u64,
|
||||
size: u64,
|
||||
sparse: u64,
|
||||
hard_links: u32,
|
||||
frag_idx: u32,
|
||||
frag_offset: u32,
|
||||
xattr_idx: u32,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self {
|
||||
var raw: Raw = undefined;
|
||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
||||
|
||||
var blocks_num = raw.size / block_size;
|
||||
if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0)
|
||||
blocks_num += 1;
|
||||
|
||||
const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num);
|
||||
errdefer alloc.free(blocks);
|
||||
|
||||
try rdr.readSliceEndian(DataBlock, blocks, .little);
|
||||
return .{
|
||||
.data_start = raw.data_start,
|
||||
.size = raw.size,
|
||||
.sparse = raw.sparse,
|
||||
.hard_links = raw.hard_links,
|
||||
.frag_idx = raw.frag_idx,
|
||||
.frag_offset = raw.frag_offset,
|
||||
.xattr_idx = raw.xattr_idx,
|
||||
.blocks = blocks,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.blocks);
|
||||
}
|
||||
};
|
||||
|
||||
const Symlink = struct {
|
||||
hard_links: u32,
|
||||
target: []const u8,
|
||||
|
||||
const Raw = extern struct {
|
||||
hard_links: u32,
|
||||
target_size: u32,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self {
|
||||
var raw: Raw = undefined;
|
||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
||||
|
||||
const target = try alloc.alloc(u8, raw.target_size);
|
||||
try rdr.readSliceEndian(u8, target, .little);
|
||||
|
||||
return .{
|
||||
.hard_links = raw.hard_links,
|
||||
.target = target,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.target);
|
||||
}
|
||||
};
|
||||
const ExtSymlink = struct {
|
||||
hard_links: u32,
|
||||
xattr_idx: u32,
|
||||
target: []const u8,
|
||||
|
||||
const Raw = extern struct {
|
||||
hard_links: u32,
|
||||
target_size: u32,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self {
|
||||
var raw: Raw = undefined;
|
||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
||||
|
||||
const target = try alloc.alloc(u8, raw.target_size);
|
||||
errdefer alloc.free(target);
|
||||
try rdr.readSliceEndian(u8, target, .little);
|
||||
|
||||
var xattr_idx: u32 = undefined;
|
||||
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
||||
|
||||
return .{
|
||||
.hard_links = raw.hard_links,
|
||||
.target = target,
|
||||
.xattr_idx = xattr_idx,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.target);
|
||||
}
|
||||
};
|
||||
|
||||
const Device = extern struct {
|
||||
hard_links: u32,
|
||||
device: u32,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn init(rdr: *Reader) !Self {
|
||||
var dir: Self = undefined;
|
||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
||||
return dir;
|
||||
}
|
||||
};
|
||||
const ExtDevice = extern struct {
|
||||
hard_links: u32,
|
||||
device: u32,
|
||||
xattr_idx: u32,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn init(rdr: *Reader) !Self {
|
||||
var dir: Self = undefined;
|
||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
||||
return dir;
|
||||
}
|
||||
};
|
||||
|
||||
const IPC = extern struct {
|
||||
hard_links: u32,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn init(rdr: *Reader) !Self {
|
||||
var dir: Self = undefined;
|
||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
||||
return dir;
|
||||
}
|
||||
};
|
||||
const ExtIPC = extern struct {
|
||||
hard_links: u32,
|
||||
xattr_idx: u32,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn init(rdr: *Reader) !Self {
|
||||
var dir: Self = undefined;
|
||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
||||
return dir;
|
||||
}
|
||||
};
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
|
||||
const DecompCache = @import("decomp_cache.zig");
|
||||
const MetadataReader = @import("meta_rdr.zig");
|
||||
|
||||
pub fn stateless(comptime T: anytype, io: Io, cache: *DecompCache, table_start: u64, idx: u32) !T {
|
||||
const PER_BLOCK = 8192 / @sizeOf(T);
|
||||
|
||||
const block = idx / PER_BLOCK;
|
||||
const block_idx = idx % PER_BLOCK;
|
||||
|
||||
const offset_offset = table_start + (block * 8);
|
||||
const offset: u64 = std.mem.readInt(u64, cache.map.memory[offset_offset..][0..2], .little);
|
||||
|
||||
var meta: MetadataReader = .init(io, cache, offset);
|
||||
defer meta.deinit(io);
|
||||
try meta.discardAll(block_idx * @sizeOf(T));
|
||||
|
||||
var new: T = undefined;
|
||||
try meta.interface.readSliceEndian(T, @ptrCast(&new), .little);
|
||||
return new;
|
||||
}
|
||||
|
||||
pub fn Table(comptime T: anytype) type {
|
||||
return struct {
|
||||
const PER_BLOCK = 8192 / @sizeOf(T);
|
||||
|
||||
const Table = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
cache: *DecompCache,
|
||||
table_start: u64,
|
||||
|
||||
num: u32,
|
||||
values: std.AutoHashMap(u32, []T),
|
||||
mut: Io.RwLock,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, cache: *DecompCache, table_start: u64, num_values: u32) Table {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
|
||||
.cache = cache,
|
||||
.table_start = table_start,
|
||||
|
||||
.num = num_values,
|
||||
.values = .init(alloc),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *Table) void {
|
||||
var iter = self.values.valueIterator();
|
||||
while (iter.next()) |v|
|
||||
self.alloc.free(v);
|
||||
self.values.deinit();
|
||||
}
|
||||
|
||||
pub fn get(self: *Table, io: Io, idx: u32) Error!T {
|
||||
const block = idx / PER_BLOCK;
|
||||
const block_idx = idx % PER_BLOCK;
|
||||
{
|
||||
try self.mut.lockShared(io);
|
||||
defer self.mut.unlockShared(io);
|
||||
|
||||
const val = self.values.get(block);
|
||||
if (val != null) return val.*[block_idx];
|
||||
}
|
||||
try self.mut.lock(io);
|
||||
defer self.mut.unlock(io);
|
||||
|
||||
const val = try self.values.getOrPut(block);
|
||||
if (val.found_existing)
|
||||
return val.value_ptr.*[block_idx];
|
||||
errdefer self.values.removeByPtr(val.key_ptr);
|
||||
|
||||
const offset_offset = self.table_start + (block * 8);
|
||||
const offset: u64 = std.mem.readInt(u64, self.cache.map.memory[offset_offset..][0..2], .little);
|
||||
|
||||
var meta: MetadataReader = .init(io, self.cache, offset);
|
||||
defer meta.deinit(io);
|
||||
|
||||
const size = if (block == ((self.num - 1) / PER_BLOCK))
|
||||
self.num % PER_BLOCK
|
||||
else
|
||||
PER_BLOCK;
|
||||
|
||||
const new_block = try self.alloc.alloc(T, size);
|
||||
errdefer self.alloc.free(new_block);
|
||||
try meta.interface.readSliceEndian(T, new_block, .little);
|
||||
|
||||
val.value_ptr.* = new_block;
|
||||
|
||||
return new_block[block_idx];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
pub const Error = error{} || std.mem.Allocator.Error;
|
||||
|
||||
pub const FragmentEntry = extern struct {};
|
||||
|
||||
pub const XattrEntry = extern struct {};
|
||||
@@ -0,0 +1,109 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Reader = Io.Reader;
|
||||
const Writer = Io.Writer;
|
||||
const Limit = Io.Limit;
|
||||
|
||||
const DecompCache = @import("decomp_cache.zig");
|
||||
|
||||
const MetadataReader = @This();
|
||||
|
||||
io: Io,
|
||||
cache: *DecompCache,
|
||||
|
||||
cur_offset: u64 = 0,
|
||||
next_offset: u64,
|
||||
|
||||
interface: Reader = .{
|
||||
.buffer = &[0]u8{},
|
||||
.end = 0,
|
||||
.seek = 0,
|
||||
.vtable = &.{
|
||||
.stream = stream,
|
||||
.discard = discard,
|
||||
.readVec = readVec,
|
||||
},
|
||||
},
|
||||
|
||||
pub fn init(io: Io, cache: *DecompCache, start: u64) MetadataReader {
|
||||
return .{
|
||||
.io = io,
|
||||
.cache = cache,
|
||||
|
||||
.next_offset = start,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *MetadataReader, io: Io) void {
|
||||
self.cache.finished(io, self.cur_offset);
|
||||
}
|
||||
|
||||
fn advance(self: *MetadataReader) !void {
|
||||
self.cache.finished(self.io, self.cur_offset);
|
||||
|
||||
self.interface.seek = 0;
|
||||
errdefer self.interface.end = 0;
|
||||
|
||||
const hdr: Header = @bitCast(std.mem.readInt(u16, self.cache.map.memory[self.next_offset..][0..2], .little));
|
||||
self.cur_offset = self.next_offset + 2;
|
||||
self.next_offset = self.cur_offset + hdr.size;
|
||||
|
||||
if (hdr.uncompressed) {
|
||||
self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..hdr.size];
|
||||
self.interface.end = hdr.size;
|
||||
return;
|
||||
}
|
||||
self.interface.buffer = try self.cache.get(self.io, self.cur_offset, hdr.size, 8192);
|
||||
self.interface.end = self.interface.buffer.len;
|
||||
}
|
||||
|
||||
fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
|
||||
if (r.seek >= r.end) {
|
||||
const self: *MetadataReader = @fieldParentPtr("interface", r);
|
||||
self.advance() catch |err| {
|
||||
std.debug.print("error advancing metadata reader: {}\n", .{err});
|
||||
return Reader.Error.ReadFailed;
|
||||
};
|
||||
}
|
||||
const to_write = @min(r.end - r.seek, @intFromEnum(limit));
|
||||
const wrote = try w.write(r.buffer[r.seek..][0..to_write]);
|
||||
r.seek += wrote;
|
||||
return wrote;
|
||||
}
|
||||
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
|
||||
if (r.seek >= r.end) {
|
||||
const self: *MetadataReader = @fieldParentPtr("interface", r);
|
||||
self.advance() catch |err| {
|
||||
std.debug.print("error advancing metadata reader: {}\n", .{err});
|
||||
return Reader.Error.ReadFailed;
|
||||
};
|
||||
}
|
||||
const to_discard = @min(r.end - r.seek, @intFromEnum(limit));
|
||||
r.seek += to_discard;
|
||||
return to_discard;
|
||||
}
|
||||
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||
if (r.seek >= r.end) {
|
||||
const self: *MetadataReader = @fieldParentPtr("interface", r);
|
||||
self.advance() catch |err| {
|
||||
std.debug.print("error advancing metadata reader: {}\n", .{err});
|
||||
return Reader.Error.ReadFailed;
|
||||
};
|
||||
}
|
||||
var total: usize = 0;
|
||||
for (vec) |v| {
|
||||
const to_copy = @min(r.end - r.seek, v.len);
|
||||
@memcpy(v[0..to_copy], r.buffer[r.seek..][0..to_copy]);
|
||||
r.seek += to_copy;
|
||||
total += to_copy;
|
||||
if (r.seek >= r.end)
|
||||
break;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
const Header = packed struct(u16) {
|
||||
size: u15,
|
||||
uncompressed: bool,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
//! Options for file/directory extraction.
|
||||
|
||||
const std = @import("std");
|
||||
const Writer = std.Io.Writer;
|
||||
|
||||
const ExtractionOptions = @This();
|
||||
|
||||
/// Don't set the file's owner & permissions after extraction
|
||||
ignore_permissions: bool = false,
|
||||
/// Don't set xattr values. Currently xattrs are never set anyway.
|
||||
ignore_xattr: bool = false,
|
||||
/// Replace symlinks with their target.
|
||||
dereference_symlinks: bool = false,
|
||||
/// Verbose logging. If true, verbose_writer must be set
|
||||
verbose: bool = false,
|
||||
/// Where to print verbose log.
|
||||
verbose_writer: ?*Writer = null,
|
||||
|
||||
pub const default: ExtractionOptions = .{};
|
||||
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||
return .{
|
||||
.verbose = true,
|
||||
.verbose_writer = wrt,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub const Archive = @import("archive.zig");
|
||||
pub const ExtractionOptions = @import("options.zig");
|
||||
|
||||
test {
|
||||
const std = @import("std");
|
||||
|
||||
std.testing.refAllDecls(Archive);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
const std = @import("std");
|
||||
const stuff = @import("builtin");
|
||||
|
||||
const Archive = @import("archive.zig");
|
||||
const Superblock = @import("super.zig").Superblock;
|
||||
|
||||
const TestArchive = "testing/LinuxPATest.sfs";
|
||||
|
||||
test "Basics" {
|
||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||
defer fil.close();
|
||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||
defer sfs.deinit();
|
||||
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
||||
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
||||
return error.BadSuperblock;
|
||||
}
|
||||
}
|
||||
|
||||
const TestFile = "Start.exe";
|
||||
const TestFileExtractLocation = "testing/Start.exe";
|
||||
|
||||
test "ExtractSingleFile" {
|
||||
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||
defer fil.close();
|
||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||
defer sfs.deinit();
|
||||
var test_fil = try sfs.open(TestFile);
|
||||
defer test_fil.deinit();
|
||||
try test_fil.extract(TestFileExtractLocation, .Default);
|
||||
//TODO: validate extracted file.
|
||||
}
|
||||
|
||||
const TestFullExtractLocation = "testing/TestExtract";
|
||||
|
||||
test "ExtractCompleteArchive" {
|
||||
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||
defer fil.close();
|
||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||
defer sfs.deinit();
|
||||
try sfs.extract(TestFullExtractLocation, .Default);
|
||||
}
|
||||
|
||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||
.magic = std.mem.readInt(u32, "hsqs", .little),
|
||||
.inode_count = 2974,
|
||||
.mod_time = 1632696724,
|
||||
.block_size = 131072,
|
||||
.frag_count = 264,
|
||||
.compression = .zstd,
|
||||
.block_log = 17,
|
||||
.flags = .{
|
||||
.inode_uncompressed = false,
|
||||
.data_uncompressed = false,
|
||||
.check = false,
|
||||
.frag_uncompressed = false,
|
||||
.fragment_never = false,
|
||||
.fragment_always = false,
|
||||
.duplicates = true,
|
||||
.exportable = true,
|
||||
.xattr_uncompressed = false,
|
||||
.xattr_never = false,
|
||||
.compression_options = false,
|
||||
.ids_uncompressed = false,
|
||||
._ = 0,
|
||||
},
|
||||
.id_count = 1,
|
||||
.ver_maj = 4,
|
||||
.ver_min = 0,
|
||||
.root_ref = .{
|
||||
.block_offset = 1363,
|
||||
.block_start = 29237,
|
||||
._ = 0,
|
||||
},
|
||||
.size = 106841744,
|
||||
.id_start = 106841632,
|
||||
.xattr_start = 106841720,
|
||||
.inode_start = 106778274,
|
||||
.dir_start = 106807998,
|
||||
.frag_start = 106837747,
|
||||
.export_start = 106841602,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
const flate = std.compress.flate;
|
||||
const zstd = std.compress.zstd;
|
||||
const xz = std.compress.xz;
|
||||
const lzma = std.compress.lzma;
|
||||
|
||||
const Error = @import("decomp.zig").Error;
|
||||
|
||||
pub fn zlibDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var buf: [flate.max_window_len]u8 = undefined;
|
||||
|
||||
var rdr: Reader = .fixed(in);
|
||||
var decomp: flate.Decompress = .init(&rdr, .zlib, &buf);
|
||||
|
||||
return decomp.reader.readSliceShort(out);
|
||||
}
|
||||
pub fn zstdDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
const buf = try alloc.alloc(u8, in.len + zstd.block_size_max);
|
||||
defer alloc.free(buf);
|
||||
|
||||
var rdr: Reader = .fixed(in);
|
||||
var decomp: zstd.Decompress = .init(&rdr, buf, .{ .window_len = in.len });
|
||||
|
||||
return decomp.reader.readSliceShort(out);
|
||||
}
|
||||
pub fn lzmaDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var rdr: Reader = .fixed(in);
|
||||
var decomp: lzma.Decompress = .initOptions(&rdr, alloc, &[0]u8{}, .{}, 2 * out.len);
|
||||
defer decomp.deinit();
|
||||
|
||||
return decomp.reader.readSliceShort(out);
|
||||
}
|
||||
pub fn xzDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var rdr: Reader = .fixed(in);
|
||||
var decomp: xz.Decompress = .init(&rdr, alloc, &[0]u8{});
|
||||
defer decomp.deinit();
|
||||
|
||||
return decomp.reader.readSliceShort(out);
|
||||
}
|
||||
Reference in New Issue
Block a user