Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
||||
- name: Build normal version
|
||||
run: zig build -Drelease=true -Dversion=${{ github.ref_name }}
|
||||
- name: Move normal build out
|
||||
run: mv zig-out/bin/unsquashfs ./
|
||||
- name: Rebuild with C libraries
|
||||
run: zig build -Drelease=true -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
|
||||
- name: Move C build out
|
||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: true
|
||||
files: |
|
||||
unsquashfs
|
||||
unsquashfs-c-libs
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,53 @@
|
||||
# zig-squashfs
|
||||
|
||||
Messing around with zig via making a squashfs library. May amount to something. Or not.
|
||||
This is my experiments to learn Zig. Might amount to something. Might not.
|
||||
|
||||
## Current state
|
||||
A library and application to decompress or view squashfs archives.
|
||||
|
||||
Performance is reatively bad (when compared to the official [squashfs-tools](https://github.com/plougher/squashfs-tools), but the basics should fully work.
|
||||
## Current State
|
||||
|
||||
Overall works, but currently is missing some features (see below). Extraction is a bit slow compared to the normal `unsquashfs` (from my _very_ basic testing it's about ~3x slower). Only properly work on Linux, any other OSes probably won't work fully and are untested.
|
||||
|
||||
## Build options
|
||||
|
||||
> `-Duse_c_libs`
|
||||
|
||||
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`
|
||||
|
||||
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
||||
|
||||
> `-Dversion`
|
||||
|
||||
Sets the version of `unsquashfs` shown when `--version` is passed.
|
||||
|
||||
## Capabilities
|
||||
|
||||
Most features are present except for the following:
|
||||
|
||||
* mod_time is not set on extraction
|
||||
* xattrs are not applied on extraction
|
||||
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||
|
||||
## Building considerations
|
||||
|
||||
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
|
||||
|
||||
```zig
|
||||
pub const lzo_bytep = @compileError("unable to translate C expr: unexpected token ''");
|
||||
// /usr/include/lzo/lzoconf.h:148:9
|
||||
pub const lzo_charp = @compileError("unable to translate C expr: unexpected token ''");
|
||||
// /usr/include/lzo/lzoconf.h:149:9
|
||||
pub const lzo_voidp = @compileError("unable to translate C expr: unexpected token ''");
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```zig
|
||||
pub const lzo_bytep = *u8;
|
||||
// /usr/include/lzo/lzoconf.h:148:9
|
||||
pub const lzo_charp = @compileError("unable to translate C expr: unexpected token ''");
|
||||
// /usr/include/lzo/lzoconf.h:149:9
|
||||
pub const lzo_voidp = ?*anyopaque;
|
||||
```
|
||||
|
||||
@@ -1,53 +1,79 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// version if version isn't provided during build
|
||||
const def_version = "0.0.0+testing";
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const opt = b.addOptions();
|
||||
const ver = b.option([]const u8, "version", "sematic version") orelse def_version;
|
||||
const sem_ver = try std.SemanticVersion.parse(ver);
|
||||
opt.addOption(std.SemanticVersion, "version", sem_ver);
|
||||
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
|
||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support");
|
||||
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_c_libs", use_c_libs_option orelse false);
|
||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false);
|
||||
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const lib_mod = b.createModule(.{
|
||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||
const mod = b.addModule("zig_squashfs", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = if (use_c_libs_option == true) true else false,
|
||||
});
|
||||
const lib = b.addLibrary(.{
|
||||
.linkage = .static,
|
||||
.name = "zig_squashfs",
|
||||
.root_module = lib_mod,
|
||||
.version = sem_ver,
|
||||
});
|
||||
mod.addOptions("config", zig_squashfs_options);
|
||||
if (use_c_libs_option == true) {
|
||||
mod.linkSystemLibrary("zlib", .{});
|
||||
mod.linkSystemLibrary("lzma", .{});
|
||||
if (allow_lzo == true)
|
||||
mod.linkSystemLibrary("minilzo", .{});
|
||||
mod.linkSystemLibrary("lz4", .{});
|
||||
mod.linkSystemLibrary("zstd", .{});
|
||||
}
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
var version = version_string_option orelse "0.0.0-testing";
|
||||
if (version[0] == 'v') version = version[1..];
|
||||
const unsquashfs_options = b.addOptions();
|
||||
unsquashfs_options.addOption(
|
||||
std.SemanticVersion,
|
||||
"version",
|
||||
try std.SemanticVersion.parse(version),
|
||||
);
|
||||
|
||||
var exe_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = if (use_c_libs_option == true) true else false,
|
||||
.imports = &.{
|
||||
.{ .name = "zig_squashfs", .module = mod },
|
||||
},
|
||||
});
|
||||
exe_mod.addImport("squashfs", lib_mod);
|
||||
exe_mod.addOptions("config", opt);
|
||||
exe_mod.addOptions("config", unsquashfs_options);
|
||||
const exe = b.addExecutable(.{
|
||||
.linkage = .static,
|
||||
.name = "unsquashfs",
|
||||
.root_module = exe_mod,
|
||||
.version = sem_ver,
|
||||
});
|
||||
|
||||
const lib = b.addLibrary(.{
|
||||
.name = "squashfs",
|
||||
.root_module = mod,
|
||||
});
|
||||
|
||||
b.installArtifact(lib);
|
||||
b.installArtifact(exe);
|
||||
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_module = lib_mod,
|
||||
const run_step = b.step("run", "Run the app");
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
const mod_tests = b.addTest(.{
|
||||
.root_module = mod,
|
||||
});
|
||||
const exe_unit_test = b.addTest(.{
|
||||
.root_module = exe_mod,
|
||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||
const exe_tests = b.addTest(.{
|
||||
.root_module = exe.root_module,
|
||||
});
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_test);
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_lib_unit_tests.step);
|
||||
test_step.dependOn(&run_exe_unit_tests.step);
|
||||
const run_exe_tests = b.addRunArtifact(exe_tests);
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&run_mod_tests.step);
|
||||
test_step.dependOn(&run_exe_tests.step);
|
||||
}
|
||||
|
||||
+3
-14
@@ -1,10 +1,8 @@
|
||||
.{
|
||||
.name = .zig_squashfs,
|
||||
.name = .squashfs,
|
||||
.version = "0.0.1",
|
||||
.fingerprint = 0x527960c72c03ffe3, // Changing this has security and trust implications.
|
||||
|
||||
.minimum_zig_version = "0.14.0",
|
||||
|
||||
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
||||
.minimum_zig_version = "0.15.2",
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
@@ -41,15 +39,6 @@
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
zig test \
|
||||
-lc \
|
||||
-lz \
|
||||
-llzma \
|
||||
-lminilzo \
|
||||
-llz4 \
|
||||
-lzstd \
|
||||
src/test.zig
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
//! A squashfs archive read from a file.
|
||||
//! Can be used to directly access File's contents or extract to the filesystem.
|
||||
|
||||
const std = @import("std");
|
||||
const File = std.fs.File;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Decomp = @import("decomp.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const Inode = @import("inode.zig");
|
||||
const InodeRef = Inode.Ref;
|
||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
||||
const SfsFile = @import("file.zig");
|
||||
const Superblock = @import("super.zig").Superblock;
|
||||
const Table = @import("table.zig").Table;
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const OffsetFile = @import("util/offset_file.zig");
|
||||
|
||||
const config = if (builtin.is_test) .{
|
||||
.use_c_libs = true,
|
||||
.allow_lzo = false,
|
||||
} else @import("config");
|
||||
|
||||
/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry.
|
||||
/// The offset into the block and fragment size is stored in the file's inode.
|
||||
pub const FragEntry = packed struct {
|
||||
start: u64,
|
||||
size: BlockSize,
|
||||
_: u32,
|
||||
};
|
||||
|
||||
const Archive = @This();
|
||||
|
||||
// 4 Gigs
|
||||
const DEFAULT_MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
||||
|
||||
parent_alloc: std.mem.Allocator,
|
||||
alloc: std.heap.ThreadSafeAllocator,
|
||||
// alloc: std.heap.FixedBufferAllocator,
|
||||
// fixed_buf: []u8,
|
||||
thread_count: usize,
|
||||
|
||||
fil: OffsetFile,
|
||||
|
||||
super: Superblock,
|
||||
|
||||
setup: bool = false,
|
||||
|
||||
decomp: Decomp.DecompFn,
|
||||
|
||||
frag_table: Table(FragEntry) = undefined,
|
||||
id_table: Table(u16) = undefined,
|
||||
export_table: Table(InodeRef) = undefined,
|
||||
|
||||
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
||||
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
||||
return initAdvanced(
|
||||
alloc,
|
||||
fil,
|
||||
0,
|
||||
try std.Thread.getCpuCount(),
|
||||
);
|
||||
}
|
||||
/// Create the Archive dictating the amount of threads & memory used.
|
||||
/// If trying to extract a full archive, a large memory size & thread count could help.
|
||||
/// If you're planning on only interacting with a small number of files, it should be fine to use few threads and a small memory size.
|
||||
pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: usize) !Archive {
|
||||
var super: Superblock = undefined;
|
||||
const red = try fil.pread(@ptrCast(&super), offset);
|
||||
std.debug.assert(red == @sizeOf(Superblock));
|
||||
try super.validate();
|
||||
// const fixed_buf = try alloc.alloc(u8, mem);
|
||||
return .{
|
||||
.parent_alloc = alloc,
|
||||
.alloc = .{ .child_allocator = alloc },
|
||||
// .fixed_buf = fixed_buf,
|
||||
.thread_count = threads,
|
||||
.fil = .init(fil, offset),
|
||||
.decomp = switch (super.compression) {
|
||||
.gzip => Decomp.gzipDecompress,
|
||||
.lzma => Decomp.lzmaDecompress,
|
||||
.xz => Decomp.xzDecompress,
|
||||
.zstd => Decomp.zstdDecompress,
|
||||
.lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported,
|
||||
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
||||
},
|
||||
|
||||
.super = super,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *Archive) void {
|
||||
// self.parent_alloc.free(self.fixed_buf);
|
||||
if (self.setup) {
|
||||
self.frag_table.deinit();
|
||||
self.export_table.deinit();
|
||||
self.id_table.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocator(self: *Archive) std.mem.Allocator {
|
||||
return self.alloc.allocator();
|
||||
}
|
||||
|
||||
fn setupValues(self: *Archive) !void {
|
||||
const alloc = self.allocator();
|
||||
self.frag_table = try .init(alloc, self.fil, self.decomp, self.super.frag_start, self.super.frag_count);
|
||||
self.id_table = try .init(alloc, self.fil, self.decomp, self.super.id_start, self.super.id_count);
|
||||
self.export_table = try .init(alloc, self.fil, self.decomp, self.super.export_start, self.super.inode_count);
|
||||
self.setup = true;
|
||||
}
|
||||
|
||||
pub fn id(self: *Archive, idx: u32) !u16 {
|
||||
if (!self.setup) try self.setupValues();
|
||||
return self.id_table.get(idx);
|
||||
}
|
||||
|
||||
pub fn frag(self: *Archive, idx: u32) !FragEntry {
|
||||
if (!self.setup) try self.setupValues();
|
||||
return self.frag_table.get(idx);
|
||||
}
|
||||
|
||||
pub fn inode(self: *Archive, num: u32) !Inode {
|
||||
if (!self.setup) try self.setupValues();
|
||||
const ref = try self.export_table.get(num - 1);
|
||||
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
|
||||
try meta.interface.discardAll(ref.block_offset);
|
||||
return try .read(self.allocator(), &meta.interface, self.super.block_size);
|
||||
}
|
||||
|
||||
pub fn root(self: *Archive) !SfsFile {
|
||||
if (!self.setup) try self.setupValues();
|
||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
||||
return .init(self, in, "");
|
||||
}
|
||||
|
||||
pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
||||
if (!self.setup) try self.setupValues();
|
||||
var root_fil = try self.root();
|
||||
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
||||
return root_fil.open(path);
|
||||
}
|
||||
|
||||
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
if (!self.setup) try self.setupValues();
|
||||
var alloc = self.allocator();
|
||||
var ext_path: []u8 = undefined;
|
||||
if (std.fs.cwd().statFile(path)) |stat| {
|
||||
if (stat.kind == .directory) {
|
||||
ext_path = @constCast(path);
|
||||
} else return error.ExtractionPathExists;
|
||||
} else |err| {
|
||||
if (err == error.FileNotFound) {
|
||||
ext_path = @constCast(path);
|
||||
} else {
|
||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
||||
return err;
|
||||
}
|
||||
}
|
||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
||||
try in.extractToThreaded(self, ext_path, options, self.thread_count);
|
||||
}
|
||||
+78
-121
@@ -1,146 +1,103 @@
|
||||
const std = @import("std");
|
||||
const config = @import("config");
|
||||
const squashfs = @import("squashfs");
|
||||
const Writer = std.Io.Writer;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const help_msg =
|
||||
\\Basic Usage: zig-unsquashfs [Options] SQUASHFS_FILE <EXTRACT_LOCATION>
|
||||
const config = @import("config");
|
||||
const squashfs = @import("zig_squashfs");
|
||||
|
||||
//TODO: Add more options
|
||||
const help_mgs =
|
||||
\\
|
||||
\\General options:
|
||||
\\ -e <path> Path to a file or directory inside the archive to extract instead of the whole archive.
|
||||
\\ Can be given multiple times.
|
||||
\\ -o <bytes> Skip <bytes> before reading from the archive.
|
||||
\\ -v Verbose output.
|
||||
\\Usage: unsquashfs [options] <archive>
|
||||
\\
|
||||
\\Extraction options:
|
||||
\\ --unbreak-symlinks Attempt extract symlink targets along with symlinks. Will not place files outside of the extraction location.
|
||||
\\ -us Same as --unbreak-symlinks
|
||||
\\ --deref-symlinks Replace symlink files with their target.
|
||||
\\ -ds Same as --deref-symlinks
|
||||
\\ -p <#> Use at most # of processors. Defaults to logical core count.
|
||||
\\Options:
|
||||
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
||||
\\
|
||||
\\Listing Options:
|
||||
\\ -l List files instead of extracting. When used, you do not need to specify an extraction location.
|
||||
\\ -ll Similiar to -l, but with file attributes.
|
||||
\\ -lln Similiar to -ll, but with numeric uids and gids.
|
||||
\\ -o <offset> Start reading the archive at the given offset.
|
||||
\\
|
||||
\\Other:
|
||||
\\ --help Prints this help message.
|
||||
\\ -h Same as --help
|
||||
\\ --version Print version number.
|
||||
\\ -p <threads> Specify how many threads to use. If no present, the system's logical cores count is used.
|
||||
\\
|
||||
\\ --help Display this messages
|
||||
\\ --version Display the version
|
||||
\\
|
||||
;
|
||||
|
||||
const stdout = std.io.getStdOut();
|
||||
const errors = error{InvalidArguments};
|
||||
|
||||
var extr_files: std.ArrayList([]const u8) = undefined;
|
||||
var archive: []const u8 = "";
|
||||
var extLoc: []const u8 = "squashfs-root";
|
||||
var offset: u64 = 0;
|
||||
var verbose: bool = false;
|
||||
var unbreak: bool = false;
|
||||
var deref: bool = false;
|
||||
var processors: u16 = 0;
|
||||
var list: ListTypes = .None;
|
||||
|
||||
var filename: []const u8 = "";
|
||||
var extr_location: []const u8 = "";
|
||||
|
||||
const ListTypes = enum {
|
||||
None,
|
||||
List,
|
||||
ListAttr,
|
||||
ListNumeric,
|
||||
};
|
||||
var threads: u32 = 0;
|
||||
|
||||
pub fn main() !void {
|
||||
const alloc = std.heap.smp_allocator;
|
||||
extr_files = .init(alloc);
|
||||
defer extr_files.deinit();
|
||||
var args = std.process.argsWithAllocator(alloc) catch {
|
||||
_ = try stdout.writeAll("Unable to allocate memory");
|
||||
var stdout = std.fs.File.stdout();
|
||||
var out = stdout.writer(&[0]u8{});
|
||||
defer out.interface.flush() catch {};
|
||||
try handleArgs(alloc, &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: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
||||
defer fil.close();
|
||||
var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, threads); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||
defer arc.deinit();
|
||||
try arc.extract(extLoc, .Default); //TODO: Handle error gracefully.
|
||||
}
|
||||
|
||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
||||
var args = try std.process.argsWithAllocator(alloc);
|
||||
defer args.deinit();
|
||||
_ = args.next();
|
||||
_ = args.next(); // args[0] is the application launch command.
|
||||
while (args.next()) |arg| {
|
||||
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||
_ = try stdout.writeAll(help_msg);
|
||||
return;
|
||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
||||
try config.version.format("", .{}, stdout.writer());
|
||||
_ = try stdout.write("\n");
|
||||
return;
|
||||
} else if (std.mem.eql(u8, arg, "-v")) {
|
||||
verbose = true;
|
||||
} else if (std.mem.eql(u8, arg, "--unbreak-symlinks") or std.mem.eql(u8, arg, "-us")) {
|
||||
unbreak = true;
|
||||
} else if (std.mem.eql(u8, arg, "--deref-symlinks") or std.mem.eql(u8, arg, "-ds")) {
|
||||
deref = true;
|
||||
} else if (std.mem.eql(u8, arg, "-l")) {
|
||||
list = .List;
|
||||
} else if (std.mem.eql(u8, arg, "-ll")) {
|
||||
list = .ListAttr;
|
||||
} else if (std.mem.eql(u8, arg, "-lln")) {
|
||||
list = .ListNumeric;
|
||||
} else if (std.mem.eql(u8, arg, "-e")) {
|
||||
const next = args.next();
|
||||
if (next == null) {
|
||||
_ = try stdout.writeAll("path required after -e\n");
|
||||
return;
|
||||
if (std.mem.eql(u8, arg, "-o")) {
|
||||
const nxt = args.next();
|
||||
if (nxt == null or nxt.?.len == 0) {
|
||||
try out.print("-o must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
try extr_files.append(next.?);
|
||||
} else if (std.mem.eql(u8, arg, "-o")) {
|
||||
const next = args.next();
|
||||
if (next == null) {
|
||||
_ = try stdout.writeAll("offset required after -o\n");
|
||||
return;
|
||||
offset = std.fmt.parseInt(u64, nxt.?, 10) catch {
|
||||
try out.print("-o must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
};
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, arg, "-d")) {
|
||||
const nxt = args.next();
|
||||
if (nxt == null or nxt.?.len == 0) {
|
||||
try out.print("-d must be followed by a location\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
offset = try std.fmt.parseInt(u64, next.?, 10);
|
||||
extLoc = nxt.?;
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
||||
const next = args.next();
|
||||
if (next == null) {
|
||||
_ = try stdout.writeAll("number required after -p\n");
|
||||
return;
|
||||
const nxt = args.next();
|
||||
if (nxt == null or nxt.?.len == 0) {
|
||||
try out.print("-p must be followed by a number\n", .{});
|
||||
return errors.InvalidArguments;
|
||||
}
|
||||
processors = try std.fmt.parseInt(u16, next.?, 10);
|
||||
} else if (filename.len == 0) {
|
||||
filename = arg;
|
||||
} else if (extr_location.len == 0) {
|
||||
extr_location = arg;
|
||||
} else {
|
||||
_ = try stdout.writeAll("invalid or too many arguments\n");
|
||||
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, "--version")) {
|
||||
try out.print("zig-unsquashfs version ", .{});
|
||||
try config.version.format(out);
|
||||
try out.print("\nBuilt using Zig {s} with {} backend in {} mode.\n", .{ builtin.zig_version_string, builtin.zig_backend, 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;
|
||||
}
|
||||
if (filename.len == 0) {
|
||||
_ = try stdout.writeAll("no archive given\n");
|
||||
return;
|
||||
}
|
||||
if (list == .None and extr_location.len == 0) {
|
||||
_ = try stdout.writeAll("no extract location given\n");
|
||||
return;
|
||||
}
|
||||
const fil = try std.fs.cwd().openFile(filename, .{});
|
||||
defer fil.close();
|
||||
var th_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = std.heap.smp_allocator };
|
||||
var rdr = squashfs.SfsFile.init(
|
||||
th_alloc.allocator(),
|
||||
fil,
|
||||
offset,
|
||||
) catch |err| {
|
||||
try std.fmt.format(stdout.writer(), "Error opening {s} as squashfs: {any}\n", .{ filename, err });
|
||||
return;
|
||||
};
|
||||
defer rdr.deinit();
|
||||
//TODO: list and extr_files;
|
||||
var op: squashfs.ExtractionOptions = squashfs.ExtractionOptions.init() catch |err| {
|
||||
try std.fmt.format(stdout.writer(), "Error setting extraction options: {any}\n", .{err});
|
||||
return;
|
||||
};
|
||||
op.verbose = verbose;
|
||||
op.dereference_symlinks = deref;
|
||||
op.unbreak_symlinks = unbreak;
|
||||
if (processors != 0) op.thread_count = processors;
|
||||
rdr.extract(op, extr_location) catch |err| {
|
||||
try std.fmt.format(stdout.writer(), "Error extracting archive: {any}\n", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
//! Implementations for decompression.
|
||||
//! TODO: change to vtable interface to allow for shared decompressors for better performance/resource usage.
|
||||
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const config = if (builtin.is_test) .{
|
||||
.use_c_libs = builtin.link_libc == true,
|
||||
.allow_lzo = false, // Change once LZO compilation is fixed
|
||||
} else @import("config");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("zlib.h");
|
||||
@cInclude("lzma.h");
|
||||
@cInclude("lz4.h");
|
||||
@cInclude("zstd.h");
|
||||
@cInclude("zstd_errors.h");
|
||||
if (config.allow_lzo)
|
||||
@cInclude("lzo/minilzo.h");
|
||||
});
|
||||
|
||||
pub const CompressionType = enum(u16) {
|
||||
gzip = 1,
|
||||
lzma,
|
||||
lzo,
|
||||
xz,
|
||||
lz4,
|
||||
zstd,
|
||||
};
|
||||
|
||||
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
||||
|
||||
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
||||
|
||||
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
var rdr: Reader = .fixed(in);
|
||||
const buf = try alloc.alloc(u8, out.len);
|
||||
defer alloc.free(buf);
|
||||
var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, buf);
|
||||
return decomp.reader.readSliceShort(out);
|
||||
}
|
||||
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
_ = alloc;
|
||||
var out_len: usize = out.len;
|
||||
const res = c.uncompress(out.ptr, &out_len, in.ptr, in.len);
|
||||
return switch (res) {
|
||||
c.Z_OK => out_len,
|
||||
c.Z_MEM_ERROR => error.NotEnoughMemory,
|
||||
c.Z_BUF_ERROR => error.OutBufferTooSmall,
|
||||
c.Z_DATA_ERROR => error.BadData,
|
||||
else => error.UnknownResult,
|
||||
};
|
||||
}
|
||||
|
||||
pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma;
|
||||
|
||||
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
var rdr: Reader = .fixed(in);
|
||||
var decomp = try std.compress.lzma.decompress(alloc, rdr.adaptToOldInterface());
|
||||
return decomp.read(out);
|
||||
}
|
||||
fn cLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
_ = alloc;
|
||||
var stream: c.lzma_stream = .{
|
||||
.next_in = in.ptr,
|
||||
.avail_in = in.len,
|
||||
.next_out = out.ptr,
|
||||
.avail_out = out.len,
|
||||
};
|
||||
var res = c.lzma_alone_decoder(&stream, in.len * 2);
|
||||
switch (res) {
|
||||
c.LZMA_OK => {},
|
||||
c.LZMA_MEM_ERROR => return error.LzmaMemoryError,
|
||||
c.LZMA_PROG_ERROR => return error.LzmaProgramError,
|
||||
else => return error.UnknownResult,
|
||||
}
|
||||
defer c.lzma_end(&stream);
|
||||
while (res == c.LZMA_OK)
|
||||
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||
return switch (res) {
|
||||
c.LZMA_STREAM_END => stream.total_out,
|
||||
c.LZMA_MEM_ERROR => error.LzmaMemoryError,
|
||||
c.LZMA_MEMLIMIT_ERROR => error.LzmaMemoryLimit,
|
||||
c.LZMA_FORMAT_ERROR => error.LzmaBadFormat,
|
||||
c.LZMA_DATA_ERROR => error.LzmaDataCorrupt,
|
||||
c.LZMA_BUF_ERROR => error.LzmaCannotProgress,
|
||||
c.LZMA_PROG_ERROR => error.LzmaProgramError,
|
||||
else => error.UnknownResult,
|
||||
};
|
||||
}
|
||||
|
||||
// pub const lzoDecompress = if (config.use_c_libs) cLzo else zigLzo;
|
||||
|
||||
// fn zigLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
// _ = alloc;
|
||||
// _ = in;
|
||||
// _ = out;
|
||||
// return error.LzoUnsupported;
|
||||
// }
|
||||
pub fn cLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
_ = alloc;
|
||||
var res = c.lzo_init();
|
||||
if (res != 0) return error.LzoInitFailed;
|
||||
var out_len: usize = out.len;
|
||||
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 => error.LzoError,
|
||||
c.LZO_E_OUT_OF_MEMORY => error.LzoOutOfMemory,
|
||||
c.LZO_E_NOT_COMPRESSIBLE => error.LzoNotCompressible,
|
||||
c.LZO_E_INPUT_OVERRUN => error.LzoInputOverrun,
|
||||
c.LZO_E_OUTPUT_OVERRUN => error.LzoOutputOverrun,
|
||||
c.LZO_E_LOOKBEHIND_OVERRUN => error.LzoLookbehindOverrun,
|
||||
c.LZO_E_EOF_NOT_FOUND => error.LzoEofNotFound,
|
||||
c.LZO_E_INPUT_NOT_CONSUMED => error.LzoInputNotConsumed,
|
||||
c.LZO_E_NOT_YET_IMPLEMENTED => error.LzoNotYetImplemented,
|
||||
c.LZO_E_INVALID_ARGUMENT => error.LzoInvalidArgument,
|
||||
c.LZO_E_INVALID_ALIGNMENT => error.LzoInvalidAlignment,
|
||||
c.LZO_E_OUTPUT_NOT_CONSUMED => error.LzoOutputNotConsumed,
|
||||
c.LZO_E_INTERNAL_ERROR => error.LzoInternalError,
|
||||
else => error.UnknownResult,
|
||||
};
|
||||
}
|
||||
|
||||
pub const xzDecompress = if (config.use_c_libs) cXz else zigXz;
|
||||
|
||||
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
var rdr: Reader = .fixed(in);
|
||||
var decomp = try std.compress.xz.decompress(alloc, rdr.adaptToOldInterface());
|
||||
return decomp.read(out);
|
||||
}
|
||||
fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
_ = alloc;
|
||||
var stream: c.lzma_stream = .{
|
||||
.next_in = in.ptr,
|
||||
.avail_in = in.len,
|
||||
.next_out = out.ptr,
|
||||
.avail_out = out.len,
|
||||
};
|
||||
var res = c.lzma_stream_decoder(&stream, in.len * 2, 0);
|
||||
switch (res) {
|
||||
c.LZMA_OK => {},
|
||||
c.LZMA_MEM_ERROR => return error.LzmaMemoryError,
|
||||
c.LZMA_PROG_ERROR => return error.LzmaProgramError,
|
||||
else => return error.UnknownResult,
|
||||
}
|
||||
defer c.lzma_end(&stream);
|
||||
while (res == c.LZMA_OK)
|
||||
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||
return switch (res) {
|
||||
c.LZMA_STREAM_END => stream.total_out,
|
||||
c.LZMA_MEM_ERROR => error.LzmaMemoryError,
|
||||
c.LZMA_MEMLIMIT_ERROR => error.LzmaMemoryLimit,
|
||||
c.LZMA_FORMAT_ERROR => error.LzmaBadFormat,
|
||||
c.LZMA_DATA_ERROR => error.LzmaDataCorrupt,
|
||||
c.LZMA_BUF_ERROR => error.LzmaCannotProgress,
|
||||
c.LZMA_PROG_ERROR => error.LzmaProgramError,
|
||||
else => error.UnknownResult,
|
||||
};
|
||||
}
|
||||
|
||||
// pub const lz4Decompress = if (config.use_c_libs) cLz4 else zigLz4;
|
||||
|
||||
// fn zigLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
// _ = alloc;
|
||||
// _ = in;
|
||||
// _ = out;
|
||||
// return error.Lz4Unsupported;
|
||||
// }
|
||||
pub fn cLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
_ = alloc;
|
||||
const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
|
||||
if (res > 0) return @abs(res); // TODO: Find out what error values it can return.
|
||||
return error.Lz4DecompressFailed;
|
||||
}
|
||||
|
||||
pub const zstdDecompress = if (config.use_c_libs) cZstd else zigZstd;
|
||||
|
||||
pub fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
var rdr: Reader = .fixed(in);
|
||||
const buf = try alloc.alloc(u8, 1024 * 1024);
|
||||
defer alloc.free(buf);
|
||||
var decomp = std.compress.zstd.Decompress.init(&rdr, buf, .{});
|
||||
return decomp.reader.readSliceShort(out) catch |err| {
|
||||
return decomp.err orelse err;
|
||||
};
|
||||
}
|
||||
fn cZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||
_ = alloc;
|
||||
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
|
||||
if (c.ZSTD_isError(res) == 0) return res;
|
||||
return switch (c.ZSTD_getErrorCode(res)) {
|
||||
c.ZSTD_error_prefix_unknown => cZstdError.PrefixUnknown,
|
||||
c.ZSTD_error_version_unsupported => cZstdError.VersionUnsupported,
|
||||
c.ZSTD_error_frameParameter_unsupported => cZstdError.FrameParameterUnsupported,
|
||||
c.ZSTD_error_frameParameter_windowTooLarge => cZstdError.FrameParameterWindowTooLarge,
|
||||
c.ZSTD_error_corruption_detected => cZstdError.CorruptionDetected,
|
||||
c.ZSTD_error_checksum_wrong => cZstdError.ChecksumWrong,
|
||||
c.ZSTD_error_literals_headerWrong => cZstdError.LiteralsHeaderWrong,
|
||||
c.ZSTD_error_dictionary_corrupted => cZstdError.DictionaryCorrupted,
|
||||
c.ZSTD_error_dictionary_wrong => cZstdError.DictionaryWrong,
|
||||
c.ZSTD_error_dictionaryCreation_failed => cZstdError.DictionaryCreationFailed,
|
||||
c.ZSTD_error_parameter_unsupported => cZstdError.ParameterUnsupported,
|
||||
c.ZSTD_error_parameter_combination_unsupported => cZstdError.ParameterCombinationUnsupported,
|
||||
c.ZSTD_error_parameter_outOfBound => cZstdError.ParameterOutOfBound,
|
||||
c.ZSTD_error_tableLog_tooLarge => cZstdError.TableLogTooLarge,
|
||||
c.ZSTD_error_maxSymbolValue_tooLarge => cZstdError.MaxSymbolValueTooLarge,
|
||||
c.ZSTD_error_maxSymbolValue_tooSmall => cZstdError.MaxSymbolValueTooSmall,
|
||||
c.ZSTD_error_stabilityCondition_notRespected => cZstdError.StabilityConditionNotRespected,
|
||||
c.ZSTD_error_stage_wrong => cZstdError.StageWrong,
|
||||
c.ZSTD_error_init_missing => cZstdError.InitMissing,
|
||||
c.ZSTD_error_memory_allocation => cZstdError.MemoryAllocation,
|
||||
c.ZSTD_error_workSpace_tooSmall => cZstdError.WorkSpaceTooSmall,
|
||||
c.ZSTD_error_dstSize_tooSmall => cZstdError.DstSizeTooSmall,
|
||||
c.ZSTD_error_srcSize_wrong => cZstdError.SrcSizeWrong,
|
||||
c.ZSTD_error_dstBuffer_null => cZstdError.DstBufferNull,
|
||||
c.ZSTD_error_noForwardProgress_destFull => cZstdError.NoForwardProgressDestFull,
|
||||
c.ZSTD_error_noForwardProgress_inputEmpty => cZstdError.NoForwardProgressInputEmpty,
|
||||
else => cZstdError.Generic,
|
||||
};
|
||||
}
|
||||
|
||||
pub const cZstdError = error{
|
||||
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,
|
||||
MaxCode,
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
//! Directory entry from the directory table.
|
||||
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
|
||||
const InodeType = @import("inode.zig").InodeType;
|
||||
|
||||
const Entry = @This();
|
||||
|
||||
const Header = extern struct { // use extern due to bad alignment with packed.
|
||||
count: u32,
|
||||
block_start: u32,
|
||||
num: u32,
|
||||
};
|
||||
|
||||
const RawEntry = packed struct {
|
||||
offset: u16,
|
||||
inode_offset: i16,
|
||||
inode_type: InodeType,
|
||||
name_size: u16,
|
||||
};
|
||||
|
||||
block_start: u32,
|
||||
block_offset: u16,
|
||||
num: u32,
|
||||
inode_type: InodeType,
|
||||
name: []const u8,
|
||||
|
||||
pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry {
|
||||
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
|
||||
var hdr: Header = undefined;
|
||||
var raw: RawEntry = undefined;
|
||||
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 100); // Start out with a decent capacity instead of needing to allocate per header.
|
||||
errdefer out.deinit(alloc);
|
||||
while (cur_red < size) {
|
||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||
cur_red += @sizeOf(Header);
|
||||
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||
for (0..hdr.count + 1) |_| {
|
||||
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
||||
const name = try alloc.alloc(u8, raw.name_size + 1);
|
||||
errdefer alloc.free(name);
|
||||
try rdr.readSliceEndian(u8, name, .little);
|
||||
const val = out.addOneAssumeCapacity();
|
||||
val.* = .{
|
||||
.block_start = hdr.block_start,
|
||||
.block_offset = raw.offset,
|
||||
.num = @abs(hdr.num + raw.offset),
|
||||
.inode_type = raw.inode_type,
|
||||
.name = name,
|
||||
};
|
||||
cur_red += @sizeOf(RawEntry) + raw.name_size + 1;
|
||||
}
|
||||
}
|
||||
return out.toOwnedSlice(alloc);
|
||||
}
|
||||
|
||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.name);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const InodeType = @import("inode.zig").Type;
|
||||
const Compression = @import("superblock.zig").Compression;
|
||||
|
||||
const Header = extern struct { //use extern instead of packed, due to bit alignment
|
||||
count: u32,
|
||||
block: u32,
|
||||
num: u32,
|
||||
};
|
||||
|
||||
const RawEntry = struct {
|
||||
offset: u16,
|
||||
num_offset: i16,
|
||||
type: InodeType,
|
||||
size: u16,
|
||||
name: []const u8,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: anytype) !RawEntry {
|
||||
var fixed: [8]u8 = undefined;
|
||||
_ = try rdr.read(&fixed);
|
||||
const size = std.mem.readInt(u16, fixed[6..8], .little);
|
||||
const name = try alloc.alloc(u8, size + 1);
|
||||
_ = try rdr.read(name);
|
||||
return .{
|
||||
.offset = std.mem.readInt(u16, fixed[0..2], .little),
|
||||
.num_offset = std.mem.readInt(i16, fixed[2..4], .little),
|
||||
.type = @enumFromInt(std.mem.readInt(u16, fixed[4..6], .little)),
|
||||
.size = size,
|
||||
.name = name,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Entry = struct {
|
||||
block: u32,
|
||||
offset: u16,
|
||||
num: u32,
|
||||
type: InodeType,
|
||||
name: []const u8,
|
||||
|
||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.name);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn readDirectory(alloc: std.mem.Allocator, rdr: anytype, size: u32) ![]Entry {
|
||||
var entries: std.ArrayList(Entry) = .init(alloc);
|
||||
errdefer entries.deinit();
|
||||
var cur_red: u32 = 3; // dir size includes "." & "..", so its actual size is off by 3.
|
||||
var hdr: Header = undefined;
|
||||
while (cur_red < size) {
|
||||
_ = try rdr.read(std.mem.asBytes(&hdr));
|
||||
cur_red += 12;
|
||||
try entries.ensureUnusedCapacity(hdr.count + 1);
|
||||
for (0..hdr.count + 1) |_| {
|
||||
const raw_ent: RawEntry = try .init(alloc, rdr);
|
||||
cur_red += 9 + raw_ent.size;
|
||||
errdefer alloc.free(raw_ent.name);
|
||||
entries.appendAssumeCapacity(.{
|
||||
.block = hdr.block,
|
||||
.offset = raw_ent.offset,
|
||||
.num = @truncate(@abs(@as(i64, hdr.num) + raw_ent.num_offset)),
|
||||
.type = raw_ent.type,
|
||||
.name = raw_ent.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
return entries.toOwnedSlice();
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Replace symlinks with their targets
|
||||
dereference_symlinks: bool = false,
|
||||
/// Always extract a symlink's target if it's part of the archive.
|
||||
/// May result in the symlink's target being changed.
|
||||
unbreak_symlinks: bool = false,
|
||||
/// Do not set file's permissions & owner when extracted.
|
||||
ignore_permissions: bool = false,
|
||||
/// Verbose logging
|
||||
verbose: bool = false,
|
||||
/// Verbose logging writer. If not set, stdout is used.
|
||||
verbose_logger: std.io.AnyWriter = std.io.getStdOut().writer().any(),
|
||||
/// Number of threads used during extraction. Defualts to std.Thread.getCpuCount().
|
||||
thread_count: usize,
|
||||
|
||||
pub fn init() !Self {
|
||||
return .{
|
||||
.thread_count = try std.Thread.getCpuCount(),
|
||||
};
|
||||
}
|
||||
+220
-345
@@ -1,355 +1,230 @@
|
||||
//! A file/directory within the squashfs archive.
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const File = std.fs.File;
|
||||
const WaitGroup = std.Thread.WaitGroup;
|
||||
const Mutex = std.Thread.Mutex;
|
||||
|
||||
const dir = @import("directory.zig");
|
||||
|
||||
const DirEntry = dir.Entry;
|
||||
const Archive = @import("archive.zig");
|
||||
const DirEntry = @import("dir_entry.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const Inode = @import("inode.zig");
|
||||
const SfsReader = @import("reader.zig").SfsReader;
|
||||
const ToReader = @import("reader/to_read.zig").ToRead;
|
||||
const ExtractionOptions = @import("extract_options.zig");
|
||||
const DataReader = @import("reader/data.zig").DataReader;
|
||||
const Compression = @import("superblock.zig").Compression;
|
||||
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
||||
const DataReader = @import("util/data.zig");
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
|
||||
pub fn File(comptime T: type) type {
|
||||
return struct {
|
||||
pub const FileError = error{
|
||||
NotRegular,
|
||||
NotDirectory,
|
||||
NotFound,
|
||||
};
|
||||
const FileError = error{
|
||||
NotDirectory,
|
||||
NotRegularFile,
|
||||
NotSymlink,
|
||||
NotDevice,
|
||||
NotFound,
|
||||
ExtractionPathExists,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
const SfsFile = @This();
|
||||
|
||||
rdr: *SfsReader(T),
|
||||
// parent: *File(T),
|
||||
archive: *Archive,
|
||||
|
||||
inode: Inode,
|
||||
name: []const u8,
|
||||
inode: Inode,
|
||||
name: []const u8,
|
||||
|
||||
/// Directory entries. Only populated on directories.
|
||||
entries: ?[]DirEntry = null,
|
||||
/// File reader. Only populated on regular files.
|
||||
data_reader: ?DataReader(T) = null,
|
||||
|
||||
pub fn init(rdr: *SfsReader(T), inode: Inode, name: []const u8) !Self {
|
||||
const name_cpy: []u8 = try rdr.alloc.alloc(u8, name.len);
|
||||
@memcpy(name_cpy, name);
|
||||
var out = Self{
|
||||
.rdr = rdr,
|
||||
.inode = inode,
|
||||
.name = name_cpy,
|
||||
};
|
||||
switch (inode.data) {
|
||||
.dir => |d| {
|
||||
var meta = MetadataReader(T).init(
|
||||
rdr.alloc,
|
||||
rdr.super.comp,
|
||||
rdr.rdr,
|
||||
d.block + rdr.super.dir_start,
|
||||
);
|
||||
try meta.skip(d.offset);
|
||||
out.entries = try dir.readDirectory(rdr.alloc, &meta, d.size);
|
||||
},
|
||||
.ext_dir => |d| {
|
||||
var meta = MetadataReader(T).init(
|
||||
rdr.alloc,
|
||||
rdr.super.comp,
|
||||
rdr.rdr,
|
||||
d.block + rdr.super.dir_start,
|
||||
);
|
||||
try meta.skip(d.offset);
|
||||
out.entries = try dir.readDirectory(rdr.alloc, &meta, d.size);
|
||||
},
|
||||
.file => |f| {
|
||||
out.data_reader = try .init(rdr, inode);
|
||||
_ = f;
|
||||
//TODO: fragments
|
||||
// if (f.hasFragment()) {
|
||||
// try out.data_reader.?.addFragment(
|
||||
// try rdr.frag_table.get(f.frag_idx),
|
||||
// f.frag_offset,
|
||||
// );
|
||||
// }
|
||||
},
|
||||
.ext_file => |f| {
|
||||
out.data_reader = try .init(rdr, inode);
|
||||
_ = f;
|
||||
//TODO: Fragments
|
||||
// if (f.hasFragment()) {
|
||||
// try out.data_reader.?.addFragment(
|
||||
// try rdr.frag_table.get(f.frag_idx),
|
||||
// f.frag_offset,
|
||||
// );
|
||||
// }
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return out;
|
||||
}
|
||||
pub fn initFromRef(rdr: *SfsReader(T), ref: Inode.Ref, name: []const u8) !Self {
|
||||
var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ref.block + rdr.super.inode_start);
|
||||
try meta.skip(ref.offset);
|
||||
const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
|
||||
return .init(rdr, inode, name);
|
||||
}
|
||||
pub fn initFromEntry(rdr: *SfsReader(T), ent: DirEntry) !Self {
|
||||
var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ent.block + rdr.super.inode_start);
|
||||
try meta.skip(ent.offset);
|
||||
const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
|
||||
return .init(rdr, inode, ent.name);
|
||||
}
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.rdr.alloc.free(self.name);
|
||||
self.inode.deinit(self.rdr.alloc);
|
||||
if (self.entries != null) {
|
||||
for (self.entries.?) |e| {
|
||||
e.deinit(self.rdr.alloc);
|
||||
}
|
||||
self.rdr.alloc.free(self.entries.?);
|
||||
}
|
||||
if (self.data_reader != null) {
|
||||
self.data_reader.?.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uid(self: Self) !u32 {
|
||||
return self.rdr.id_table.get(self.inode.hdr.uid_idx);
|
||||
}
|
||||
pub fn gid(self: Self) !u32 {
|
||||
return self.rdr.id_table.get(self.inode.hdr.uid_idx);
|
||||
}
|
||||
|
||||
const Reader = std.io.GenericReader(*DataReader(T), anyerror, DataReader(T).read);
|
||||
|
||||
pub fn read(self: *Self, buf: []u8) !usize {
|
||||
if (self.data_reader == null) return FileError.NotRegular;
|
||||
return self.data_reader.?.read(buf);
|
||||
}
|
||||
pub fn reader(self: *Self) !Reader {
|
||||
if (self.data_reader == null) return FileError.NotRegular;
|
||||
return self.data_reader.?.reader();
|
||||
}
|
||||
|
||||
pub fn open(self: Self, path: []const u8) !Self {
|
||||
if (self.entries == null) return FileError.NotDirectory;
|
||||
if (path.len == 0) return self;
|
||||
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
|
||||
if (idx == 0) return self.open(path[1..]);
|
||||
const name = path[0..idx];
|
||||
for (self.entries.?) |e| {
|
||||
if (std.mem.eql(u8, e.name, name)) {
|
||||
var fil: Self = try .initFromEntry(self.rdr, e);
|
||||
if (idx >= path.len - 1) return fil;
|
||||
defer fil.deinit();
|
||||
return fil.open(path[idx + 1 ..]);
|
||||
}
|
||||
}
|
||||
return FileError.NotFound;
|
||||
}
|
||||
pub fn iterate(self: Self) Iterator {
|
||||
return .{
|
||||
.rdr = self.rdr,
|
||||
.entries = self.entries.?,
|
||||
};
|
||||
}
|
||||
|
||||
const Iterator = struct {
|
||||
rdr: *SfsReader(T),
|
||||
entries: []DirEntry,
|
||||
|
||||
idx: u32 = 0,
|
||||
|
||||
pub fn next(self: *Iterator) !?File(T) {
|
||||
if (self.idx >= self.entries.len) return null;
|
||||
const out = try Self.initFromEntry(self.rdr, self.entries[self.idx]);
|
||||
self.idx += 1;
|
||||
return out;
|
||||
}
|
||||
pub fn reset(self: *Iterator) void {
|
||||
self.idx = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const WaitGroup = std.Thread.WaitGroup;
|
||||
const Pool = std.Thread.Pool;
|
||||
const Mutex = std.Thread.Mutex;
|
||||
|
||||
pub const ExtractError = error{FileExists};
|
||||
|
||||
pub fn extract(self: *Self, op: ExtractionOptions, path: []const u8) !void {
|
||||
var wg: WaitGroup = .{};
|
||||
var pol: Pool = undefined;
|
||||
try pol.init(.{
|
||||
.n_jobs = op.thread_count,
|
||||
.allocator = self.rdr.alloc,
|
||||
});
|
||||
defer pol.deinit();
|
||||
var errs: std.ArrayList(anyerror) = .init(self.rdr.alloc);
|
||||
defer errs.deinit();
|
||||
try self.extractInode(op, &wg, &errs, &pol, self.inode, path);
|
||||
wg.wait();
|
||||
if (errs.items.len > 0) return errs.items[0];
|
||||
}
|
||||
fn extractInode(
|
||||
self: *Self,
|
||||
op: ExtractionOptions,
|
||||
wg: *WaitGroup,
|
||||
errs: *std.ArrayList(anyerror),
|
||||
pol: *Pool,
|
||||
inode: Inode,
|
||||
path: []const u8,
|
||||
) !void {
|
||||
wg.start();
|
||||
defer wg.finish(); //TODO: When everthing is threaded, this will need to be handled by the threads, not here.
|
||||
switch (inode.hdr.type) {
|
||||
.file, .ext_file => {
|
||||
var fil = try std.fs.cwd().createFile(path, .{});
|
||||
defer fil.close();
|
||||
var data: DataReader(T) = try .init(self.rdr, inode);
|
||||
defer data.deinit();
|
||||
try data.writeTo(fil); // TODO: Thread
|
||||
const fil_uid = self.rdr.id_table.get(inode.hdr.uid_idx) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error getting uid {} from table: {}\n", .{ inode.hdr.uid_idx, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
const fil_gid = self.rdr.id_table.get(inode.hdr.gid_idx) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error getting gid {} from table: {}\n", .{ inode.hdr.gid_idx, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
fil.chmod(inode.hdr.perm) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
fil.chown(fil_uid, fil_gid) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
//TODO: update mtime.
|
||||
},
|
||||
.dir, .ext_dir => {
|
||||
std.fs.cwd().makeDir(path) catch |err| {
|
||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) {
|
||||
return err;
|
||||
}
|
||||
};
|
||||
var dir_block: u32 = 0;
|
||||
var dir_offset: u16 = 0;
|
||||
var dir_size: u32 = 0;
|
||||
switch (inode.data) {
|
||||
.dir => |d| {
|
||||
dir_block = d.block;
|
||||
dir_offset = d.offset;
|
||||
dir_size = d.size;
|
||||
},
|
||||
.ext_dir => |d| {
|
||||
dir_block = d.block;
|
||||
dir_offset = d.offset;
|
||||
dir_size = d.size;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
var meta: MetadataReader(T) = .init(self.rdr.alloc, self.rdr.super.comp, self.rdr.rdr, dir_block + self.rdr.super.dir_start);
|
||||
try meta.skip(dir_offset);
|
||||
const entries = try dir.readDirectory(self.rdr.alloc, &meta, dir_size);
|
||||
defer self.rdr.alloc.free(entries);
|
||||
for (entries) |ent| {
|
||||
defer ent.deinit(self.rdr.alloc);
|
||||
var new_path: []u8 = undefined;
|
||||
if (path[path.len - 1] == '/') {
|
||||
new_path = self.rdr.alloc.alloc(u8, path.len + ent.name.len) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error allocating memory: {}\n", .{err}) catch {};
|
||||
}
|
||||
errs.append(err) catch {};
|
||||
continue;
|
||||
};
|
||||
@memcpy(new_path[0..path.len], path);
|
||||
@memcpy(new_path[path.len..], ent.name);
|
||||
} else {
|
||||
new_path = self.rdr.alloc.alloc(u8, path.len + ent.name.len + 1) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error allocating memory: {}\n", .{err}) catch {};
|
||||
}
|
||||
errs.append(err) catch {};
|
||||
continue;
|
||||
};
|
||||
@memcpy(new_path[0..path.len], path);
|
||||
new_path[path.len] = '/';
|
||||
@memcpy(new_path[path.len + 1 ..], ent.name);
|
||||
}
|
||||
defer self.rdr.alloc.free(new_path);
|
||||
|
||||
meta = .init(self.rdr.alloc, self.rdr.super.comp, self.rdr.rdr, ent.block + self.rdr.super.inode_start);
|
||||
meta.skip(ent.offset) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error reading inode: {}\n", .{err}) catch {};
|
||||
}
|
||||
errs.append(err) catch {};
|
||||
continue;
|
||||
};
|
||||
const new_inode = Inode.init(&meta, self.rdr.alloc, self.rdr.super.block_size) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error reading inode: {}\n", .{err}) catch {};
|
||||
}
|
||||
errs.append(err) catch {};
|
||||
continue;
|
||||
};
|
||||
defer new_inode.deinit(self.rdr.alloc);
|
||||
self.extractInode(op, wg, errs, pol, new_inode, new_path) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error extracting {s}: {}\n", .{ new_path, err }) catch {};
|
||||
}
|
||||
errs.append(err) catch {};
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
var fil = std.fs.cwd().openDir(path, .{ .iterate = true }) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error openning {s} to set permissions: {}\n", .{ path, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
const fil_uid = self.rdr.id_table.get(inode.hdr.uid_idx) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error getting uid {} from table: {}\n", .{ inode.hdr.uid_idx, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
const fil_gid = self.rdr.id_table.get(inode.hdr.gid_idx) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error getting gid {} from table: {}\n", .{ inode.hdr.gid_idx, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
fil.chmod(inode.hdr.perm) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
fil.chown(fil_uid, fil_gid) catch |err| {
|
||||
if (op.verbose) {
|
||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||
}
|
||||
return;
|
||||
};
|
||||
},
|
||||
// .symlink, .ext_symlink => {},
|
||||
else => {
|
||||
std.debug.print("TODO: {}\n", .{inode.hdr.type});
|
||||
},
|
||||
}
|
||||
}
|
||||
/// Initialize a new File.
|
||||
/// name is copied to the File so can be safely freed afterwards.
|
||||
pub fn init(archive: *Archive, inode: Inode, name: []const u8) !SfsFile {
|
||||
const new_name = try archive.allocator().alloc(u8, name.len);
|
||||
@memcpy(new_name, name);
|
||||
return .{
|
||||
.archive = archive,
|
||||
.inode = inode,
|
||||
.name = new_name,
|
||||
};
|
||||
}
|
||||
pub fn fromEntry(archive: *Archive, entry: DirEntry) !SfsFile {
|
||||
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, archive.decomp);
|
||||
try meta.interface.discardAll(entry.block_offset);
|
||||
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
||||
errdefer inode.deinit(archive.allocator());
|
||||
return .init(archive, inode, entry.name);
|
||||
}
|
||||
|
||||
pub fn deinit(self: SfsFile) void {
|
||||
var alloc = self.archive.allocator();
|
||||
alloc.free(self.name);
|
||||
self.inode.deinit(alloc);
|
||||
}
|
||||
|
||||
fn getEntries(self: SfsFile) ![]DirEntry {
|
||||
return self.inode.dirEntries(self.archive.allocator(), self.archive.*);
|
||||
}
|
||||
|
||||
pub fn ownerUid(self: SfsFile) !u16 {
|
||||
return self.archive.id(self.inode.hdr.uid_idx);
|
||||
}
|
||||
pub fn ownerGid(self: SfsFile) !u16 {
|
||||
return self.archive.id(self.inode.hdr.gid_idx);
|
||||
}
|
||||
pub fn permissions(self: SfsFile) u16 {
|
||||
return self.inode.hdr.permissions;
|
||||
}
|
||||
|
||||
pub fn isRegular(self: SfsFile) bool {
|
||||
return switch (self.inode.hdr.inode_type) {
|
||||
.file, .ext_file => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
/// The returned DataReader will no longer work if the File's deinit function is called
|
||||
/// or, more specifically, it's inode's deinit function is called.
|
||||
pub fn dataReader(self: SfsFile) !DataReader {
|
||||
return self.inode.dataReader(self.archive);
|
||||
}
|
||||
|
||||
pub fn isDir(self: SfsFile) bool {
|
||||
return switch (self.inode.hdr.inode_type) {
|
||||
.dir, .ext_dir => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
pub fn iterate(self: SfsFile) !Iterator {
|
||||
if (!self.isDir()) return FileError.NotDirectory;
|
||||
return .{
|
||||
.entries = try self.getEntries(),
|
||||
.archive = self.archive,
|
||||
};
|
||||
}
|
||||
/// Open a sub-file/folder within a directory at the given path.
|
||||
/// If path is "", ".", "/", or "./", this File is returned.
|
||||
pub fn open(self: SfsFile, path: []const u8) !SfsFile {
|
||||
if (!self.isDir()) return FileError.NotDirectory;
|
||||
if (pathIsSelf(path)) return self;
|
||||
|
||||
// Recursively stip ending & leading path separators.
|
||||
if (path[0] == '/') return self.open(path[1..]);
|
||||
if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]);
|
||||
|
||||
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
|
||||
const first_element = path[0..idx];
|
||||
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]);
|
||||
const entries = try self.getEntries();
|
||||
defer {
|
||||
var alloc = self.archive.allocator();
|
||||
for (entries) |e| {
|
||||
e.deinit(alloc);
|
||||
}
|
||||
alloc.free(entries);
|
||||
}
|
||||
var cur_slice = entries;
|
||||
var split = cur_slice.len / 2;
|
||||
while (cur_slice.len > 0) {
|
||||
split = cur_slice.len / 2;
|
||||
const comp = std.mem.order(u8, first_element, cur_slice[split].name);
|
||||
switch (comp) {
|
||||
.eq => {
|
||||
var fil: SfsFile = try .fromEntry(self.archive, cur_slice[split]);
|
||||
if (idx == path.len) {
|
||||
return fil;
|
||||
}
|
||||
defer fil.deinit();
|
||||
return fil.open(path[idx + 1 ..]);
|
||||
},
|
||||
.lt => cur_slice = cur_slice[0..split],
|
||||
.gt => cur_slice = cur_slice[split + 1 ..],
|
||||
}
|
||||
}
|
||||
return FileError.NotFound;
|
||||
}
|
||||
|
||||
pub fn isSymlink(self: SfsFile) bool {
|
||||
return switch (self.inode.hdr.inode_type) {
|
||||
.symlink, .ext_symlink => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
pub fn symlinkPath(self: SfsFile) ![]const u8 {
|
||||
if (!self.isSymlink()) return FileError.NotSymlink;
|
||||
return switch (self.inode.data) {
|
||||
.symlink => |s| s.target,
|
||||
.ext_symlink => |s| s.target,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Check if the File is a block or character device.
|
||||
pub fn isDevice(self: SfsFile) bool {
|
||||
return switch (self.inode.hdr.inode_type) {
|
||||
.block_dev, .char_dev, .ext_block_dev, .ext_char_dev => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
/// If the File is a block or character device, get's it's device number.
|
||||
pub fn devNum(self: SfsFile) !u32 {
|
||||
if (!self.isDevice()) return FileError.NotDevice;
|
||||
return switch (self.inode.data) {
|
||||
.block_dev, .char_dev => |d| d.dev,
|
||||
.ext_block_dev, .ext_char_dev => |d| d.dev,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist.
|
||||
/// If the gievn path is a folder, the File's contents will be extracted within.
|
||||
pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !void {
|
||||
var alloc = self.archive.allocator();
|
||||
var ext_path: []u8 = undefined;
|
||||
if (std.fs.cwd().statFile(path)) |stat| {
|
||||
if (stat.kind == .directory) {
|
||||
if (!self.isDir()) {
|
||||
const has_end_sep = path[path.len - 1] == '/';
|
||||
const alloc_size = if (has_end_sep)
|
||||
path.len + self.name.len
|
||||
else
|
||||
path.len + self.name.len + 1;
|
||||
ext_path = try alloc.alloc(u8, alloc_size);
|
||||
@memcpy(ext_path[0..path.len], path);
|
||||
@memcpy(ext_path[ext_path.len - self.name.len ..], self.name);
|
||||
if (!has_end_sep) ext_path[path.len] = '/';
|
||||
} else {
|
||||
ext_path = @constCast(path);
|
||||
}
|
||||
} else return FileError.ExtractionPathExists;
|
||||
} else |err| {
|
||||
if (err == error.FileNotFound) {
|
||||
ext_path = @constCast(path);
|
||||
} else {
|
||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
||||
return err;
|
||||
}
|
||||
}
|
||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
||||
return self.inode.extractToThreaded(self.archive, path, options, self.archive.thread_count);
|
||||
}
|
||||
|
||||
/// Utility function.
|
||||
pub fn pathIsSelf(path: []const u8) bool {
|
||||
if (path.len == 0) return true;
|
||||
if (path.len == 1 and (path[0] == '/' or path[0] == '.')) return true;
|
||||
if (path.len == 2 and (path[0] == '.' and path[1] == '/')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
entries: []DirEntry,
|
||||
archive: *Archive,
|
||||
|
||||
idx: u32 = 0,
|
||||
|
||||
pub fn next(self: *Iterator) !?SfsFile {
|
||||
if (self.idx >= self.entries.len) return null;
|
||||
defer self.idx += 1;
|
||||
return try SfsFile.fromEntry(self.archive, self.entries[self.idx]);
|
||||
}
|
||||
pub fn deinit(self: Iterator) void {
|
||||
var alloc = self.archive.allocator();
|
||||
for (self.entries) |e| {
|
||||
e.deinit(alloc);
|
||||
}
|
||||
alloc.free(self.entries);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const BlockSize = @import("inode/file.zig").BlockSize;
|
||||
|
||||
pub const FragEntry = packed struct {
|
||||
block: u64,
|
||||
size: BlockSize,
|
||||
_: u32,
|
||||
};
|
||||
+424
-39
@@ -1,16 +1,27 @@
|
||||
const std = @import("std");
|
||||
//! A file-system object. Represents a File or directory.
|
||||
|
||||
const dir = @import("inode/dir.zig");
|
||||
const file = @import("inode/file.zig");
|
||||
const misc = @import("inode/misc.zig");
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
const WaitGroup = std.Thread.WaitGroup;
|
||||
const Pool = std.Thread.Pool;
|
||||
|
||||
const Archive = @import("archive.zig");
|
||||
const DirEntry = @import("dir_entry.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const dir = @import("inode_data/dir.zig");
|
||||
const file = @import("inode_data/file.zig");
|
||||
const misc = @import("inode_data/misc.zig");
|
||||
const DataReader = @import("util/data.zig");
|
||||
const ThreadedDataReader = @import("util/data_threaded.zig");
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
|
||||
pub const Ref = packed struct {
|
||||
offset: u16,
|
||||
block: u32,
|
||||
block_offset: u16,
|
||||
block_start: u32,
|
||||
_: u16,
|
||||
};
|
||||
|
||||
pub const Type = enum(u16) {
|
||||
pub const InodeType = enum(u16) {
|
||||
dir = 1,
|
||||
file,
|
||||
symlink,
|
||||
@@ -27,16 +38,7 @@ pub const Type = enum(u16) {
|
||||
ext_socket,
|
||||
};
|
||||
|
||||
pub const Header = packed struct {
|
||||
type: Type,
|
||||
perm: u16,
|
||||
uid_idx: u16,
|
||||
gid_idx: u16,
|
||||
mod_time: u32,
|
||||
num: u32,
|
||||
};
|
||||
|
||||
pub const Data = union(enum) {
|
||||
pub const InodeData = union(InodeType) {
|
||||
dir: dir.Dir,
|
||||
file: file.File,
|
||||
symlink: misc.Symlink,
|
||||
@@ -53,36 +55,51 @@ pub const Data = union(enum) {
|
||||
ext_socket: misc.ExtIPC,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
pub const Header = packed struct {
|
||||
inode_type: InodeType,
|
||||
permissions: u16,
|
||||
uid_idx: u16,
|
||||
gid_idx: u16,
|
||||
mod_time: u32,
|
||||
num: u32,
|
||||
};
|
||||
|
||||
const Inode = @This();
|
||||
|
||||
hdr: Header,
|
||||
data: Data,
|
||||
data: InodeData,
|
||||
|
||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !Self {
|
||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
||||
var hdr: Header = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&hdr));
|
||||
const data: Data = switch (hdr.type) {
|
||||
.dir => .{ .dir = try .init(rdr) },
|
||||
.file => .{ .file = try .init(rdr, alloc, block_size) },
|
||||
.symlink => .{ .symlink = try .init(rdr, alloc) },
|
||||
.block_dev => .{ .block_dev = try .init(rdr) },
|
||||
.char_dev => .{ .char_dev = try .init(rdr) },
|
||||
.fifo => .{ .fifo = try .init(rdr) },
|
||||
.socket => .{ .socket = try .init(rdr) },
|
||||
.ext_dir => .{ .ext_dir = try .init(rdr) },
|
||||
.ext_file => .{ .ext_file = try .init(rdr, alloc, block_size) },
|
||||
.ext_symlink => .{ .ext_symlink = try .init(rdr, alloc) },
|
||||
.ext_block_dev => .{ .ext_block_dev = try .init(rdr) },
|
||||
.ext_char_dev => .{ .ext_char_dev = try .init(rdr) },
|
||||
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
|
||||
.ext_socket => .{ .ext_socket = try .init(rdr) },
|
||||
};
|
||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||
return .{
|
||||
.hdr = hdr,
|
||||
.data = data,
|
||||
.data = switch (hdr.inode_type) {
|
||||
.dir => .{ .dir = try .read(rdr) },
|
||||
.file => .{ .file = try .read(alloc, rdr, block_size) },
|
||||
.symlink => .{ .symlink = try .read(alloc, rdr) },
|
||||
.block_dev => .{ .block_dev = try .read(rdr) },
|
||||
.char_dev => .{ .char_dev = try .read(rdr) },
|
||||
.fifo => .{ .fifo = try .read(rdr) },
|
||||
.socket => .{ .socket = try .read(rdr) },
|
||||
.ext_dir => .{ .ext_dir = try .read(rdr) },
|
||||
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
|
||||
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
|
||||
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
|
||||
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
|
||||
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
|
||||
.ext_socket => .{ .ext_socket = try .read(rdr) },
|
||||
},
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: Self, alloc: std.mem.Allocator) void {
|
||||
pub fn readFromEntry(alloc: std.mem.Allocator, archive: *Archive, entry: DirEntry) !Inode {
|
||||
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||
try meta.interface.discardAll(entry.block_offset);
|
||||
return read(alloc, &meta.interface, archive.super.block_size);
|
||||
}
|
||||
|
||||
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||
switch (self.data) {
|
||||
.file => |f| alloc.free(f.block_sizes),
|
||||
.ext_file => |f| alloc.free(f.block_sizes),
|
||||
@@ -91,3 +108,371 @@ pub fn deinit(self: Self, alloc: std.mem.Allocator) void {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the data reader for a file inode.
|
||||
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !DataReader {
|
||||
return switch (self.hdr.inode_type) {
|
||||
.file => readerFromData(alloc, archive, self.data.file),
|
||||
.ext_file => readerFromData(alloc, archive, self.data.ext_file),
|
||||
else => error.NotRegularFile,
|
||||
};
|
||||
}
|
||||
fn readerFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !DataReader {
|
||||
var out: DataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
||||
if (data.frag_idx != 0xFFFFFFFF)
|
||||
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
||||
return out;
|
||||
}
|
||||
/// Get a threaded data reader for a file inode.
|
||||
pub fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !ThreadedDataReader {
|
||||
return switch (self.hdr.inode_type) {
|
||||
.file => threadedReaderFromData(alloc, archive, self.data.file),
|
||||
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
|
||||
else => error.NotRegularFile,
|
||||
};
|
||||
}
|
||||
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !ThreadedDataReader {
|
||||
var out: ThreadedDataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
||||
if (data.frag_idx != 0xFFFFFFFF)
|
||||
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Get the directory entries for a directory inode.
|
||||
pub fn dirEntries(self: Inode, alloc: std.mem.Allocator, archive: Archive) ![]DirEntry {
|
||||
return switch (self.hdr.inode_type) {
|
||||
.dir => entriesFromData(alloc, archive, self.data.dir),
|
||||
.ext_dir => entriesFromData(alloc, archive, self.data.ext_dir),
|
||||
else => error.NotDirectory,
|
||||
};
|
||||
}
|
||||
fn entriesFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) ![]DirEntry {
|
||||
var rdr = try archive.fil.readerAt(archive.super.dir_start + data.block_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||
try meta.interface.discardAll(data.block_offset);
|
||||
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
||||
}
|
||||
|
||||
/// Extract the inode to the given path. Single threaded.
|
||||
pub fn extractTo(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
// Removing any trailing separators since that's the easiest path forward.
|
||||
if (path[path.len - 1] == '/') return self.extractTo(archive, path[0 .. path.len - 1], options);
|
||||
std.fs.cwd().makeDir(path) catch |err| {
|
||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
|
||||
};
|
||||
var alloc = archive.allocator();
|
||||
const entries = try self.dirEntries(alloc, archive.*);
|
||||
defer {
|
||||
for (entries) |entry| entry.deinit(alloc);
|
||||
alloc.free(entries);
|
||||
}
|
||||
for (entries) |entry| {
|
||||
var new_path = try alloc.alloc(u8, path.len + 1 + entry.name.len);
|
||||
@memcpy(new_path[0..path.len], path);
|
||||
@memcpy(new_path[path.len + 1 ..], entry.name);
|
||||
new_path[path.len] = '/';
|
||||
defer alloc.free(new_path);
|
||||
|
||||
var inode: Inode = try readFromEntry(alloc, archive, entry);
|
||||
defer inode.deinit(alloc);
|
||||
try inode.extractTo(archive, new_path, options);
|
||||
}
|
||||
},
|
||||
.file, .ext_file => try self.extractRegFile(archive.allocator(), archive, path, options),
|
||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||
else => try self.extractDevice(archive, path, options),
|
||||
}
|
||||
}
|
||||
|
||||
const Perms = struct {
|
||||
path: []const u8,
|
||||
uid: u16,
|
||||
gid: u16,
|
||||
perm: u16,
|
||||
};
|
||||
|
||||
/// Extract the inode to the given path. Multi-threaded.
|
||||
/// Functions identically to extractTo on all but regular files and directories.
|
||||
///
|
||||
/// If threads <= 1, then this just calls extractTo.
|
||||
pub fn extractToThreaded(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, threads: usize) !void {
|
||||
if (threads <= 1) return self.extractTo(archive, path, options);
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
// Removing any trailing separators since that's the easiest path forward.
|
||||
if (path[path.len - 1] == '/') return self.extractToThreaded(archive, path[0 .. path.len - 1], options, threads);
|
||||
|
||||
// Fixed Allocator
|
||||
// const mem_buf = archive.allocator().alloc(u8, 2 * 1024 * 1024 * 1024);
|
||||
// defer archive.allocator().free(mem_buf);
|
||||
// var fixed_alloc: std.heap.FixedBufferAllocator = .init(mem_buf);
|
||||
// const alloc = fixed_alloc.threadSafeAllocator();
|
||||
|
||||
// Arena Allocator
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(archive.allocator());
|
||||
defer arena_alloc.deinit();
|
||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
||||
const alloc = thread_alloc.allocator();
|
||||
|
||||
var wg: WaitGroup = .{};
|
||||
var perms: ?std.ArrayList(Perms) = if (options.ignore_permissions) null else try .initCapacity(alloc, 100);
|
||||
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
|
||||
var pool: Pool = undefined;
|
||||
try pool.init(.{ .allocator = alloc, .n_jobs = threads - 1 });
|
||||
defer pool.deinit();
|
||||
var out_err: ?anyerror = null;
|
||||
|
||||
wg.start();
|
||||
self.extractThread(alloc, archive, path, options, &wg, &pool, &out_err, &perms);
|
||||
pool.waitAndWork(&wg);
|
||||
if (out_err != null) return out_err.?;
|
||||
|
||||
if (perms != null) {
|
||||
for (perms.?.items) |p| {
|
||||
var fil = try std.fs.cwd().openFile(p.path, .{});
|
||||
try fil.chmod(p.perm);
|
||||
try fil.chown(p.uid, p.gid);
|
||||
}
|
||||
}
|
||||
},
|
||||
.file, .ext_file => {
|
||||
const alloc = archive.allocator();
|
||||
|
||||
var pool: Pool = undefined;
|
||||
try pool.init(.{ .allocator = alloc, .n_jobs = threads });
|
||||
defer pool.deinit();
|
||||
|
||||
try self.extractRegFileThreaded(alloc, archive, path, options, &pool);
|
||||
|
||||
if (!options.ignore_permissions) {
|
||||
var fil = try std.fs.cwd().openFile(path, .{});
|
||||
try fil.chmod(self.hdr.permissions);
|
||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
||||
}
|
||||
},
|
||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||
else => try self.extractDevice(archive, path, options),
|
||||
}
|
||||
}
|
||||
|
||||
fn extractThreadEntry(
|
||||
entry: DirEntry,
|
||||
alloc: std.mem.Allocator,
|
||||
archive: *Archive,
|
||||
path: []const u8,
|
||||
options: ExtractionOptions,
|
||||
wg: *WaitGroup,
|
||||
pool: *Pool,
|
||||
out_err: *?anyerror,
|
||||
perms: *?std.ArrayList(Perms),
|
||||
) void {
|
||||
var new_path = alloc.alloc(u8, path.len + entry.name.len + 1) catch |err| {
|
||||
wg.finish();
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
@memcpy(new_path[0..path.len], path);
|
||||
@memcpy(new_path[path.len + 1 ..], entry.name);
|
||||
new_path[path.len] = '/';
|
||||
var inode = readFromEntry(alloc, archive, entry) catch |err| {
|
||||
out_err.* = err;
|
||||
wg.finish();
|
||||
return;
|
||||
};
|
||||
inode.extractThread(alloc, archive, new_path, options, wg, pool, out_err, perms);
|
||||
}
|
||||
|
||||
/// Extract threadedly the inode to the path.
|
||||
fn extractThread(
|
||||
self: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
archive: *Archive,
|
||||
path: []const u8,
|
||||
options: ExtractionOptions,
|
||||
wg: *WaitGroup,
|
||||
pool: *Pool,
|
||||
out_err: *?anyerror,
|
||||
perms: *?std.ArrayList(Perms),
|
||||
) void {
|
||||
defer wg.finish();
|
||||
if (out_err.* != null) return;
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
std.fs.cwd().makeDir(path) catch |err| {
|
||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) {
|
||||
out_err.* = err;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const entries = self.dirEntries(alloc, archive.*) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
wg.startMany(entries.len);
|
||||
// defer files.deinit(alloc); We don't need to do this due to ArenaAllocator
|
||||
for (entries) |entry| {
|
||||
if (entry.inode_type == .dir) {
|
||||
extractThreadEntry(entry, alloc, archive, path, options, wg, pool, out_err, perms);
|
||||
continue;
|
||||
}
|
||||
pool.spawn(
|
||||
extractThreadEntry,
|
||||
.{
|
||||
entry,
|
||||
alloc,
|
||||
archive,
|
||||
path,
|
||||
options,
|
||||
wg,
|
||||
pool,
|
||||
out_err,
|
||||
perms,
|
||||
},
|
||||
) catch |err| {
|
||||
wg.finish();
|
||||
out_err.* = err;
|
||||
continue;
|
||||
};
|
||||
}
|
||||
if (!options.ignore_permissions) {
|
||||
const new_val = perms.*.?.addOne(alloc) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
new_val.* = .{
|
||||
.path = path,
|
||||
.uid = archive.id(self.hdr.uid_idx) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
},
|
||||
.gid = archive.id(self.hdr.gid_idx) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
},
|
||||
.perm = self.hdr.permissions,
|
||||
};
|
||||
}
|
||||
},
|
||||
.file, .ext_file => {
|
||||
self.extractRegFileThreaded(alloc, archive, path, options, pool) catch |err| {
|
||||
out_err.* = err;
|
||||
};
|
||||
},
|
||||
.symlink, .ext_symlink => {
|
||||
self.extractSymlink(path) catch |err| {
|
||||
out_err.* = err;
|
||||
};
|
||||
},
|
||||
else => {
|
||||
self.extractDevice(archive, path, options) catch |err| {
|
||||
out_err.* = err;
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
/// Creates and writes the inode file contents to the given path.
|
||||
/// Optionally set owner & permissions.
|
||||
///
|
||||
/// Assumes the inode is a file or ext_file type.
|
||||
fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
var fil = try std.fs.cwd().createFile(path, .{});
|
||||
defer fil.close();
|
||||
var wrt = fil.writer(&[0]u8{});
|
||||
var dat_rdr = try self.dataReader(alloc, archive);
|
||||
defer dat_rdr.deinit();
|
||||
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
||||
try wrt.interface.flush();
|
||||
// updateTime is in nanoseconds (a billionth of a second). mod_time is in seconds.
|
||||
// TODO: fix
|
||||
// try fil.updateTimes(self.hdr.mod_time, self.hdr.mod_time);
|
||||
if (!options.ignore_permissions) {
|
||||
try fil.chmod(self.hdr.permissions);
|
||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
||||
}
|
||||
}
|
||||
/// Extract the inode file contents to the given path threadedly.
|
||||
/// pool is used to spawn threads.
|
||||
///
|
||||
/// Assumes the inode is a file or ext_file type.
|
||||
fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions, pool: *Pool) !void {
|
||||
var fil = try std.fs.cwd().createFile(path, .{});
|
||||
var data = try self.threadedDataReader(alloc, archive);
|
||||
try data.extractThreaded(fil, pool);
|
||||
if (!options.ignore_permissions) {
|
||||
try fil.chmod(self.hdr.permissions);
|
||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
||||
}
|
||||
}
|
||||
/// Creates the symlink described by the inode.
|
||||
///
|
||||
/// Assumes the inode is a symlink or ext_symlink type.
|
||||
fn extractSymlink(self: Inode, path: []const u8) !void {
|
||||
const target = switch (self.data) {
|
||||
.symlink => |s| s.target,
|
||||
.ext_symlink => |s| s.target,
|
||||
else => unreachable,
|
||||
};
|
||||
try std.fs.cwd().symLink(target, path, .{});
|
||||
}
|
||||
/// Creates the device described by the inode.
|
||||
///
|
||||
/// Optionally set owner & permissions.
|
||||
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
|
||||
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
var mode: u32 = undefined;
|
||||
var dev: u32 = 0;
|
||||
switch (self.data) {
|
||||
.char_dev => |d| {
|
||||
mode = std.posix.S.IFCHR;
|
||||
dev = d.dev;
|
||||
},
|
||||
.ext_char_dev => |d| {
|
||||
mode = std.posix.S.IFCHR;
|
||||
dev = d.dev;
|
||||
},
|
||||
.block_dev => |d| {
|
||||
mode = std.posix.S.IFBLK;
|
||||
dev = d.dev;
|
||||
},
|
||||
.ext_block_dev => |d| {
|
||||
mode = std.posix.S.IFBLK;
|
||||
dev = d.dev;
|
||||
},
|
||||
.fifo, .ext_fifo => mode = std.posix.S.IFIFO,
|
||||
.socket, .ext_socket => mode = std.posix.S.IFSOCK,
|
||||
else => unreachable,
|
||||
}
|
||||
const res: std.os.linux.E = @enumFromInt(std.os.linux.mknod(@ptrCast(path), mode, dev));
|
||||
switch (res) {
|
||||
.SUCCESS => {},
|
||||
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
|
||||
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
|
||||
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
|
||||
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
|
||||
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
|
||||
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
|
||||
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
|
||||
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
|
||||
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
|
||||
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
|
||||
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
|
||||
else => return blk: {
|
||||
std.debug.print("unhandled mknod result: {}\n", .{res});
|
||||
break :blk std.fs.Dir.MakeError.Unexpected;
|
||||
},
|
||||
}
|
||||
var fil = try std.fs.cwd().openFile(path, .{});
|
||||
// updateTime is in nanoseconds (a billionth of a second). mod_time is in seconds.
|
||||
// TODO: fix
|
||||
// try fil.updateTimes(self.hdr.mod_time, self.hdr.mod_time);
|
||||
if (!options.ignore_permissions) {
|
||||
try fil.chmod(self.hdr.permissions);
|
||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
||||
}
|
||||
if (!options.ignore_xattr) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Dir = packed struct {
|
||||
block: u32,
|
||||
hard_link: u32,
|
||||
size: u16,
|
||||
offset: u16,
|
||||
parent_num: u32,
|
||||
|
||||
pub fn init(rdr: anytype) !Dir {
|
||||
var out: Dir = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&out));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtDir = packed struct {
|
||||
hard_link: u32,
|
||||
size: u32,
|
||||
block: u32,
|
||||
parent_num: u32,
|
||||
idx_count: u16,
|
||||
offset: u16,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn init(rdr: anytype) !ExtDir {
|
||||
var out: ExtDir = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&out));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const BlockSize = packed struct {
|
||||
size: u24,
|
||||
uncompressed: bool,
|
||||
_: u7,
|
||||
};
|
||||
|
||||
pub const File = struct {
|
||||
block: u32,
|
||||
frag_idx: u32,
|
||||
frag_offset: u32,
|
||||
size: u32,
|
||||
block_sizes: []BlockSize,
|
||||
|
||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !File {
|
||||
var fixed: [16]u8 = undefined;
|
||||
_ = try rdr.read(&fixed);
|
||||
const frag_idx = std.mem.readInt(u32, fixed[4..8], .little);
|
||||
const size = std.mem.readInt(u32, fixed[12..16], .little);
|
||||
var blocks: u32 = size / block_size;
|
||||
if (size % block_size > 0 and frag_idx == 0xffffffff) {
|
||||
blocks += 1;
|
||||
}
|
||||
const block_sizes = try alloc.alloc(BlockSize, blocks);
|
||||
errdefer alloc.free(block_sizes);
|
||||
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
|
||||
return .{
|
||||
.block = std.mem.readInt(u32, fixed[0..4], .little),
|
||||
.frag_idx = frag_idx,
|
||||
.frag_offset = std.mem.readInt(u32, fixed[8..12], .little),
|
||||
.size = size,
|
||||
.block_sizes = block_sizes,
|
||||
};
|
||||
}
|
||||
pub fn hasFragment(self: File) bool {
|
||||
return self.frag_idx != 0xffffffff;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtFile = struct {
|
||||
block: u64,
|
||||
size: u64,
|
||||
sparse: u64,
|
||||
hard_link: u32,
|
||||
frag_idx: u32,
|
||||
frag_offset: u32,
|
||||
xattr_idx: u32,
|
||||
block_sizes: []BlockSize,
|
||||
|
||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !ExtFile {
|
||||
var fixed: [40]u8 = undefined;
|
||||
_ = try rdr.read(&fixed);
|
||||
const size = std.mem.readInt(u64, fixed[8..16], .little);
|
||||
const frag_idx = std.mem.readInt(u32, fixed[28..32], .little);
|
||||
var blocks: u32 = @truncate(size / block_size);
|
||||
if (size % block_size > 0 and frag_idx == 0xffffffff) {
|
||||
blocks += 1;
|
||||
}
|
||||
const block_sizes = try alloc.alloc(BlockSize, blocks);
|
||||
errdefer alloc.free(block_sizes);
|
||||
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
|
||||
return .{
|
||||
.block = std.mem.readInt(u64, fixed[0..8], .little),
|
||||
.size = size,
|
||||
.sparse = std.mem.readInt(u64, fixed[16..24], .little),
|
||||
.hard_link = std.mem.readInt(u32, fixed[24..28], .little),
|
||||
.frag_idx = frag_idx,
|
||||
.frag_offset = std.mem.readInt(u32, fixed[32..36], .little),
|
||||
.xattr_idx = std.mem.readInt(u32, fixed[36..40], .little),
|
||||
.block_sizes = block_sizes,
|
||||
};
|
||||
}
|
||||
pub fn hasFragment(self: ExtFile) bool {
|
||||
return self.frag_idx != 0xffffffff;
|
||||
}
|
||||
};
|
||||
@@ -1,87 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Symlink = struct {
|
||||
hard_link: u32,
|
||||
// size: u32,
|
||||
target: []const u8,
|
||||
|
||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !Symlink {
|
||||
var fixed: [8]u8 = undefined;
|
||||
_ = try rdr.read(&fixed);
|
||||
const size = std.mem.readInt(u32, fixed[4..8], .little);
|
||||
const target = try alloc.alloc(u8, size);
|
||||
errdefer alloc.free(target);
|
||||
_ = try rdr.read(target);
|
||||
return .{
|
||||
.hard_link = std.mem.readInt(u32, fixed[0..4], .little),
|
||||
.target = target,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtSymlink = struct {
|
||||
hard_link: u32,
|
||||
// size: u32,
|
||||
target: []const u8,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !ExtSymlink {
|
||||
var fixed: [8]u8 = undefined;
|
||||
_ = try rdr.read(&fixed);
|
||||
const size = std.mem.readInt(u32, fixed[4..8], .little);
|
||||
const target = try alloc.alloc(u8, size);
|
||||
errdefer alloc.free(target);
|
||||
_ = try rdr.read(target);
|
||||
var xattr_idx: u32 = 0;
|
||||
_ = try rdr.read(std.mem.asBytes(&xattr_idx));
|
||||
return .{
|
||||
.hard_link = std.mem.readInt(u32, fixed[0..4], .little),
|
||||
.target = target,
|
||||
.xattr_idx = xattr_idx,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Dev = packed struct {
|
||||
hard_link: u32,
|
||||
device: u32,
|
||||
|
||||
pub fn init(rdr: anytype) !Dev {
|
||||
var out: Dev = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&out));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtDev = packed struct {
|
||||
hard_link: u32,
|
||||
device: u32,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn init(rdr: anytype) !ExtDev {
|
||||
var out: ExtDev = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&out));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
pub const IPC = packed struct {
|
||||
hard_link: u32,
|
||||
|
||||
pub fn init(rdr: anytype) !IPC {
|
||||
var out: IPC = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&out));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtIPC = packed struct {
|
||||
hard_link: u32,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn init(rdr: anytype) !ExtIPC {
|
||||
var out: ExtIPC = undefined;
|
||||
_ = try rdr.read(std.mem.asBytes(&out));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
const Reader = @import("std").Io.Reader;
|
||||
|
||||
pub const Dir = packed struct {
|
||||
block_start: u32,
|
||||
hard_links: u32,
|
||||
size: u16,
|
||||
block_offset: u16,
|
||||
parent_num: u32,
|
||||
|
||||
pub fn read(rdr: *Reader) !Dir {
|
||||
var d: Dir = undefined;
|
||||
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtDir = packed struct {
|
||||
hard_links: u32,
|
||||
size: u32,
|
||||
block_start: u32,
|
||||
parent_num: u32,
|
||||
idx_count: u16,
|
||||
block_offset: u16,
|
||||
xattr_id: u32,
|
||||
// index: []DirIndex
|
||||
|
||||
pub fn read(rdr: *Reader) !ExtDir {
|
||||
var d: ExtDir = undefined;
|
||||
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
||||
return d;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
|
||||
pub const BlockSize = packed struct {
|
||||
size: u24,
|
||||
uncompressed: bool,
|
||||
_: u7,
|
||||
};
|
||||
|
||||
pub const File = struct {
|
||||
block_start: u32, // bytes 0-3
|
||||
frag_idx: u32, // bytes 4-7
|
||||
frag_block_offset: u32, // bytes 8-11
|
||||
size: u32, // bytes 12-15
|
||||
block_sizes: []BlockSize,
|
||||
|
||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||
var start: [16]u8 = undefined;
|
||||
try rdr.readSliceAll(&start);
|
||||
const frag_idx: u32 = std.mem.readInt(u32, start[4..8], .little);
|
||||
const size: u32 = std.mem.readInt(u32, start[12..16], .little);
|
||||
var num_blocks: u32 = size / block_size;
|
||||
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||
errdefer alloc.free(sizes);
|
||||
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||
return .{
|
||||
.block_start = std.mem.readInt(u32, start[0..4], .little),
|
||||
.frag_idx = frag_idx,
|
||||
.frag_block_offset = std.mem.readInt(u32, start[8..12], .little),
|
||||
.size = size,
|
||||
.block_sizes = sizes,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.block_sizes);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtFile = struct {
|
||||
block_start: u64, // bytes 0-7
|
||||
size: u64, // bytes 8-15
|
||||
sparse: u64, // bytes 16-23
|
||||
hard_links: u32, // bytes 24-27
|
||||
frag_idx: u32, // bytes 28-31
|
||||
frag_block_offset: u32, // bytes 32-35
|
||||
xattr_idx: u32, // bytes 36-39
|
||||
block_sizes: []BlockSize,
|
||||
|
||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
||||
var start: [40]u8 = undefined;
|
||||
try rdr.readSliceAll(&start);
|
||||
const frag_idx: u32 = std.mem.readInt(u32, start[28..32], .little);
|
||||
const size: u64 = std.mem.readInt(u64, start[8..16], .little);
|
||||
var num_blocks: u32 = @truncate(size / block_size);
|
||||
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||
errdefer alloc.free(sizes);
|
||||
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||
return .{
|
||||
.block_start = std.mem.readInt(u64, start[0..8], .little),
|
||||
.size = size,
|
||||
.sparse = std.mem.readInt(u64, start[16..24], .little),
|
||||
.hard_links = std.mem.readInt(u32, start[24..28], .little),
|
||||
.frag_idx = frag_idx,
|
||||
.frag_block_offset = std.mem.readInt(u32, start[32..36], .little),
|
||||
.xattr_idx = std.mem.readInt(u32, start[36..40], .little),
|
||||
.block_sizes = sizes,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.block_sizes);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
|
||||
pub const Symlink = struct {
|
||||
hard_links: u32,
|
||||
target: []const u8,
|
||||
|
||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
|
||||
var start: [8]u8 = undefined;
|
||||
try rdr.readSliceAll(&start);
|
||||
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
||||
const target = try alloc.alloc(u8, target_size + 1);
|
||||
errdefer alloc.free(target);
|
||||
try rdr.readSliceEndian(u8, target, .little);
|
||||
return .{
|
||||
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
||||
.target = target,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.target);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ExtSymlink = struct {
|
||||
hard_links: u32,
|
||||
target: []const u8,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
|
||||
var start: [8]u8 = undefined;
|
||||
try rdr.readSliceAll(&start);
|
||||
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
||||
const target = try alloc.alloc(u8, target_size + 1);
|
||||
errdefer alloc.free(target);
|
||||
try rdr.readSliceEndian(u8, target, .little);
|
||||
var xattr_idx: u32 = undefined;
|
||||
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
||||
return .{
|
||||
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
||||
.target = target,
|
||||
.xattr_idx = xattr_idx,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.target);
|
||||
}
|
||||
};
|
||||
|
||||
/// A block or character device.
|
||||
pub const Dev = packed struct {
|
||||
hard_links: u32,
|
||||
dev: u32,
|
||||
|
||||
pub fn read(rdr: *Reader) !Dev {
|
||||
var d: Dev = undefined;
|
||||
try rdr.readSliceEndian(Dev, @ptrCast(&d), .little);
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
/// An extended block or character device.
|
||||
pub const ExtDev = packed struct {
|
||||
hard_links: u32,
|
||||
dev: u32,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn read(rdr: *Reader) !ExtDev {
|
||||
var d: ExtDev = undefined;
|
||||
try rdr.readSliceEndian(ExtDev, @ptrCast(&d), .little);
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
/// A socket or FIFO file.
|
||||
pub const IPC = packed struct {
|
||||
hard_links: u32,
|
||||
|
||||
pub fn read(rdr: *Reader) !IPC {
|
||||
var d: IPC = undefined;
|
||||
try rdr.readSliceEndian(IPC, @ptrCast(&d), .little);
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
/// An extended socket or FIFO file.
|
||||
pub const ExtIPC = packed struct {
|
||||
hard_links: u32,
|
||||
xattr_idx: u32,
|
||||
|
||||
pub fn read(rdr: *Reader) !ExtIPC {
|
||||
var d: ExtIPC = undefined;
|
||||
try rdr.readSliceEndian(ExtIPC, @ptrCast(&d), .little);
|
||||
return d;
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Inode = @import("inode.zig");
|
||||
const File = @import("file.zig").File;
|
||||
const Table = @import("table.zig").Table;
|
||||
const PRead = @import("reader/p_read.zig").PRead;
|
||||
const FragEntry = @import("fragment.zig").FragEntry;
|
||||
const Superblock = @import("superblock.zig").Superblock;
|
||||
const ExtractionOptions = @import("extract_options.zig");
|
||||
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
||||
|
||||
pub const SfsError = error{
|
||||
NotExportable,
|
||||
};
|
||||
|
||||
pub fn SfsReader(comptime T: type) type {
|
||||
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
rdr: PRead(T),
|
||||
|
||||
super: Superblock = undefined,
|
||||
/// ID table. Can be accessed directly
|
||||
id_table: Table(u32, T) = undefined,
|
||||
/// Fragment table. Can be accessed directly
|
||||
frag_table: Table(FragEntry, T) = undefined,
|
||||
/// Export table. Each element is an inode referce.
|
||||
/// If accessing directly, keep in mind, the table starts at inode 1, as such it's recommended to use the InodeAt function instead.
|
||||
export_table: Table(Inode.Ref, T) = undefined,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: T, offset: u64) !Self {
|
||||
var out: Self = .{
|
||||
.alloc = alloc,
|
||||
.rdr = .init(rdr, offset),
|
||||
};
|
||||
_ = try rdr.pread(std.mem.asBytes(&out.super), 0);
|
||||
out.frag_table = .init(alloc, out.rdr, out.super.comp, out.super.frag_start, out.super.frag_count);
|
||||
out.id_table = .init(alloc, out.rdr, out.super.comp, out.super.id_start, out.super.id_count);
|
||||
out.export_table = .init(alloc, out.rdr, out.super.comp, out.super.export_start, out.super.inode_count);
|
||||
return out;
|
||||
}
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.id_table.deinit();
|
||||
self.frag_table.deinit();
|
||||
self.export_table.deinit();
|
||||
}
|
||||
|
||||
/// A representation of the archives root folder.
|
||||
pub fn root(self: *Self) !File(T) {
|
||||
return .initFromRef(self, self.super.root_ref, "");
|
||||
}
|
||||
/// Get the file at path. Equivelent to calling open on the root File.
|
||||
pub fn open(self: *Self, path: []const u8) !File(T) {
|
||||
var rt = try self.root();
|
||||
if (path.len == 0 or (path.len == 1 and path[0] == '/') or path.len == 1 and path[0] == '.') return rt;
|
||||
defer rt.deinit();
|
||||
return rt.open(path);
|
||||
}
|
||||
/// Extract the entire archive to the given path & with the given options.
|
||||
/// Equivelent to calling extract on the root File.
|
||||
pub fn extract(self: *Self, op: ExtractionOptions, path: []const u8) !void {
|
||||
var rt = try self.root();
|
||||
defer rt.deinit();
|
||||
return rt.extract(op, path);
|
||||
}
|
||||
|
||||
/// Returns the Inode with the given Inode Number.
|
||||
/// Requires the archive to have an export table.
|
||||
pub fn inodeAt(self: Self, num: u32) !Inode {
|
||||
if (!self.super.flags.has_export) return SfsError.NotExportable;
|
||||
const ref = try self.export_table.get(num - 1);
|
||||
var meta = MetadataReader(T).init(
|
||||
self.alloc,
|
||||
self.super.comp,
|
||||
self.rdr,
|
||||
self.super.inode_start + ref.block,
|
||||
);
|
||||
try meta.skip(ref.offset);
|
||||
return .init(meta, self.alloc, self.super.block_size);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Inode = @import("../inode.zig");
|
||||
const PRead = @import("p_read.zig").PRead;
|
||||
const SfsReader = @import("../reader.zig").SfsReader;
|
||||
const FragEntry = @import("../fragment.zig").FragEntry;
|
||||
const BlockSize = @import("../inode/file.zig").BlockSize;
|
||||
const Compression = @import("../superblock.zig").Compression;
|
||||
|
||||
const DataReaderError = error{
|
||||
EOF,
|
||||
InvalidIndex,
|
||||
ExtractionActive,
|
||||
};
|
||||
|
||||
const DecompCompletion = struct {
|
||||
errs: std.ArrayList(anyerror),
|
||||
map: std.AutoArrayHashMap(usize, []u8),
|
||||
mut: std.Thread.Mutex = .{},
|
||||
cond: std.Thread.Condition = .{},
|
||||
|
||||
fn init(alloc: std.mem.Allocator) DecompCompletion {
|
||||
return .{
|
||||
.errs = .init(alloc),
|
||||
.map = .init(alloc),
|
||||
};
|
||||
}
|
||||
fn deinit(self: *DecompCompletion) void {
|
||||
self.errs.deinit();
|
||||
self.map.deinit();
|
||||
}
|
||||
|
||||
fn clear(self: *DecompCompletion) void {
|
||||
self.mut.lock();
|
||||
defer self.mut.unlock();
|
||||
self.errs.clearAndFree();
|
||||
self.map.clearAndFree();
|
||||
}
|
||||
|
||||
fn add(self: *DecompCompletion, idx: usize, data: []u8) !void {
|
||||
self.mut.lock();
|
||||
defer self.mut.unlock();
|
||||
defer self.cond.signal();
|
||||
try self.map.put(idx, data);
|
||||
}
|
||||
fn addErr(self: *DecompCompletion, err: anyerror) void {
|
||||
self.mut.lock();
|
||||
defer self.mut.unlock();
|
||||
defer self.cond.signal();
|
||||
self.errs.append(err) catch {};
|
||||
}
|
||||
|
||||
fn getBlock(self: *DecompCompletion, idx: usize) ?[]u8 {
|
||||
const res = self.map.fetchSwapRemove(idx);
|
||||
if (res == null) return null;
|
||||
return res.?.value;
|
||||
}
|
||||
fn hasErrs(self: DecompCompletion) bool {
|
||||
return self.errs.items.len > 0;
|
||||
}
|
||||
fn condWait(self: *DecompCompletion) void {
|
||||
self.cond.wait(&self.mut);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn DataReader(comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
rdr: PRead(T),
|
||||
comp: Compression,
|
||||
block_size: u32,
|
||||
|
||||
sizes: []BlockSize,
|
||||
offsets: []u64,
|
||||
file_size: u64,
|
||||
|
||||
frag: ?[]u8 = null,
|
||||
|
||||
completion: DecompCompletion,
|
||||
|
||||
pub fn init(rdr: *SfsReader(T), inode: Inode) !Self {
|
||||
var sizes: []BlockSize = undefined;
|
||||
var file_size: u64 = 0;
|
||||
var offsets: []u64 = undefined;
|
||||
switch (inode.data) {
|
||||
.file => |f| {
|
||||
sizes = f.block_sizes;
|
||||
file_size = f.size;
|
||||
offsets = try rdr.alloc.alloc(u64, sizes.len);
|
||||
if (sizes.len > 0) offsets[0] = f.block;
|
||||
},
|
||||
.ext_file => |f| {
|
||||
sizes = f.block_sizes;
|
||||
file_size = f.size;
|
||||
offsets = try rdr.alloc.alloc(u64, sizes.len);
|
||||
if (sizes.len > 0) offsets[0] = f.block;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
if (offsets.len > 1) {
|
||||
for (1..offsets.len) |i| {
|
||||
offsets[i] = offsets[i - 1] + sizes[i - 1].size;
|
||||
}
|
||||
}
|
||||
return .{
|
||||
.alloc = rdr.alloc,
|
||||
.rdr = rdr.rdr,
|
||||
.comp = rdr.super.comp,
|
||||
.block_size = rdr.super.block_size,
|
||||
.sizes = sizes,
|
||||
.offsets = offsets,
|
||||
.file_size = file_size,
|
||||
.completion = .init(rdr.alloc),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.alloc.free(self.offsets);
|
||||
self.completion.deinit();
|
||||
}
|
||||
|
||||
pub fn addFragment(self: *Self, data: []u8) void {
|
||||
self.frag = data;
|
||||
}
|
||||
|
||||
pub fn writeTo(self: *Self, wrt: anytype) !void {
|
||||
comptime std.debug.assert(std.meta.hasFn(@TypeOf(wrt), "write") or std.meta.hasFn(@TypeOf(wrt), "pwrite"));
|
||||
var write_thr = try std.Thread.spawn(
|
||||
.{ .allocator = self.alloc },
|
||||
writeThread,
|
||||
.{ self, wrt, null, null },
|
||||
);
|
||||
defer self.completion.clear();
|
||||
for (0..self.numBlocks()) |i| {
|
||||
var thr = std.Thread.spawn(
|
||||
.{ .allocator = self.alloc },
|
||||
decompThread,
|
||||
.{ self, i },
|
||||
) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
continue;
|
||||
};
|
||||
thr.detach();
|
||||
}
|
||||
write_thr.join();
|
||||
if (self.completion.hasErrs()) return self.completion.errs.items[0];
|
||||
}
|
||||
|
||||
pub fn writeToNoBlock(self: *Self, wrt: anytype, comptime finish: anytype, finish_args: anytype) !void {
|
||||
comptime std.debug.assert(std.meta.hasFn(@TypeOf(wrt), "write") or std.meta.hasFn(@TypeOf(wrt), "pwrite"));
|
||||
errdefer self.completion.clear();
|
||||
var write_thr = try std.Thread.spawn(
|
||||
.{ .allocator = self.alloc },
|
||||
writeThread,
|
||||
.{ self, wrt, finish, finish_args },
|
||||
);
|
||||
write_thr.detach();
|
||||
for (0..self.numBlocks()) |i| {
|
||||
var thr = std.Thread.spawn(
|
||||
.{ .allocator = self.alloc },
|
||||
decompThread,
|
||||
.{ self, i },
|
||||
) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
continue;
|
||||
};
|
||||
thr.detach();
|
||||
}
|
||||
}
|
||||
|
||||
fn numBlocks(self: Self) usize {
|
||||
var out = self.sizes.len;
|
||||
if (self.frag != null) out += 1;
|
||||
return out;
|
||||
}
|
||||
/// Returns the decompressed data block at the given idx.
|
||||
/// If the block is sparse (filled with 0s), a zero length slice is returned.
|
||||
fn blockAt(self: Self, idx: usize) ![]u8 {
|
||||
if (idx >= self.numBlocks()) return DataReaderError.InvalidIndex;
|
||||
const size = self.sizes[idx];
|
||||
if (size.size == 0) return &[0]u8{};
|
||||
const block = try self.alloc.alloc(u8, blk: {
|
||||
if (idx == self.numBlocks() - 1) break :blk self.file_size % self.block_size;
|
||||
break :blk self.block_size;
|
||||
});
|
||||
if (idx == self.sizes.len and self.frag != null) {
|
||||
@memcpy(block, self.frag.?);
|
||||
return block;
|
||||
}
|
||||
if (size.uncompressed) {
|
||||
_ = try self.rdr.pread(block, self.offsets[idx]);
|
||||
return block;
|
||||
}
|
||||
_ = try self.comp.decompress(
|
||||
1024 * 1024,
|
||||
self.alloc,
|
||||
self.rdr.readerAt(self.offsets[idx]).reader(),
|
||||
block,
|
||||
);
|
||||
return block;
|
||||
}
|
||||
|
||||
fn writeThread(
|
||||
self: *Self,
|
||||
wrt: anytype,
|
||||
comptime finish: anytype,
|
||||
finish_args: anytype,
|
||||
) void {
|
||||
var cur_idx: usize = 0;
|
||||
self.completion.mut.lock();
|
||||
defer self.completion.mut.unlock();
|
||||
while (cur_idx < self.numBlocks() and !self.completion.hasErrs()) {
|
||||
self.completion.condWait();
|
||||
if (self.completion.hasErrs()) break;
|
||||
if (comptime std.meta.hasFn(@TypeOf(wrt), "pwrite")) {
|
||||
for (self.completion.map.keys()) |_| {
|
||||
const k = self.completion.map.keys()[0];
|
||||
const blk = self.completion.getBlock(k).?;
|
||||
defer self.alloc.free(blk);
|
||||
if (blk.len > 0) {
|
||||
_ = wrt.pwrite(blk, self.block_size * k) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
break;
|
||||
};
|
||||
} else {
|
||||
_ = wrt.pwrite(&[1]u8{0}, (self.block_size * (k + 1)) - 1) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
break;
|
||||
};
|
||||
}
|
||||
cur_idx += 1;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
while (self.completion.getBlock(cur_idx)) |blk| {
|
||||
defer self.alloc.free(blk);
|
||||
if (blk.len > 0) {
|
||||
_ = wrt.write(blk) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
break;
|
||||
};
|
||||
} else {
|
||||
const blank: [1024 * 1024]u8 = [1]u8{0} ** (1024 * 1024);
|
||||
_ = wrt.write(blank[0..self.block_size]) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
break;
|
||||
};
|
||||
}
|
||||
cur_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (comptime @TypeOf(finish) != @TypeOf(null) and @TypeOf(finish_args) != @TypeOf(null)) @call(.auto, finish, finish_args);
|
||||
}
|
||||
fn decompThread(
|
||||
self: *Self,
|
||||
idx: usize,
|
||||
) void {
|
||||
if (self.completion.hasErrs()) return;
|
||||
defer self.completion.cond.signal();
|
||||
const block = self.blockAt(idx) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
return;
|
||||
};
|
||||
self.completion.add(idx, block) catch |err| {
|
||||
self.completion.addErr(err);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const PRead = @import("p_read.zig").PRead;
|
||||
const Compression = @import("../superblock.zig").Compression;
|
||||
|
||||
const MetaHeader = packed struct {
|
||||
size: u15,
|
||||
uncompressed: bool,
|
||||
};
|
||||
|
||||
pub fn MetadataReader(comptime T: type) type {
|
||||
comptime std.debug.assert(std.meta.hasFn(T, "read"));
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
comp: Compression,
|
||||
rdr: PRead(T),
|
||||
offset: u64,
|
||||
|
||||
block: [8192]u8 = undefined,
|
||||
block_size: usize = 0,
|
||||
block_offset: u32 = 0,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, comp: Compression, rdr: PRead(T), offset: u64) Self {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.comp = comp,
|
||||
.rdr = rdr,
|
||||
.offset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
fn readNextBlock(self: *Self) !void {
|
||||
var hdr: MetaHeader = undefined;
|
||||
_ = try self.rdr.pread(std.mem.asBytes(&hdr), self.offset);
|
||||
self.offset += 2;
|
||||
if (hdr.uncompressed) {
|
||||
self.block_size = try self.rdr.pread(self.block[0..hdr.size], self.offset);
|
||||
} else {
|
||||
self.block_size = try self.comp.decompress(
|
||||
8192,
|
||||
self.alloc,
|
||||
self.rdr.readerAt(self.offset).reader(),
|
||||
&self.block,
|
||||
);
|
||||
}
|
||||
self.offset += hdr.size;
|
||||
self.block_offset = 0;
|
||||
}
|
||||
|
||||
pub fn skip(self: *Self, offset: u32) !void {
|
||||
var skipped: u32 = 0;
|
||||
var hdr: MetaHeader = undefined;
|
||||
while (offset - skipped >= 8192) {
|
||||
_ = try self.rdr.pread(std.mem.asBytes(&hdr), self.offset);
|
||||
self.offset += 2 + hdr.size;
|
||||
skipped += 8192;
|
||||
}
|
||||
var to_skip: u32 = 0;
|
||||
while (skipped < offset) {
|
||||
if (self.block_offset >= self.block_size) try self.readNextBlock();
|
||||
to_skip = @min(self.block_size - self.block_offset, offset - skipped);
|
||||
self.block_offset += to_skip;
|
||||
skipped += to_skip;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(self: *Self, buf: []u8) !usize {
|
||||
var cur_red: usize = 0;
|
||||
var to_read: usize = 0;
|
||||
while (cur_red < buf.len) {
|
||||
if (self.block_offset >= self.block_size) try self.readNextBlock();
|
||||
to_read = @min(buf.len - cur_red, self.block_size - self.block_offset);
|
||||
@memcpy(buf[cur_red .. cur_red + to_read], self.block[self.block_offset .. self.block_offset + to_read]);
|
||||
cur_red += to_read;
|
||||
self.block_offset += @truncate(to_read);
|
||||
}
|
||||
return cur_red;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const ToRead = @import("to_read.zig").ToRead;
|
||||
|
||||
/// A simple wrapper around a type with the pread([]u8, u64) function.
|
||||
/// Provides a couple useful utility functions.
|
||||
pub fn PRead(comptime T: type) type {
|
||||
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
rdr: T,
|
||||
offset: u64,
|
||||
|
||||
pub fn init(rdr: T, offset: u64) Self {
|
||||
return .{
|
||||
.rdr = rdr,
|
||||
.offset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pread(self: Self, buf: []u8, offset: u64) !usize {
|
||||
return self.rdr.pread(buf, self.offset + offset);
|
||||
}
|
||||
pub fn readerAt(self: Self, offset: u64) ToRead(T) {
|
||||
return .init(self.rdr, self.offset + offset);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn ToRead(comptime T: type) type {
|
||||
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = anyerror;
|
||||
|
||||
rdr: T,
|
||||
offset: u64,
|
||||
|
||||
pub fn init(rdr: T, init_offset: u64) Self {
|
||||
return .{
|
||||
.rdr = rdr,
|
||||
.offset = init_offset,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read(self: *Self, buf: []u8) !usize {
|
||||
const red = try self.rdr.pread(buf, self.offset);
|
||||
self.offset += red;
|
||||
return red;
|
||||
}
|
||||
pub fn readAll(self: *Self, buf: []u8) !usize {
|
||||
var cur_red = try self.read(buf);
|
||||
if (cur_red == 0) return cur_red;
|
||||
var res: usize = 0;
|
||||
while (cur_red < buf.len) {
|
||||
res = try self.read(buf[cur_red..]);
|
||||
if (res == 0) break;
|
||||
cur_red += res;
|
||||
}
|
||||
return cur_red;
|
||||
}
|
||||
const Reader = std.io.GenericReader(*Self, anyerror, read);
|
||||
pub fn reader(self: anytype) Reader {
|
||||
return .{
|
||||
.context = @constCast(self),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
+1
-62
@@ -1,62 +1 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const SfsReader = @import("reader.zig").SfsReader;
|
||||
pub const ExtractionOptions = @import("extract_options.zig");
|
||||
|
||||
pub const SfsFile = SfsReader(std.fs.File);
|
||||
|
||||
const test_archive = "testing/LinuxPATest.sfs";
|
||||
|
||||
test "OpenFile" {
|
||||
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
|
||||
defer sfs_fil.close();
|
||||
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
|
||||
defer rdr.deinit();
|
||||
_ = try rdr.frag_table.get(rdr.super.frag_count - 1);
|
||||
_ = try rdr.id_table.get(rdr.super.id_count - 1);
|
||||
_ = try rdr.export_table.get(rdr.super.inode_count - 1);
|
||||
std.debug.print("{}\n", .{rdr.super});
|
||||
var root = try rdr.root();
|
||||
defer root.deinit();
|
||||
var iter = root.iterate();
|
||||
while (true) {
|
||||
var f = try iter.next();
|
||||
if (f == null) break;
|
||||
defer f.?.deinit();
|
||||
std.debug.print("{s}\n", .{f.?.name});
|
||||
}
|
||||
std.debug.print("Finished OpenFile test\n", .{});
|
||||
}
|
||||
|
||||
test "ExtractSingleFile" {
|
||||
const single_file = "PortableApps/Notepad++Portable/App/Notepad++/doLocalConf.xml";
|
||||
const single_file_extr_loc = "testing/doLocalConf.xml";
|
||||
|
||||
std.fs.cwd().deleteFile(single_file_extr_loc) catch {};
|
||||
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
|
||||
defer sfs_fil.close();
|
||||
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
|
||||
defer rdr.deinit();
|
||||
var fil = try rdr.open(single_file);
|
||||
defer fil.deinit();
|
||||
var op: ExtractionOptions = try .init();
|
||||
op.verbose = true;
|
||||
try fil.extract(op, single_file_extr_loc);
|
||||
|
||||
std.debug.print("Finished ExtractSingleFile test\n", .{});
|
||||
}
|
||||
|
||||
test "ExtractAll" {
|
||||
const extr_dir = "testing/testExtract";
|
||||
|
||||
std.fs.cwd().deleteTree(extr_dir) catch {};
|
||||
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
|
||||
defer sfs_fil.close();
|
||||
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
|
||||
defer rdr.deinit();
|
||||
const op: ExtractionOptions = try .init();
|
||||
// op.verbose = true;
|
||||
try rdr.extract(op, extr_dir);
|
||||
|
||||
std.debug.print("Finished ExtractAll test\n", .{});
|
||||
}
|
||||
pub const Archive = @import("archive.zig");
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
const std = @import("std");
|
||||
const math = std.math;
|
||||
|
||||
const CompressionType = @import("decomp.zig").CompressionType;
|
||||
const InodeRef = @import("inode.zig").Ref;
|
||||
|
||||
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
||||
|
||||
const SuperblockError = error{
|
||||
InvalidMagic,
|
||||
InvalidBlockLog,
|
||||
InvalidVersion,
|
||||
InvalidCheck,
|
||||
};
|
||||
|
||||
/// A squashfs Superblock
|
||||
pub const Superblock = packed struct {
|
||||
magic: u32,
|
||||
inode_count: u32,
|
||||
mod_time: u32,
|
||||
block_size: u32,
|
||||
frag_count: u32,
|
||||
compression: CompressionType,
|
||||
block_log: u16,
|
||||
flags: packed struct {
|
||||
inode_uncompressed: bool,
|
||||
data_uncompressed: bool,
|
||||
check: bool,
|
||||
frag_uncompressed: bool,
|
||||
fragment_never: bool,
|
||||
fragment_always: bool,
|
||||
duplicates: bool,
|
||||
exportable: bool,
|
||||
xattr_uncompressed: bool,
|
||||
xattr_never: bool,
|
||||
compression_options: bool,
|
||||
ids_uncompressed: bool,
|
||||
_: u4,
|
||||
},
|
||||
id_count: u16,
|
||||
ver_maj: u16,
|
||||
ver_min: u16,
|
||||
root_ref: InodeRef,
|
||||
size: u64,
|
||||
id_start: u64,
|
||||
xattr_start: u64,
|
||||
inode_start: u64,
|
||||
dir_start: u64,
|
||||
frag_start: u64,
|
||||
export_start: u64,
|
||||
|
||||
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive.
|
||||
pub fn validate(self: Superblock) !void {
|
||||
if (self.magic != SQUASHFS_MAGIC)
|
||||
return SuperblockError.InvalidMagic;
|
||||
if (self.flags.check)
|
||||
return SuperblockError.InvalidCheck;
|
||||
if (self.ver_maj != 4 or self.ver_min != 0)
|
||||
return SuperblockError.InvalidVersion;
|
||||
if (math.log2(self.block_size) != self.block_log)
|
||||
return SuperblockError.InvalidBlockLog;
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const InodeRef = @import("inode.zig").Ref;
|
||||
|
||||
pub const Superblock = packed struct {
|
||||
magic: u32,
|
||||
inode_count: u32,
|
||||
mod_time: u32,
|
||||
block_size: u32,
|
||||
frag_count: u32,
|
||||
comp: Compression,
|
||||
block_log: u16,
|
||||
flags: packed struct {
|
||||
_: u4,
|
||||
id_uncomp: bool,
|
||||
comp_options: bool,
|
||||
no_xattr: bool,
|
||||
xattr_uncomp: bool,
|
||||
has_export: bool,
|
||||
de_dupe: bool,
|
||||
frag_always: bool,
|
||||
no_frag: bool,
|
||||
frag_uncomp: bool,
|
||||
check: bool,
|
||||
data_uncomp: bool,
|
||||
inode_uncomp: bool,
|
||||
},
|
||||
id_count: u16,
|
||||
ver_maj: u16,
|
||||
ver_min: u16,
|
||||
root_ref: InodeRef,
|
||||
size: u64,
|
||||
id_start: u64,
|
||||
xattr_start: u64,
|
||||
inode_start: u64,
|
||||
dir_start: u64,
|
||||
frag_start: u64,
|
||||
export_start: u64,
|
||||
};
|
||||
|
||||
pub const DecompressError = error{
|
||||
LzoUnavailable,
|
||||
Lz4Unavailable,
|
||||
};
|
||||
|
||||
pub const Compression = enum(u16) {
|
||||
gzip = 1,
|
||||
lzma,
|
||||
lzo,
|
||||
xz,
|
||||
lz4,
|
||||
zstd,
|
||||
|
||||
pub fn decompress(self: Compression, comptime max_size: u32, alloc: std.mem.Allocator, source: anytype, dest: []u8) !usize {
|
||||
switch (self) {
|
||||
.gzip => {
|
||||
var decomp = std.compress.zlib.decompressor(source);
|
||||
return decomp.read(dest);
|
||||
},
|
||||
.lzma => {
|
||||
var decomp = try std.compress.lzma.decompress(alloc, source);
|
||||
defer decomp.deinit();
|
||||
return decomp.read(dest);
|
||||
},
|
||||
.lzo => return DecompressError.LzoUnavailable,
|
||||
.xz => {
|
||||
var decomp = try std.compress.xz.decompress(alloc, source);
|
||||
defer decomp.deinit();
|
||||
return decomp.read(dest);
|
||||
},
|
||||
.lz4 => return DecompressError.Lz4Unavailable,
|
||||
.zstd => {
|
||||
var window: [max_size]u8 = undefined;
|
||||
var decomp = std.compress.zstd.decompressor(source, .{ .window_buffer = &window });
|
||||
return decomp.read(dest);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
+48
-46
@@ -1,75 +1,77 @@
|
||||
const std = @import("std");
|
||||
const Mutex = std.Thread.Mutex;
|
||||
|
||||
const PRead = @import("reader/p_read.zig").PRead;
|
||||
const Compression = @import("superblock.zig").Compression;
|
||||
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
||||
const DecompFn = @import("decomp.zig").DecompFn;
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const OffsetFile = @import("util/offset_file.zig");
|
||||
|
||||
pub const TableError = error{
|
||||
const TableError = error{
|
||||
InvalidIndex,
|
||||
};
|
||||
|
||||
pub fn Table(comptime T: type, comptime R: type) type {
|
||||
comptime std.debug.assert(std.meta.hasFn(R, "pread"));
|
||||
/// A two-layer metadata table.
|
||||
pub fn Table(T: anytype) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
rdr: PRead(R),
|
||||
comp: Compression,
|
||||
fil: OffsetFile,
|
||||
decomp: DecompFn,
|
||||
tab_start: u64,
|
||||
|
||||
offset: u64,
|
||||
table_count: u32,
|
||||
mut: std.Thread.RwLock = .{},
|
||||
tab: std.AutoHashMap(u32, []T),
|
||||
values: u32,
|
||||
|
||||
table: []T = &[0]T{},
|
||||
mut: Mutex = .{},
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: PRead(R), comp: Compression, offset: u64, table_count: u32) Self {
|
||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.rdr = rdr,
|
||||
.comp = comp,
|
||||
.offset = offset,
|
||||
.table_count = table_count,
|
||||
.fil = fil,
|
||||
.decomp = decomp,
|
||||
.tab_start = tab_start,
|
||||
|
||||
.tab = .init(alloc),
|
||||
.values = values,
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: Self) void {
|
||||
self.alloc.free(self.table);
|
||||
}
|
||||
|
||||
fn resize(self: *Self, to_add: usize) !void {
|
||||
if (!self.alloc.resize(self.table, self.table.len + to_add)) {
|
||||
const new_table = try self.alloc.alloc(T, self.table.len + to_add);
|
||||
@memcpy(new_table[0..self.table.len], self.table);
|
||||
self.alloc.free(self.table);
|
||||
self.table = new_table;
|
||||
pub fn deinit(self: *Self) void {
|
||||
var iter = self.tab.valueIterator();
|
||||
while (iter.next()) |s| {
|
||||
self.alloc.free(s.*);
|
||||
}
|
||||
self.tab.deinit();
|
||||
}
|
||||
|
||||
pub fn get(self: *Self, idx: u32) !T {
|
||||
if (idx >= self.table_count) return TableError.InvalidIndex;
|
||||
self.mut.lockShared();
|
||||
defer self.mut.unlockShared();
|
||||
if (idx >= self.table.len) {
|
||||
return self.getAndFill(idx);
|
||||
if (idx >= self.values) return TableError.InvalidIndex;
|
||||
const block_num = idx / VALS_PER_BLOCK;
|
||||
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
||||
if (self.tab.contains(block_num)) {
|
||||
const block = self.tab.get(block_num).?;
|
||||
return block[idx_offset];
|
||||
}
|
||||
return self.table[idx];
|
||||
}
|
||||
fn getAndFill(self: *Self, idx: u32) !T {
|
||||
self.mut.unlockShared();
|
||||
defer self.mut.lockShared();
|
||||
self.mut.lock();
|
||||
defer self.mut.unlock();
|
||||
var to_read: usize = 0;
|
||||
var offset: u64 = 0;
|
||||
while (idx >= self.table.len) {
|
||||
to_read = @min(self.table_count - self.table.len, comptime 8192 / @sizeOf(T));
|
||||
try self.resize(to_read);
|
||||
_ = try self.rdr.pread(std.mem.asBytes(&offset), self.offset);
|
||||
self.offset += 8;
|
||||
var meta: MetadataReader(R) = .init(self.alloc, self.comp, self.rdr, offset);
|
||||
_ = try meta.read(std.mem.sliceAsBytes(self.table[self.table.len - to_read ..]));
|
||||
// Double check in case of race condition..
|
||||
if (self.tab.contains(block_num)) {
|
||||
const block = self.tab.get(block_num).?;
|
||||
return block[idx_offset];
|
||||
}
|
||||
return self.table[idx];
|
||||
const is_last = (self.values - 1) / VALS_PER_BLOCK == block_num;
|
||||
const slice_size = if (is_last) self.values - (block_num * VALS_PER_BLOCK) else VALS_PER_BLOCK;
|
||||
const slice = try self.alloc.alloc(T, slice_size);
|
||||
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
|
||||
var offset: u64 = 0;
|
||||
try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little);
|
||||
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
||||
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
||||
try meta.interface.readSliceEndian(T, @ptrCast(slice), .little);
|
||||
try self.tab.put(block_num, slice);
|
||||
return slice[idx_offset];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,174 @@
|
||||
//! A reader for a regular file.
|
||||
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
const Writer = std.Io.Writer;
|
||||
const Limit = std.Io.Limit;
|
||||
|
||||
const Archive = @import("../archive.zig");
|
||||
const FragEntry = Archive.FragEntry;
|
||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||
const OffsetFile = @import("offset_file.zig");
|
||||
|
||||
const DataReader = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
fil: OffsetFile,
|
||||
decomp: DecompFn,
|
||||
block_size: u32,
|
||||
|
||||
blocks: []BlockSize,
|
||||
|
||||
frag: ?FragEntry = null, // TODO: do something better?
|
||||
frag_offset: u32 = 0,
|
||||
size: u64,
|
||||
|
||||
interface: Reader,
|
||||
|
||||
cur_offset: u64,
|
||||
block_idx: u32 = 0,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) DataReader {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.fil = archive.fil,
|
||||
.decomp = archive.decomp,
|
||||
.block_size = archive.super.block_size,
|
||||
.blocks = blocks,
|
||||
.size = size,
|
||||
.cur_offset = start,
|
||||
.interface = .{
|
||||
.end = 0,
|
||||
.seek = 0,
|
||||
.buffer = &[0]u8{},
|
||||
.vtable = &.{
|
||||
.stream = stream,
|
||||
.discard = discard,
|
||||
.readVec = readVec,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *DataReader) void {
|
||||
self.alloc.free(self.interface.buffer);
|
||||
self.interface.end = 0;
|
||||
self.interface.seek = 0;
|
||||
}
|
||||
|
||||
pub fn addFragment(self: *DataReader, entry: FragEntry, frag_offset: u32) void {
|
||||
self.frag = entry;
|
||||
self.frag_offset = frag_offset;
|
||||
}
|
||||
|
||||
fn numBlocks(self: DataReader) usize {
|
||||
var res = self.blocks.len;
|
||||
if (self.frag != null) res += 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
fn advance(self: *DataReader) !void {
|
||||
if (self.block_idx > self.blocks.len or (self.block_idx == self.blocks.len and self.frag == null)) {
|
||||
if (self.interface.buffer.len > 0) {
|
||||
self.alloc.free(self.interface.buffer);
|
||||
self.interface.buffer = &[0]u8{};
|
||||
self.interface.end = 0;
|
||||
self.interface.seek = 0;
|
||||
}
|
||||
return Reader.Error.EndOfStream;
|
||||
}
|
||||
defer self.block_idx += 1;
|
||||
const cur_block_size = if (self.block_idx == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
||||
try self.resizeBuffer(cur_block_size);
|
||||
self.interface.seek = 0;
|
||||
self.interface.end = cur_block_size;
|
||||
if (self.block_idx == self.blocks.len) { // fragment
|
||||
var rdr = try self.fil.readerAt(self.frag.?.start, &[0]u8{});
|
||||
if (self.frag.?.size.uncompressed) {
|
||||
try rdr.interface.discardAll(self.frag_offset);
|
||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
||||
return;
|
||||
}
|
||||
const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size);
|
||||
defer self.alloc.free(tmp_buf);
|
||||
try rdr.interface.readSliceAll(tmp_buf);
|
||||
const needed_block = try self.alloc.alloc(u8, self.block_size);
|
||||
defer self.alloc.free(needed_block);
|
||||
_ = try self.decomp(self.alloc, tmp_buf, needed_block);
|
||||
@memcpy(self.interface.buffer, needed_block[self.frag_offset .. self.frag_offset + cur_block_size]);
|
||||
return;
|
||||
}
|
||||
const block = self.blocks[self.block_idx];
|
||||
if (block.size == 0) {
|
||||
@memset(self.interface.buffer, 0);
|
||||
return;
|
||||
}
|
||||
var rdr = try self.fil.readerAt(self.cur_offset, &[0]u8{});
|
||||
self.cur_offset += block.size;
|
||||
if (block.uncompressed) {
|
||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
||||
return;
|
||||
}
|
||||
const tmp_buf = try self.alloc.alloc(u8, block.size);
|
||||
defer self.alloc.free(tmp_buf);
|
||||
try rdr.interface.readSliceAll(tmp_buf);
|
||||
_ = try self.decomp(self.alloc, tmp_buf, self.interface.buffer);
|
||||
}
|
||||
/// Does not guarentee that data currently in the buffer is retained.
|
||||
fn resizeBuffer(self: *DataReader, size: usize) !void {
|
||||
if (self.interface.buffer.len == size) return;
|
||||
if (!self.alloc.resize(self.interface.buffer, size)) {
|
||||
self.alloc.free(self.interface.buffer);
|
||||
self.interface.buffer = self.alloc.alloc(u8, size) catch |err| {
|
||||
self.interface.buffer = &[0]u8{};
|
||||
return err;
|
||||
};
|
||||
} else {
|
||||
self.interface.buffer.len = size;
|
||||
}
|
||||
}
|
||||
|
||||
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
|
||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
||||
if (err == error.EndOfStream) return error.EndOfStream;
|
||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
||||
return Reader.Error.ReadFailed;
|
||||
};
|
||||
if (limit == .nothing) return 0;
|
||||
const to_read = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||
const res = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_read]);
|
||||
rdr.seek += res;
|
||||
return res;
|
||||
}
|
||||
|
||||
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
||||
if (err == error.EndOfStream) return error.EndOfStream;
|
||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
||||
return Reader.Error.ReadFailed;
|
||||
};
|
||||
if (limit == .nothing) return 0;
|
||||
const to_adv = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||
rdr.seek += to_adv;
|
||||
return to_adv;
|
||||
}
|
||||
|
||||
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
||||
if (err == error.EndOfStream) return error.EndOfStream;
|
||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
||||
return Reader.Error.ReadFailed;
|
||||
};
|
||||
var cur_red: usize = 0;
|
||||
for (vec) |s| {
|
||||
const to_copy: usize = @min(rdr.end - rdr.seek, s.len);
|
||||
@memcpy(s[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]);
|
||||
rdr.seek += to_copy;
|
||||
cur_red += to_copy;
|
||||
if (rdr.end == rdr.seek) break;
|
||||
}
|
||||
return cur_red;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
//! Similiar to DataReader, but set-up for threaded writing to files.
|
||||
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
const Writer = std.Io.Writer;
|
||||
const Limit = std.Io.Limit;
|
||||
const WaitGroup = std.Thread.WaitGroup;
|
||||
const Pool = std.Thread.Pool;
|
||||
|
||||
const Archive = @import("../archive.zig");
|
||||
const FragEntry = Archive.FragEntry;
|
||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||
const OffsetFile = @import("offset_file.zig");
|
||||
|
||||
const ThreadedDataReader = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
fil: OffsetFile,
|
||||
decomp: DecompFn,
|
||||
block_size: u32,
|
||||
|
||||
blocks: []BlockSize,
|
||||
|
||||
frag: ?FragEntry = null, // TODO: do something better?
|
||||
frag_offset: u32 = 0,
|
||||
size: u64,
|
||||
|
||||
start_offset: u64,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) ThreadedDataReader {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.fil = archive.fil,
|
||||
.decomp = archive.decomp,
|
||||
.block_size = archive.super.block_size,
|
||||
.blocks = blocks,
|
||||
.size = size,
|
||||
.start_offset = start,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void {
|
||||
self.frag = entry;
|
||||
self.frag_offset = frag_offset;
|
||||
}
|
||||
|
||||
fn numBlocks(self: ThreadedDataReader) usize {
|
||||
var res = self.blocks.len;
|
||||
if (self.frag != null) res += 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Extract the data to the file threadedly, using pool to spawn threads.
|
||||
/// If multiple errors occur, thread spawning errors will have, then the last decompression error that occurs;
|
||||
///
|
||||
/// The function must be called from an unused DataReader. The DataReader is still usable afterwards.
|
||||
/// If only extractThreaded is used, there is no need to call deinit() afterwards.
|
||||
///
|
||||
/// The file will always be written to starting at 0.
|
||||
pub fn extractThreaded(self: ThreadedDataReader, file: std.fs.File, pool: *Pool) !void {
|
||||
var wg: WaitGroup = .{};
|
||||
wg.startMany(self.numBlocks());
|
||||
var out_err: ?anyerror = null;
|
||||
|
||||
var cur_write_offset: u64 = 0;
|
||||
var cur_read_offset: u64 = self.start_offset;
|
||||
for (0..self.blocks.len) |i| {
|
||||
const cur_block_size = if (i == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
||||
try pool.spawn(workThreadBlocks, .{ self, file, cur_write_offset, cur_read_offset, self.blocks[i], cur_block_size, &wg, &out_err });
|
||||
cur_write_offset += cur_block_size;
|
||||
cur_read_offset += self.blocks[i].size;
|
||||
}
|
||||
if (self.frag != null) {
|
||||
try pool.spawn(workThreadFragment, .{ self, file, cur_write_offset, &wg, &out_err });
|
||||
}
|
||||
pool.waitAndWork(&wg);
|
||||
if (out_err != null) return out_err.?;
|
||||
}
|
||||
|
||||
fn workThreadBlocks(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, read_offset: u64, block: BlockSize, cur_block_size: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
||||
defer wg.finish();
|
||||
var wrt = fil.writer(&[0]u8{});
|
||||
wrt.seekTo(write_offset) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
defer wrt.interface.flush() catch |err| {
|
||||
out_err.* = err;
|
||||
};
|
||||
if (block.size == 0) {
|
||||
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
return;
|
||||
}
|
||||
var rdr = self.fil.readerAt(read_offset, &[0]u8{}) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
if (block.uncompressed) {
|
||||
rdr.interface.streamExact(&wrt.interface, block.size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
return;
|
||||
}
|
||||
// TODO: shared buffers
|
||||
const read_buf = self.alloc.alloc(u8, block.size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
defer self.alloc.free(read_buf);
|
||||
rdr.interface.readSliceAll(read_buf) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
// TODO: shared buffers
|
||||
const res_buf = self.alloc.alloc(u8, cur_block_size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
defer self.alloc.free(res_buf);
|
||||
_ = self.decomp(self.alloc, read_buf, res_buf) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
wrt.interface.writeAll(res_buf) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
}
|
||||
fn workThreadFragment(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
||||
defer wg.finish();
|
||||
|
||||
var wrt = fil.writer(&[0]u8{});
|
||||
wrt.seekTo(write_offset) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
defer wrt.interface.flush() catch |err| {
|
||||
out_err.* = err;
|
||||
};
|
||||
|
||||
var rdr = self.fil.readerAt(self.frag.?.start, &[0]u8{}) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
if (self.frag.?.size.uncompressed) {
|
||||
rdr.interface.discardAll(self.frag_offset) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
rdr.interface.streamExact(&wrt.interface, self.size % self.block_size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
return;
|
||||
}
|
||||
const tmp_buf = self.alloc.alloc(u8, self.frag.?.size.size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
defer self.alloc.free(tmp_buf);
|
||||
rdr.interface.readSliceAll(tmp_buf) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
const needed_block = self.alloc.alloc(u8, self.block_size) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
defer self.alloc.free(needed_block);
|
||||
_ = self.decomp(self.alloc, tmp_buf, needed_block) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
wrt.interface.writeAll(needed_block[self.frag_offset .. self.frag_offset + (self.size % self.block_size)]) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
const Writer = std.Io.Writer;
|
||||
const Limit = std.Io.Limit;
|
||||
const StreamError = std.Io.Reader.StreamError;
|
||||
|
||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||
|
||||
const BlockHeader = packed struct {
|
||||
size: u15,
|
||||
uncompressed: bool,
|
||||
};
|
||||
|
||||
const This = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
rdr: *Reader,
|
||||
decomp: DecompFn,
|
||||
|
||||
buf: [8192]u8 = undefined,
|
||||
|
||||
interface: Reader,
|
||||
err: ?anyerror = null,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: DecompFn) This {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.rdr = rdr,
|
||||
.decomp = decomp,
|
||||
.interface = .{
|
||||
.buffer = &[0]u8{},
|
||||
.end = 0,
|
||||
.seek = 0,
|
||||
.vtable = &.{
|
||||
.stream = stream,
|
||||
.discard = discard,
|
||||
.readVec = readVec,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn advance(self: *This) !void {
|
||||
self.interface.seek = 0;
|
||||
var hdr: BlockHeader = undefined;
|
||||
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little);
|
||||
if (hdr.uncompressed) {
|
||||
try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little);
|
||||
self.interface.end = hdr.size;
|
||||
self.interface.buffer = self.buf[0..hdr.size];
|
||||
return;
|
||||
}
|
||||
var tmp_buf: [8192]u8 = undefined;
|
||||
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
|
||||
self.interface.end = try self.decomp(self.alloc, tmp_buf[0..hdr.size], &self.buf);
|
||||
self.interface.buffer = self.buf[0..self.interface.end];
|
||||
}
|
||||
|
||||
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
|
||||
const self: *This = @fieldParentPtr("interface", rdr);
|
||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||
self.err = err;
|
||||
return StreamError.ReadFailed;
|
||||
};
|
||||
if (@intFromEnum(limit) == 0) return 0;
|
||||
const to_write = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||
const wrote = try wrt.write(self.buf[rdr.seek .. rdr.seek + to_write]);
|
||||
self.interface.seek += wrote;
|
||||
return wrote;
|
||||
}
|
||||
fn discard(rdr: *Reader, limit: Limit) error{ EndOfStream, ReadFailed }!usize {
|
||||
const self: *This = @fieldParentPtr("interface", rdr);
|
||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||
self.err = err;
|
||||
return StreamError.ReadFailed;
|
||||
};
|
||||
if (@intFromEnum(limit) == 0) return 0;
|
||||
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||
rdr.seek += to_skip;
|
||||
return to_skip;
|
||||
}
|
||||
fn readVec(rdr: *Reader, vec: [][]u8) error{ EndOfStream, ReadFailed }!usize {
|
||||
const self: *This = @fieldParentPtr("interface", rdr);
|
||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||
self.err = err;
|
||||
return StreamError.ReadFailed;
|
||||
};
|
||||
var cur_red: usize = 0;
|
||||
for (vec) |s| {
|
||||
const to_copy: usize = @min(rdr.end - rdr.seek, s.len);
|
||||
@memcpy(s[0..to_copy], self.buf[rdr.seek .. rdr.seek + to_copy]);
|
||||
rdr.seek += to_copy;
|
||||
cur_red += to_copy;
|
||||
if (rdr.end == rdr.seek) break;
|
||||
}
|
||||
return cur_red;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//! A File where it's meaningful (to us) content starts at a given offset.
|
||||
|
||||
const std = @import("std");
|
||||
const File = std.fs.File;
|
||||
const Reader = std.fs.File.Reader;
|
||||
|
||||
const OffsetFile = @This();
|
||||
|
||||
fil: File,
|
||||
offset: u64,
|
||||
|
||||
pub fn init(fil: File, init_offset: u64) OffsetFile {
|
||||
return .{ .fil = fil, .offset = init_offset };
|
||||
}
|
||||
|
||||
pub fn readerAt(self: OffsetFile, offset: u64, buffer: []u8) !Reader {
|
||||
var rdr = self.fil.reader(buffer);
|
||||
try rdr.seekTo(self.offset + offset);
|
||||
return rdr;
|
||||
}
|
||||
Reference in New Issue
Block a user