11 Commits
Author SHA1 Message Date
Caleb J. Gardner d00acb03b9 Small changed & fixes 2026-09-09 20:54:36 -05:00
Caleb J. Gardner 7f09a8ff69 Fixed File.open 2026-09-08 21:54:47 -05:00
Caleb J. Gardner 68d1a9d26d Renamed Table.zig to Lookup.Zig
Added Lookup.Value (reason for above)
Added FragCache
Small tweaks & fixes (I forgot about)
2026-09-08 21:37:30 -05:00
Caleb J. Gardner 2e4364b863 Started work on xattr table reading 2026-09-05 22:37:04 -05:00
Caleb J. Gardner 2e33e19cdd Added lookup Tables
Simplified Inode Data types & union
Started work on decompression
2026-09-02 10:53:01 -05:00
Caleb J. Gardner 173e76469f Created DataReader (untested)
Other minor fixes & tweaks
2026-08-30 02:37:37 -05:00
Caleb J. Gardner c5d75d3839 Further work on making everything work
Directory table reading
Finished MetadataReader
2026-08-29 06:40:34 -05:00
Caleb J. Gardner e0b697dd0b More work, specifically on decompression 2026-08-14 17:17:22 -05:00
Caleb J. Gardner 9f2a4bcc1e More work on stuff 2026-08-14 10:55:09 -05:00
Caleb J. Gardner 78dbaaed7e Re-adding stuff 2026-08-08 21:56:22 -05:00
Caleb J. Gardner 0039b19b4b Restarting...... Again. I have a problem. 2026-08-08 02:07:32 -05:00
45 changed files with 1564 additions and 3125 deletions
+1 -1
View File
@@ -2,4 +2,4 @@ testing/
.zig-cache/ .zig-cache/
zig-out/ zig-out/
zig-pkg/ zig-pkg/
+1 -1
View File
@@ -11,7 +11,7 @@
"build": { "build": {
"command": "zig", "command": "zig",
"args": ["build", "-Ddebug=true"], "args": ["build", "-Ddebug=true", "-Dstatic=true"],
}, },
"program": "zig-out/bin/unsquashfs", "program": "zig-out/bin/unsquashfs",
+10 -14
View File
@@ -10,13 +10,9 @@ Overall works, but currently is missing some features ([see below](#capabilities
## Build options ## Build options
> `-Duse_zig_decomp=true` > `-Duse_c_libs=true`
Instead of using C libraries for decompression, use Zig's standard library for decompression. If using this option LZO and LZ4 decomrpession types are unsupported and decompression times will be significantly longer. 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.
> `-Ddynamic=true`
Dynamicly link C libraries (if they're used) instead of statically linking them.
> `-Dallow_lzo=true` > `-Dallow_lzo=true`
@@ -39,22 +35,22 @@ Most features are present except for the following:
## Performance ## Performance
This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive which is fairly small and uses zstd compression with `-Doptimize=ReleaseFast`. This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) and with `--release=fast`.
Currently, my only performance checks are checking execution time, nothing deeper. Currently, my only performance checks are checking execution time, nothing deeper.
* Currently, using my test archive, performance aproximately matches `unsquashfs` when multi-threaded, but significantly slower when single-threaded. * Under ideal circumstances, my library is ~70% slower (.12s vs .20s).
* Using Zig decompression libraries *significantly* increases decompression time. * Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances.
* Performance improvements/regressions will be common. I'm still learning Zig. * Performance improvements/regressions will be common. I'm still learning Zig.
Example Times: Example Times:
* *unsquashfs, multi-threaded*: .11s * *unsquashfs, multi-threaded*: .12s
* *unsquashfs, single-threaded*: .13s * *unsquashfs, single-threaded*: .13s
* *C-libs, multi-threaded*: .10s * *C-libs, single-threaded*: .45s
* *C-libs, single-threaded*: ..28s * *C-libs, multi-threaded*: .20s
* *Zig-libs, single-threaded*: .74s * *Zig-libs, single-threaded*: 5.78s
* *Zig-libs, multi-threaded*: 2.70s * *Zig-libs, multi-threaded*: 1.08s
## Build considerations ## Build considerations
-17
View File
@@ -1,17 +0,0 @@
#! /usr/bin/env bash
ARCHIVE="testing/LinuxPATest.sfs"
REF_EXT_LOC="testing/LinuxPAReference"
PROG_EXT_LOC="testing/LinuxPABinTest"
echo "Testing Multi-threaded Performance"
echo ""
hyperfine --warmup 5 --prepare "rm -rf $REF_EXT_LOC && rm -rf $PROG_EXT_LOC" "unsquashfs -d $REF_EXT_LOC $ARCHIVE" "zig-out/bin/unsquashfs -d $PROG_EXT_LOC $ARCHIVE"
echo ""
echo "Testing Single-threaded Performance"
echo ""
hyperfine --warmup 5 --prepare "rm -rf $REF_EXT_LOC && rm -rf $PROG_EXT_LOC" "unsquashfs -p 1 -d $REF_EXT_LOC $ARCHIVE" "zig-out/bin/unsquashfs -p 1 -d $PROG_EXT_LOC $ARCHIVE"
+69 -117
View File
@@ -1,111 +1,100 @@
const std = @import("std"); const std = @import("std");
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse false; const zig_decomp = b.option(bool, "zig_decomp", "Use zig standard library for decompression.") orelse false;
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false; const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support.") orelse false;
const dynamic = b.option(bool, "dynamic", "Dynamicly link C decompression libraries") orelse false; const static = b.option(bool, "static", "Statically link libraries.") orelse false;
var debug = b.option(bool, "debug", "Enable options to make debugging easier."); const debug = b.option(bool, "debug", "Enable options to make debugging easier.") orelse false;
const version_string_option = b.option([]const u8, "version", "Version of the library/binary"); const version_string_option = b.option([]const u8, "version", "Version of the library/binary") orelse "0.0.0-testing";
const zig_squashfs_options = b.addOptions();
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
var optimize = b.standardOptimizeOption(.{}); const optimize: std.builtin.OptimizeMode = if (debug) .Debug else b.standardOptimizeOption(.{});
if (debug == true) const version: std.SemanticVersion = try .parse(if (version_string_option[0] == 'v') version_string_option[1..] else version_string_option);
optimize = .Debug;
if (optimize == .Debug) const build_config = b.addOptions();
debug = true; build_config.addOption(bool, "zig_decomp", zig_decomp);
build_config.addOption(bool, "allow_lzo", allow_lzo);
build_config.addOption(bool, "debug", debug);
build_config.addOption(std.SemanticVersion, "version", version);
const c_h = b.addTranslateC(.{
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/c.h"),
});
if (allow_lzo) c_h.defineCMacro("ALLOW_LZO", null);
const c = c_h.createModule();
if (static) {
const zng = b.dependency("zlib_ng", .{
.target = target,
.optimize = optimize,
.zlib_compat = true,
});
c.linkLibrary(zng.artifact("zng"));
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
c.linkLibrary(zstd.artifact("zstd"));
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
c.linkLibrary(lz4.artifact("lz4"));
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
c.linkLibrary(xz.artifact("lzma"));
if (allow_lzo) {
const lzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
c.linkLibrary(lzo.artifact("minilzo"));
}
} else {
c.linkSystemLibrary("z", .{});
c.linkSystemLibrary("zstd", .{});
c.linkSystemLibrary("lz4", .{});
c.linkSystemLibrary("lzma", .{});
if (allow_lzo)
c.linkSystemLibrary("minilzo", .{});
}
const lib = b.addLibrary(.{ const lib = b.addLibrary(.{
.name = "squashfs", .name = "squashfs",
.root_module = b.createModule(.{ .root_module = b.addModule("zig_squashfs", .{
.optimize = if (debug == true) .Debug else optimize,
.target = target,
.valgrind = debug,
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.valgrind = debug,
.imports = &.{ .imports = &.{
.{ .name = "options", .module = zig_squashfs_options.createModule() }, .{ .name = "c", .module = c },
.{ .name = "build_config", .module = build_config.createModule() },
}, },
}), }),
.use_llvm = debug, .use_llvm = debug,
.version = version,
}); });
const deps = try dependencies(b, optimize, target, use_zig_decomp, allow_lzo, dynamic);
defer b.allocator.free(deps);
for (deps) |d|
lib.root_module.linkLibrary(d);
if (!use_zig_decomp) {
const c = b.addTranslateC(.{
.optimize = optimize,
.target = target,
.root_source_file = b.path("src/c.h"),
});
if (allow_lzo) c.defineCMacro("ALLOW_LZO", null);
lib.root_module.addImport("c", c.createModule());
if (dynamic)
dynamicLinkLibraries(c, allow_lzo);
}
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),
);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "unsquashfs", .name = "unsquashfs",
.root_module = b.createModule(.{ .root_module = b.createModule(.{
.optimize = if (debug == true) .Debug else optimize,
.target = target,
.valgrind = debug,
.root_source_file = b.path("src/bin/unsquashfs.zig"), .root_source_file = b.path("src/bin/unsquashfs.zig"),
.target = target,
.optimize = optimize,
.imports = &.{ .imports = &.{
.{ .name = "zig_squashfs", .module = lib.root_module }, .{ .name = "squashfs", .module = lib.root_module },
.{ .name = "build_config", .module = build_config.createModule() },
}, },
.valgrind = debug,
}), }),
.use_llvm = debug, .use_llvm = debug,
.version = version,
}); });
exe.root_module.addOptions("config", unsquashfs_options);
b.installArtifact(lib); b.installArtifact(lib);
b.installArtifact(exe); b.installArtifact(exe);
const mod_tests = b.addTest(.{ const mod_tests = b.addTest(.{
.root_module = b.createModule(.{ .root_module = lib.root_module,
.optimize = optimize,
.target = target,
.root_source_file = b.path("src/root.zig"),
.imports = &.{
.{ .name = "options", .module = zig_squashfs_options.createModule() },
},
.valgrind = debug,
}),
.use_llvm = debug, .use_llvm = debug,
}); });
for (deps) |d|
mod_tests.root_module.linkLibrary(d);
if (!use_zig_decomp) {
const c = b.addTranslateC(.{
.optimize = optimize,
.target = target,
.root_source_file = b.path("src/c.h"),
});
mod_tests.root_module.addImport("c", c.createModule());
if (allow_lzo) c.defineCMacro("ALLOW_LZO", null);
if (dynamic)
dynamicLinkLibraries(c, allow_lzo);
}
const run_mod_tests = b.addRunArtifact(mod_tests); const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step); test_step.dependOn(&run_mod_tests.step);
@@ -113,52 +102,15 @@ pub fn build(b: *std.Build) !void {
// zls build check steps // zls build check steps
const lib_check = b.addLibrary(.{ const lib_check = b.addLibrary(.{
.name = "squashfs", .name = "squashfs",
.root_module = exe.root_module, .root_module = lib.root_module,
.use_llvm = debug,
}); });
const exe_check = b.addExecutable(.{ const exe_check = b.addExecutable(.{
.name = "unsquashfs", .name = "unsquashfs",
.root_module = lib.root_module, .root_module = exe.root_module,
.use_llvm = debug,
}); });
const check = b.step("check", "Check if unsquashfs compiles"); const check = b.step("check", "Check if unsquashfs compiles");
check.dependOn(&lib_check.step); check.dependOn(&lib_check.step);
check.dependOn(&exe_check.step); check.dependOn(&exe_check.step);
} }
pub fn dynamicLinkLibraries(mod: *std.Build.Step.TranslateC, allow_lzo: bool) void {
mod.linkSystemLibrary("zstd", .{});
mod.linkSystemLibrary("zlib-ng", .{});
mod.linkSystemLibrary("lzma", .{});
mod.linkSystemLibrary("lz4", .{});
if (allow_lzo)
mod.linkSystemLibrary("minilzo", .{});
}
fn dependencies(
b: *std.Build,
optimize: std.builtin.OptimizeMode,
target: std.Build.ResolvedTarget,
use_zig_decomp: bool,
allow_lzo: bool,
dynamic: bool,
) ![]*std.Build.Step.Compile {
if (use_zig_decomp or dynamic) return &.{};
var list: std.ArrayList(*std.Build.Step.Compile) = .empty;
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
try list.append(b.allocator, zstd.artifact("zstd"));
const zng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target });
try list.append(b.allocator, zng.artifact("zng"));
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
try list.append(b.allocator, xz.artifact("lzma"));
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
try list.append(b.allocator, lz4.artifact("lz4"));
if (allow_lzo) {
const minilzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
try list.append(b.allocator, minilzo.artifact("minilzo"));
}
return list.toOwnedSlice(b.allocator);
}
+7 -11
View File
@@ -2,27 +2,23 @@
.name = .squashfs, .name = .squashfs,
.version = "0.0.6", .version = "0.0.6",
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications. .fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
.minimum_zig_version = "0.15.2", .minimum_zig_version = "0.16.0",
.dependencies = .{ .dependencies = .{
.zlib_ng = .{ .zlib_ng = .{
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133", .url = "git+https://github.com/CalebQ42/zig-zlib-ng#915db2dbd4a540d8a933336d67520b786f168f01",
.hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa", .hash = "zlib_ng-2.3.3-pre1-2HYS4MlFAADc-eSZv4_xjkxZRCdGUVOYZKdtqhybstgk",
}, },
.zstd = .{ .zstd = .{
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3", .url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-2#4ccb01e6fceae86fb9fe36c60b8a103cdc058155",
.hash = "zstd-1.5.7-1-KEItkAMwAAD6OKY3m0OOmXG7aL-aLUfrDqbP5J5oYapU", .hash = "zstd-1.5.7-2-KEItkAMwAADNhCbGaImKrjXjnSA51jcqEbGCA3RasVZ9",
}, },
.lz4 = .{ .lz4 = .{
.url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102", .url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102",
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2", .hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
}, },
.minilzo = .{ .minilzo = .{
.url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90", .url = "git+https://github.com/CalebQ42/zig-minilzo.git#f72a0e20a30fad38cd10104f393c8f3440478697",
.hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP", .hash = "minilzo-2.10.0-Ij7BO7YLAABd6oK2YdimXwvFTOC8wD1sNm0PHGoRNEwR",
},
.xz = .{
.url = "git+https://github.com/akunaakwei/zig-xz.git#e2d389262c8291907e3e4c6fb119819141c16c0f",
.hash = "xz-5.8.2-6v47_JYeAABSL-jonprpL5-E_YaaGc4B5xrbe93WsJ3G",
}, },
}, },
.paths = .{ .paths = .{
+93 -230
View File
@@ -1,126 +1,84 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Decomp = @import("decomp.zig"); const util = @import("utils/util.zig");
const ExtractionOptions = @import("options.zig");
const File = @import("file.zig"); const File = @import("file.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const LookupTable = @import("lookup_table.zig"); const Decomp = @import("decomp.zig");
const Decompressor = @import("util/decompressor.zig"); const Options = @import("options.zig");
const MetadataReader = @import("util/metadata.zig"); const Decompress = @import("utils/decompress.zig");
const Utils = @import("util/misc.zig");
const OffsetFile = @import("util/offset_file.zig");
const Archive = @This(); const Archive = @This();
file: OffsetFile, map: Io.File.MemoryMap,
super: Superblock,
stateless_decomp: Decompressor, super: Super,
root_inode_ref: Inode.Reference,
pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive { pub fn init(io: Io, file: Io.File, offset: u64) !Archive {
var rdr = file.reader(io, &[0]u8{}); var map = try file.createMemoryMap(io, .{
try rdr.seekTo(offset); .len = try file.length(io) - offset,
var super: Superblock = undefined; .offset = offset,
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little); .protection = .{ .read = true },
try super.validate(); });
var superblock = util.readValue(Superblock, map.memory[0..@sizeOf(Superblock)]);
const super = try superblock.checkAndMinimize();
return .{ return .{
.file = try .init(io, file, super.size, offset), .map = map,
.super = super,
.stateless_decomp = try Decomp.StatelessDecomp(super.compression), .super = super,
.root_inode_ref = superblock.root_inode_ref,
}; };
} }
pub fn deinit(self: *Archive, io: Io) void { pub fn deinit(self: *Archive, io: Io) void {
self.file.deinit(io); self.map.destroy(io);
} }
/// The root folder of the Archive. Used to open other Files.
pub fn root(self: *Archive, alloc: std.mem.Allocator) !File { pub fn root(self: *Archive, alloc: std.mem.Allocator) !File {
const root_inode = try Utils.inodeFromRef( return .{
alloc, .alloc = alloc,
self.file,
&self.stateless_decomp, .data = self.map.memory,
self.super.inode_start, .super = self.super,
self.super.block_size,
self.super.root_ref, .inode = try .readLocation(
); alloc,
return .init(alloc, self.*, root_inode, ""); self.map.memory,
self.root_inode_ref.start,
self.root_inode_ref.offset,
self.super,
),
.name = "",
};
} }
/// Opens a File within the archive. pub fn open(self: *Archive, alloc: std.mem.Allocator, filepath: []const u8) !File {
pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
var root_file = try self.root(alloc); var root_file = try self.root(alloc);
const path = std.mem.trim(u8, filepath, "/");
if (Utils.pathIsSelf(path))
return root_file;
defer root_file.deinit(); defer root_file.deinit();
return root_file.open(alloc, io, filepath);
}
/// Returns the inode with the given inode number. return root.open(alloc, filepath);
/// Requires that the archive is exportable (has an export lookup table).
pub fn inode(self: *Archive, alloc: std.mem.Allocator, io: Io, num: u32) !Inode {
if (!self.super.flags.exportable)
return error.NotExportable;
const ref = try LookupTable.lookupValue(
Inode.Ref,
alloc,
io,
&self.stateless_decomp,
self.file,
self.super.export_start,
num + 1,
);
return Utils.inodeFromRef(
alloc,
io,
self.file,
&self.stateless_decomp,
self.super.inode_start,
self.super.block_size,
ref,
);
} }
/// Returns a value at the given index from the Archive's id (uid/gid) table. pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: Options) !void {
pub fn idTable(self: *Archive, alloc: std.mem.Allocator, io: Io, idx: u32) !u16 { const root_inode: Inode = try .readLocation(
return LookupTable.lookupValue(
u16,
alloc, alloc,
io, self.map.memory,
&self.stateless_decomp, self.root_inode_ref.start,
self.file, self.root_inode_ref.offset,
self.super.id_start, self.super,
idx,
); );
}
/// Extract the entire archive contents to the given directory. if (options.single_threaded)
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void { return Decompress.single(alloc, io, self.map.memory, self.super, root_inode, ext_loc, options);
const root_inode = try Utils.inodeFromRef( return Decompress.multi(alloc, io, self.map.memory, self.super, root_inode, ext_loc, options);
alloc,
self.file,
&self.stateless_decomp,
self.super.inode_start,
self.super.block_size,
self.super.root_ref,
);
return root_inode.extract(alloc, io, self.file, self.super, extract_dir, options);
} }
// Superblock // Superblock
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little); const Superblock = extern struct {
pub const MAGIC: u32 = 0x73717368;
const SuperblockError = error{
InvalidMagic,
InvalidBlockLog,
InvalidVersion,
InvalidCheck,
};
/// A squashfs Superblock
pub const Superblock = extern struct {
magic: u32, magic: u32,
inode_count: u32, inode_count: u32,
mod_time: u32, mod_time: u32,
@@ -132,157 +90,62 @@ pub const Superblock = extern struct {
inode_uncompressed: bool, inode_uncompressed: bool,
data_uncompressed: bool, data_uncompressed: bool,
check: bool, check: bool,
frag_uncompressed: bool, fragment_uncompressed: bool,
fragment_never: bool, fragment_never: bool,
fragment_always: bool, fragment_always: bool,
duplicates: bool, de_duplicate: bool,
exportable: bool, exportable: bool,
xattr_uncompressed: bool, xattr_uncompressed: bool,
xattr_never: bool, xattr_never: bool,
compression_options: bool, compression_options: bool,
ids_uncompressed: bool, id_uncompressed: bool,
_: u4, _: u4,
}, },
id_count: u16, id_count: u16,
ver_maj: u16, version_major: u16,
ver_min: u16, version_minor: u16,
root_ref: Inode.Ref, root_inode_ref: Inode.Reference,
size: u64, size: u64,
id_start: u64, id_table_start: u64,
xattr_start: u64, xattr_table_start: u64,
inode_start: u64, inode_table_start: u64,
dir_start: u64, dir_table_start: u64,
frag_start: u64, frag_table_start: u64,
export_start: u64, export_table_start: u64,
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive. fn checkAndMinimize(self: Superblock) !Super {
pub fn validate(self: Superblock) !void { if (self.magic != MAGIC)
if (self.magic != SQUASHFS_MAGIC) return error.InvalidMagic;
return SuperblockError.InvalidMagic; if (self.version_major != 4 or self.version_minor != 0)
if (self.flags.check) return error.IncompatibleVersion;
return SuperblockError.InvalidCheck;
if (self.ver_maj != 4 or self.ver_min != 0)
return SuperblockError.InvalidVersion;
if (std.math.log2(self.block_size) != self.block_log) if (std.math.log2(self.block_size) != self.block_log)
return SuperblockError.InvalidBlockLog; return error.BadBlockLog;
if (self.flags.check)
return error.BadCheckFlag;
return .{
.block_size = self.block_size,
.frag_count = self.frag_count,
.decomp_fn = try self.compression.func(),
.id_count = self.id_count,
.id_table_start = self.id_table_start,
.xattr_table_start = self.xattr_table_start,
.inode_table_start = self.inode_table_start,
.dir_table_start = self.dir_table_start,
.frag_table_start = self.frag_table_start,
.export_table_start = self.export_table_start,
};
} }
}; };
pub const Super = struct {
// Tests block_size: u32,
frag_count: u32,
const TestArchive = "testing/LinuxPATest.sfs"; decomp_fn: Decomp.Fn,
id_count: u16,
test "Basics" { id_table_start: u64,
std.debug.print("Starting test: Basics...\n", .{}); xattr_table_start: u64,
inode_table_start: u64,
const alloc = std.testing.allocator; dir_table_start: u64,
const io = std.testing.io; frag_table_start: u64,
export_table_start: u64,
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer fil.close(io);
var sfs: Archive = try .init(io, fil, 0);
defer sfs.deinit(io);
try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock);
const root_file = try sfs.root(alloc);
defer root_file.deinit();
}
const TestFile = "Start.exe";
const TestFileExtractLocation = "testing/Start.exe";
test "ExtractSingleFile" {
std.debug.print("Starting test: ExtractSingleFile...\n", .{});
const alloc = std.testing.allocator;
const io = std.testing.io;
Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {};
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer fil.close(io);
var sfs: Archive = try .init(io, fil, 0);
defer sfs.deinit(io);
var test_fil = try sfs.open(alloc, io, TestFile);
defer test_fil.deinit();
try test_fil.extract(alloc, io, TestFileExtractLocation, .default);
//TODO: validate extracted file.
}
const TestFullExtractLocation = "testing/TestExtract";
test "ExtractCompleteArchive" {
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
const alloc = std.testing.allocator;
const io = std.testing.io;
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer fil.close(io);
var sfs: Archive = try .init(io, fil, 0);
defer sfs.deinit(io);
try sfs.extract(alloc, io, TestFullExtractLocation, .default);
}
test "ExtractCompleteArchiveSingleThreaded" {
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
const alloc = std.testing.allocator;
const io = std.testing.io;
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer fil.close(io);
{
std.debug.print("First testing using Threaded.global_single_threaded...\n", .{});
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
var sfs: Archive = try .init(Io.Threaded.global_single_threaded.io(), fil, 0);
defer sfs.deinit(Io.Threaded.global_single_threaded.io());
try sfs.extract(alloc, Io.Threaded.global_single_threaded.io(), TestFullExtractLocation, .default);
}
{
std.debug.print("Next testing using ExtractionOptions.single_threaded...\n", .{});
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
var sfs: Archive = try .init(io, fil, 0);
defer sfs.deinit(io);
try sfs.extract(alloc, io, TestFullExtractLocation, .default_single_threaded);
}
}
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,
}; };
+28 -112
View File
@@ -1,10 +1,11 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Writer = Io.Writer; const Writer = Io.Writer;
const builtin = @import("builtin");
const config = @import("config"); const config = @import("build_config");
const squashfs = @import("zig_squashfs"); const squashfs = @import("squashfs");
const Archive = squashfs.Archive;
const Options = squashfs.Options;
//TODO: Add more options //TODO: Add more options
const help_mgs = const help_mgs =
@@ -18,7 +19,7 @@ const help_mgs =
\\ -dx Don't set xattr values \\ -dx Don't set xattr values
\\ -dp Don't set permissions (includes setting uid & gid owner) \\ -dp Don't set permissions (includes setting uid & gid owner)
\\ \\
\\ -p <threads> Specify how many threads to use. If not present or zero, the system's logical cores count is used. \\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
\\ -v Verbose \\ -v Verbose
\\ \\
\\ --force Force extraction. If the destination already exists, it will be deleted. \\ --force Force extraction. If the destination already exists, it will be deleted.
@@ -28,125 +29,40 @@ const help_mgs =
\\ \\
; ;
const errors = error{InvalidArguments}; var arc_loc: []const u8 = "";
var ext_loc: []const u8 = "squashfs-root";
var archive: []const u8 = "";
var extLoc: []const u8 = "squashfs-root";
var offset: u64 = 0; var offset: u64 = 0;
var threads: u32 = 0; var threads: usize = 0;
var verbose: bool = false;
var ignore_xattrs: bool = false;
var ignore_permissions: bool = false;
var force: bool = false; var force: bool = false;
var options: Options = .default;
pub fn main(init: std.process.Init) !void { pub fn main(init: std.process.Init) !void {
var io = init.io;
const alloc = init.gpa; const alloc = init.gpa;
// const io = init.io;
var evented: Io.Evented = undefined;
try evented.init(alloc, .{});
const io = evented.io();
var stdout = std.Io.File.stdout(); // TODO: process args
defer stdout.close(io);
var out = stdout.writer(io, &[0]u8{});
defer out.interface.flush() catch {};
try handleArgs(init.minimal.args, &out.interface); var limited_io: Io.Threaded = undefined;
if (archive.len == 0) { if (threads != 0) {
try out.interface.print("You must provide a squashfs archive\n", .{}); limited_io = if (threads == 1)
try out.interface.print(help_mgs, .{}); Io.Threaded.init_single_threaded
return; else
Io.Threaded.init(alloc, .{
.async_limit = .limited(threads),
.concurrent_limit = .limited(threads),
.argv0 = .init(init.minimal.args),
.environ = init.minimal.environ,
});
io = limited_io.io();
} }
var fil = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully. var fil = try Io.Dir.cwd().openFile(io, arc_loc, .{});
defer fil.close(io); defer fil.close(io);
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully. var arc: Archive = try .open(io, fil, offset);
const options: squashfs.ExtractionOptions = .{ defer arc.close(io);
.single_threaded = threads == 1,
.verbose = verbose,
.verbose_writer = if (verbose) &out.interface else null,
.ignore_xattr = ignore_xattrs,
.ignore_permissions = ignore_permissions,
};
if (force) try arc.extract(alloc, io, ext_loc, options);
try Io.Dir.cwd().deleteTree(io, extLoc);
if (threads != 0) {
var limited_io = Io.Threaded.init(alloc, .{
.async_limit = .limited(threads - 1),
.concurrent_limit = .limited(threads - 1),
.argv0 = .init(init.minimal.args),
.environ = init.minimal.environ,
});
return arc.extract(alloc, limited_io.io(), extLoc, options); //TODO: Handle error gracefully.
}
return arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
}
fn handleArgs(args: std.process.Args, out: *Writer) !void {
var arg_iter = args.iterate();
defer arg_iter.deinit();
_ = arg_iter.next(); // args[0] is the application launch command.
while (arg_iter.next()) |arg| {
if (std.mem.eql(u8, arg, "-o")) {
const nxt = arg_iter.next();
if (nxt == null or nxt.?.len == 0) {
try out.print("-o must be followed by a number\n", .{});
return errors.InvalidArguments;
}
offset = std.fmt.parseInt(u64, nxt.?, 10) catch {
try out.print("-o must be followed by a number\n", .{});
return errors.InvalidArguments;
};
continue;
} else if (std.mem.eql(u8, arg, "-d")) {
const nxt = arg_iter.next();
if (nxt == null or nxt.?.len == 0) {
try out.print("-d must be followed by a location\n", .{});
return errors.InvalidArguments;
}
extLoc = nxt.?;
continue;
} else if (std.mem.eql(u8, arg, "-p")) {
const nxt = arg_iter.next();
if (nxt == null or nxt.?.len == 0) {
try out.print("-p must be followed by a number\n", .{});
return errors.InvalidArguments;
}
threads = std.fmt.parseInt(u32, nxt.?, 10) catch {
try out.print("-p must be followed by a number\n", .{});
return errors.InvalidArguments;
};
continue;
} else if (std.mem.eql(u8, arg, "-v")) {
verbose = true;
continue;
} else if (std.mem.eql(u8, arg, "-dx")) {
ignore_xattrs = true;
continue;
} else if (std.mem.eql(u8, arg, "-dp")) {
ignore_permissions = true;
continue;
} else if (std.mem.eql(u8, arg, "--force")) {
force = true;
continue;
} else if (std.mem.eql(u8, arg, "--version")) {
try out.print("zig-unsquashfs v", .{});
try config.version.format(out);
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
std.process.exit(0);
return;
} else if (std.mem.eql(u8, arg, "--help")) {
try out.print(help_mgs, .{});
std.process.exit(0);
return;
}
if (archive.len > 0) {
try out.print("you can only provide one file at a time\n", .{});
try out.print(help_mgs, .{});
return errors.InvalidArguments;
}
archive = arg;
}
} }
+4 -3
View File
@@ -1,7 +1,8 @@
#include <zstd.h> #include <lz4.h>
#include <zlib-ng.h>
#include <lzma.h> #include <lzma.h>
#include <zlib.h>
#include <zstd.h>
#ifdef ALLOW_LZO #ifdef ALLOW_LZO
#include <lzo/minilzo.h> #include <lzo/minilzo.h>
#endif #endif
#include <lz4.h>
+130 -69
View File
@@ -1,16 +1,8 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const options = @import("options"); const c = @import("c");
const build = @import("build_config");
const Decompressor = @import("util/decompressor.zig");
const zlib = if (options.use_zig_decomp) @import("decomp/zig_zlib.zig") else @import("decomp/c_zlib.zig");
const lzma = if (options.use_zig_decomp) @import("decomp/zig_lzma.zig") else @import("decomp/c_lzma.zig");
const lzo = if (options.use_zig_decomp or !options.allow_lzo) void else @import("decomp/c_lzo.zig");
const xz = if (options.use_zig_decomp) @import("decomp/zig_xz.zig") else @import("decomp/c_xz.zig");
const lz4 = if (options.use_zig_decomp) void else @import("decomp/c_lz4.zig");
const zstd = if (options.use_zig_decomp) @import("decomp/zig_zstd.zig") else @import("decomp/c_zstd.zig");
pub const Enum = enum(u16) { pub const Enum = enum(u16) {
gzip = 1, gzip = 1,
@@ -19,69 +11,138 @@ pub const Enum = enum(u16) {
xz, xz,
lz4, lz4,
zstd, zstd,
};
pub fn StatelessDecomp(val: Enum) !Decompressor { pub fn func(self: Enum) !Fn {
return switch (val) { return switch (self) {
.gzip => zlib.stateless_decompressor, .gzip => if (build.zig_decomp) zigZlib else cZlib,
.lzma => lzma.stateless_decompressor, .lzma => if (build.zig_decomp) zigLzma else cLzma,
.lzo => if (options.use_zig_decomp or !options.allow_lzo) .lzo => if (build.zig_decomp or !build.allow_lzo) error.LzoUnsupported else cLzo,
error.LzoUnsupported .xz => if (build.zig_decomp) zigXz else cXz,
else .lz4 => if (build.zig_decomp) error.Lz4Unsupported else cLz4,
lzo.stateless_decompressor, .zstd => if (build.zig_decomp) zigZstd else cZstd,
.xz => xz.stateless_decompressor, };
.lz4 => if (options.use_zig_decomp) }
error.Lz4Unsupported };
else pub const Fn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize;
lz4.stateless_decompressor,
.zstd => zstd.stateless_decompressor, pub const Error = Io.Reader.Error || std.mem.Allocator.Error;
// Actual decomp functions
fn zigZlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var rdr: Io.Reader = .fixed(in);
var decomp: std.compress.flate.Decompress = .init(&rdr, .zlib, &[0]u8{});
return decomp.reader.readSliceShort(out);
}
fn cZlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.z_stream = .{
.avail_in = @truncate(in.len),
.next_in = in.ptr,
.avail_out = @truncate(out.len),
.next_out = out.ptr,
}; };
const res = c.inflate(&stream, c.Z_FULL_FLUSH);
if (res != c.Z_OK)
return Error.ReadFailed;
return stream.total_out;
} }
pub const Decomp = union(enum) { fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
gzip: zlib, var rdr: Io.Reader = .fixed(in);
lzma: lzma,
lzo: lzo,
xz: xz,
lz4: lz4,
zstd: zstd,
pub fn init(val: Enum, alloc: std.mem.Allocator, io: Io, block_size: u32) !Decomp { var decomp: std.compress.lzma.Decompress = .initOptions(&rdr, alloc, &[0]u8{}, .{}, 0);
return switch (val) { defer decomp.deinit();
.gzip => .{ .gzip = if (options.use_zig_decomp) try zlib.init(alloc, io, block_size) else try zlib.init(alloc, io) },
.lzma => .{ .lzma = if (options.use_zig_decomp) try lzma.init(alloc, io, block_size) else .{} },
.lzo => if (options.use_zig_decomp or !options.allow_lzo) error.LzoUnsupported else .{ .lzo = .{} },
.xz => .{ .xz = if (options.use_zig_decomp) try xz.init(alloc, io, block_size) else .{} },
.lz4 => if (options.use_zig_decomp) error.Lz4Unsupported else .{ .lz4 = .{} },
.zstd => .{ .zstd = if (options.use_zig_decomp) try zstd.init(alloc, io, block_size) else try zstd.init(alloc, io) },
};
}
pub fn deinit(self: *Decomp, alloc: std.mem.Allocator) void {
if (options.use_zig_decomp) {
switch (self.*) {
.gzip => self.gzip.deinit(),
.lzma => self.lzma.deinit(),
.xz => self.xz.deinit(),
.zstd => self.zstd.deinit(),
else => {},
}
} else {
switch (self.*) {
.gzip => self.gzip.deinit(alloc),
.zstd => self.zstd.deinit(alloc),
else => {},
}
}
}
pub fn decompressor(self: *Decomp) *Decompressor { return decomp.reader.readSliceShort(out);
return switch (self.*) { }
.gzip => &self.gzip.interface, fn cLzma(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
.lzma => &self.lzma.interface, var stream: c.lzma_stream = .{
.lzo => if (options.use_zig_decomp or !options.allow_lzo) unreachable else &self.lzo.interface, .avail_in = in.len,
.xz => &self.xz.interface, .next_in = in.ptr,
.lz4 => if (options.use_zig_decomp) unreachable else &self.lz4.interface, .avail_out = out.len,
.zstd => &self.zstd.interface, .next_out = out.ptr,
}; };
defer c.lzma_end(&stream);
var res = c.lzma_alone_decoder(&stream, 0);
if (res != c.LZMA_OK)
return Error.ReadFailed;
while (res == c.LZMA_OK)
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_FINISH)
return Error.ReadFailed;
return stream.total_out;
}
fn cLzo(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var res = c.lzo_init();
if (res != c.LZO_E_OK)
return Error.ReadFailed;
var len = out.len;
res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &len, null);
if (res != c.LZO_E_OK)
return Error.ReadFailed;
return Error.ReadFailed;
}
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var rdr: Io.Reader = .fixed(in);
var decomp: std.compress.xz.Decompress = .init(&rdr, alloc, &[0]u8{});
defer decomp.deinit();
return decomp.reader.readSliceShort(out);
}
fn cXz(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.lzma_stream = .{
.avail_in = in.len,
.next_in = in.ptr,
.avail_out = out.len,
.next_out = out.ptr,
};
defer c.lzma_end(&stream);
var res = c.lzma_stream_decoder(&stream, 0, 0);
if (res != c.LZMA_OK)
return Error.ReadFailed;
while (res == c.LZMA_OK)
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_FINISH)
return Error.ReadFailed;
return stream.total_out;
}
fn cLz4(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
if (res < 0) return Error.ReadFailed;
return @abs(res);
}
fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var rdr: Io.Reader = .fixed(in);
const buf = try alloc.alloc(u8, 1024 * 1024);
defer alloc.free(buf);
var decomp: std.compress.zstd.Decompress = .init(&rdr, buf, .{
.window_len = 1024 * 1024,
});
return decomp.reader.readSliceShort(out);
}
fn cZstd(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
if (c.ZSTD_isError(res) == 1) {
std.debug.print("{s}\n", .{c.ZSTD_getErrorName(res)});
return Error.ReadFailed;
} }
}; return res;
}
-17
View File
@@ -1,17 +0,0 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const out_len: c_int = @bitCast(@as(u32, @truncate(out.len)));
const res = c.LZ4_decompress_fast(in.ptr, out.ptr, out_len);
if (res < 0) return Error.ReadFailed;
return @abs(res);
}
-46
View File
@@ -1,46 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.lzma_stream);
const Self = @This();
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
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, stream.avail_out * 2);
if (res != c.LZMA_OK) return Error.ReadFailed;
while (res == c.LZMA_OK)
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_FINISH) return Error.ReadFailed;
return stream.total_out;
}
// lzma_allocator
// fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
// return alloc.rawAlloc(size, .@"1", 0);
// }
// fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
// if (mem_ptr == null) return;
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
// alloc.free(@as([*]u8, @ptrCast(mem_ptr.?)));
// }
-26
View File
@@ -1,26 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.lzma_stream);
const Self = @This();
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
_ = c.lzo_init();
var out_len = out.len;
const res = c.lzo1x_decompress_safe(in.ptr, in.len, out.ptr, &out_len, null);
if (res != c.LZO_E_OK) return Error.ReadFailed;
return out_len;
}
-47
View File
@@ -1,47 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.lzma_stream);
const Self = @This();
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
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, stream.avail_out * 2);
if (res != c.LZMA_OK) return Error.ReadFailed;
while (res == c.LZMA_OK)
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_FINISH) return Error.ReadFailed;
return stream.total_out;
}
// lzma_allocator
// fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
// const mem = alloc.alloc(u8, size) catch return null;
// return mem.ptr;
// }
// fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
// if (mem_ptr == null) return;
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
// alloc.free(@as([*]u8, @ptrCast(mem_ptr.?)));
// }
-80
View File
@@ -1,80 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.zng_stream);
const Self = @This();
interface: Decompressor = .{ .decomp_fn = decomp },
io: Io,
ctx: []c.zng_stream,
ctx_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io) !Self {
const buf = try alloc.alloc(c.zng_stream, 20); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, .{});
return .{
.io = io,
.ctx = buf,
.ctx_queue = queue,
};
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
self.ctx_queue.close(self.io);
alloc.free(self.ctx);
}
fn decomp(d: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
var stream = self.ctx_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.ctx_queue.putOne(self.io, stream) catch {};
stream.next_in = in.ptr;
stream.avail_in = @truncate(in.len);
stream.next_out = out.ptr;
stream.avail_out = @truncate(out.len);
try zlibDecomp(&stream);
return stream.total_out;
}
inline fn zlibDecomp(stream: *c.zng_stream) !void {
_ = c.zng_inflateReset(stream);
const res = c.zng_inflate(stream, c.Z_FULL_FLUSH);
if (res != c.Z_OK) return Error.ReadFailed;
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.zng_stream = .{
.next_in = in.ptr,
.avail_in = @truncate(in.len),
.next_out = out.ptr,
.avail_out = @truncate(out.len),
};
try zlibDecomp(&stream);
return stream.total_out;
}
-69
View File
@@ -1,69 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(?*c.ZSTD_DCtx);
const Self = @This();
interface: Decompressor = .{ .decomp_fn = decomp },
io: Io,
ctx: []?*c.ZSTD_DCtx,
ctx_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io) !Self {
const buf = try alloc.alloc(?*c.ZSTD_DCtx, 20); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, c.ZSTD_createDCtx());
return .{
.io = io,
.ctx = buf,
.ctx_queue = queue,
};
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
self.ctx_queue.close(self.io);
for (self.ctx) |ctx|
_ = c.ZSTD_freeDCtx(ctx);
alloc.free(self.ctx);
}
fn decomp(d: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
const ctx = self.ctx_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.ctx_queue.putOne(self.io, ctx) catch {};
_ = c.ZSTD_DCtx_reset(ctx, c.ZSTD_reset_session_only);
const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len);
if (c.ZSTD_isError(res) != 0)
return Error.ReadFailed;
return res;
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
if (c.ZSTD_isError(res) != 0)
return Error.ReadFailed;
return res;
}
-81
View File
@@ -1,81 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const lzma = std.compress.lzma;
const Node = std.SinglyLinkedList.Node;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = Io.Queue([]u8);
const Self = @This();
const Buffer = struct {
node: Node,
buf: []u8,
};
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
buf: [][]u8,
buf_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
const buf = try alloc.alloc([]u8, 20); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
var buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return lzmaDecomp(self.alloc, &buf, in, out) catch return Error.ReadFailed;
}
inline fn lzmaDecomp(alloc: std.mem.Allocator, buffer: *[]u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = try lzma.Decompress.initOptions(&rdr, alloc, buffer.*, .{}, in.len * 2);
defer {
buffer.* = d.takeBuffer();
d.deinit();
}
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
return lzmaDecomp(alloc, &buf, in, out) catch return Error.ReadFailed;
}
-81
View File
@@ -1,81 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const xz = std.compress.xz;
const Node = std.SinglyLinkedList.Node;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = Io.Queue([]u8);
const Self = @This();
const Buffer = struct {
node: Node,
buf: []u8,
};
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
buf: [][]u8,
buf_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
const buf = try alloc.alloc([]u8, 20); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
var buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return xzDecomp(self.alloc, &buf, in, out) catch return Error.ReadFailed;
}
inline fn xzDecomp(alloc: std.mem.Allocator, buffer: *[]u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = try xz.Decompress.init(&rdr, alloc, buffer.*);
defer {
buffer.* = d.takeBuffer();
d.deinit();
}
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
return xzDecomp(alloc, &buf, in, out) catch return Error.ReadFailed;
}
-77
View File
@@ -1,77 +0,0 @@
const std = @import("std");
const Io = std.Io;
const flate = std.compress.flate;
const Node = std.SinglyLinkedList.Node;
const Reader = Io.Reader;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = Io.Queue([]u8);
const Self = @This();
const Buffer = struct {
node: Node,
buf: []u8,
};
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
buf: [][]u8,
buf_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
const buf = try alloc.alloc([]u8, 20); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
const buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return zlibDecomp(buf, in, out);
}
inline fn zlibDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = flate.Decompress.init(&rdr, .zlib, buffer);
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const buf = try alloc.alloc(u8, out.len);
defer alloc.free(buf);
return zlibDecomp(buf, in, out);
}
-73
View File
@@ -1,73 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue([]u8);
const Self = @This();
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
buf: [][]u8,
buf_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
const buf = try alloc.alloc([]u8, 5); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (buf) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size + zstd.block_size_max));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
const buf = try alloc.alloc(u8, in.len * 2);
defer alloc.free(buf);
return zstdDecomp(buf, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
const buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return zstdDecomp(buf, in, out);
}
inline fn zstdDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = zstd.Decompress.init(&rdr, buffer, .{ .window_len = @truncate(out.len) });
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const buf = try alloc.alloc(u8, out.len + zstd.block_size_max);
defer alloc.free(buf);
return zstdDecomp(buf, in, out);
}
+74
View File
@@ -0,0 +1,74 @@
const std = @import("std");
const Io = std.Io;
const utils = @import("utils/util.zig");
const Inode = @import("inode.zig");
const Directory = @This();
entries: []Entry,
pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, size: u32) !Directory {
var red: u32 = 3;
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 10);
errdefer out.deinit(alloc);
while (red < size) {
const hdr: Header = try utils.readValueRdr(Header, rdr);
red += @sizeOf(Header);
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
for (0..hdr.count + 1) |_| {
const raw: RawEntry = try utils.readValueRdr(RawEntry, rdr);
const name = try alloc.alloc(u8, raw.name_size + 1);
errdefer alloc.free(name);
try rdr.readSliceAll(name);
out.addOneAssumeCapacity().* = .{
.start = hdr.start,
.offset = raw.offset,
.type = raw.type,
.name = name,
};
red += @sizeOf(RawEntry) + raw.name_size + 1;
}
}
return .{ .entries = try out.toOwnedSlice(alloc) };
}
pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
for (self.entries) |entry|
entry.deinit(alloc);
alloc.free(self.entries);
}
// Types
pub const Entry = struct {
start: u32,
offset: u16,
type: Inode.Type,
name: []const u8,
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
};
const Header = extern struct {
count: u32,
start: u32,
inode_num: u32,
};
const RawEntry = extern struct {
offset: u16,
inode_num_offset: i16,
type: Inode.Type,
name_size: u16,
};
-69
View File
@@ -1,69 +0,0 @@
const std = @import("std");
const Reader = std.Io.Reader;
const Inode = @import("inode.zig");
pub const Error = error{OutOfMemory} || Reader.Error;
const DirEntry = @This();
block_start: u32,
block_offset: u16,
type: Inode.Type,
name: []const u8,
pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) Error![]DirEntry {
var hdr: Header = undefined;
var raw: RawEntry = undefined;
var out: std.ArrayList(DirEntry) = try .initCapacity(alloc, 30);
errdefer {
for (out.items) |ent|
alloc.free(ent.name);
out.deinit(alloc);
}
var tot_red: u32 = 3;
while (tot_red < size) {
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
tot_red += @sizeOf(Header);
for (0..hdr.count + 1) |_| {
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
const new_name = try alloc.alloc(u8, raw.name_size + 1);
try rdr.readSliceEndian(u8, new_name, .little);
const new = out.addOneAssumeCapacity();
new.* = .{
.block_start = hdr.block_start,
.block_offset = raw.block_offset,
.type = raw.type,
.name = new_name,
};
tot_red += @sizeOf(RawEntry) + raw.name_size + 1;
}
}
return out.toOwnedSlice(alloc);
}
// Types
const Header = extern struct {
count: u32,
block_start: u32,
num: u32,
};
const RawEntry = extern struct {
block_offset: u16,
num_offset: i16,
type: Inode.Type,
name_size: u16,
};
+126 -61
View File
@@ -1,93 +1,158 @@
//! An easier to use wrapper around an inode.
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Archive = @import("archive.zig");
const DirEntry = @import("directory.zig");
const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const DataExtractor = @import("util/data_extractor.zig"); const Directory = @import("dir.zig");
const Decompressor = @import("util/decompressor.zig"); const MetadataReader = @import("utils/meta.zig");
const MetadataReader = @import("util/metadata.zig"); const Options = @import("options.zig");
const DataReader = @import("utils/data_reader.zig");
const Decompress = @import("utils/decompress.zig");
const Super = @import("archive.zig").Super;
const File = @This(); const File = @This();
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
archive: Archive, data: []u8,
super: Super,
inode: Inode,
name: []const u8, name: []const u8,
inode: Inode,
pub fn copy(self: File, alloc: std.mem.Allocator) !File {
const new_name = try alloc.dupe(u8, self.name);
errdefer alloc.free(new_name);
/// Creates a new File from an inode. Takes ownership of the Inode and creates a copy of the given name.
/// Requires the given allocator was used to create the Inode.
pub fn init(alloc: std.mem.Allocator, archive: Archive, in: Inode, name: []const u8) !File {
const new_name = try alloc.alloc(u8, name.len);
@memcpy(new_name, name);
return .{ return .{
.alloc = alloc, .alloc = alloc,
.archive = archive, .data = self.data,
.super = self.super,
.inode = in,
.name = new_name, .name = new_name,
.inode = try self.inode.copy(alloc),
}; };
} }
pub fn fromDirEntry(alloc: std.mem.Allocator, archive: *Archive, ent: DirEntry) !File {
var rdr = archive.file.readerAt(archive.super.inode_start + ent.block_start);
var meta: MetadataReader = .init(alloc, &rdr, &archive.stateless_decomp);
try meta.interface.discardAll(ent.block_offset);
var in: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
errdefer in.deinit(alloc);
return .init(alloc, archive.*, in, ent.name);
}
pub fn deinit(self: File) void { pub fn deinit(self: File) void {
self.alloc.free(self.name);
self.inode.deinit(self.alloc); self.inode.deinit(self.alloc);
self.alloc.free(self.name);
} }
pub fn open(self: *File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File { /// Reader interface to read a regular file's contents.
const entries = try self.inode.readDirectory( pub fn reader(self: File, alloc: std.mem.Allocator) !DataReader {
alloc, return switch (self.inode.header.type) {
self.archive.file, .file => |f| .init(
&self.archive.stateless_decomp, alloc,
self.archive.super.dir_start, self.data,
); self.super.decomp_fn,
defer { self.super.block_size,
for (entries) |ent| f.start,
alloc.free(ent.name); f.size,
alloc.free(entries); f.blocks,
),
.ext_file => |f| .init(
alloc,
self.data,
self.super.decomp_fn,
self.super.block_size,
f.start,
f.size,
f.blocks,
),
else => error.NotRegularFile,
};
}
pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
switch (self.inode.header.type) {
.dir, .ext_dir => {},
else => return error.NotDirectory,
} }
const path = std.mem.trim(u8, filepath, "/"); const path = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return self.copy(alloc);
const first_element: []const u8 = std.mem.sliceTo(path, '/'); const first_element: []const u8 = std.mem.sliceTo(path, '/');
const final = first_element.len == path.len;
var search_slice = entries; var dir_rdr: MetadataReader = .init(alloc, self.data[self.inode.data.dir.start + self.super.dir_table_start ..], self.super.decomp_fn);
var idx: usize = undefined; try dir_rdr.interface.discardAll(self.inode.data.dir.offset);
while (search_slice.len > 0) {
idx = search_slice.len / 2; const dir_size = self.inode.data.dir.size;
const middle = search_slice[idx];
switch (std.mem.order(u8, first_element, middle.name)) { const entry: Directory.Entry = blk: {
.eq => break, var dir: Directory = try .read(alloc, &dir_rdr.interface, dir_size);
.lt => search_slice = search_slice[0..idx], defer dir.deinit(alloc);
.gt => search_slice = search_slice[idx + 1 ..],
var entries = dir.entries;
var idx: usize = undefined;
while (entries.len > 1) {
idx = entries.len / 2;
const val = entries[idx];
switch (std.mem.order(u8, first_element, val.name)) {
.eq => break,
.lt => entries = entries[0..idx],
.gt => entries = entries[idx..],
}
} else {
if (!std.mem.eql(u8, first_element, entries[0].name))
return error.NotFound;
idx = 0;
} }
} else return Error.FileNotFound;
var first_elem_file = try fromDirEntry(alloc, &self.archive, search_slice[idx]); if (!final) break :blk entries[idx];
if (first_element.len == path.len)
return first_elem_file; const inode: Inode = try .readLocation(
defer first_elem_file.deinit(); alloc,
return first_elem_file.open(alloc, io, path[first_element.len + 1 ..]); self.data,
entries[idx].start,
entries[idx].offset,
self.super,
);
errdefer inode.deinit(alloc);
return .{
.alloc = alloc,
.data = self.data,
.super = self.super,
.inode = inode,
.name = try alloc.dupe(u8, entries[idx].name),
};
};
if (entry.type != .dir)
return error.NotFound;
const transient_file: File = .{
.alloc = alloc,
.data = self.data,
.super = self.super,
.inode = try .readLocation(
alloc,
self.data,
entry.start,
entry.offset,
self.super,
),
.name = "",
}; // We don't care about deiniting this file since dir inodes don't need to be deinited and we don't have an allocd name.
return transient_file.open(alloc, path[first_element.len + 1 ..]);
} }
pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8, options: ExtractionOptions) !void { /// Extract the given File to the location.
return self.inode.extract(alloc, io, self.archive.file, self.archive.super, filepath, options); pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: Options) !void {
// TODO: do some basic processing to check if ext_loc is a folder & if self is regular file and adjust accordingly.
if (options.single_threaded)
return Decompress.single(alloc, io, self.map.memory, self.super, self.inode, ext_loc, options);
return Decompress.multi(alloc, io, self.map.memory, self.super, self.inode, ext_loc, options);
} }
// Types
pub const Error = error{
FileNotFound,
} || Inode.Error;
-85
View File
@@ -1,85 +0,0 @@
const std = @import("std");
const Io = std.Io;
const BlockSize = @import("inode_data/file.zig").BlockSize;
const LookupTable = @import("lookup_table.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const FragManager = @This();
pub const FragEntry = extern struct {
start: u64,
size: BlockSize,
_: u32,
};
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *Decompressor,
block_size: u32,
entries: []FragEntry,
frag_cache: std.array_hash_map.Auto(u32, []u8),
cache_mut: std.Io.RwLock = .init,
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *Decompressor, frag_start: u64, frag_num: u32, block_size: u32) !FragManager {
const first_offset: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[frag_start .. frag_start + 8]), .little);
var rdr = fil.readerAt(first_offset);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
const entries = try alloc.alloc(FragEntry, frag_num);
errdefer alloc.free(entries);
try meta.interface.readSliceEndian(FragEntry, entries, .little);
return .{
.alloc = alloc,
.fil = fil,
.decomp = decomp,
.block_size = block_size,
.entries = entries,
.frag_cache = .empty,
};
}
pub fn deinit(self: *FragManager, io: Io) void {
self.cache_mut.lockUncancelable(io);
self.alloc.free(self.entries);
for (self.frag_cache.values()) |v|
self.alloc.free(v);
self.frag_cache.deinit(self.alloc);
}
pub fn get(self: *FragManager, io: Io, idx: u32) ![]u8 {
{
try self.cache_mut.lockShared(io);
defer self.cache_mut.unlockShared(io);
if (self.frag_cache.contains(idx))
return self.frag_cache.get(idx).?;
}
try self.cache_mut.lock(io);
defer self.cache_mut.unlock(io);
if (self.frag_cache.contains(idx))
return self.frag_cache.get(idx).?;
const entry = self.entries[idx];
const out = try self.alloc.alloc(u8, if (entry.size.uncompressed) entry.size.size else self.block_size);
if (entry.size.uncompressed) {
@memcpy(out, self.fil.map.memory[entry.start .. entry.start + entry.size.size]);
} else {
@branchHint(.likely);
_ = try self.decomp.Decompress(self.alloc, self.fil.map.memory[entry.start .. entry.start + entry.size.size], out);
}
try self.frag_cache.put(self.alloc, idx, out);
return out;
}
+72
View File
@@ -0,0 +1,72 @@
const std = @import("std");
const Io = std.Io;
const Lookup = @import("lookup.zig");
const Decomp = @import("decomp.zig");
const BlockSize = @import("inode.zig").BlockSize;
const FragCache = @This();
block_size: u32,
entries: Lookup.Table(Entry),
cache: std.AutoHashMapUnmanaged(u32, CacheData) = .empty,
mut: Io.Mutex = .init,
pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, start: u64, count: u32) !FragCache {
return .{
.block_size = block_size,
.entries = try .init(data, decomp, start, count),
};
}
pub fn deinit(self: *FragCache, alloc: std.mem.Allocator) void {
self.entries.deinit(alloc);
var iter = self.cache.valueIterator();
while (iter.next()) |cache| {
if (cache.allocd)
alloc.free(cache.data);
}
self.cache.deinit(alloc);
}
pub fn get(self: *FragCache, alloc: std.mem.Allocator, io: Io, idx: u32) ![]u8 {
const cache = self.cache.get(idx);
if (cache != null) return cache.?.data;
const entry = try self.entries.get(alloc, io, idx);
const data = self.entries.data[entry.start..][0..entry.size.size];
if (entry.size.uncompressed)
return data;
try self.mut.lock(io);
defer self.mut.unlock(io);
const cache_data = try alloc.alloc(u8, self.block_size);
errdefer alloc.free(cache_data);
_ = try self.entries.decomp(alloc, data, cache_data);
try self.cache.put(alloc, idx, .{
.data = cache_data,
.allocd = true,
});
return cache_data;
}
// Types
pub const Entry = extern struct {
start: u64,
size: BlockSize,
_: u32,
};
const CacheData = struct {
data: []u8,
allocd: bool,
};
+222 -553
View File
@@ -1,165 +1,71 @@
//! A file-system object. Represents a File or directory.
const std = @import("std"); const std = @import("std");
const Reader = std.Io.Reader;
const Io = std.Io; const Io = std.Io;
const Archive = @import("archive.zig"); const util = @import("utils/util.zig");
const Decomp = @import("decomp.zig").Decomp;
const DirEntry = @import("directory.zig"); const MetadataReader = @import("utils/meta.zig");
const ExtractionOptions = @import("options.zig"); const Super = @import("archive.zig").Super;
const FragEntry = @import("frag.zig").FragEntry;
const FragManager = @import("frag.zig");
const dir = @import("inode_data/dir.zig");
const file = @import("inode_data/file.zig");
const misc = @import("inode_data/misc.zig");
const LookupTable = @import("lookup_table.zig");
const CachedTable = LookupTable.CachedTable;
const DataExtractor = @import("util/data_extractor.zig");
const DataReader = @import("util/data_reader.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const XattrTable = @import("xattr_table.zig");
const Inode = @This(); const Inode = @This();
hdr: Header, header: Header,
data: Data, data: Data,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode { pub fn readLocation(alloc: std.mem.Allocator, data: []u8, start: u32, offset: u16, super: Super) !Inode {
var hdr: Header = undefined; var meta: MetadataReader = .init(alloc, data[super.inode_table_start + start ..], super.decomp_fn);
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); try meta.interface.discardAll(offset);
return read(alloc, &meta.interface, super.block_size);
}
pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, block_size: u32) !Inode {
const hdr = try util.readValueRdr(Header, rdr);
return .{ return .{
.hdr = hdr, .header = hdr,
.data = switch (hdr.inode_type) { .data = switch (hdr.type) {
.dir => .{ .dir = try .read(rdr) }, .dir => .{ .dir = try .readBasic(rdr) },
.file => .{ .file = try .read(alloc, rdr, block_size) }, .ext_dir => .{ .dir = try .readExt(rdr) },
.symlink => .{ .symlink = try .read(alloc, rdr) }, .file => .{ .file = try .readBasic(rdr, alloc, block_size) },
.block_dev => .{ .block_dev = try .read(rdr) }, .ext_file => .{ .file = try .readExt(rdr, alloc, block_size) },
.char_dev => .{ .char_dev = try .read(rdr) }, .symlink, .ext_symlink => .{ .symlink = try .read(rdr, alloc) },
.fifo => .{ .fifo = try .read(rdr) }, .block_dev, .char_dev => .{ .dev = try .readBasic(rdr) },
.socket => .{ .socket = try .read(rdr) }, .ext_block_dev, .ext_char_dev => .{ .dev = try .readExt(rdr) },
.ext_dir => .{ .ext_dir = try .read(rdr) }, .fifo, .socket => .{ .fifo = try .readBasic(rdr) },
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) }, .ext_fifo, .ext_socket => .{ .fifo = try .readExt(rdr) },
.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 copy(self: Inode, alloc: std.mem.Allocator) !Inode {
var new_inode = self;
switch (new_inode.data) {
.file => |*f| f.blocks = try alloc.dupe(BlockSize, f.blocks),
.symlink => |*f| f.target = try alloc.dupe(u8, f.target),
else => {},
}
return new_inode;
}
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) { switch (self.data) {
.file => |d| d.deinit(alloc), .file => |f| alloc.free(f.blocks),
.symlink => |d| d.deinit(alloc), .symlink => |s| alloc.free(s.target),
.ext_file => |d| d.deinit(alloc),
.ext_symlink => |d| d.deinit(alloc),
else => {}, else => {},
} }
} }
// Utility Functions
/// Read the directory entries
pub fn readDirectory(self: Inode, alloc: std.mem.Allocator, fil: OffsetFile, decomp: *Decompressor, dir_offset: u64) ![]DirEntry {
return switch (self.data) {
.dir => |d| readDirFromData(alloc, fil, decomp, dir_offset, d),
.ext_dir => |d| readDirFromData(alloc, fil, decomp, dir_offset, d),
else => Error.NotDirectory,
};
}
fn readDirFromData(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *Decompressor, dir_offset: u64, d: anytype) ![]DirEntry {
var rdr = fil.readerAt(dir_offset + d.block_start);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(d.block_offset);
return DirEntry.readDirectory(alloc, &meta.interface, d.size);
}
/// Get a reader for a regular file's data.
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, block_size: u32, frag_block: ?[]u8) !DataReader {
return switch (self.data) {
.file => |f| getReaderFromData(alloc, io, fil, decomp, block_size, frag_block, f),
.ext_file => |f| getReaderFromData(alloc, io, fil, decomp, block_size, frag_block, f),
else => Error.NotRegularFile,
};
}
fn getReaderFromData(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, block_size: u32, frag_block: ?[]u8, d: anytype) !DataReader {
const ext: DataReader = .init(alloc, io, fil, decomp, block_size, d.size, d.block_start, d.blocks);
if (d.frag_block_offset == 0xFFFFFFFF) {
if (frag_block == null) return error.FragBlockNotProvided;
ext.addFrag(d.frag_block_offset, frag_block.?);
}
return ext;
}
/// Get an extractor for a regular file's data.
pub fn dataExtractor(self: Inode, fil: OffsetFile, decomp: *Decompressor, block_size: u32, frag_block: ?[]u8) !DataExtractor {
return switch (self.data) {
.file => |f| getExtractorFromData(fil, decomp, block_size, frag_block, f),
.ext_file => |f| getExtractorFromData(fil, decomp, block_size, frag_block, f),
else => Error.NotRegularFile,
};
}
fn getExtractorFromData(fil: OffsetFile, decomp: *Decompressor, block_size: u32, frag_block: ?[]u8, d: anytype) !DataExtractor {
const ext: DataExtractor = .init(fil, decomp, block_size, d.size, d.block_start, d.blocks);
if (d.frag_block_offset == 0xFFFFFFFF) {
if (frag_block == null) return error.FragBlockNotProvided;
ext.addFrag(d.frag_block_offset, frag_block.?);
}
return ext;
}
/// Get a symlink's target path
pub fn symlinkTarget(self: Inode) ![]const u8 {
return switch (self.data) {
.symlink => |s| s.target,
.ext_symlink => |s| s.target,
else => Error.NotSymlink,
};
}
/// Get inode's gid
pub fn gid(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, id_table_start: u64) !u16 {
return LookupTable.lookupValue(u16, alloc, io, decomp, fil, id_table_start, self.hdr.gid_idx);
}
/// Get inode's uid
pub fn uid(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, id_table_start: u64) !u16 {
return LookupTable.lookupValue(u16, alloc, io, decomp, fil, id_table_start, self.hdr.uid_idx);
}
/// Get the inode's xattr values as an index into the Archive's xattr table.
/// Returns error.NoXattr if the inode doesn't have extended attributes.
pub fn xattrIndex(self: Inode) !u32 {
const idx = switch (self.data) {
.ext_dir => |e| e.xattr_idx,
.ext_file => |e| e.xattr_idx,
.ext_symlink => |e| e.xattr_idx,
.ext_block_dev => |e| e.xattr_idx,
.ext_char_dev => |e| e.xattr_idx,
.ext_fifo => |e| e.xattr_idx,
.ext_socket => |e| e.xattr_idx,
else => return error.NoXattr,
};
if (idx == 0xFFFFFFFF) return error.NoXattr;
return idx;
}
// Get an inode's xattr values. If the inode does not have xattr values (including if the inode is not an extended type), an empty slice is returned.
pub fn xattrValues(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, xattr_table_start: u64) ![]XattrTable.XattrOwned {
const idx = self.xattrIndex() catch &[0]XattrTable.XattrOwned{};
return XattrTable.statelessLookup(alloc, io, decomp, fil, xattr_table_start, idx);
}
// Types // Types
pub const Error = error{ pub const Reference = packed struct(u64) {
NotDirectory, offset: u16,
NotRegularFile, start: u32,
NotSymlink, _: u16,
NotExtended,
}; };
pub const Ref = packed struct(u64) { pub const BlockSize = packed struct(u32) {
block_offset: u16, size: u23,
block_start: u32, uncompressed: bool,
_: u16 = 0, _: u8,
}; };
pub const Type = enum(u16) { pub const Type = enum(u16) {
@@ -179,455 +85,218 @@ pub const Type = enum(u16) {
ext_socket, ext_socket,
}; };
pub const Data = union(Type) {
dir: dir.Dir,
file: file.File,
symlink: misc.Symlink,
block_dev: misc.Dev,
char_dev: misc.Dev,
fifo: misc.IPC,
socket: misc.IPC,
ext_dir: dir.ExtDir,
ext_file: file.ExtFile,
ext_symlink: misc.ExtSymlink,
ext_block_dev: misc.ExtDev,
ext_char_dev: misc.ExtDev,
ext_fifo: misc.ExtIPC,
ext_socket: misc.ExtIPC,
};
pub const Header = extern struct { pub const Header = extern struct {
inode_type: Type, type: Type,
permissions: u16, permission: u16,
uid_idx: u16, uid_idx: u16,
gid_idx: u16, gid_idx: u16,
mod_time: u32, mod_time: u32,
num: u32, inode_num: u32,
}; };
// Extract pub const Data = union(enum) {
dir: Dir,
const ExtractError = error{ MknodFailed, CannotSetXattr } || DataExtractor.Error || DirEntry.Error || file: File,
Decompressor.Error || Io.File.Atomic.InitError || Io.File.Atomic.LinkError || Io.Dir.SymLinkError; symlink: Symlink,
const PathRet = struct { dev: Dev,
path: []const u8, fifo: Fifo,
inode: Inode,
origin: bool,
fn deinit(self: PathRet, alloc: std.mem.Allocator) void {
if (self.origin) return;
alloc.free(self.path);
self.inode.deinit(alloc);
}
fn setMetadata(self: PathRet, alloc: std.mem.Allocator, io: Io, id_table: *CachedTable(u16), xattr_table: ?*XattrTable, options: ExtractionOptions) !void {
var fil = try Io.Dir.cwd().openFile(io, self.path, .{});
defer fil.close(io);
const inode = self.inode;
if (!options.ignore_permissions) {
try fil.setPermissions(io, @enumFromInt(inode.hdr.permissions));
try fil.setOwner(io, try id_table.get(io, inode.hdr.uid_idx), try id_table.get(io, inode.hdr.gid_idx));
}
if (xattr_table != null) {
const idx = inode.xattrIndex() catch return;
const xattrs = try xattr_table.?.get(alloc, io, idx);
defer {
for (xattrs) |x|
x.deinit(alloc);
alloc.free(xattrs);
}
const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &[_][]const u8{self.path}, 0);
defer alloc.free(sentinel_path);
for (xattrs) |x| {
const xattr_ret = std.os.linux.fsetxattr(fil.handle, x.key, x.value.ptr, x.value.len, 0);
if (xattr_ret != 0)
return ExtractError.CannotSetXattr;
}
}
}
};
fn DirCompare(_: void, a: PathRet, b: PathRet) std.math.Order {
return std.math.order(std.mem.count(u8, a.path, "/"), std.mem.count(u8, b.path, "/"));
}
const ExtractReturnUnion = union(enum) {
path_ret: ExtractError!PathRet,
};
const Tables = struct {
id: LookupTable.CachedTable(u16),
frag: LookupTable.CachedTable(FragEntry),
xattr: XattrTable,
}; };
/// Extracts the given inode to the given path. If the inode not a directory, the given path must not exist. // Inode data types
/// If the inode is a directory the path must not exist or be a directory.
pub fn extract(
self: Inode,
alloc: std.mem.Allocator,
io: Io,
fil: OffsetFile,
super: Archive.Superblock,
filepath: []const u8,
options: ExtractionOptions,
) !void {
const path = std.mem.trimEnd(u8, filepath, "/");
var decomp_base: Decompressor = try @import("decomp.zig").StatelessDecomp(super.compression); // TODO: Replace with actual Decomp value to share states & caches for efficiency. pub const Dir = struct {
const decomp = &decomp_base; //RegularDir structure:
// start: u32,
// hard_links: u32,
// size: u16,
// offset: u16,
// parent_inode_num: u32,
// ExtDir structure
// hard_links: u32,
// size: u32,
// start: u32,
// parent_inode_num: u32,
// idx_count: u16,
// offset: u16,
// xattr_idx: u32,
// dir_indexes: []DirIndex,
var frag_mgr: FragManager = try .init(alloc, fil, decomp, super.frag_start, super.frag_count, super.block_size); hard_links: u32,
defer frag_mgr.deinit(io); size: u32,
start: u32,
offset: u16,
xattr_idx: ?u32,
if (options.single_threaded) pub fn readBasic(rdr: *Io.Reader) !Dir {
return self.extractSinglethreaded(alloc, io, fil, super, path, options, decomp, &frag_mgr); var raw: [12]u8 = undefined;
try rdr.readSliceAll(&raw);
var sel_buf: [10]ExtractReturnUnion = undefined; return .{
var sel: Io.Select(ExtractReturnUnion) = .init(io, &sel_buf); .start = util.readValue(u32, raw[0..4]),
defer sel.cancelDiscard(); .hard_links = util.readValue(u32, raw[4..8]),
.size = util.readValue(u16, raw[8..10]),
var loop = io.async(finishLoop, .{ alloc, io, fil, decomp, super, options, &sel }); .offset = util.readValue(u16, raw[10..12]),
.xattr_idx = null,
sel.async(.path_ret, extractRealAsync, .{ self, alloc, io, fil, super, decomp, &sel, &frag_mgr, path, true }); };
try loop.await(io);
}
fn extractRealAsync(
self: Inode,
alloc: std.mem.Allocator,
io: Io,
fil: OffsetFile,
super: Archive.Superblock,
decomp: *Decompressor,
sel: *Io.Select(ExtractReturnUnion),
frag_mgr: *FragManager,
path: []const u8,
origin: bool,
) ExtractError!PathRet {
errdefer {
if (!origin) {
self.deinit(alloc);
alloc.free(path);
}
} }
switch (self.hdr.inode_type) { pub fn readExt(rdr: *Io.Reader) !Dir {
.dir, .ext_dir => { var raw: [24]u8 = undefined;
try Io.Dir.cwd().createDir(io, path, @enumFromInt(0o777)); try rdr.readSliceAll(&raw);
const entries = self.readDirectory(alloc, fil, decomp, super.dir_start) catch |err| switch (err) { return .{
Error.NotDirectory, Error.NotExtended, Error.NotRegularFile, Error.NotSymlink => unreachable, .hard_links = util.readValue(u32, raw[0..4]),
else => |e| return e, .size = util.readValue(u32, raw[4..8]),
}; .start = util.readValue(u32, raw[8..12]),
defer { .offset = util.readValue(u16, raw[18..20]),
for (entries) |e| .xattr_idx = util.readValue(u32, raw[20..24]),
e.deinit(alloc); };
alloc.free(entries);
}
for (entries) |e| {
const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", e.name });
errdefer alloc.free(new_path);
var rdr = fil.readerAt(super.inode_start + e.block_start);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(e.block_offset);
const new_inode = try read(alloc, &meta.interface, super.block_size);
errdefer new_inode.deinit(alloc);
sel.async(.path_ret, extractRealAsync, .{ new_inode, alloc, io, fil, super, decomp, sel, frag_mgr, new_path, false });
}
},
.file, .ext_file => {
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{ .make_path = true });
defer atomic.deinit(io);
var ext: DataExtractor = switch (self.data) {
.file => |f| blk: {
var ext: DataExtractor = .init(fil, decomp, super.block_size, f.size, f.block_start, f.block_sizes);
if (f.frag_idx != 0xFFFFFFFF)
ext.addFrag(f.frag_block_offset, try frag_mgr.get(io, f.frag_idx));
break :blk ext;
},
.ext_file => |f| blk: {
var ext: DataExtractor = .init(fil, decomp, super.block_size, f.size, f.block_start, f.block_sizes);
if (f.frag_idx != 0xFFFFFFFF)
ext.addFrag(f.frag_block_offset, try frag_mgr.get(io, f.frag_idx));
break :blk ext;
},
else => unreachable,
};
try ext.extractAsync(alloc, io, atomic.file);
try atomic.link(io);
},
.symlink, .ext_symlink => try Io.Dir.cwd().symLink(io, self.symlinkTarget() catch unreachable, path, .{}),
else => {
var mode: u32 = undefined;
var dev: u32 = 0;
const DT = std.posix.DT;
switch (self.data) {
.char_dev => |d| {
dev = d.dev;
mode = DT.CHR;
},
.ext_char_dev => |d| {
dev = d.dev;
mode = DT.CHR;
},
.block_dev => |d| {
dev = d.dev;
mode = DT.BLK;
},
.ext_block_dev => |d| {
dev = d.dev;
mode = DT.BLK;
},
.fifo, .ext_fifo => mode = DT.FIFO,
.socket, .ext_socket => mode = DT.SOCK,
else => unreachable,
}
const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &[_][]const u8{path}, 0);
const res = std.os.linux.mknod(sentinel_path, mode, dev);
alloc.free(sentinel_path);
if (res != 0)
return ExtractError.MknodFailed;
},
} }
return .{ };
.path = path,
.inode = self,
.origin = origin,
};
}
fn finishLoop(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, super: Archive.Superblock, options: ExtractionOptions, sel: *Io.Select(ExtractReturnUnion)) !void {
var id_table: CachedTable(u16) = .init(alloc, fil, decomp, super.id_start, super.id_count);
defer id_table.deinit(io);
var xattr_table: ?XattrTable = if (super.flags.xattr_never or options.ignore_xattr or !@hasField(std.os, "linux")) pub const File = struct {
null //RegularFile structure:
else // start: u32,
try .init(alloc, fil, decomp, super.xattr_start); // frag_idx: u32,
defer if (xattr_table != null) xattr_table.?.deinit(io); // frag_offset: u32,
// size: u32,
// block_sizes: []BlockSize,
//ExtFile structure:
// start: u64,
// size: u64,
// sparse: u64,
// hard_links: u32,
// frag_idx: u32,
// frag_offset: u32,
// xattr_idx: u32,
// block_sizes: []BlockSize,
var dir_queue: std.PriorityDequeue(PathRet, void, DirCompare) = .empty; start: u64,
defer dir_queue.deinit(alloc); frag_idx: u32,
frag_offset: u32,
size: u64,
blocks: []BlockSize,
xattr_idx: ?u32,
while (true) { pub fn readBasic(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File {
if (sel.group.token.load(.unordered) == null) break; var raw: [16]u8 = undefined;
try rdr.readSliceAll(&raw);
const ret = try sel.await(); const frag_idx = util.readValue(u32, raw[4..8]);
const path_ret = try ret.path_ret; const size = util.readValue(u32, raw[12..16]);
if (options.ignore_permissions and xattr_table == null) { const block_num = if (frag_idx == 0xFFFFFFFF)
path_ret.deinit(alloc); try std.math.divCeil(usize, size, block_size)
continue; else
} size / block_size;
if (path_ret.inode.hdr.inode_type == .dir or path_ret.inode.hdr.inode_type == .ext_dir) { const sizes = try alloc.alloc(BlockSize, block_num);
try dir_queue.push(alloc, path_ret); try rdr.readSliceEndian(BlockSize, sizes, .little);
continue;
}
defer path_ret.deinit(alloc);
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options); return .{
.start = util.readValue(u32, raw[0..4]),
.frag_idx = frag_idx,
.frag_offset = util.readValue(u32, raw[8..12]),
.size = size,
.blocks = sizes,
.xattr_idx = null,
};
} }
pub fn readExt(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File {
var raw: [40]u8 = undefined;
try rdr.readSliceAll(&raw);
while (sel.cancel()) |ret| { const frag_idx = util.readValue(u32, raw[28..32]);
const path_ret = try ret.path_ret; const size = util.readValue(u64, raw[8..16]);
if (options.ignore_permissions and xattr_table == null) { const block_num = if (frag_idx == 0xFFFFFFFF)
path_ret.deinit(alloc); try std.math.divCeil(usize, size, block_size)
continue; else
} size / block_size;
if (path_ret.inode.hdr.inode_type == .dir or path_ret.inode.hdr.inode_type == .ext_dir) { const sizes = try alloc.alloc(BlockSize, block_num);
try dir_queue.push(alloc, path_ret); try rdr.readSliceEndian(BlockSize, sizes, .little);
continue;
}
defer path_ret.deinit(alloc); return .{
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options); .start = util.readValue(u64, raw[0..8]),
.size = size,
.frag_idx = frag_idx,
.frag_offset = util.readValue(u32, raw[32..36]),
.blocks = sizes,
.xattr_idx = util.readValue(u32, raw[36..40]),
};
} }
};
var iter = dir_queue.iterator(); pub const Symlink = struct {
while (iter.next()) |path_ret| { hard_links: u32,
defer path_ret.deinit(alloc); target: []const u8,
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options); // xattr_idx is ignored on extended symlinks as you can't apply them to symlinks anyway.
pub fn read(rdr: *Io.Reader, alloc: std.mem.Allocator) !Symlink {
var raw: [8]u8 = undefined;
try rdr.readSliceAll(&raw);
const target = try alloc.alloc(u8, std.mem.readInt(u32, raw[4..], .little));
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
return .{
.hard_links = std.mem.readInt(u32, raw[0..4], .little),
.target = target,
};
} }
} };
/// Extracts the given inode to the given path. If the inode not a directory, the given path must not exist.
/// If the inode is a directory the path must not exist or be a directory.
fn extractSinglethreaded(
self: Inode,
alloc: std.mem.Allocator,
io: Io,
fil: OffsetFile,
super: Archive.Superblock,
path: []const u8,
options: ExtractionOptions,
decomp: *Decompressor,
frag: *FragManager,
) !void {
var id_table: CachedTable(u16) = .init(alloc, fil, decomp, super.id_start, super.id_count);
defer id_table.deinit(io);
var xattr_table: ?XattrTable = if (super.flags.xattr_never or options.ignore_xattr or !@hasField(std.os, "linux")) pub const Dev = struct {
null hard_links: u32,
else device: u32,
try .init(alloc, fil, decomp, super.xattr_start); // xattr_idx omitted on basic device inodes.
defer if (xattr_table != null) xattr_table.?.deinit(io); xattr_idx: ?u32,
return self.extractReal( pub fn readBasic(rdr: *Io.Reader) !Dev {
alloc, var raw: [8]u8 = undefined;
io, try rdr.readSliceAll(&raw);
fil,
super,
decomp,
frag,
&id_table,
if (xattr_table == null) null else &xattr_table.?,
path,
options,
);
}
fn extractReal(
self: Inode,
alloc: std.mem.Allocator,
io: Io,
fil: OffsetFile,
super: Archive.Superblock,
decomp: *Decompressor,
frag_mgr: *FragManager,
id_table: *CachedTable(u16),
xattr_table: ?*XattrTable,
path: []const u8,
options: ExtractionOptions,
) !void {
switch (self.hdr.inode_type) {
.dir, .ext_dir => {
try Io.Dir.cwd().createDir(io, path, @enumFromInt(0o777));
const entries = self.readDirectory(alloc, fil, decomp, super.dir_start) catch |err| switch (err) { return .{
Error.NotDirectory, Error.NotExtended, Error.NotRegularFile, Error.NotSymlink => unreachable, .hard_links = util.readValue(u32, raw[0..4]),
else => |e| return e, .device = util.readValue(u32, raw[4..8]),
}; .xattr_idx = null,
defer { };
for (entries) |e|
e.deinit(alloc);
alloc.free(entries);
}
for (entries) |e| {
const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", e.name });
defer alloc.free(new_path);
var rdr = fil.readerAt(super.inode_start + e.block_start);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(e.block_offset);
const new_inode = try read(alloc, &meta.interface, super.block_size);
defer new_inode.deinit(alloc);
try new_inode.extractReal(alloc, io, fil, super, decomp, frag_mgr, id_table, xattr_table, new_path, options);
}
},
.file, .ext_file => {
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{ .make_path = true });
defer atomic.deinit(io);
var rdr: DataReader = switch (self.data) {
.file => |f| blk: {
var ext: DataReader = try .init(alloc, io, fil, decomp, super.block_size, f.size, f.block_start, f.block_sizes);
if (f.frag_idx != 0xFFFFFFFF)
ext.addFrag(f.frag_block_offset, try frag_mgr.get(io, f.frag_idx));
break :blk ext;
},
.ext_file => |f| blk: {
var ext: DataReader = try .init(alloc, io, fil, decomp, super.block_size, f.size, f.block_start, f.block_sizes);
if (f.frag_idx != 0xFFFFFFFF)
ext.addFrag(f.frag_block_offset, try frag_mgr.get(io, f.frag_idx));
break :blk ext;
},
else => unreachable,
};
defer rdr.deinit();
var buf: [512 * 1024]u8 = undefined;
var wrt = atomic.file.writer(io, &buf);
_ = try rdr.interface.streamRemaining(&wrt.interface);
try wrt.flush();
try atomic.link(io);
},
.symlink, .ext_symlink => try Io.Dir.cwd().symLink(io, self.symlinkTarget() catch unreachable, path, .{}),
else => {
var mode: u32 = undefined;
var dev: u32 = 0;
const DT = std.posix.DT;
switch (self.data) {
.char_dev => |d| {
dev = d.dev;
mode = DT.CHR;
},
.ext_char_dev => |d| {
dev = d.dev;
mode = DT.CHR;
},
.block_dev => |d| {
dev = d.dev;
mode = DT.BLK;
},
.ext_block_dev => |d| {
dev = d.dev;
mode = DT.BLK;
},
.fifo, .ext_fifo => mode = DT.FIFO,
.socket, .ext_socket => mode = DT.SOCK,
else => unreachable,
}
const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &[_][]const u8{path}, 0);
const res = std.os.linux.mknod(sentinel_path, mode, dev);
alloc.free(sentinel_path);
if (res != 0)
return ExtractError.MknodFailed;
},
} }
if (options.ignore_permissions and options.ignore_xattr) return; pub fn readExt(rdr: *Io.Reader) !Dev {
var raw: [12]u8 = undefined;
try rdr.readSliceAll(&raw);
var f = try Io.Dir.cwd().openFile(io, path, .{}); return .{
defer f.close(io); .hard_links = util.readValue(u32, raw[0..4]),
.device = util.readValue(u32, raw[4..8]),
if (!options.ignore_permissions) { .xattr_idx = util.readValue(u32, raw[8..12]),
try f.setPermissions(io, @enumFromInt(self.hdr.permissions)); };
try f.setOwner(io, try id_table.get(io, self.hdr.uid_idx), try id_table.get(io, self.hdr.gid_idx));
} }
if (xattr_table != null) { };
const idx = self.xattrIndex() catch return;
const xattrs = try xattr_table.?.get(alloc, io, idx); pub const Fifo = struct {
defer { hard_links: u32,
for (xattrs) |x| // Xattr_idx omitted on basic fifo inodes.
x.deinit(alloc); xattr_idx: ?u32,
alloc.free(xattrs);
}
const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &[_][]const u8{path}, 0); pub fn readBasic(rdr: *Io.Reader) !Fifo {
defer alloc.free(sentinel_path); var raw: [4]u8 = undefined;
for (xattrs) |x| { try rdr.readSliceAll(&raw);
const xattr_ret = std.os.linux.fsetxattr(f.handle, x.key, x.value.ptr, x.value.len, 0);
if (xattr_ret != 0) return .{
return ExtractError.CannotSetXattr; .hard_links = util.readValue(u32, raw[0..4]),
} .xattr_idx = null,
};
} }
} pub fn readExt(rdr: *Io.Reader) !Fifo {
var raw: [8]u8 = undefined;
try rdr.readSliceAll(&raw);
return .{
.hard_links = util.readValue(u32, raw[0..4]),
.xattr_idx = util.readValue(u32, raw[4..8]),
};
}
};
-32
View File
@@ -1,32 +0,0 @@
const Reader = @import("std").Io.Reader;
pub const Dir = extern 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 = extern struct {
hard_links: u32,
size: u32,
block_start: u32,
parent_num: u32,
idx_count: u16,
block_offset: u16,
xattr_idx: u32,
// index: []DirIndex
pub fn read(rdr: *Reader) !ExtDir {
var d: ExtDir = undefined;
try rdr.readSliceEndian(ExtDir, @ptrCast(&d), .little);
return d;
}
};
-97
View File
@@ -1,97 +0,0 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const BlockSize = packed struct(u32) {
size: u24,
uncompressed: bool,
_: u7,
};
const FileRawRead = extern struct {
block_start: u32,
frag_idx: u32,
frag_block_offset: u32,
size: u32,
};
pub const File = struct {
block_start: u32,
frag_idx: u32,
frag_block_offset: u32,
size: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
var raw: FileRawRead = undefined;
try rdr.readSliceEndian(FileRawRead, @ptrCast(&raw), .little);
var num_blocks: u32 = raw.size / block_size;
if (raw.size % block_size != 0 and raw.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 = raw.block_start,
.frag_idx = raw.frag_idx,
.frag_block_offset = raw.frag_block_offset,
.size = raw.size,
.block_sizes = sizes,
};
}
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
alloc.free(self.block_sizes);
}
};
const ExtFileRawRead = extern struct {
block_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_block_offset: u32,
xattr_idx: u32,
};
pub const ExtFile = struct {
block_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_block_offset: u32,
xattr_idx: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
var raw: ExtFileRawRead = undefined;
try rdr.readSliceEndian(ExtFileRawRead, @ptrCast(&raw), .little);
var num_blocks: u32 = @truncate(raw.size / block_size);
if (raw.size % block_size != 0 and raw.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 = raw.block_start,
.size = raw.size,
.sparse = raw.sparse,
.hard_links = raw.hard_links,
.frag_idx = raw.frag_idx,
.frag_block_offset = raw.frag_block_offset,
.xattr_idx = raw.xattr_idx,
.block_sizes = sizes,
};
}
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
alloc.free(self.block_sizes);
}
};
-98
View File
@@ -1,98 +0,0 @@
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 = extern 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 = extern 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 = extern 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 = extern 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;
}
};
+179
View File
@@ -0,0 +1,179 @@
const std = @import("std");
const Io = std.Io;
const Decomp = @import("decomp.zig");
const Reference = @import("inode.zig").Reference;
const MetadataReader = @import("utils/meta.zig");
const util = @import("utils/util.zig");
pub fn Value(comptime T: type, alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64, idx: u32) !T {
const ITEMS_PER_BLOCK = 8192 / @sizeOf(T);
const block_idx = idx / ITEMS_PER_BLOCK;
const value_idx = idx % ITEMS_PER_BLOCK;
const start = util.readValue(u64, data[table_start + (block_idx * 8) ..][0..8]);
var meta: MetadataReader = .init(alloc, data[start..], decomp);
try meta.interface.discardAll(value_idx * @sizeOf(T));
return util.readValueRdr(T, &meta.interface);
}
pub fn Table(comptime T: type) type {
return struct {
const Self = @This();
const ITEMS_PER_BLOCK = 8192 / @sizeOf(T);
data: []u8,
decomp: Decomp.Fn,
start: u64,
count: u32,
block_count: u32,
table: std.hash_map.AutoHashMapUnmanaged(u32, []T) = .empty,
mut: Io.Mutex = .init,
pub fn init(data: []u8, decomp: Decomp.Fn, start: u64, count: u32) !Self {
return .{
.data = data,
.decomp = decomp,
.start = start,
.count = count,
.block_count = try std.math.divCeil(u32, count, ITEMS_PER_BLOCK),
};
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
var iter = self.table.valueIterator();
while (iter.next()) |block|
alloc.free(block.*);
self.table.deinit(alloc);
}
pub fn get(self: *Self, alloc: std.mem.Allocator, io: Io, idx: u32) !T {
if (idx >= self.count) return error.InvalidIndex;
const block_idx = idx / ITEMS_PER_BLOCK;
const value_idx = idx % ITEMS_PER_BLOCK;
const init_get = self.table.get(block_idx);
if (init_get != null) return init_get.?[value_idx];
try self.mut.lock(io);
defer self.mut.unlock(io);
const start = util.readValue(u64, self.data[self.start + (block_idx * 8) ..][0..8]);
const len = if (block_idx == self.block_count - 1) self.count % ITEMS_PER_BLOCK else ITEMS_PER_BLOCK;
const new_block = try alloc.alloc(T, len);
errdefer alloc.free(new_block);
var meta: MetadataReader = .init(alloc, self.data[start..], self.decomp);
try meta.interface.readSliceEndian(T, new_block, .little);
try self.table.put(alloc, block_idx, new_block);
return new_block[value_idx];
}
};
}
pub const Xattr = struct {
kv_start: u64,
lookup: Table(XattrLookup),
pub fn init(data: []u8, decomp: Decomp.Fn, start: u64) !Xattr {
const kv_start = util.readValue(u64, data[start..][0..8]);
const count = util.readValue(u32, data[start..][8..12]);
return .{
.kv_start = kv_start,
.lookup = try .init(data, decomp, start + 16, count),
};
}
pub fn deinit(self: *Xattr, alloc: std.mem.Allocator) void {
self.lookup.deinit(alloc);
}
pub fn get(self: *Xattr, alloc: std.mem.Allocator, io: Io, idx: u32) ![]KV {
const lookup = try self.lookup.get(alloc, io, idx);
var meta: MetadataReader = .init(alloc, self.lookup.data[self.kv_start + lookup.ref.start ..], self.lookup.decomp);
try meta.interface.discardAll(lookup.ref.offset);
const kvs = try alloc.alloc(KV, lookup.count);
errdefer alloc.free(kvs);
for (kvs) |*kv| {
const entry = try util.readValueRdr(KeyEntry, &meta.interface);
const prefix = switch (entry.type.type) {
.user => "user.",
.trusted => "trusted.",
.security => "security.",
};
kv.name = try alloc.allocSentinel(u8, entry.name_size + prefix.len, 0);
errdefer alloc.free(kv.name);
try meta.interface.readSliceEndian(u8, kv.name[prefix.len..], .little);
@memcpy(kv.name[0..prefix.len], prefix);
var value_meta = if (entry.type.out_of_line) blk: {
const ool_ref = try util.readValueRdr(Reference, &meta.interface);
var ool_meta: MetadataReader = .init(alloc, self.lookup.data[self.kv_start + ool_ref.start ..], self.lookup.decomp);
try ool_meta.interface.discardAll(ool_ref.offset);
break :blk ool_meta;
} else meta;
const value_len = try util.readValueRdr(u32, &value_meta.interface);
kv.value = try alloc.alloc(u8, value_len);
errdefer alloc.free(kv.value);
try value_meta.interface.readSliceEndian(u8, kv.value, .little);
}
return kvs;
}
//types
pub const KV = struct {
name: [:0]u8,
value: []u8,
pub fn deinit(self: KV, alloc: std.mem.Allocator) void {
alloc.free(self.name);
alloc.free(self.value);
}
};
const XattrLookup = extern struct {
ref: Reference,
count: u32,
size: u32,
};
const KeyEntry = extern struct {
type: packed struct(u16) {
type: enum(u8) {
user,
trusted,
security,
},
out_of_line: bool,
_: u7,
},
name_size: u16,
};
};
-125
View File
@@ -1,125 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
pub fn lookupValue(comptime T: anytype, alloc: std.mem.Allocator, decomp: *Decompressor, file: OffsetFile, table_start: u64, idx: u32) !T {
const T_PER_BLOCK: u16 = 8192 / @sizeOf(T);
const block = idx / T_PER_BLOCK;
const block_offset = idx % T_PER_BLOCK;
const offset_pos = table_start + (8 * block);
const offset: u64 = std.mem.readInt(u64, @ptrCast(file.map.memory[offset_pos .. offset_pos + 8]), .little);
var rdr = file.readerAt(offset);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(@sizeOf(T) * block_offset);
var out: T = undefined;
try meta.interface.readSliceEndian(T, @ptrCast(&out), .little);
return out;
}
pub const Error = Io.Cancelable || Io.File.Reader.SeekError || Io.Reader.ReadAllocError;
pub fn CachedTable(comptime T: anytype) type {
return struct {
const T_PER_BLOCK: u16 = 8192 / @sizeOf(T);
const Table = @This();
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *Decompressor,
table_start: u64,
total_num: u32,
table: std.AutoHashMap(u32, []T),
mut: Io.RwLock = .init,
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *Decompressor, offset: u64, total_num: u32) Table {
return .{
.alloc = alloc,
.fil = fil,
.decomp = decomp,
.table_start = offset,
.total_num = total_num,
.table = .init(alloc),
};
}
pub fn deinit(self: *Table, io: Io) void {
self.mut.lockUncancelable(io);
var iter = self.table.valueIterator();
while (iter.next()) |val|
self.alloc.free(val.*);
self.table.deinit();
}
pub fn fill(self: *Table, io: Io) Error!void {
try self.mut.lock(io);
defer self.mut.unlock(io);
var num_blocks = self.total_num / T_PER_BLOCK;
if (self.total_num % T_PER_BLOCK > 0)
num_blocks += 1;
for (0..num_blocks) |block| {
const offset_pos = self.table_start + (8 * block);
const offset: u64 = std.mem.readInt(u64, @ptrCast(self.fil.map.memory[offset_pos .. offset_pos + 8]), .little);
const len: u16 = if (self.total_num % T_PER_BLOCK != 0 and block == (self.total_num - 1) / T_PER_BLOCK)
@truncate(self.total_num % T_PER_BLOCK)
else
T_PER_BLOCK;
var rdr = self.fil.readerAt(offset);
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
const slice = try meta.interface.readSliceEndianAlloc(self.alloc, T, len, .little);
try self.table.put(@truncate(block), slice);
}
}
pub fn get(self: *Table, io: Io, idx: u32) Error!T {
const block = idx / T_PER_BLOCK;
const block_offset = idx % T_PER_BLOCK;
{
try self.mut.lockShared(io);
defer self.mut.unlockShared(io);
if (self.table.contains(block))
return self.table.get(block).?[block_offset];
}
try self.mut.lock(io);
defer self.mut.unlock(io);
if (self.table.contains(block))
return self.table.get(block).?[block_offset];
const offset_pos = self.table_start + (8 * block);
const offset: u64 = std.mem.readInt(u64, @ptrCast(self.fil.map.memory[offset_pos .. offset_pos + 8]), .little);
const len: u16 = if (self.total_num % T_PER_BLOCK != 0 and block == (self.total_num - 1) / T_PER_BLOCK)
@truncate(self.total_num % T_PER_BLOCK)
else
T_PER_BLOCK;
var rdr = self.fil.readerAt(offset);
var meta: MetadataReader = .init(self.alloc, &rdr, self.decomp);
const slice = try meta.interface.readSliceEndianAlloc(self.alloc, T, len, .little);
try self.table.put(@truncate(block), slice);
return slice[block_offset];
}
};
}
+10 -18
View File
@@ -1,29 +1,21 @@
//! Options for file/directory extraction.
const std = @import("std"); const std = @import("std");
const Writer = std.Io.Writer; const Writer = std.Io.Writer;
const ExtractionOptions = @This(); const Options = @This();
/// Extract single-threaded only.
/// Though not necessary if using Threaded.single_threaded,
/// setting single_threaded is more efficient.
single_threaded: bool = false,
/// 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, verbose: bool = false,
/// Where to print verbose log.
verbose_writer: ?*Writer = null, verbose_writer: ?*Writer = null,
pub const default: ExtractionOptions = .{}; ignore_permissions: bool = false,
pub const default_single_threaded: ExtractionOptions = .{ .single_threaded = true }; ignore_xattr: bool = false,
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions { dereference_symlink: bool = false,
single_threaded: bool = false,
pub const default: Options = .{};
pub const single_threaded_default: Options = .{ .single_threaded = true };
pub fn verboseDefault(wrt: *Writer) Options {
return .{ return .{
.verbose = true, .verbose = true,
.verbose_writer = wrt, .verbose_writer = wrt,
+2 -2
View File
@@ -1,6 +1,6 @@
pub const Archive = @import("archive.zig"); pub const Archive = @import("archive.zig");
pub const ExtractionOptions = @import("options.zig"); pub const Options = @import("options.zig");
test { test {
@import("std").testing.refAllDecls(@This()); @import("std").testing.refAllDecls(@import("tests.zig"));
} }
+39
View File
@@ -0,0 +1,39 @@
const std = @import("std");
const Io = std.Io;
var alloc = std.testing.allocator;
var io = std.testing.io;
const Archive = @import("archive.zig");
const TEST_ARCHIVE = "testing/LinuxPATest.sfs";
const OPEN_TEST_LOCATION = "PortableApps/gVimPortable/GVim-v8.2.2965.glibc2.15-x86_64.AppImage";
test "Open" {
var archive_file = try Io.Dir.cwd().openFile(io, TEST_ARCHIVE, .{});
defer archive_file.close(io);
var arc: Archive = try .init(io, archive_file, 0);
defer arc.deinit(io);
var root = try arc.root(alloc);
defer root.deinit();
var test_open = try root.open(alloc, OPEN_TEST_LOCATION);
defer test_open.deinit();
}
const FULL_EXTRACT_LOCATION = "testing/TestExtractST";
test "FullExtractSingleThreaded" {
Io.Dir.cwd().deleteTree(io, FULL_EXTRACT_LOCATION) catch {};
var archive_file = try Io.Dir.cwd().openFile(io, TEST_ARCHIVE, .{});
defer archive_file.close(io);
var arc: Archive = try .init(io, archive_file, 0);
defer arc.deinit(io);
try arc.extract(alloc, io, FULL_EXTRACT_LOCATION, .default);
}
-162
View File
@@ -1,162 +0,0 @@
//! The DataExtractor is meant to extract a regular file's data to a given file asyncronously.
const std = @import("std");
const Io = std.Io;
const FragEntry = @import("../frag.zig").FragEntry;
const BlockSize = @import("../inode_data/file.zig").BlockSize;
const Decompressor = @import("decompressor.zig");
const OffsetFile = @import("offset_file.zig");
// const SharedCache = @import("shared_cache.zig");
pub const Error = Decompressor.Error || Io.File.MemoryMap.CreateError || Io.File.WritePositionalError;
const DataExtractor = @This();
fil: OffsetFile,
decomp: *Decompressor,
block_size: u32,
file_size: u64,
start: u64,
blocks: []BlockSize,
frag_offset: u32 = 0,
frag_block: ?[]u8 = null,
err: ?Error = null,
pub fn init(fil: OffsetFile, decomp: *Decompressor, block_size: u32, file_size: u64, data_start: u64, blocks: []BlockSize) DataExtractor {
return .{
.fil = fil,
.decomp = decomp,
.block_size = block_size,
.file_size = file_size,
.start = data_start,
.blocks = blocks,
};
}
pub fn addFrag(self: *DataExtractor, frag_offset: u32, block: []u8) void {
self.frag_offset = frag_offset;
self.frag_block = block;
}
fn numBlocks(self: DataExtractor) usize {
var num = self.blocks.len;
if (self.frag_block != null) num += 1;
return num;
}
/// Starts extracting the data using the given group to spawn async tasks.
pub fn extractConcurrent(self: DataExtractor, alloc: std.mem.Allocator, io: Io, fil: Io.File) (Error || Io.ConcurrentError)!void {
var group: Io.Group = .init;
defer group.cancel(io);
var err: ?Error = null;
var read_offset: u64 = self.start;
for (0..self.blocks.len) |idx| {
try group.concurrent(io, blockThread, .{ self, alloc, io, fil, read_offset, idx, &err });
read_offset += self.blocks[idx].size;
}
if (self.frag_block != null)
try group.concurrent(io, fragThread, .{ self, io, fil, &err });
group.await(io) catch |cancel| return err orelse cancel;
}
/// Starts extracting the data using the given group to spawn async tasks.
pub fn extractAsync(self: DataExtractor, alloc: std.mem.Allocator, io: Io, fil: Io.File) Error!void {
var group: Io.Group = .init;
defer group.cancel(io);
var err: ?Error = null;
var read_offset: u64 = self.start;
for (0..self.blocks.len) |idx| {
group.async(io, blockThread, .{ self, alloc, io, fil, read_offset, idx, &err });
read_offset += self.blocks[idx].size;
}
if (self.frag_block != null)
group.async(io, fragThread, .{ self, io, fil, &err });
group.await(io) catch |cancel| return err orelse cancel;
}
fn blockThread(self: DataExtractor, alloc: std.mem.Allocator, io: Io, fil: Io.File, read_offset: u64, idx: usize, ret_err: *?Error) Io.Cancelable!void {
const block = self.blocks[idx];
const cur_block_size = if (idx == self.numBlocks() - 1)
self.file_size % self.block_size
else
self.block_size;
const write_offset = self.block_size * idx;
var wrt = fil.writer(io, &[0]u8{});
wrt.seekTo(write_offset) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
if (block.size == 0) {
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
} else {
if (block.uncompressed) {
wrt.interface.writeAll(self.fil.map.memory[read_offset..][0..cur_block_size]) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
} else {
@branchHint(.likely);
var tmp: [1024 * 1024]u8 = undefined;
_ = self.decomp.Decompress(alloc, self.fil.map.memory[read_offset..][0..block.size], tmp[0..cur_block_size]) catch |err| {
ret_err.* = err;
return Io.Cancelable.Canceled;
};
wrt.interface.writeAll(tmp[0..cur_block_size]) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
}
}
wrt.flush() catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
}
fn fragThread(self: DataExtractor, io: Io, fil: Io.File, ret_err: *?Error) Io.Cancelable!void {
const cur_block_size = self.file_size % self.block_size;
const write_offset = self.blocks.len * self.block_size;
var wrt = fil.writer(io, &[0]u8{});
wrt.seekTo(write_offset) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
wrt.interface.writeAll(self.frag_block.?[self.frag_offset..][0..cur_block_size]) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
wrt.flush() catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
}
-189
View File
@@ -1,189 +0,0 @@
//! DataReader reads a regular file's data linearly from start to finish using Io.Reader interface.
const std = @import("std");
const Io = std.Io;
const Reader = Io.Reader;
const Writer = Io.Writer;
const Limit = Io.Limit;
const BlockSize = @import("../inode_data/file.zig").BlockSize;
const Decompressor = @import("decompressor.zig");
const OffsetFile = @import("offset_file.zig");
// const SharedCache = @import("shared_cache.zig");
const DataReader = @This();
alloc: std.mem.Allocator,
fil: OffsetFile,
io: Io,
decomp: *Decompressor,
block_size: u32,
file_size: u64,
cur_offset: u64,
blocks: []BlockSize,
frag_offset: u32 = 0,
frag_block: ?[]u8 = null,
block_idx: usize = 0,
sparse_block: bool = false,
interface: Io.Reader,
pub fn init(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, block_size: u32, file_size: u64, data_start: u64, blocks: []BlockSize) !DataReader {
return .{
.alloc = alloc,
.fil = fil,
.io = io,
.decomp = decomp,
.block_size = block_size,
.file_size = file_size,
.cur_offset = data_start,
.blocks = blocks,
.interface = .{
.buffer = try alloc.alloc(u8, block_size),
.seek = 0,
.end = 0,
.vtable = &.{
.stream = stream,
.discard = discard,
.readVec = readVec,
},
},
};
}
pub fn deinit(self: *DataReader) void {
self.alloc.free(self.interface.buffer);
}
pub fn addFrag(self: *DataReader, frag_offset: u32, block: []u8) void {
self.frag_offset = frag_offset;
self.frag_block = block;
}
fn numBlocks(self: DataReader) usize {
var num = self.blocks.len;
if (self.frag_block != null) num += 1;
return num;
}
fn advanceBuffer(self: *DataReader) !void {
if (self.block_idx >= self.numBlocks())
return Reader.Error.EndOfStream;
errdefer self.interface.end = 0;
defer self.block_idx += 1;
self.interface.end = if (self.block_idx == self.numBlocks() - 1)
self.file_size % self.block_size
else
self.block_size;
// Fragment
if (self.block_idx == self.blocks.len) {
@memcpy(self.interface.buffer[0..self.interface.end], self.frag_block.?[self.frag_offset .. self.frag_offset + self.interface.end]);
self.interface.seek = 0;
return;
}
// Normal Block
const block = self.blocks[self.block_idx];
if (block.size == 0) {
self.interface.seek = 0;
self.sparse_block = true;
return;
} else {
self.sparse_block = false;
}
if (block.uncompressed) {
@memcpy(self.interface.buffer[0..self.interface.end], self.fil.map.memory[self.cur_offset .. self.cur_offset + self.interface.end]);
self.cur_offset += self.interface.end;
} else {
@branchHint(.likely);
_ = try self.decomp.Decompress(self.alloc, self.fil.map.memory[self.cur_offset .. self.cur_offset + block.size], self.interface.buffer[0..self.interface.end]);
self.cur_offset += block.size;
}
self.interface.seek = 0;
}
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
var data: *DataReader = @fieldParentPtr("interface", rdr);
if (rdr.seek == rdr.end)
data.advanceBuffer() catch |err| return switch (err) {
error.ReadFailed => error.ReadFailed,
error.EndOfStream => error.EndOfStream,
else => error.ReadFailed,
};
switch (limit) {
.nothing => return 0,
.unlimited => {
const wrote = if (data.sparse_block)
try wrt.splatByte(0, rdr.end - rdr.seek)
else
try wrt.write(rdr.buffer[rdr.seek..rdr.end]);
rdr.seek += wrote;
return wrote;
},
else => {
const to_read = @min(rdr.end - rdr.seek, @intFromEnum(limit));
const wrote = if (data.sparse_block)
try wrt.splatByte(0, to_read)
else
try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_read]);
rdr.seek += wrote;
return wrote;
},
}
}
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
var data: *DataReader = @fieldParentPtr("interface", rdr);
if (rdr.seek == rdr.end)
data.advanceBuffer() catch |err| return switch (err) {
error.ReadFailed => error.ReadFailed,
error.EndOfStream => error.EndOfStream,
else => error.ReadFailed,
};
switch (limit) {
.nothing => return 0,
.unlimited => {
const adv = rdr.end - rdr.seek;
rdr.seek = rdr.end;
return adv;
},
else => {
const adv = @min(rdr.end - rdr.seek, @intFromEnum(limit));
rdr.seek += adv;
return adv;
},
}
}
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
var data: *DataReader = @fieldParentPtr("interface", rdr);
if (rdr.seek == rdr.end)
data.advanceBuffer() catch |err| return switch (err) {
error.ReadFailed => error.ReadFailed,
error.EndOfStream => error.EndOfStream,
else => error.ReadFailed,
};
var wrote: usize = 0;
for (vec) |buf| {
if (rdr.seek == rdr.end) break;
const to_copy = @min(rdr.end - rdr.seek, buf.len);
if (data.sparse_block)
@memset(buf[0..to_copy], 0)
else
@memcpy(buf[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]);
rdr.seek += to_copy;
wrote += to_copy;
}
return wrote;
}
-15
View File
@@ -1,15 +0,0 @@
//! A decompression interface
const std = @import("std");
const Decompressor = @This();
pub const Error = std.Io.Reader.StreamError || std.mem.Allocator.Error;
/// The actual decompression function.
/// If the given decompressor is null, then the decompression should be done "stateless" without lasting allocations.
decomp_fn: *const fn (?*Decompressor, std.mem.Allocator, in: []u8, out: []u8) Error!usize,
pub fn Decompress(self: *Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
return self.decomp_fn(self, alloc, in, out);
}
-103
View File
@@ -1,103 +0,0 @@
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 Decompressor = @import("decompressor.zig");
const BlockHeader = packed struct(u16) {
size: u15,
uncompressed: bool,
};
const This = @This();
alloc: std.mem.Allocator,
rdr: *Reader,
decomp: *Decompressor,
cur_block_start: u32 = 0,
next_start_start: u32 = 0,
buf: [8192]u8 = undefined,
err: ?anyerror = null,
interface: Reader = .{
.buffer = &[0]u8{},
.end = 0,
.seek = 0,
.vtable = &.{
.stream = stream,
.discard = discard,
.readVec = readVec,
// TODO: Potentially add rebase so that we can guarentee that self.block_start & interface.seek is correct.
},
},
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *Decompressor) This {
return .{
.alloc = alloc,
.rdr = rdr,
.decomp = decomp,
};
}
fn advance(self: *This) !void {
self.interface.seek = 0;
var hdr: BlockHeader = undefined;
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little);
self.cur_block_start = self.next_start_start;
self.next_start_start += hdr.size;
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;
} else {
@branchHint(.likely);
var tmp_buf: [8192]u8 = undefined;
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
self.interface.end = try self.decomp.Decompress(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) Reader.Error!usize {
const self: *This = @fieldParentPtr("interface", rdr);
if (rdr.end == rdr.seek) self.advance() catch |err| {
self.err = err;
return error.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) Reader.Error!usize {
const self: *This = @fieldParentPtr("interface", rdr);
if (rdr.end == rdr.seek) self.advance() catch |err| {
self.err = err;
return 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], 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;
}
-25
View File
@@ -1,25 +0,0 @@
//! Miscellaneous utility functions.
const std = @import("std");
const Io = std.Io;
const Inode = @import("../inode.zig");
const Decompressor = @import("decompressor.zig");
const MetadataReader = @import("metadata.zig");
const OffsetFile = @import("offset_file.zig");
/// check is the path is referencing itself ("" or ".").
/// separators must be trimmed before calling this function for it to work properly.
pub fn pathIsSelf(path: []const u8) bool {
if (path.len == 0) return true;
if (path.len > 1) return false;
return path[0] == '.';
}
/// Creates an Inode from an Inode.Ref.
pub fn inodeFromRef(alloc: std.mem.Allocator, file: OffsetFile, decomp: *Decompressor, inode_start: u64, block_size: u32, ref: Inode.Ref) !Inode {
var rdr = file.readerAt(inode_start + ref.block_start);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(ref.block_offset);
return .read(alloc, &meta.interface, block_size);
}
-27
View File
@@ -1,27 +0,0 @@
//! A File where it's meaningful (to us) content starts at a given offset.
const std = @import("std");
const Io = std.Io;
const File = Io.File;
const Reader = Io.Reader;
const OffsetFile = @This();
map: Io.File.MemoryMap,
pub fn init(io: Io, fil: File, archive_size: u64, init_offset: u64) !OffsetFile {
return .{
.map = try fil.createMemoryMap(io, .{
.protection = .{ .read = true, .write = false, .execute = false },
.len = archive_size,
.offset = init_offset,
}),
};
}
pub fn deinit(self: *OffsetFile, io: Io) void {
self.map.destroy(io);
}
pub fn readerAt(self: OffsetFile, offset: u64) Reader {
return .fixed(self.map.memory[offset..]);
}
+141
View File
@@ -0,0 +1,141 @@
const std = @import("std");
const Io = std.Io;
const Decomp = @import("../decomp.zig");
const BlockSize = @import("../inode.zig").BlockSize;
const DataReader = @This();
alloc: std.mem.Allocator,
data: []u8,
decomp: Decomp.Fn,
block_size: u32,
size: u64,
blocks: []BlockSize,
frag: ?[]u8 = null,
idx: u32 = 0,
buf: []u8,
interface: Io.Reader = .{
.buffer = &[0]u8{},
.seek = 0,
.end = 0,
.vtable = &.{
.stream = stream,
.discard = discard,
.readVec = readVec,
},
},
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, block_size: u32, size: u64, blocks: []BlockSize) !DataReader {
return .{
.alloc = alloc,
.data = data,
.decomp = decomp,
.block_size = block_size,
.size = size,
.blocks = blocks,
.buf = if (blocks.len > 0) try alloc.alloc(u8, 1024 * 1024) else &[0]u8{},
};
}
pub fn deinit(self: DataReader) void {
self.alloc.free(self.buf);
}
pub fn addFrag(self: *DataReader, frag: []u8) void {
self.frag = frag;
}
fn advance(self: *DataReader) Io.Reader.Error!void {
if (self.idx > self.blocks.len) return error.EndOfStream;
defer self.idx += 1;
self.interface.seek = 0;
errdefer self.interface.end = 0;
if (self.idx == self.blocks.len) {
if (self.frag == null) return error.EndOfStream;
self.interface.buffer = self.frag.?;
self.interface.end = self.frag.?.len;
return;
}
self.interface.end = if (self.frag == null and self.idx == self.blocks.len - 1)
self.size % self.block_size
else
self.block_size;
const block = self.blocks[self.idx];
if (block.size == 0) {
@memset(self.buf[0..self.interface.end], 0);
self.interface.buffer = self.buf;
return;
}
if (block.uncompressed) {
self.interface.buffer = self.data;
self.data = self.data[self.interface.end..];
return;
}
_ = self.decomp(self.alloc, self.data[0..block.size], self.buf[0..self.interface.end]) catch return error.ReadFailed;
self.data = self.data[block.size..];
self.interface.buffer = self.buf;
return;
}
fn stream(rdr: *Io.Reader, wrt: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
if (rdr.seek >= rdr.end) {
var meta: *DataReader = @fieldParentPtr("interface", rdr);
try meta.advance();
}
if (limit == .nothing) return 0;
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
const wrote = try wrt.write(rdr.buffer[rdr.seek..][0..size]);
rdr.seek += wrote;
return wrote;
}
fn discard(rdr: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
if (rdr.seek >= rdr.end) {
var meta: *DataReader = @fieldParentPtr("interface", rdr);
try meta.advance();
}
if (limit == .nothing) return 0;
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
rdr.seek += size;
return size;
}
fn readVec(rdr: *Io.Reader, vec: [][]u8) Io.Reader.Error!usize {
if (rdr.seek >= rdr.end) {
var meta: *DataReader = @fieldParentPtr("interface", rdr);
try meta.advance();
}
var wrote: usize = 0;
for (vec) |s| {
const size = @min(rdr.end - rdr.seek, s.len);
@memcpy(s[0..size], rdr.buffer[rdr.seek..][0..size]);
wrote += size;
rdr.seek += size;
if (rdr.seek >= rdr.end) break;
}
return wrote;
}
+219
View File
@@ -0,0 +1,219 @@
const std = @import("std");
const Io = std.Io;
const Super = @import("../archive.zig").Super;
const Decomp = @import("../decomp.zig");
const Directory = @import("../dir.zig");
const FragCache = @import("../frag_cache.zig");
const Inode = @import("../inode.zig");
const Lookup = @import("../lookup.zig");
const Options = @import("../options.zig");
const DataReader = @import("data_reader.zig");
const MetadataReader = @import("meta.zig");
// TODO: Add verbose logging.
pub fn single(alloc: std.mem.Allocator, io: Io, data: []u8, super: Super, inode: Inode, filepath: []const u8, options: Options) !void {
const path = std.mem.trim(u8, filepath, "/");
var id_table: Lookup.Table(u16) = try .init(data, super.decomp_fn, super.id_table_start, super.id_count);
defer id_table.deinit(alloc);
var xattr_table: Lookup.Xattr = try .init(data, super.decomp_fn, super.xattr_table_start);
defer xattr_table.deinit(alloc);
var frag_cache: FragCache = try .init(data, super.decomp_fn, super.block_size, super.frag_table_start, super.frag_count);
defer frag_cache.deinit(alloc);
var pool: std.ArrayList(InodeAndPath) = try .initCapacity(alloc, 15);
pool.appendAssumeCapacity(.{
.inode = inode,
.path = path,
.root = true,
});
defer {
while (pool.pop()) |in|
in.deinit(alloc);
pool.deinit(alloc);
}
var dirs: std.ArrayList(InodeAndPath) = .empty;
defer {
while (dirs.pop()) |dir|
alloc.free(dir.path);
dirs.deinit(alloc);
}
var cwd = Io.Dir.cwd();
while (pool.pop()) |in| {
switch (in.inode.data) {
.dir => |d| {
try cwd.createDirPath(io, in.path);
if (d.size <= 3) {
defer if (in.path.len != path.len)
in.deinit(alloc);
var fil = try cwd.openFile(io, in.path, .{});
defer fil.close(io);
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, d.xattr_idx, fil, options);
continue;
}
var meta: MetadataReader = .init(alloc, data[d.start..], super.decomp_fn);
try meta.interface.discardAll(d.offset);
var dir: Directory = try .read(alloc, &meta.interface, d.size);
defer dir.deinit(alloc);
for (dir.entries) |entry| {
const new_inode: Inode = try .readLocation(alloc, data, entry.start, entry.offset, super);
const new_path = std.mem.concat(alloc, u8, &.{ in.path, "/", entry.name }) catch |err| {
new_inode.deinit(alloc);
return err;
};
try pool.append(alloc, .{
.inode = new_inode,
.path = new_path,
.root = false,
});
}
try dirs.append(alloc, in);
},
.file => |f| {
defer if (in.path.len != path.len)
in.deinit(alloc);
var atomic = try cwd.createFileAtomic(io, in.path, .{});
defer atomic.deinit(io);
var rdr: DataReader = try .init(alloc, data[f.start..], super.decomp_fn, super.block_size, f.size, f.blocks);
defer rdr.deinit();
if (f.frag_idx != 0xFFFFFFFF) {
const frag = try frag_cache.get(alloc, io, f.frag_idx);
rdr.addFrag(frag);
}
var buf: [1024 * 50]u8 = undefined; // TODO: find a resonable buffer size.
var writer = atomic.file.writer(io, &buf);
_ = try rdr.interface.streamRemaining(&writer.interface);
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, f.xattr_idx, atomic.file, options);
try atomic.link(io);
},
.symlink => |s| {
defer if (in.path.len != path.len)
in.deinit(alloc);
try cwd.symLink(io, s.target, in.path, .{});
},
.dev => |d| {
defer if (in.path.len != path.len)
in.deinit(alloc);
const mode: u32 = switch (in.inode.header.type) {
.char_dev, .ext_char_dev => std.os.linux.DT.CHR,
.block_dev, .ext_block_dev => std.os.linux.DT.BLK,
else => unreachable,
};
const sent_path = try alloc.dupeSentinel(u8, in.path, 0);
const res = std.os.linux.mknod(sent_path, mode, d.device);
alloc.free(sent_path);
if (res != 0)
return error.MkNodError;
var fil = try cwd.openFile(io, in.path, .{});
defer fil.close(io);
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, d.xattr_idx, fil, options);
},
.fifo => |f| {
defer if (in.path.len != path.len)
in.deinit(alloc);
const mode: u32 = switch (in.inode.header.type) {
.fifo, .ext_fifo => std.os.linux.DT.FIFO,
.socket, .ext_socket => std.os.linux.DT.SOCK,
else => unreachable,
};
const sent_path = try alloc.dupeSentinel(u8, in.path, 0);
const res = std.os.linux.mknod(sent_path, mode, 0);
alloc.free(sent_path);
if (res != 0)
return error.MkNodError;
var fil = try cwd.openFile(io, in.path, .{});
defer fil.close(io);
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, f.xattr_idx, fil, options);
},
}
}
while (dirs.pop()) |dir| {
defer if (dir.path.len != path.len)
alloc.free(dir.path);
var fil = try cwd.openFile(io, dir.path, .{});
defer fil.close(io);
try applyPermissions(alloc, io, &id_table, &xattr_table, dir.inode.header, dir.inode.data.dir.xattr_idx, fil, options);
}
}
pub fn multi(alloc: std.mem.Allocator, io: Io, data: []const u8, super: Super, inode: Inode, filepath: []const u8, options: Options) !void {
_ = alloc;
_ = io;
_ = data;
_ = super;
_ = inode;
_ = filepath;
_ = options;
return error.TODO;
}
// Utils
const InodeAndPath = struct {
inode: Inode,
path: []const u8,
root: bool,
fn deinit(self: InodeAndPath, alloc: std.mem.Allocator) void {
if (self.root) return;
self.inode.deinit(alloc);
alloc.free(self.path);
}
};
fn applyPermissions(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xattr_table: *Lookup.Xattr, header: Inode.Header, xattr_idx: ?u32, fil: Io.File, options: Options) !void {
if (!options.ignore_permissions) {
try fil.setPermissions(io, @enumFromInt(header.permission));
try fil.setTimestamps(io, .{ .modify_timestamp = .{ .new = .fromNanoseconds(@as(i96, @intCast(header.mod_time)) * std.time.ns_per_ms) } });
try fil.setOwner(io, try id_table.get(alloc, io, header.uid_idx), try id_table.get(alloc, io, header.gid_idx));
}
if (!options.ignore_xattr and xattr_idx != null) {
const xattrs = try xattr_table.get(alloc, io, xattr_idx.?);
defer {
for (xattrs) |kv|
kv.deinit(alloc);
alloc.free(xattrs);
}
for (xattrs) |kv| {
const res = std.os.linux.fsetxattr(fil.handle, kv.name, kv.value.ptr, kv.value.len, 0);
if (res != 0)
return error.FSetXattrError;
}
}
}
+109
View File
@@ -0,0 +1,109 @@
const std = @import("std");
const Io = std.Io;
const util = @import("util.zig");
const Decomp = @import("../decomp.zig");
const MetadataReader = @This();
alloc: std.mem.Allocator,
data: []u8,
decomp: Decomp.Fn,
decomp_block: [8192]u8 = undefined,
interface: Io.Reader = .{
.buffer = &[0]u8{},
.end = 0,
.seek = 0,
.vtable = &.{
.stream = stream,
.discard = discard,
.readVec = readVec,
},
},
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn) MetadataReader {
return .{
.alloc = alloc,
.data = data,
.decomp = decomp,
};
}
fn advance(self: *MetadataReader) Io.Reader.Error!void {
self.interface.seek = 0;
errdefer self.interface.end = 0;
const hdr = util.readValue(Header, self.data[0..2]);
const block = self.data[2..][0..hdr.size];
self.data = self.data[hdr.size + 2 ..];
if (hdr.uncompressed) {
self.interface.end = hdr.size;
self.interface.buffer = block;
return;
}
self.interface.end = self.decomp(self.alloc, block, &self.decomp_block) catch return error.ReadFailed;
self.interface.buffer = self.decomp_block[0..self.interface.end];
}
fn stream(rdr: *Io.Reader, wrt: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
if (rdr.seek >= rdr.end) {
var meta: *MetadataReader = @fieldParentPtr("interface", rdr);
try meta.advance();
}
if (limit == .nothing) return 0;
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
const wrote = try wrt.write(rdr.buffer[rdr.seek..][0..size]);
rdr.seek += wrote;
return wrote;
}
fn discard(rdr: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
if (rdr.seek >= rdr.end) {
var meta: *MetadataReader = @fieldParentPtr("interface", rdr);
try meta.advance();
}
if (limit == .nothing) return 0;
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
rdr.seek += size;
return size;
}
fn readVec(rdr: *Io.Reader, vec: [][]u8) Io.Reader.Error!usize {
if (rdr.seek >= rdr.end) {
var meta: *MetadataReader = @fieldParentPtr("interface", rdr);
try meta.advance();
}
var wrote: usize = 0;
for (vec) |s| {
const size = @min(rdr.end - rdr.seek, s.len);
@memcpy(s[0..size], rdr.buffer[rdr.seek..][0..size]);
wrote += size;
rdr.seek += size;
if (rdr.seek >= rdr.end) break;
}
return wrote;
}
// Types
const Header = packed struct(u16) {
size: u15,
uncompressed: bool,
};
+28
View File
@@ -0,0 +1,28 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn readValue(comptime T: type, bytes: []u8) T {
var res = std.mem.bytesToValue(T, bytes);
if (comptime builtin.cpu.arch.endian() != .little) {
switch (@typeInfo(T)) {
.@"enum" => res = std.mem.nativeToLittle(@typeInfo(T).@"enum".tag_type, res),
.@"struct" => |s| {
if (s.layout == .@"packed") {
res = @bitCast(std.mem.nativeToLittle(s.backing_integer.?, @bitCast(res)));
} else {
std.mem.byteSwapAllFields(T, &res);
}
},
.int => res = std.mem.nativeToLittle(T, res),
.array => |a| std.mem.byteSwapAllElements(a.child, res),
else => unreachable,
}
}
return res;
}
pub fn readValueRdr(comptime T: type, rdr: *std.Io.Reader) !T {
var tmp: [@sizeOf(T)]u8 = undefined;
try rdr.readSliceAll(&tmp);
return readValue(T, &tmp);
}
-292
View File
@@ -1,292 +0,0 @@
const std = @import("std");
const Io = std.Io;
const InodeRef = @import("inode.zig").Ref;
const LookupTable = @import("lookup_table.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const XattrCachedTable = @This();
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *Decompressor,
kv_start: u64,
table: LookupTable.CachedTable(TableValue),
value_cache: std.AutoHashMap(InodeRef, []const u8),
value_mut: Io.RwLock = .init,
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *Decompressor, xattr_start: u64) !XattrCachedTable {
const start: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[xattr_start .. xattr_start + 8]), .little);
const num: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[xattr_start + 8 .. xattr_start + 16]), .little);
return .{
.alloc = alloc,
.fil = fil,
.decomp = decomp,
.kv_start = start,
.table = .init(alloc, fil, decomp, xattr_start + 16, num),
.value_cache = .init(alloc),
};
}
pub fn deinit(self: *XattrCachedTable, io: Io) void {
self.value_mut.lockUncancelable(io);
self.table.deinit(io);
self.value_cache.deinit();
}
pub fn get(self: *XattrCachedTable, alloc: std.mem.Allocator, io: Io, idx: u32) ![]XattrSemiOwned {
const lookup = try self.table.get(io, idx);
var rdr = self.fil.readerAt(self.kv_start + lookup.ref.block_start);
var meta: MetadataReader = .init(alloc, &rdr, self.decomp);
try meta.interface.discardAll(lookup.ref.block_offset);
const out = try alloc.alloc(XattrSemiOwned, lookup.count);
errdefer alloc.free(out);
for (0..lookup.count) |i| {
var key_entry: KeyEntry = undefined;
try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little);
const key: [:0]u8 = switch (key_entry.type.namespace) {
.user => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 5);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[5 .. tmp.len - 1], .little);
@memcpy(tmp[0..5], "user.");
tmp[tmp.len - 1] = 0;
break :blk @ptrCast(tmp);
},
.trusted => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 8);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[8 .. tmp.len - 1], .little);
@memcpy(tmp[0..8], "trusted.");
tmp[tmp.len - 1] = 0;
break :blk @ptrCast(tmp);
},
.security => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 9);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[9 .. tmp.len - 1], .little);
@memcpy(tmp[0..9], "security.");
tmp[tmp.len - 1] = 0;
break :blk @ptrCast(tmp);
},
};
errdefer alloc.free(key);
if (key_entry.type.out_of_line) {
var value: ValueOutOfLineEntry = undefined;
try meta.interface.readSliceEndian(ValueOutOfLineEntry, @ptrCast(&value), .little);
out[i] = .{
.key = key,
.value = try self.valueAt(io, value.ref),
};
continue;
}
const val_ref: InodeRef = .{ .block_start = meta.cur_block_start, .block_offset = @truncate(meta.interface.seek) };
{
try self.value_mut.lockShared(io);
defer self.value_mut.unlockShared(io);
if (self.value_cache.contains(val_ref)) {
out[i] = .{
.key = key,
.value = try self.valueAt(io, val_ref),
};
continue;
}
}
try self.value_mut.lock(io);
defer self.value_mut.unlock(io);
if (self.value_cache.contains(val_ref)) {
out[i] = .{
.key = key,
.value = try self.valueAt(io, val_ref),
};
continue;
}
var val_size: u32 = undefined;
try meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const val = try self.alloc.alloc(u8, val_size);
errdefer alloc.free(val);
try meta.interface.readSliceEndian(u8, val, .little);
try self.value_cache.put(val_ref, val);
out[i] = .{
.key = key,
.value = val,
};
}
return out;
}
fn valueAt(self: *XattrCachedTable, io: Io, ref: InodeRef) ![]const u8 {
try self.value_mut.lock(io);
defer self.value_mut.unlock(io);
if (self.value_cache.contains(ref)) return self.value_cache.get(ref).?;
var rdr = self.fil.readerAt(self.kv_start + ref.block_start);
var meta: MetadataReader = .init(self.alloc, &rdr, self.decomp);
try meta.interface.discardAll(ref.block_offset);
var val_size: u32 = undefined;
try meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const val = try self.alloc.alloc(u8, val_size);
errdefer self.alloc.free(val);
try meta.interface.readSliceEndian(u8, val, .little);
try self.value_cache.put(ref, val);
return val;
}
// Types
/// An Xattr return value where the reciever only owns the key value.
pub const XattrSemiOwned = struct {
key: [:0]const u8,
value: []const u8,
pub fn deinit(self: XattrSemiOwned, alloc: std.mem.Allocator) void {
alloc.free(self.key);
}
};
/// An Xattr return value where the reciever owns both the key & value.
pub const XattrOwned = struct {
key: [:0]const u8,
value: []const u8,
pub fn deinit(self: XattrSemiOwned, alloc: std.mem.Allocator) void {
alloc.free(self.key);
alloc.free(self.value);
}
};
const TableValue = extern struct {
ref: InodeRef,
count: u32,
size: u32,
};
const KeyEntry = extern struct {
type: XattrPrefix,
name_size: u16,
};
const ValueOutOfLineEntry = extern struct {
_: u32,
ref: InodeRef,
};
const XattrPrefix = packed struct(u16) {
namespace: enum(u8) {
user,
trusted,
security,
fn prefixSize(self: @This()) u16 {
return switch (self) {
.user => 5,
.trusted => 8,
.security => 9,
};
}
},
out_of_line: bool,
_: u7,
};
// Stateless
pub fn statelessLookup(alloc: std.mem.Allocator, io: Io, decomp: *Decompressor, fil: OffsetFile, table_start: u64, idx: u16) ![]XattrOwned {
const kv_start: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[table_start .. table_start + 8]), .little);
const lookup = try LookupTable.lookupValue(TableValue, alloc, io, decomp, fil, table_start + 16, idx);
var rdr = fil.readerAt(kv_start + lookup.ref.block_start);
var meta: MetadataReader = .init(alloc, &rdr.interface, decomp);
try meta.interface.discardAll(lookup.ref.block_offset);
const out = try alloc.alloc(XattrOwned, lookup.count);
errdefer alloc.free(out);
for (0..lookup.count) |i| {
const key_entry: KeyEntry = undefined;
try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little);
const key = switch (key_entry.type.namespace) {
.user => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 5);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[5 .. tmp.len - 1], .little);
@memset(tmp[0..5], "user.");
break :blk tmp;
},
.trusted => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 8);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[8 .. tmp.len - 1], .little);
@memset(tmp[0..8], "trusted.");
break :blk tmp;
},
.security => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 9);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[9 .. tmp.len - 1], .little);
@memset(tmp[0..9], "security.");
break :blk tmp;
},
};
key[key.len - 1] = 0;
errdefer alloc.free(key);
if (key_entry.type.out_of_line) {
const value: ValueOutOfLineEntry = undefined;
try meta.interface.readSliceEndian(ValueOutOfLineEntry, @ptrCast(&value), .little);
var ool_rdr = fil.readerAt(kv_start + value.ref.block_start);
var ool_meta: MetadataReader = .init(alloc, &ool_rdr.interface, decomp);
try ool_meta.interface.discardAll(value.ref.block_offset);
var val_size: u32 = undefined;
try ool_meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little);
const val = try alloc.alloc(u8, val_size);
errdefer alloc.free(val);
try ool_meta.interface.readSliceEndian(u8, val, .little);
out[i] = .{
.key = key,
.value = val,
};
continue;
}
var val_size: u32 = undefined;
try meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little);
const val = try alloc.alloc(u8, val_size);
errdefer alloc.free(val);
try meta.interface.readSliceEndian(u8, val, .little);
out[i] = .{
.key = key,
.value = val,
};
}
return out;
}