91 Commits

Author SHA1 Message Date
Caleb Gardner 21f1a304ca Started changes to Zig 0.16.0 2026-04-29 02:20:46 -05:00
Caleb Gardner cfc0e58905 Added xattr & cached uid/gid to Inode 2026-04-24 05:52:31 -05:00
Caleb Gardner 0d5b727919 Added stateless Xattr lookup 2026-04-24 04:16:22 -05:00
Caleb J. Gardner b111859c4d DataReader! 2026-04-08 02:33:58 -05:00
Caleb J. Gardner 3e97aabe53 Fleshing out more of the library.
Mainly adding functions to File
2026-04-07 15:41:30 -05:00
Caleb J. Gardner eec468ff17 File stuff 2026-04-04 18:48:57 -05:00
Caleb J. Gardner c7c44029c9 Started working on Archive functions
Started working on File
2026-04-04 14:17:50 -05:00
Caleb J. Gardner e07f11d195 Lookup tables! 2026-04-04 01:37:36 -05:00
Caleb J. Gardner d0787a5200 Fixed a couple decompressor issues.
Re-added ExtractionOptions
2026-04-03 22:27:40 -05:00
Caleb J. Gardner 4ee15b036a Finished(?) decompression stuff 2026-04-03 21:25:13 -05:00
Caleb J. Gardner c01caba69b More decompressor work 2026-04-02 01:42:43 -05:00
Caleb J. Gardner 3093994ac1 Started work on actual decompression implementations 2026-03-29 03:36:43 -05:00
Caleb J. Gardner ad8222911f Further implementing everthing again.
Added Decompressor vtable interface.
Directory table parsing
Inode reading
Metadata Reader
2026-03-27 02:39:07 -05:00
Caleb J. Gardner a8c067e933 Some initial work
Some basic Inode stuff
Some basic Archive stuff
2026-03-26 22:30:15 -05:00
Caleb J. Gardner 30755f7d5c Reseting. Again. 2026-03-26 21:46:01 -05:00
Caleb J. Gardner a1b9828578 Finished (?) decompression restructuring 2026-03-26 06:40:17 -05:00
Caleb J. Gardner 8e4661c4c6 Moving decompression to a vtable interface 2026-03-22 06:35:25 -05:00
Caleb J. Gardner 54aaf30ea5 Working on re-doing decompression 2026-03-21 02:13:36 -05:00
Caleb J. Gardner df22cf6529 Started work on stateful decompression 2026-03-20 01:55:00 -05:00
Caleb J. Gardner 6b5c830234 Actually fix build 2026-03-18 06:17:07 -05:00
Caleb J. Gardner 116234cf9c Fixed build 2026-03-18 06:15:52 -05:00
Caleb J. Gardner 4601e8f323 Updated build 2026-03-18 06:13:19 -05:00
Caleb J. Gardner 50cae8b63d Use zig packed versions of zlib-ng, lz4, and zstd.
Changed use_c_libs to use_zig_decomp so c libraries are now default
2026-03-18 05:24:58 -05:00
Caleb J. Gardner 8b8c9a772f Added -Ddebug build option
Re-added errors from settings xattr values
2026-03-09 00:16:19 -05:00
Caleb Gardner f563648b20 Merge pull request #4 from CalebQ42/inode_finish
Re-do much of extraction
2026-03-08 22:36:37 -05:00
Caleb Gardner 8d4f3d72f8 Merge branch 'main' into inode_finish 2026-03-08 22:34:50 -05:00
Caleb J. Gardner 0b6129f1ae Switch to zlib-ng 2026-03-08 22:15:28 -05:00
Caleb J. Gardner 7308faad36 Moved kv free's in setMetadata 2026-03-05 12:54:25 -06:00
Caleb J. Gardner c9499251f8 Moved lookup tables into separate struct to fix some race conditions
Fixed lingering issues due to zero work size InodeFinish
Fixed xattrs not applying due to the keys sometimes not being
null-terminated.
Updated performance numbers
2026-03-05 12:20:30 -06:00
Caleb J. Gardner d470ca98e3 Added --force to unsquashfs
Fixing race condition bugs (yay)
2026-03-05 07:04:24 -06:00
Caleb J. Gardner a606f5e11a Move extract logic to util/extract.zig to make it easier to read. 2026-03-05 03:03:37 -06:00
Caleb J. Gardner a4e23a840d Updated performance values in README.
Added ability to ignore xattrs & permissions.
Ignore setting xattr errors due to an unknown issues.
2026-03-05 03:03:34 -06:00
Caleb J. Gardner 3a10572953 Updated performance values in README.
Added ability to ignore xattrs & permissions.
Ignore setting xattr errors due to an unknown issues.
2026-03-04 13:28:29 -06:00
Caleb J. Gardner edfe919c1b Expirementation with a new way to finish threads. Currently not working. 2026-03-04 06:39:44 -06:00
Caleb J. Gardner 4515610082 Added xattr support (currently untested)
Use stack fallback allocator on extraction for better performance.
2026-03-04 03:24:30 -06:00
Caleb J. Gardner beca6a2ae6 Small tweaks 2026-03-01 02:27:54 -06:00
Caleb J. Gardner 760e11df0b Fixed github workflow 2026-02-28 22:38:47 -06:00
Caleb J. Gardner 5f629df47c alloc-ified many functions.
Updated README
2026-02-28 22:33:57 -06:00
Caleb J. Gardner b0160e005b Some fixes 2026-02-17 05:54:32 -06:00
Caleb J. Gardner b7b99325da Slight improvement to how permissions are applied to folders
unsquashfs Verbose flag
2026-02-13 01:35:20 -06:00
Caleb J. Gardner b22e4d003d Update times 2026-02-12 08:57:36 -06:00
Caleb J. Gardner a41c37fef4 Updated unsquashfs version print out 2026-02-12 06:54:20 -06:00
Caleb J. Gardner 2d079d77f7 Exclusive file creation 2026-02-12 05:19:25 -06:00
Caleb J. Gardner 48f4235875 Fixed threads == 0 causing single threaded extraction.
Set exclusive file creation
2026-02-12 05:08:16 -06:00
Caleb J. Gardner 567dea8a0b Re-fix Action name 2026-02-12 04:28:32 -06:00
Caleb J. Gardner f78e5c7386 Rever to previous Action 2026-02-12 04:27:06 -06:00
Caleb J. Gardner 81e975c0d9 Try doing upload in two separate steps 2026-02-12 04:22:20 -06:00
Caleb J. Gardner fd274a8072 Different try to upload release files 2026-02-12 04:18:19 -06:00
Caleb J. Gardner 50ae79637e Removed broken ZSTD error value 2026-02-12 04:06:18 -06:00
Caleb J. Gardner ee41dc7278 Added import of zstd_errors.h 2026-02-12 03:58:08 -06:00
Caleb J. Gardner c34acebf51 Only link lzo if allow_lzo 2026-02-12 03:46:36 -06:00
Caleb J. Gardner bdbda29d39 Fixed wrong zig action line 2026-02-12 03:42:48 -06:00
Caleb J. Gardner ca4e867ddc Use setup zig Action 2026-02-12 03:39:05 -06:00
Caleb J. Gardner b066835066 Update apt 2026-02-12 02:51:01 -06:00
Caleb J. Gardner 2363bd7d10 sudo apt Actions 2026-02-12 02:43:55 -06:00
Caleb J. Gardner e619005b77 Fix action 2026-02-12 02:40:37 -06:00
Caleb J. Gardner 28b44891b3 Forgot Zig itself to the build 2026-02-12 02:32:34 -06:00
Caleb J. Gardner 4829c802a3 Clean version string 2026-02-12 02:32:06 -06:00
Caleb Gardner 570db9632a GH Action to create a release when release tagged 2026-02-12 02:30:21 -06:00
Caleb J. Gardner 0076294675 Small fix to testing to check if libc is linked 2026-02-12 02:19:38 -06:00
Caleb J. Gardner fd9e3d595b A bit of README cleanup
Added allow_lzo build option due to lzo build issues
2026-02-11 06:57:52 -06:00
Caleb J. Gardner b8189490eb Small change/fix when applying permissions 2026-02-09 13:50:56 -06:00
Caleb J. Gardner 6adc1d5c0c Fixes for threaded extraction.
Archive.extract now uses threaded extraction
2026-02-09 13:45:38 -06:00
Caleb J. Gardner 5ec12b5786 Finished adding multi-threaded extraction.
Added option in unsquashfs to specify the number of threads used.
Changed some functions to accept an allocator instead of just using
Archive's
Fixed run_tests.sh due to new c libraries
2026-02-08 15:14:35 -06:00
Caleb J. Gardner b892adacd7 Some work for threaded extraction 2026-02-08 10:27:35 -06:00
Caleb J. Gardner 2760ad6ccb Finished adding (untested) C decompression libraries. 2026-02-08 06:52:14 -06:00
Caleb J. Gardner 61311433b9 unsquashfs --help 2026-02-07 17:20:03 -06:00
Caleb J. Gardner 053d64a954 Some cleanup to build zon.
Minor start on threaded extraction
2026-02-07 17:20:03 -06:00
Caleb Gardner 0e0222cd02 Fix formatting for build flags in README 2026-02-07 11:34:34 -06:00
Caleb Gardner 9c0dfbadc2 Merge pull request #2 from CalebQ42/extract_expirement
Extract expirement
2026-02-07 11:31:51 -06:00
Caleb J. Gardner db2fb4b9f2 Fixed compilation when using zig test.
Remove option for static since -static should suffice in most situations
2026-02-07 11:24:58 -06:00
Caleb J. Gardner 067eaa87c2 You can now set when building to use c or zig libraries. 2026-02-07 10:58:32 -06:00
Caleb J. Gardner b64a3ec44a Archive.extract now directly uses Inode instead of File. 2026-02-07 06:57:55 -06:00
Caleb J. Gardner 704215e1a9 Remove updateTimes because I was running into integer overlow issues. 2026-02-07 06:42:34 -06:00
Caleb J. Gardner bcfd983f8d Fixed a handful of errors when extracting.
Fixed issues with unsquashfs
2026-02-07 06:28:27 -06:00
Caleb J. Gardner 75502da1d0 Remove DecompMgr in favor of a much simpler fn ptr.
Moved more functionality to Inode instead of File.
Started doing some optimization around allocation.
Slight rework of ExtractionOptions.
2026-02-07 05:09:17 -06:00
Caleb J. Gardner a316ba569f Renamed File.dev to File.devNum 2026-02-06 23:22:21 -06:00
Caleb J. Gardner a0f3f45885 Unsquashfs 2026-02-06 09:51:27 -06:00
Caleb J. Gardner f771ef7623 Work on extraction 2026-02-06 06:56:28 -06:00
Caleb J. Gardner 0d2576f5ee Fixed tables getting re-created 2026-01-31 06:39:41 -06:00
Caleb J. Gardner a76803aad1 Comments! 2026-01-31 05:14:00 -06:00
Caleb J. Gardner 1ff1e91d5e Added ExtractCompleteArchive test 2026-01-29 22:23:46 -06:00
Caleb J. Gardner 2bcbc16613 Extraction Finished-ish 2026-01-29 06:46:52 -06:00
Caleb J. Gardner 3c98cf2cdb EXTRACTION 2026-01-28 11:19:38 -06:00
Caleb J. Gardner 2c392cf250 Fixed a few bugs preventing basic functions
Worked on extraction, including creating DataReader
Added proper access to id, fragment, and export tables
2026-01-28 06:55:01 -06:00
Caleb J. Gardner 5d4e7b1435 Fixed things 2026-01-25 07:06:30 -06:00
Caleb J. Gardner 7aed59b5b1 Some fixes 2026-01-17 05:30:19 -06:00
Caleb J. Gardner f3fb8a128f Inodes! ExtractionOptions! Files! Directories! 2026-01-16 06:53:10 -06:00
Caleb J. Gardner 23bb19644b Finished (?) decompression, metadata reader, and Table. 2026-01-15 11:22:02 -06:00
Caleb J. Gardner ed14f13d9a Some work on tests & metadata 2026-01-15 06:56:43 -06:00
Caleb J. Gardner 428f938c3a Restart (once again) 2026-01-15 06:40:59 -06:00
37 changed files with 2646 additions and 1 deletions
+31
View File
@@ -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
+3
View File
@@ -1,2 +1,5 @@
testing/
.zig-cache/
zig-out/
zig-pkg/
+25
View File
@@ -0,0 +1,25 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[
{
"label": "Build & Run",
"adapter": "CodeLLDB",
"request": "launch",
"build": {
"command": "zig",
"args": ["build", "-Duse_c_libs=true", "-Ddebug=true"],
},
"program": "zig-out/bin/unsquashfs",
"args": [
"--force",
"-d",
"testing/TestExtractUnsquashfs",
"testing/LinuxPATest.sfs",
],
},
]
+1 -1
View File
@@ -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
+75
View File
@@ -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;
```
+121
View File
@@ -0,0 +1,121 @@
const std = @import("std");
pub fn build(b: *std.Build) !void {
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse true;
// 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.") orelse false;
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
const zig_squashfs_options = b.addOptions();
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
// zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const mod = b.addModule("zig_squashfs", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = if (debug == true) .Debug else optimize,
.valgrind = debug,
.error_tracing = debug,
.strip = if (debug == true) false else null,
});
mod.addOptions("config", zig_squashfs_options);
if (!use_zig_decomp) {
mod.link_libc = true;
const c_imports = b.addTranslateC(.{
.optimize = optimize,
.target = target,
.root_source_file = b.path("src/imports.c"),
});
mod.addImport("c", c_imports.createModule());
var zlib_ng = b.dependency("zlib_ng", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(zlib_ng.artifact("zng"));
mod.linkSystemLibrary("lzma", .{ .preferred_link_mode = .static });
var minilzo = b.dependency("minilzo", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(minilzo.artifact("minilzo"));
var lz4 = b.dependency("lz4", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(lz4.artifact("lz4"));
var zstd = b.dependency("zstd", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(zstd.artifact("zstd"));
}
var version = version_string_option orelse "0.0.0-testing";
if (version[0] == 'v') version = version[1..];
const unsquashfs_options = b.addOptions();
unsquashfs_options.addOption(
std.SemanticVersion,
"version",
try std.SemanticVersion.parse(version),
);
var exe_mod = b.createModule(.{
.root_source_file = b.path("src/bin/unsquashfs.zig"),
.target = target,
.optimize = if (debug == true) .Debug else optimize,
.link_libc = !use_zig_decomp,
.imports = &.{
.{ .name = "zig_squashfs", .module = mod },
},
.valgrind = debug,
.error_tracing = debug,
.strip = if (debug == true) false else null,
});
exe_mod.addOptions("config", unsquashfs_options);
const exe = b.addExecutable(.{
.name = "unsquashfs",
.root_module = exe_mod,
.use_llvm = debug,
});
const lib = b.addLibrary(.{
.name = "squashfs",
.root_module = mod,
.use_llvm = debug,
});
b.installArtifact(lib);
b.installArtifact(exe);
const mod_tests = b.addTest(.{
.root_module = mod,
.test_runner = .{
.mode = .simple,
.path = b.path("src/test.zig"),
},
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
// zls build check steps
const lib_check = b.addLibrary(.{
.name = "squashfs",
.root_module = mod,
});
const exe_check = b.addExecutable(.{
.name = "unsquashfs",
.root_module = exe_mod,
});
const check = b.step("check", "Check if unsquashfs compiles");
check.dependOn(&exe_check.step);
check.dependOn(&lib_check.step);
}
+38
View File
@@ -0,0 +1,38 @@
.{
.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",
.path = "../zig-zlib-ng",
},
.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",
.path = "../zig-minilzo",
},
// .fastlzma2 = .{
// .url = "git+https://github.com/allyourcodebase/fast-lzma2#d7615e0c957a62fcd6691b3fe9519a091885bfa2",
// .hash = "fastlzma2-0.0.0-gNWHgVeLAAD0Tlak3xhNcgpPSYcjyJppq0tlGmPKCC_V",
// },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"LICENSE",
"README.md",
},
}
+184
View File
@@ -0,0 +1,184 @@
const std = @import("std");
const Io = std.Io;
const DecompTypes = @import("decomp/types.zig");
const Decompressor = @import("decomp.zig");
const ExtractionOptions = @import("options.zig");
const File = @import("file.zig");
const Inode = @import("inode.zig");
const BlockSize = @import("inode/file.zig").BlockSize;
const LookupTable = @import("lookup_table.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const Utils = @import("util/utils.zig");
pub const Error = error{
BadMagic,
BadBlockLog,
BadVersion,
BadCheck,
};
const Archive = @This();
file: OffsetFile,
super: Superblock,
stateless_decomp: Decompressor,
/// Create an Archive from a File.
pub fn init(io: Io, fil: Io.File, offset: u64) !Archive {
var super: Superblock = undefined;
var fil_rdr = fil.reader(io, &[0]u8{});
if (offset > 0)
try fil_rdr.seekTo(offset);
try fil_rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
try super.validate();
return .{
.file = .{ .fil = fil, .offset = offset },
.super = super,
.stateless_decomp = .{ .vtable = &.{ .stateless = try DecompTypes.getStatelessFn(super.compression) } },
};
}
pub fn root(self: Archive, alloc: std.mem.Allocator) !File {
return .{
.file = self.file,
.super = self.super.toMinimal(),
.decomp = self.stateless_decomp.statelessCopy(alloc),
.inode = try Utils.readInode(
alloc,
&self.stateless_decomp,
self.file,
self.super.inode_start,
self.super.block_size,
self.super.root_ref.block_start,
self.super.root_ref.block_offset,
),
.name = "",
};
}
pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !File {
if (Utils.pathIsSelf(path)) return self.root(alloc);
var root_file = self.root(alloc);
defer root_file.deinit();
return root_file.open(alloc, path);
}
pub fn fragEntry(self: Archive, idx: u32) !FragEntry {
return LookupTable.stateless(FragEntry, self.fil, &self.stateless_decomp, self.super.frag_start, idx);
}
pub fn id(self: Archive, idx: u32) !u16 {
return LookupTable.stateless(u16, self.fil, &self.stateless_decomp, self.super.id_start, idx);
}
pub fn inode(self: Archive, alloc: std.mem.Allocator, inode_num: u32) !Inode {
const ref = try LookupTable.stateless(Inode.Ref, self.file, &self.stateless_decomp, self.super.export_start, inode_num - 1);
return Utils.readInode(
alloc,
&self.stateless_decomp,
self.file,
self.super.inode_start,
self.super.block_size,
ref.block_start,
ref.block_offset,
);
}
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
_ = self;
_ = alloc;
_ = path;
_ = options;
return error.TODO;
}
// Superblock
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
pub const Superblock = packed struct {
magic: u32,
inode_count: u32,
mod_time: u32,
block_size: u32,
frag_count: u32,
compression: DecompTypes.Enum,
block_log: u16,
flags: packed struct {
inode_uncompressed: bool,
data_uncompressed: bool,
check: bool,
frag_uncompressed: bool,
fragment_never: bool,
fragment_always: bool,
duplicates: bool,
exportable: bool,
xattr_uncompressed: bool,
xattr_never: bool,
compression_options: bool,
ids_uncompressed: bool,
_: u4,
},
id_count: u16,
ver_maj: u16,
ver_min: u16,
root_ref: Inode.Ref,
size: u64,
id_start: u64,
xattr_start: u64,
inode_start: u64,
dir_start: u64,
frag_start: u64,
export_start: u64,
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive.
fn validate(self: Superblock) !void {
if (self.magic != SQUASHFS_MAGIC)
return Error.BadMagic;
if (self.flags.check)
return Error.BadCheck;
if (self.ver_maj != 4 or self.ver_min != 0)
return Error.BadVersion;
if (std.math.log2(self.block_size) != self.block_log)
return Error.BadBlockLog;
}
pub fn toMinimal(self: Superblock) MinimalSuperblock {
return .{
.inode_count = self.inode_count,
.block_size = self.block_size,
.frag_count = self.frag_count,
.id_count = self.id_count,
.id_start = self.id_start,
.xattr_start = self.xattr_start,
.inode_start = self.inode_start,
.dir_start = self.dir_start,
.frag_start = self.frag_start,
.export_start = self.export_start,
};
}
};
pub const MinimalSuperblock = struct {
inode_count: u32,
block_size: u32,
frag_count: u32,
id_count: u16,
id_start: u64,
xattr_start: u64,
inode_start: u64,
dir_start: u64,
frag_start: u64,
export_start: u64,
};
// Frag Entry
pub const FragEntry = packed struct {
block_start: u64,
size: BlockSize,
_: u32,
};
+135
View File
@@ -0,0 +1,135 @@
const std = @import("std");
const Io = std.Io;
const Writer = Io.Writer;
const builtin = @import("builtin");
const config = @import("config");
const squashfs = @import("zig_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 = std.Io.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: Io.File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
defer fil.close(io);
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully.
const options: squashfs.ExtractionOptions = .{
.threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
.verbose = verbose,
.verbose_writer = if (verbose) &out.interface else null,
.ignore_xattr = ignore_xattrs,
.ignore_permissions = ignore_permissions,
};
if (force)
try Io.Dir.cwd().deleteTree(io, extLoc);
try arc.extract(alloc, 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.next(); // 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;
}
}
+31
View File
@@ -0,0 +1,31 @@
const std = @import("std");
pub const StatelessDecomp = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize;
pub const Error = error{
OutOfMemory,
EndOfStream,
ReadFailed,
WriteFailed,
};
const Decompressor = @This();
alloc: std.mem.Allocator = std.heap.smp_allocator,
vtable: *const struct {
decompress: *const fn (*const Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress,
stateless: StatelessDecomp,
},
/// Create a copy of the decompressor using it's stateless function and the new allocator.
pub fn statelessCopy(self: Decompressor, alloc: std.mem.Allocator) Decompressor {
return &.{ .alloc = alloc, .vtable = &.{ .stateless = self.vtable.stateless } };
}
pub fn decompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
return self.vtable.decompress(self, in, out);
}
fn defaultDecompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
return self.vtable.stateless(self.alloc, in, out);
}
+12
View File
@@ -0,0 +1,12 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
if (res > 0) return @abs(res);
return Decompressor.Error.ReadFailed; // TOOD: Find out what errors can be returned.
}
+142
View File
@@ -0,0 +1,142 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Lzma = @This();
streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Lzma {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Lzma) void {
var values = self.streams.valueIterator();
while (values.next()) |val| {
c.lzma_end(val);
}
self.streams.deinit();
}
fn getOrCreate(self: *Lzma) !*c.lzma_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{
.alloc = .{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = &self.interface.alloc,
},
};
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Lzma = @fieldParentPtr("interface", decomp);
const stream = try self.getOrCreate();
stream.next_in = in.ptr;
stream.avail_in = in.len;
stream.next_out = out.ptr;
stream.avail_out = out.len;
var res = c.lzma_alone_decoder(stream, out.len);
decodeResult(res) catch |err| {
self.err = err;
return lzmaErrorToDecompError(err);
};
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| {
self.err = err;
return lzmaErrorToDecompError(err);
};
}
return stream.total_out;
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var stream: c.lzma_stream = .{
.next_in = in.ptr,
.avail_in = in.len,
.next_out = out.ptr,
.avail_out = out.len,
.allocator = &.{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = @ptrCast(@constCast(&alloc)),
},
};
var res = c.lzma_alone_decoder(&stream, out.len);
decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
}
return stream.total_out;
}
inline fn decodeResult(res: c_uint) Error!void {
return switch (res) {
c.LZMA_OK => {},
c.LZMA_STREAM_END => {},
c.LZMA_NO_CHECK => {},
c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck,
c.LZMA_MEM_ERROR => Error.OutOfMemory,
c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory,
c.LZMA_FORMAT_ERROR => Error.Format,
c.LZMA_OPTIONS_ERROR => Error.Options,
c.LZMA_DATA_ERROR => Error.Data,
c.LZMA_BUF_ERROR => Error.BufferExhausted,
c.LZMA_PROG_ERROR => Error.Programming,
c.LZMA_SEEK_NEEDED => Error.SeekNeeded,
else => Error.Unknown,
};
}
fn lzmaErrorToDecompError(err: Error) Decompressor.Error {
switch (err) {
Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
Error.Format => return Decompressor.Error.ReadFailed,
Error.Options => return Decompressor.Error.ReadFailed,
Error.Data => return Decompressor.Error.ReadFailed,
Error.BufferExhausted => return Decompressor.Error.WriteFailed,
Error.Programming => return Decompressor.Error.ReadFailed,
Error.SeekNeeded => return Decompressor.Error.ReadFailed,
Error.Unknown => return Decompressor.Error.ReadFailed,
}
}
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
return alloc.rawAlloc(size, .@"1", 0);
}
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
if (alloc_ptr == null) return;
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
}
pub const Error = error{
OutOfMemory,
UnsupportedCheck,
Format,
Options,
Data,
BufferExhausted,
Programming,
SeekNeeded,
Unknown,
};
+28
View File
@@ -0,0 +1,28 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var out_len = out.len;
const res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null);
return switch (res) {
c.LZO_E_OK => out_len,
c.LZO_E_ERROR => Decompressor.Error.ReadFailed,
c.LZO_E_OUT_OF_MEMORY => Decompressor.Error.OutOfMemory,
c.LZO_E_NOT_COMPRESSIBLE => Decompressor.Error.ReadFailed,
c.LZO_E_INPUT_OVERRUN => Decompressor.Error.ReadFailed,
c.LZO_E_OUTPUT_OVERRUN => Decompressor.Error.WriteFailed,
c.LZO_E_LOOKBEHIND_OVERRUN => Decompressor.Error.ReadFailed,
c.LZO_E_EOF_NOT_FOUND => Decompressor.Error.ReadFailed,
c.LZO_E_INPUT_NOT_CONSUMED => Decompressor.Error.ReadFailed,
c.LZO_E_NOT_YET_IMPLEMENTED => Decompressor.Error.ReadFailed,
c.LZO_E_INVALID_ARGUMENT => Decompressor.Error.ReadFailed,
c.LZO_E_INVALID_ALIGNMENT => Decompressor.Error.ReadFailed,
c.LZO_E_OUTPUT_NOT_CONSUMED => Decompressor.Error.WriteFailed,
c.LZO_E_INTERNAL_ERROR => Decompressor.Error.ReadFailed,
else => Decompressor.Error.ReadFailed,
};
}
+142
View File
@@ -0,0 +1,142 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Xz = @This();
streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Xz {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Xz) void {
var values = self.streams.valueIterator();
while (values.next()) |val| {
c.xz_end(val);
}
self.streams.deinit();
}
fn getOrCreate(self: *Xz) !*c.xz_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{
.alloc = .{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = &self.interface.alloc,
},
};
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Xz = @fieldParentPtr("interface", decomp);
const stream = try self.getOrCreate();
stream.next_in = in.ptr;
stream.avail_in = in.len;
stream.next_out = out.ptr;
stream.avail_out = out.len;
var res = c.lzma_alone_decoder(stream, out.len);
decodeResult(res) catch |err| {
self.err = err;
return xzErrorToDecompError(err);
};
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| {
self.err = err;
return xzErrorToDecompError(err);
};
}
return stream.total_out;
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var stream: c.lzma_stream = .{
.next_in = in.ptr,
.avail_in = in.len,
.next_out = out.ptr,
.avail_out = out.len,
.allocator = &.{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = @ptrCast(@constCast(&alloc)),
},
};
var res = c.lzma_alone_decoder(&stream, out.len);
decodeResult(res) catch |err| return xzErrorToDecompError(err);
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| return xzErrorToDecompError(err);
}
return stream.total_out;
}
inline fn decodeResult(res: c_uint) Error!void {
return switch (res) {
c.LZMA_OK => {},
c.LZMA_STREAM_END => {},
c.LZMA_NO_CHECK => {},
c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck,
c.LZMA_MEM_ERROR => Error.OutOfMemory,
c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory,
c.LZMA_FORMAT_ERROR => Error.Format,
c.LZMA_OPTIONS_ERROR => Error.Options,
c.LZMA_DATA_ERROR => Error.Data,
c.LZMA_BUF_ERROR => Error.BufferExhausted,
c.LZMA_PROG_ERROR => Error.Programming,
c.LZMA_SEEK_NEEDED => Error.SeekNeeded,
else => Error.Unknown,
};
}
fn xzErrorToDecompError(err: Error) Decompressor.Error {
switch (err) {
Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
Error.Format => return Decompressor.Error.ReadFailed,
Error.Options => return Decompressor.Error.ReadFailed,
Error.Data => return Decompressor.Error.ReadFailed,
Error.BufferExhausted => return Decompressor.Error.WriteFailed,
Error.Programming => return Decompressor.Error.ReadFailed,
Error.SeekNeeded => return Decompressor.Error.ReadFailed,
Error.Unknown => return Decompressor.Error.ReadFailed,
}
}
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
return alloc.rawAlloc(size, .@"1", 0);
}
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
if (alloc_ptr == null) return;
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
}
pub const Error = error{
OutOfMemory,
UnsupportedCheck,
Format,
Options,
Data,
BufferExhausted,
Programming,
SeekNeeded,
Unknown,
};
+106
View File
@@ -0,0 +1,106 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Zlib = @This();
streams: std.AutoHashMap(std.Thread.Id, c.zng_stream),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Zlib {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Zlib) void {
var values = self.streams.valueIterator();
while (values.next()) |val| {
_ = c.zng_deflateEnd(val);
}
self.streams.deinit();
}
fn getOrCreate(self: *Zlib) !*c.zng_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{
.@"opaque" = self,
.zalloc = zalloc,
.zfree = zfree,
};
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Zlib = @fieldParentPtr("interface", decomp);
var stream = try self.getOrCreate();
stream.next_in = in.ptr;
stream.avail_in = in.len;
stream.next_out = out.ptr;
stream.avail_out = out.len;
var res = c.zng_inflateReset(stream);
decodeError(res) catch |err| {
self.err = err;
return Decompressor.Error.ReadFailed;
};
res = c.zng_inflate(stream, c.Z_FINISH);
decodeError(res) catch |err| {
self.err = err;
return switch (err) {
Error.OutOfMemory => err,
else => Decompressor.Error.ReadFailed,
};
};
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
_ = alloc;
var out_len = out.len;
const res = c.zng_uncompress(out.ptr, &out_len, in.ptr, in.len);
return switch (res) {
c.Z_OK => out_len,
c.Z_MEM_ERROR => Decompressor.Error.OutOfMemory,
c.Z_BUF_ERROR => Decompressor.Error.WriteFailed,
else => Decompressor.Error.ReadFailed,
};
}
inline fn decodeError(res: i32) Error!void {
return switch (res) {
c.Z_OK => {},
c.Z_STREAM_ERROR => Error.Stream,
c.Z_BUF_ERROR => Error.Buffer,
c.Z_MEM_ERROR => Error.OutOfMemory,
c.Z_DATA_ERROR => Error.Data,
c.Z_VERSION_ERROR => Error.Version,
else => Error.Unknown,
};
}
fn zalloc(ptr: ?*anyopaque, items: c_uint, size: c_uint) callconv(.c) ?*anyopaque {
var self: *Zlib = @ptrCast(ptr);
return self.interface.alloc.rawAlloc(items * size, .@"1", 0);
}
fn zfree(ptr: ?*anyopaque, addr: ?*anyopaque) callconv(.c) void {
var self: *Zlib = @ptrCast(ptr);
self.interface.alloc.rawFree(@ptrCast(addr), .@"1", 0);
}
pub const Error = error{
OutOfMemory,
Stream,
Buffer,
Data,
Version,
Unknown,
};
+172
View File
@@ -0,0 +1,172 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Zstd = @This();
context: std.AutoHashMap(std.Thread.Id, ?*c.ZSTD_DCtx),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Zstd {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Zstd) void {
var values = self.context.valueIterator();
while (values.next()) |val| {
_ = c.ZSTD_freeDCtx(val.*);
}
self.context.deinit();
}
fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx {
const res = try self.context.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = c.ZSTD_createDCtx();
if (res.value_ptr.* == null) return Error.OutOfMemory;
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Zstd = @fieldParentPtr("interface", decomp);
const ctx = self.getOrCreate();
const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len);
decodeError(res) catch |err| {
self.err = err;
return ZstdErrorToDecompError(err);
};
return res;
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
_ = alloc;
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
decodeError(res) catch |err| return ZstdErrorToDecompError(err);
return res;
}
inline fn decodeError(res: usize) Error!void {
if (c.ZSTD_isError(res) == 0) return;
return switch (c.ZSTD_getErrorCode(res)) {
c.ZSTD_error_prefix_unknown => Error.PrefixUnknown,
c.ZSTD_error_version_unsupported => Error.VersionUnsupported,
c.ZSTD_error_frameParameter_unsupported => Error.FrameParameterUnsupported,
c.ZSTD_error_frameParameter_windowTooLarge => Error.FrameParameterWindowTooLarge,
c.ZSTD_error_corruption_detected => Error.CorruptionDetected,
c.ZSTD_error_checksum_wrong => Error.ChecksumWrong,
c.ZSTD_error_literals_headerWrong => Error.LiteralsHeaderWrong,
c.ZSTD_error_dictionary_corrupted => Error.DictionaryCorrupted,
c.ZSTD_error_dictionary_wrong => Error.DictionaryWrong,
c.ZSTD_error_dictionaryCreation_failed => Error.DictionaryCreationFailed,
c.ZSTD_error_parameter_unsupported => Error.ParameterUnsupported,
c.ZSTD_error_parameter_combination_unsupported => Error.ParameterCombinationUnsupported,
c.ZSTD_error_parameter_outOfBound => Error.ParameterOutOfBound,
c.ZSTD_error_tableLog_tooLarge => Error.TableLogTooLarge,
c.ZSTD_error_maxSymbolValue_tooLarge => Error.MaxSymbolValueTooLarge,
c.ZSTD_error_maxSymbolValue_tooSmall => Error.MaxSymbolValueTooSmall,
c.ZSTD_error_cannotProduce_uncompressedBlock => Error.CannotProduceUncompressedBlock,
c.ZSTD_error_stabilityCondition_notRespected => Error.StabilityConditionNotRespected,
c.ZSTD_error_stage_wrong => Error.StageWrong,
c.ZSTD_error_init_missing => Error.InitMissing,
c.ZSTD_error_memory_allocation => Error.MemoryAllocation,
c.ZSTD_error_workSpace_tooSmall => Error.WorkSpaceTooSmall,
c.ZSTD_error_dstSize_tooSmall => Error.DstSizeTooSmall,
c.ZSTD_error_srcSize_wrong => Error.SrcSizeWrong,
c.ZSTD_error_dstBuffer_null => Error.DstBufferNull,
c.ZSTD_error_noForwardProgress_destFull => Error.NoForwardProgressDestFull,
c.ZSTD_error_noForwardProgress_inputEmpty => Error.NoForwardProgressInputEmpty,
c.ZSTD_error_frameIndex_tooLarge => Error.FrameIndexTooLarge,
c.ZSTD_error_seekableIO => Error.SeekableIo,
c.ZSTD_error_dstBuffer_wrong => Error.DstBufferWrong,
c.ZSTD_error_srcBuffer_wrong => Error.SrcBufferWrong,
c.ZSTD_error_sequenceProducer_failed => Error.SequenceProducerFailed,
c.ZSTD_error_externalSequences_invalid => Error.ExternalSequencesInvalid,
else => Error.Generic,
};
}
inline fn ZstdErrorToDecompError(err: Error) Decompressor.Error {
return switch (err) {
Error.OutOfMemory => Decompressor.Error.OutOfMemory,
Error.Generic => Decompressor.Error.ReadFailed,
Error.PrefixUnknown => Decompressor.Error.ReadFailed,
Error.VersionUnsupported => Decompressor.Error.ReadFailed,
Error.FrameParameterUnsupported => Decompressor.Error.ReadFailed,
Error.FrameParameterWindowTooLarge => Decompressor.Error.ReadFailed,
Error.CorruptionDetected => Decompressor.Error.ReadFailed,
Error.ChecksumWrong => Decompressor.Error.ReadFailed,
Error.LiteralsHeaderWrong => Decompressor.Error.ReadFailed,
Error.DictionaryCorrupted => Decompressor.Error.ReadFailed,
Error.DictionaryWrong => Decompressor.Error.ReadFailed,
Error.DictionaryCreationFailed => Decompressor.Error.ReadFailed,
Error.ParameterUnsupported => Decompressor.Error.ReadFailed,
Error.ParameterCombinationUnsupported => Decompressor.Error.ReadFailed,
Error.ParameterOutOfBound => Decompressor.Error.ReadFailed,
Error.TableLogTooLarge => Decompressor.Error.ReadFailed,
Error.MaxSymbolValueTooLarge => Decompressor.Error.ReadFailed,
Error.MaxSymbolValueTooSmall => Decompressor.Error.ReadFailed,
Error.CannotProduceUncompressedBlock => Decompressor.Error.ReadFailed,
Error.StabilityConditionNotRespected => Decompressor.Error.ReadFailed,
Error.StageWrong => Decompressor.Error.ReadFailed,
Error.InitMissing => Decompressor.Error.ReadFailed,
Error.MemoryAllocation => Decompressor.Error.OutOfMemory,
Error.WorkSpaceTooSmall => Decompressor.Error.WriteFailed,
Error.DstSizeTooSmall => Decompressor.Error.WriteFailed,
Error.SrcSizeWrong => Decompressor.Error.ReadFailed,
Error.DstBufferNull => Decompressor.Error.WriteFailed,
Error.NoForwardProgressDestFull => Decompressor.Error.WriteFailed,
Error.NoForwardProgressInputEmpty => Decompressor.Error.ReadFailed,
Error.FrameIndexTooLarge => Decompressor.Error.ReadFailed,
Error.SeekableIo => Decompressor.Error.ReadFailed,
Error.DstBufferWrong => Decompressor.Error.WriteFailed,
Error.SrcBufferWrong => Decompressor.Error.ReadFailed,
Error.SequenceProducerFailed => Decompressor.Error.ReadFailed,
Error.ExternalSequencesInvalid => Decompressor.Error.ReadFailed,
};
}
pub const Error = error{
OutOfMemory,
Generic,
PrefixUnknown,
VersionUnsupported,
FrameParameterUnsupported,
FrameParameterWindowTooLarge,
CorruptionDetected,
ChecksumWrong,
LiteralsHeaderWrong,
DictionaryCorrupted,
DictionaryWrong,
DictionaryCreationFailed,
ParameterUnsupported,
ParameterCombinationUnsupported,
ParameterOutOfBound,
TableLogTooLarge,
MaxSymbolValueTooLarge,
MaxSymbolValueTooSmall,
CannotProduceUncompressedBlock,
StabilityConditionNotRespected,
StageWrong,
InitMissing,
MemoryAllocation,
WorkSpaceTooSmall,
DstSizeTooSmall,
SrcSizeWrong,
DstBufferNull,
NoForwardProgressDestFull,
NoForwardProgressInputEmpty,
FrameIndexTooLarge,
SeekableIo,
DstBufferWrong,
SrcBufferWrong,
SequenceProducerFailed,
ExternalSequencesInvalid,
};
+64
View File
@@ -0,0 +1,64 @@
const config = @import("config");
const Decompressor = @import("../decomp.zig");
pub fn getStatelessFn(decomp: Enum) !Decompressor.StatelessDecomp {
if (config.use_zig_decomp) {
return switch (decomp) {
.gzip => @import("zig/zlib.zig").stateless,
.lzma => @import("zig/lzma.zig").stateless,
.xz => @import("zig/xz.zig").stateless,
.zstd => @import("zig/zstd.zig").stateless,
.lz4 => error.ZigLz4Unsupported,
.lzo => error.ZigLzoUnsupported,
};
}
return switch (decomp) {
.gzip => @import("c/zlib.zig").stateless,
.lzma => @import("c/lzma.zig").stateless,
.lzo => @import("c/lzo.zig").stateless,
.xz => @import("c/xz.zig").stateless,
.lz4 => @import("c/lz4.zig").stateless,
.zstd => @import("c/zstd.zig").stateless,
};
}
pub const Enum = enum(u16) {
gzip = 1, // Though officially named gzip, it actually uses zlib.
lzma,
lzo,
xz,
lz4,
zstd,
};
pub const Decomp = if (config.use_zig_decomp)
union(enum) {
gzip: @import("zig/zlib.zig"),
lzma: @import("zig/lzma.zig"),
xz: @import("zig/xz.zig"),
zstd: @import("zig/zstd.zig"),
pub fn deinit(_: *Decomp) void {
return;
}
}
else
union(enum) {
gzip: @import("c/zlib.zig"),
lzma: @import("c/lzma.zig"),
lzo: @import("c/lzo.zig"),
xz: @import("c/xz.zig"),
lz4: @import("c/lz4.zig"),
zstd: @import("c/zstd.zig"),
pub fn deinit(self: *Decomp) void {
switch (self) {
.gzip => self.gzip.deinit(),
.lzma => self.lzma.deinit(),
.xz => self.xz.deinit(),
.zstd => self.zstd.deinit(),
else => {},
}
}
};
+23
View File
@@ -0,0 +1,23 @@
const std = @import("std");
const lzma = std.compress.lzma;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
var decomp = lzma.Decompress.initOptions(&rdr, alloc, buf, .{}, out.len) catch |err|
return switch (err) {
error.Overflow => Decompressor.Error.ReadFailed,
error.CorruptInput => Decompressor.Error.ReadFailed,
error.InvalidRangeCode => Decompressor.Error.ReadFailed,
else => @errorCast(err),
};
defer decomp.deinit();
return decomp.reader.readSliceShort(out);
}
+22
View File
@@ -0,0 +1,22 @@
const std = @import("std");
const xz = std.compress.xz;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
var decomp = xz.Decompress.init(&rdr, alloc, buf) catch |err|
return switch (err) {
error.WrongChecksum => Decompressor.Error.ReadFailed,
error.NotXzStream => Decompressor.Error.ReadFailed,
else => @errorCast(err),
};
defer decomp.deinit();
return decomp.reader.readSliceShort(out);
}
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
const flate = std.compress.flate;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, out.len);
defer alloc.free(buf);
var decomp = flate.Decompress.init(&rdr, .zlib, buf);
return decomp.reader.readSliceShort(out);
}
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
const zstd = std.compress.zstd;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, out.len * 2);
defer alloc.free(buf);
var decomp = zstd.Decompress.init(&rdr, buf, .{ .window_len = @truncate(out.len) });
return decomp.reader.readSliceShort(out);
}
+63
View File
@@ -0,0 +1,63 @@
const std = @import("std");
const Reader = std.Io.Reader;
const InodeType = @import("inode.zig").Type;
pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) []Entry {
var read: u32 = 3;
var hdr: Header = undefined;
var raw: RawEntry = undefined;
var out: std.ArrayList(Entry) = .initCapacity(alloc, 50);
errdefer {
for (out.items) |i|
alloc.free(i.name);
out.deinit(alloc);
}
while (read < size) {
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
read += @sizeOf(Header);
for (0..hdr.count + 1) |_| {
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
read += @sizeOf(RawEntry) + raw.name_size + 1;
const new = out.addOneAssumeCapacity();
new.* = .{
.block_start = hdr.block_start,
.block_offset = raw.block_offset,
.num = @abs(hdr.num + raw.num_offset),
.inode_type = raw.inode_type,
.name = try alloc.alloc(u8, raw.name_size + 1),
};
try rdr.readSliceEndian(u8, new.name, .little);
}
}
return out.toOwnedSlice(alloc);
}
// Types
pub const Entry = struct {
block_start: u32,
block_offset: u16,
num: u32,
inode_type: InodeType,
name: []u8,
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
};
// extern instead of packed due to alignment issues (packed will read it as 16 bytes instead of 12).
const Header = extern struct {
count: u32,
block_start: u32,
num: u32,
};
const RawEntry = packed struct {
block_offset: u16,
num_offset: i16,
inode_type: InodeType,
name_size: u16,
};
+118
View File
@@ -0,0 +1,118 @@
const std = @import("std");
const Io = std.Io;
const Archive = @import("archive.zig");
const Decompressor = @import("decomp.zig");
const Directory = @import("directory.zig");
const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig");
const DataReader = @import("util/data_reader.zig");
const FileIter = @import("util/iter.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const Utils = @import("util/utils.zig");
pub const Error = error{
NotDirectory,
NotRegularFile,
};
const File = @This();
file: OffsetFile,
super: Archive.MinimalSuperblock,
decomp: Decompressor,
name: []const u8,
inode: Inode,
pub fn init(alloc: std.mem.Allocator, archive: Archive, entry: Directory.Entry) !File {
const new_name = try alloc.alloc(u8, entry.name.len);
errdefer alloc.free(new_name);
@memcpy(new_name, entry.name);
return .{
.file = archive.file,
.super = archive.super,
.decomp = archive.stateless_decomp.statelessCopy(alloc),
.name = new_name,
.inode = try Utils.readInode(
alloc,
&archive.decomp,
archive.file,
archive.super.inode_start,
archive.super.block_size,
entry.block_start,
entry.block_offset,
),
};
}
pub fn deinit(self: File) void {
self.decomp.alloc.free(self.name);
self.inode.deinit(self.decomp.alloc);
}
// Directory functions
pub fn isDir(self: File) bool {
return switch (self.inode.hdr.inode_type) {
.dir, .ext_dir => true,
else => false,
};
}
/// Opens a sub-file. If the given path is "" or "." (after trimming /) a copy of the File is returned.
pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
var res = try self.inode.findInode(
alloc,
&self.decomp,
self.file,
self.super.dir_start,
self.super.inode_start,
self.super.block_size,
filepath,
);
if (res.name.len == 0) {
res.name = try alloc.alloc(u8, self.name.len);
@memcpy(res.name, self.name);
}
return .{
.file = self.file,
.super = self.super,
.decomp = self.decomp.statelessCopy(alloc),
.name = res.name,
.inode = res.inode,
};
}
pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter {
return .{
.alloc = alloc,
.entries = try self.inode.readDirectory(alloc, &self.decomp, self.file, self.super.dir_start),
};
}
// Regular file functions
pub fn isRegularFile(self: File) bool {
return switch (self.inode.hdr.inode_type) {
.file, .ext_file => true,
else => false,
};
}
// a std.Io.Reader compatible reader that reads a regular file's data.
pub fn dataReader(self: File, alloc: std.mem.Allocator) !DataReader {
return self.inode.dataReader(
&self.decomp.statelessCopy(alloc),
self.file,
self.super.frag_start,
self.super.block_size,
);
}
// Universal functions
pub fn extract(self: File, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
_ = self;
_ = alloc;
_ = path;
_ = options;
return error.TODO;
}
+5
View File
@@ -0,0 +1,5 @@
#include <zlib-ng.h>
#include <lzo/minilzo.h>
#include <lz4.h>
#include <zstd.h>
#include <lzma.h>
+355
View File
@@ -0,0 +1,355 @@
//! This is the raw squashfs representation of a file/directory.
//! Most of the time using File is a better experience and using Inodes directory
//! is only required for more technical use cases.
const std = @import("std");
const Reader = std.Io.Reader;
const Decompressor = @import("decomp.zig");
const Directory = @import("directory.zig");
const FragEntry = @import("archive.zig").FragEntry;
const Dir = @import("inode/dir.zig");
const File = @import("inode/file.zig");
const Misc = @import("inode/misc.zig");
const Sym = @import("inode/sym.zig");
const LookupTable = @import("lookup_table.zig");
const MinimalSuperblock = @import("archive.zig").MinimalSuperblock;
const DataReader = @import("util/data_reader.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const Inode = @This();
hdr: Header,
data: Data,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
var hdr: Header = undefined;
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
return .{
.hdr = hdr,
.data = switch (hdr.inode_type) {
.dir => .{ .dir = .read(rdr) },
.file => .{ .file = .read(alloc, rdr, block_size) },
.symlink => .{ .symlink = .read(alloc, rdr) },
.block => .{ .block = .read(rdr) },
.char => .{ .char = .read(rdr) },
.fifo => .{ .fifo = .read(rdr) },
.sock => .{ .sock = .read(rdr) },
.ext_dir => .{ .ext_dir = .read(rdr) },
.ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
.ext_block => .{ .ext_block = .read(rdr) },
.ext_char => .{ .ext_char = .read(rdr) },
.ext_fifo => .{ .ext_fifo = .read(rdr) },
.ext_sock => .{ .ext_sock = .read(rdr) },
},
};
}
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) {
.file => |f| alloc.free(f.block_sizes),
.ext_file => |f| alloc.free(f.block_sizes),
.symlink => |s| alloc.free(s.target),
.ext_symlink => |s| alloc.free(s.target),
else => {},
}
}
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
switch (self.data) {
.dir,
.ext_dir,
.block,
.ext_block,
.char,
.ext_char,
.fifo,
.ext_fifo,
.sock,
.ext_sock,
=> return self,
.file => |f| {
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
@memcpy(new_sizes, f.block_sizes);
return .{
.hdr = self.hdr,
.data = .{ .file = .{
.block_start = f.block_start,
.frag_idx = f.frag_idx,
.block_offset = f.block_offset,
.size = f.size,
.block_sizes = new_sizes,
} },
};
},
.ext_file => |f| {
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
@memcpy(new_sizes, f.block_sizes);
return .{
.hdr = self.hdr,
.data = .{ .ext_file = .{
.block_start = self.block_start,
.size = self.size,
.sparse = self.sparse,
.hard_links = self.hard_links,
.frag_idx = self.frag_idx,
.block_offset = self.block_offset,
.xattr_idx = self.xattr_idx,
.block_sizes = new_sizes,
} },
};
},
.symlink => |s| {
const new_target = try alloc.alloc(u8, s.target.len);
@memcpy(new_target, s.target);
return .{
.hdr = self.hdr,
.data = .{ .symlink = .{
.hard_links = s.hard_links,
.target = new_target,
} },
};
},
.ext_symlink => |s| {
const new_target = try alloc.alloc(u8, s.target.len);
@memcpy(new_target, s.target);
return .{
.hdr = self.hdr,
.data = .{ .ext_symlink = .{
.hard_links = s.hard_links,
.xattr_idx = s.xattr_idx,
.target = new_target,
} },
};
},
}
}
// Types
pub const Ref = packed struct {
block_offset: u16,
block_start: u32,
_: u16,
};
pub const Type = enum(u16) {
dir = 1,
file,
symlink,
block,
char,
fifo,
sock,
ext_dir,
ext_file,
ext_symlink,
ext_block,
ext_char,
ext_fifo,
ext_sock,
};
const Header = packed struct {
inode_type: Type,
permission: u16,
uid_idx: u16,
gid_idx: u16,
mod_time: u32,
num: u32,
};
pub const Data = union(Type) {
dir: Dir.Dir,
file: File.File,
symlink: Sym.Symlink,
block: Misc.Device,
char: Misc.Device,
fifo: Misc.Ipc,
sock: Misc.Ipc,
ext_dir: Dir.ExtDir,
ext_file: File.ExtFile,
ext_symlink: Sym.ExtSymlink,
ext_block: Misc.ExtDevice,
ext_char: Misc.ExtDevice,
ext_fifo: Misc.ExtIpc,
ext_sock: Misc.ExtIpc,
};
// Errors
pub const Error = error{
NotDirectory,
NotFound,
NotRegularFile,
};
// Utils functions
// Universal
pub fn uid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 {
return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.uid_idx);
}
pub fn uidCached(self: Inode, table: LookupTable.CachedTable(u16)) !u16 {
return table.get(self.hdr.uid_idx);
}
pub fn gid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 {
return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.gid_idx);
}
pub fn gidCached(self: Inode, table: LookupTable.CachedTable(u16)) !u16 {
return table.get(self.hdr.gid_idx);
}
pub fn xattr(self: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, xattr_start: u64) !?LookupTable.XattrValues {
if (@intFromEnum(self.hdr.inode_type) < 8) return null;
const idx: u32 = switch (self.data) {
.ext_dir => |d| d.xattr_idx,
.ext_file => |f| f.xattr_idx,
.ext_symlink => |s| s.xattr_idx,
.ext_block, .ext_char => |d| d.xattr_idx,
.ext_fifo, .ext_sock => |d| d.xattr_idx,
else => unreachable,
};
if (idx == 0xFFFFFFFF) return null;
return LookupTable.statelessXattr(alloc, fil, decomp, xattr_start, idx);
}
// Dir inodes
/// For directory inodes, tries to find the inode at the given path. Returns both the inode, and it's file name.
/// If the path is empty or "." then a copy of this inode is returned with no name ("").
pub fn findInode(
inode: Inode,
alloc: std.mem.Allocator,
decomp: *const Decompressor,
fil: OffsetFile,
dir_start: u64,
inode_start: u64,
block_size: u32,
filepath: []const u8,
) !struct { inode: Inode, name: []const u8 } {
switch (inode.data) {
.dir => |d| {
const path: []const u8 = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return .{ .inode = inode.copy(alloc), .name = "" };
return findInodeRaw(
alloc,
decomp,
fil,
dir_start,
inode_start,
block_size,
path,
d,
);
},
.ext_dir => |d| {
const path: []const u8 = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return .{ .inode = inode.copy(alloc), .name = "" };
return findInodeRaw(
alloc,
decomp,
fil,
dir_start,
inode_start,
block_size,
path,
d,
);
},
else => return Error.NotDirectory,
}
}
inline fn findInodeRaw(
inode: Inode,
alloc: std.mem.Allocator,
decomp: *const Decompressor,
fil: OffsetFile,
dir_start: u64,
inode_start: u64,
block_size: u32,
path: []const u8,
dat: anytype,
) !struct { inode: Inode, name: []const u8 } {
const first_element: []const u8 = std.mem.sliceTo(path, '/');
const dirs = try readDirRaw(alloc, decomp, fil, dir_start, dat);
defer {
for (dirs) |dir|
dir.deinit(alloc);
alloc.free(dirs);
}
// Directories are stored ASCIIbetically, so we can use binary search.
var cur_slice = dirs;
var idx: usize = 0;
while (cur_slice.len > 0) {
idx = cur_slice.len / 2;
const mid_name = cur_slice[idx].name;
switch (std.mem.order(u8, first_element, mid_name)) {
.gt => {
cur_slice = cur_slice[idx + 1 ..];
continue;
},
.lt => {
cur_slice = cur_slice[0..idx];
continue;
},
.eq => break,
}
} else return Error.NotFound;
const entry = cur_slice[idx];
var rdr = try fil.readerAt(inode_start + entry.block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(entry.block_offset);
const ret_inode: Inode = try .read(alloc, &meta_rdr.interface, block_size);
if (first_element.len == path.len) {
const name_copy = try alloc.alloc(u8, entry.name.len);
@memcpy(name_copy, entry.name.len);
return .{ .inode = ret_inode, .name = name_copy };
}
return inode.findInode(alloc, decomp, fil, dir_start, inode_start, block_size, path[first_element.len..]);
}
/// Get the directory entries for a directory inode.
pub fn readDirectory(inode: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64) ![]Directory.Entry {
return switch (inode.data) {
.dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
.ext_dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
else => Error.NotDirectory,
};
}
inline fn readDirRaw(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64, dat: anytype) ![]Directory.Entry {
var rdr = try fil.readerAt(dir_start + dat.block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(dat.block_offset);
return Directory.readDirectory(alloc, meta_rdr, dat.size);
}
// file inodes
/// Gets the data reader for a file inode.
pub fn dataReader(inode: Inode, decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32) !DataReader {
return switch (inode.data) {
.file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f),
.ext_file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f),
else => Error.NotRegularFile,
};
}
inline fn dataReaderRaw(decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32, dat: anytype) !DataReader {
return .init(
decomp,
fil,
block_size,
dat.block_sizes,
dat.size,
dat.block_start,
if (dat.frag_idx != 0xFFFFFFFF)
try LookupTable.stateless(FragEntry, fil, decomp, frag_start, dat.frag_idx)
else
null,
dat.frag_offset,
);
}
+35
View File
@@ -0,0 +1,35 @@
const Reader = @import("std").Io.Reader;
pub const Dir = packed struct {
block_start: u32,
hard_links: u32,
size: u16,
block_offset: u16,
parent_num: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const ExtDir = packed struct {
hard_links: u32,
size: u32,
block_start: u32,
parent_num: u32,
idx_count: u16,
block_offset: u16,
xattr_idx: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
+69
View File
@@ -0,0 +1,69 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const BlockSize = packed struct {
size: u31,
uncompressed: bool,
};
pub const File = struct {
block_start: u32,
frag_idx: u32,
frag_offset: u32,
size: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
var buf: [16]u8 = undefined;
try rdr.readSliceAll(&buf);
const frag_idx = std.mem.readVarInt(u32, buf[4..8], .little);
const size = std.mem.readVarInt(u32, buf[12..], .little);
const sizes_len = size / block_size;
if (frag_idx != 0xFFFFFFFF and size % block_size > 0)
sizes_len += 1;
const sizes = try alloc.alloc(BlockSize, sizes_len);
errdefer alloc.free(sizes);
try rdr.readSliceEndian(BlockSize, sizes, .little);
return .{
.block_start = std.mem.readVarInt(u32, buf[0..4], .little),
.frag_idx = frag_idx,
.frag_offset = std.mem.readVarInt(u32, buf[8..12], .little),
.size = size,
.block_sizes = sizes,
};
}
};
pub const ExtFile = struct {
block_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_offset: u32,
xattr_idx: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
var buf: [40]u8 = undefined;
try rdr.readSliceAll(&buf);
const frag_idx = std.mem.readVarInt(u32, buf[28..32], .little);
const size = std.mem.readVarInt(u64, buf[8..16], .little);
const sizes_len = size / block_size;
if (frag_idx != 0xFFFFFFFF and size % block_size > 0)
sizes_len += 1;
const sizes = try alloc.alloc(BlockSize, sizes_len);
errdefer alloc.free(sizes);
try rdr.readSliceEndian(BlockSize, sizes, .little);
return .{
.block_start = std.mem.readVarInt(u64, buf[0..8], .little),
.size = size,
.sparse = std.mem.readVarInt(u64, buf[16..24], .little),
.hard_links = std.mem.readVarInt(u32, buf[24..28], .little),
.frag_idx = frag_idx,
.frag_offset = std.mem.readVarInt(u32, buf[32..36], .little),
.xattr_idx = std.mem.readVarInt(u32, buf[36..40], .little),
.block_sizes = sizes,
};
}
};
+53
View File
@@ -0,0 +1,53 @@
const Reader = @import("std").Io.Reader;
pub const Device = packed struct {
hard_links: u32,
device: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const ExtDevice = packed struct {
hard_links: u32,
device: u32,
xattr_idx: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const Ipc = packed struct {
hard_links: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const ExtIpc = packed struct {
hard_links: u32,
xattr_idx: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
+42
View File
@@ -0,0 +1,42 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const Symlink = struct {
hard_links: u32,
target: []const u8,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
var buf: [8]u8 = undefined;
try rdr.readSliceAll(&buf);
const size = std.mem.readVarInt(u32, buf[4..], .little);
const target = try alloc.alloc(u8, size);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
return .{
.hard_links = std.mem.readVarInt(u32, buf[0..4], .little),
.target = target,
};
}
};
pub const ExtSymlink = struct {
hard_links: u32,
xattr_idx: u32,
target: []const u8,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
var buf: [8]u8 = undefined;
try rdr.readSliceAll(&buf);
const size = std.mem.readVarInt(u32, buf[4..], .little);
const target = try alloc.alloc(u8, size);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
var xattr_idx: u32 = undefined;
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
return .{
.hard_links = std.mem.readVarInt(u32, buf[0..4], .little),
.target = target,
.xattr_idx = xattr_idx,
};
}
};
+168
View File
@@ -0,0 +1,168 @@
const std = @import("std");
const Decompressor = @import("decomp.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
pub fn stateless(comptime T: anytype, fil: OffsetFile, decomp: *const Decompressor, table_start: u64, idx: u32) !T {
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
const block = idx / VALS_PER_BLOCK;
const block_idx = idx % VALS_PER_BLOCK;
const offset = try fil.valueAt(u64, table_start + (8 * block));
var buf: [8192]u8 = undefined;
var rdr = try fil.readerAt(offset, &buf);
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(@sizeOf(T) * block_idx);
var out: T = undefined;
try meta_rdr.interface.readSliceEndian(T, @ptrCast(&out), .little);
return out;
}
const InodeRef = @import("inode.zig").Ref;
const XattrLookup = packed struct {
// This isn't actuall an inode ref, but is stored that exact same way.
ref: InodeRef,
kv_count: u32,
size: u32,
};
const XattrKey = packed struct {
type: enum(u2) {
user,
trusted,
security,
},
out_of_line: bool,
_: u13,
name_size: u16,
};
pub const XattrValues = std.AutoHashMap([:0]u8, []u8);
pub fn statelessXattr(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, table_start: u64, idx: u32) !XattrValues {
const xattr_start = try fil.valueAt(u64, table_start);
const block = idx / 512;
const block_idx = idx % 512;
const block_start = try fil.valueAt(u64, table_start + 8 + (block * 8));
var rdr = try fil.readerAt(block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(16 * block_idx);
var lookup: XattrLookup = undefined;
try meta_rdr.interface.readSliceEndian(XattrLookup, @ptrCast(&lookup), .little);
rdr = try fil.readerAt(xattr_start + lookup.ref.block_start, &[0]u8{});
meta_rdr = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(lookup.ref.block_offset);
var out: XattrValues = try .init(alloc);
for (0..lookup.kv_count) |_| {
var key: XattrKey = undefined;
try meta_rdr.interface.readSliceEndian(XattrKey, @ptrCast(&key), .little);
const prefix_size = switch (key.type) {
.user => 4,
.trusted => 7,
.security => 8,
};
const name: [:0]u8 = try alloc.alloc(u8, prefix_size + key.name_size + 1);
name[prefix_size + key.name_size] = 0;
try meta_rdr.interface.readSliceEndian(u8, name[prefix_size .. prefix_size + key.name_size], .little);
switch (key.type) {
.user => @memcpy(name[0..4], "user"),
.trusted => @memcpy(name[0..7], "trusted"),
.security => @memcpy(name[0..8], "security"),
}
if (key.out_of_line) {
try meta_rdr.interface.discardAll(4);
var value_offset: InodeRef = undefined;
try meta_rdr.interface.readSliceEndian(InodeRef, @ptrCast(&value_offset), .little);
var value_rdr = try fil.readerAt(xattr_start + value_offset.block_start, &[0]u8{});
var value_meta: MetadataReader = .init(&value_rdr.interface, decomp);
try value_meta.interface.discardAll(value_offset.block_offset);
var val_size: u32 = undefined;
try value_meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const value = try alloc.alloc(u8, val_size);
try value_meta.interface.readSliceEndian(u8, value, .little);
try out.put(name, value);
} else {
var val_size: u32 = undefined;
try meta_rdr.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const value = try alloc.alloc(u8, val_size);
try meta_rdr.interface.readSliceEndian(u8, value, .little);
try out.put(name, value);
}
}
return out;
}
pub fn CachedTable(comptime T: anytype) type {
return struct {
const Self = @This();
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
alloc: std.mem.Allocator,
decomp: *const Decompressor,
fil: OffsetFile,
table_start: u64,
num: u32,
cache: std.AutoHashMap(u32, []T),
cache_mut: std.Thread.Mutex = .{},
pub fn init(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, table_offset: u64, num: u32) !Self {
return .{
.alloc = alloc,
.decomp = decomp,
.fil = fil,
.table_start = table_offset,
.num = num,
.cache = .init(alloc),
};
}
pub fn deinit(self: *Self) void {
var values = self.cache.valueIterator();
while (values.next()) |val|
self.alloc.free(val);
self.cache.deinit();
}
pub fn get(self: *Self, idx: u32) !T {
const block = idx / VALS_PER_BLOCK;
const block_idx = idx % VALS_PER_BLOCK;
if (self.cache.get(block)) |val|
return val[block_idx];
self.cache_mut.lock();
defer self.cache_mut.unlock();
// Double check in case another thread was doing your work.
if (self.cache.get(block)) |val|
return val[block_idx];
const offset = try self.fil.valueAt(u64, self.table_start + (8 * block));
var buf: [8192]u8 = undefined;
var rdr = try self.fil.readerAt(offset, &buf);
var meta_rdr: MetadataReader = .init(&rdr.interface, self.decomp);
const block_size = if (block == (self.num - 1) / VALS_PER_BLOCK)
self.num % VALS_PER_BLOCK
else
VALS_PER_BLOCK;
const new_block = try self.alloc.alloc(T, block_size);
errdefer self.alloc.free(new_block);
try meta_rdr.interface.readSliceEndian(T, new_block, .little);
try self.cache.put(block, new_block);
return new_block[block_idx];
}
};
}
+33
View File
@@ -0,0 +1,33 @@
//! Options for file/directory extraction.
const std = @import("std");
const Writer = std.Io.Writer;
const ExtractionOptions = @This();
/// The number of threads used for extraction. 0 implies single threaded.
threads: usize = 1,
/// Don't set the file's owner & permissions after extraction
ignore_permissions: bool = false,
/// Don't set xattr values. Currently xattrs are never set anyway.
ignore_xattr: bool = false,
/// Replace symlinks with their target.
dereference_symlinks: bool = false,
/// Verbose logging. If true, verbose_writer must be set
verbose: bool = false,
/// Where to print verbose log.
verbose_writer: ?*Writer = null,
pub const SingleThreadedDefault: ExtractionOptions = .{};
pub fn Default() !ExtractionOptions {
return .{
.threads = try std.Thread.getCpuCount(),
};
}
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
return .{
.verbose = true,
.verbose_writer = wrt,
.threads = try std.Thread.getCpuCount(),
};
}
+3
View File
@@ -0,0 +1,3 @@
pub const Archive = @import("archive.zig");
pub const ExtractionOptions = @import("options.zig");
+138
View File
@@ -0,0 +1,138 @@
const std = @import("std");
const Reader = std.Io.Reader;
const Writer = std.Io.Writer;
const Limit = std.Io.Limit;
const FragEntry = @import("../archive.zig").FragEntry;
const Decompressor = @import("../decomp.zig");
const BlockSize = @import("../inode/file.zig").BlockSize;
const OffsetFile = @import("offset_file.zig");
const DataReader = @This();
decomp: *const Decompressor,
file: OffsetFile,
block_size: u32,
blocks: []BlockSize,
size: u64,
frag: ?FragEntry,
frag_offset: u32,
offset: u64,
idx: usize = 0,
sparse: bool = false,
interface: Reader,
pub fn init(decomp: *const Decompressor, file: OffsetFile, block_size: u32, blocks: []BlockSize, size: u64, init_offset: u64, frag: ?FragEntry, frag_offset: u32) DataReader {
return .{
.decomp = decomp,
.file = file,
.block_size = block_size,
.blocks = blocks,
.size = size,
.frag = frag,
.frag_offset = frag_offset,
.offset = init_offset,
.interface = .{
.buffer = &[1]u8{undefined} ** (1024 * 1024),
.end = 0,
.seek = 0,
.vtable = &.{ .stream = stream, .discard = discard, .readVec = readVec },
},
};
}
fn numBlocks(self: *DataReader) usize {
return if (self.frag == null)
self.blocks.len
else
self.blocks.len + 1;
}
fn advanceBuffer(self: *DataReader) Reader.Error!void {
if (self.idx >= self.numBlocks()) return Reader.Error.EndOfStream;
defer self.idx += 1;
self.sparse = false;
self.interface.end = 0; // If we error out and the error is ignored, we'll stil end up back here to error again.
self.interface.seek = 0;
if (self.idx == self.blocks.len) { // Fragment
var rdr = self.file.readerAt(self.frag.?.block_start, &[0]u8{}) catch return Reader.Error.ReadFailed;
const size = self.size % self.block_size;
if (self.frag.?.size.uncompressed) {
try rdr.interface.discardAll(self.frag_offset);
try rdr.interface.readSliceAll(self.interface.buffer[0..size]);
self.interface.end = size;
return;
}
const raw_loc = self.interface.buffer.len - self.frag.?.size.size;
try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]);
_ = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch
return Reader.Error.ReadFailed;
@memmove(self.interface.buffer[0..size], self.interface.buffer[self.frag_offset .. self.frag_offset + size]);
self.interface.end = size;
return;
}
const block = self.blocks[self.idx];
if (block.size == 0) {
self.interface.end = if (self.idx == self.numBlocks() - 1)
self.size % self.block_size
else
self.block_size;
self.sparse = true;
return;
}
defer self.offset += block.size;
var rdr = try self.file.readerAt(self.offset, &[0]u8{});
if (block.uncompressed) {
try rdr.interface.readSliceAll(self.interface.buffer[0..block.size]);
self.interface.end = block.size;
return;
}
const raw_loc = self.interface.buffer.len - block.size;
try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]);
self.interface.end = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch
return Reader.Error.ReadFailed;
}
fn stream(r: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
var self: *DataReader = @fieldParentPtr("interface", r);
if (r.seek == r.end) try self.advanceBuffer();
if (limit == .nothing) return 0;
const to_write = @min(r.end - r.seek, @intFromEnum(limit));
const wrote = if (self.sparse)
try wrt.splatByte(0, to_write)
else
try wrt.write(r.buffer[r.seek .. r.seek + to_write]);
r.seek += wrote;
return wrote;
}
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
var self: *DataReader = @fieldParentPtr("interface", r);
if (r.seek == r.end) try self.advanceBuffer();
if (limit == .nothing) return 0;
const adv = @min(r.end - r.seek, @intFromEnum(limit));
r.seek += adv;
return adv;
}
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
var self: *DataReader = @fieldParentPtr("interface", r);
if (r.seek == r.end) try self.advanceBuffer();
var wrote: usize = 0;
for (vec) |slice| {
if (r.seek == r.end) break;
const to_copy = @min(r.end - r.seek, slice.len);
if (self.sparse) {
@memset(slice[0..to_copy], 0);
} else {
@memcpy(slice[0..to_copy], r.buffer[r.seek .. r.seek + to_copy]);
}
r.seek += to_copy;
wrote += to_copy;
}
return wrote;
}
+54
View File
@@ -0,0 +1,54 @@
const std = @import("std");
const MinimalSuperblock = @import("../archive.zig").MinimalSuperblock;
const Decompressor = @import("../decomp.zig");
const DirEntry = @import("../directory.zig").Entry;
const File = @import("../file.zig");
const Inode = @import("../inode.zig");
const MetadataReader = @import("metadata.zig");
const OffsetFile = @import("offset_file.zig");
const Utils = @import("utils.zig");
const Iter = @This();
file: OffsetFile,
super: MinimalSuperblock,
decomp: Decompressor,
entries: []DirEntry,
idx: usize = 0,
pub fn deinit(self: Iter) void {
for (self.entries) |ent|
ent.deinit(self.decomp.alloc);
self.decomp.alloc.free(self.entries);
}
pub fn next(self: *Iter) !?File {
if (self.idx >= self.entries.len) return null;
defer self.idx += 1;
const entry = self.entries[self.idx];
const new_name = try self.decomp.alloc.alloc(u8, entry.name.len);
@memcpy(new_name, entry.name);
return .{
.file = self.file,
.super = self.super,
.decomp = self.decomp,
.name = new_name,
.inode = Utils.readInode(
self.decomp.alloc,
&self.decomp,
self.file,
self.super.inode_start,
self.super.block_size,
entry.block_start,
entry.block_offset,
),
};
}
pub fn reset(self: *Iter) void {
self.idx = 0;
}
+77
View File
@@ -0,0 +1,77 @@
const std = @import("std");
const Reader = std.Io.Reader;
const Writer = std.Io.Writer;
const Limit = std.Io.Limit;
const Decompressor = @import("../decomp.zig");
const Header = packed struct {
size: u15,
uncompressed: bool,
};
const MetadataReader = @This();
rdr: *Reader,
decomp: *const Decompressor,
read_buf: [8192]u8 = undefined,
interface: Reader = .{
.buffer = &([1]u8{undefined} ** 8192),
.end = 0,
.seek = 0,
.vtable = &.{
.stream = stream,
.discard = discard,
.readVec = readVec,
},
},
pub fn init(rdr: *Reader, decomp: *const Decompressor) MetadataReader {
return .{ .rdr = rdr, .decomp = decomp };
}
fn advanceBuffer(self: *MetadataReader) Reader.Error!void {
self.interface.seek = 0;
var hdr: Header = undefined;
try self.rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
try self.rdr.readSliceAll(self.read_buf[0..hdr.size]);
if (hdr.uncompressed) {
@memcpy(self.interface.buffer[0..hdr.size], self.read_buf[0..hdr.size]);
self.interface.end = hdr.size;
return;
}
self.interface.end = self.decomp.decompress(self.read_buf[0..hdr.size], self.interface.buffer) catch |err|
return switch (err) {
error.OutOfMemory => error.ReadFailed,
else => err,
};
}
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
var self: *MetadataReader = @fieldParentPtr("interface", rdr);
if (rdr.seek == rdr.end) try self.advanceBuffer();
const to_write = @min(@intFromEnum(limit), rdr.end - rdr.seek);
const wrote = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_write]);
rdr.seek += wrote;
return wrote;
}
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
var self: *MetadataReader = @fieldParentPtr("interface", rdr);
if (rdr.seek == rdr.end) try self.advanceBuffer();
const to_adv = @min(@intFromEnum(limit), rdr.end - rdr.seek);
rdr.seek += to_adv;
return to_adv;
}
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
var self: *MetadataReader = @fieldParentPtr("interface", rdr);
if (rdr.seek == rdr.end) try self.advanceBuffer();
var wrote = 0;
for (vec) |v| {
if (rdr.seek == rdr.end) break;
const to_write = @min(v.len, rdr.end - rdr.seek);
@memcpy(v[0..to_write], rdr.buffer[rdr.seek .. rdr.seek + to_write]);
wrote += to_write;
rdr.seek += to_write;
}
return wrote;
}
+21
View File
@@ -0,0 +1,21 @@
const std = @import("std");
const Io = std.Io;
const FileReader = Io.File.Reader;
const OffsetFile = @This();
fil: Io.File,
offset: u64 = 0,
pub fn readerAt(self: OffsetFile, io: Io, offset: u64, buf: []u8) !FileReader {
var rdr = self.fil.reader(io, buf);
try rdr.seekTo(self.offset + offset);
return rdr;
}
pub fn valueAt(self: OffsetFile, comptime T: type, io: Io, offset: u64) !T {
var rdr = self.fil.reader(io, &[0]u8{});
try rdr.seekTo(self.offset + offset);
var new: T = undefined;
try rdr.interface.readSliceEndian(T, @ptrCast(&new), .little);
return new;
}
+25
View File
@@ -0,0 +1,25 @@
const std = @import("std");
const Decompressor = @import("../decomp.zig");
const DirEntry = @import("../directory.zig").Entry;
const Inode = @import("../inode.zig");
const MetadataReader = @import("metadata.zig");
const OffsetFile = @import("offset_file.zig");
pub fn pathIsSelf(path: []const u8) bool {
if (path.len == 0) return true;
if (path.len == 1) {
return switch (path[0]) {
'.', '/' => true,
else => false,
};
}
return std.mem.eql(u8, path, "./");
}
pub fn readInode(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, inode_start: u64, block_size: u32, block_start: u32, block_offset: u16) !Inode {
var rdr = try fil.readerAt(inode_start + block_start, &[0]u8{});
var meta: MetadataReader = .init(&rdr.interface, decomp);
try meta.interface.discardAll(block_offset);
return .read(alloc, &meta.interface, block_size);
}