Merge pull request #2 from CalebQ42/extract_expirement
Extract expirement
This commit is contained in:
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
This is my experiments to learn Zig. Might amount to something. Might not.
|
This is my experiments to learn Zig. Might amount to something. Might not.
|
||||||
|
|
||||||
|
A library and application to decompress or view squashfs archives.
|
||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
Kinda works as a library, but currently has known memory leaks. `unsquashfs` is missing a lot of features (and will probably never match the official unsquashfs). Extraction is stupidly slow and uses too many resources.
|
Overall works, but currently is completely single threaded and is missing some features. Extraction is slow. Only properly work on Linux, any other OSes probably won't work fully.
|
||||||
|
|
||||||
|
## Build options
|
||||||
|
|
||||||
|
> `-Duse_c_libs`
|
||||||
|
|
||||||
|
Instead of using Zig's standard library for decompression
|
||||||
|
|
||||||
|
> `Dversion`
|
||||||
|
|
||||||
|
Sets the version of `unsquashfs` shown when `--version` is passed.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
Most features are present except for the following:
|
||||||
|
|
||||||
|
* mod_time is not set on extraction
|
||||||
|
* xattrs are not applied on extraction
|
||||||
|
* Only zstd c library is implemented (all others result in error.TODO).
|
||||||
|
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _really_ plan on spending the time to find and validate a library since neither is popular.
|
||||||
|
|||||||
@@ -1,27 +1,42 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
|
||||||
|
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
||||||
|
|
||||||
|
const zig_squashfs_options = b.addOptions();
|
||||||
|
zig_squashfs_options.addOption(bool, "use_c_libs", use_c_libs_option orelse false);
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||||
const linkage: std.builtin.LinkMode = .static; // TODO: Add argument to set link mode.
|
|
||||||
const mod = b.addModule("zig_squashfs", .{
|
const mod = b.addModule("zig_squashfs", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
// .imports = &.{},
|
.link_libc = if (use_c_libs_option == true) true else false,
|
||||||
});
|
});
|
||||||
const exe = b.addExecutable(.{
|
mod.addOptions("config", zig_squashfs_options);
|
||||||
.name = "unsquashfs",
|
if (use_c_libs_option == true)
|
||||||
.linkage = linkage,
|
mod.linkSystemLibrary("zstd", .{});
|
||||||
.root_module = b.createModule(.{
|
|
||||||
|
const unsquashfs_options = b.addOptions();
|
||||||
|
unsquashfs_options.addOption(std.SemanticVersion, "version_string", try std.SemanticVersion.parse(version_string_option orelse "0.0.0-testing"));
|
||||||
|
|
||||||
|
var exe_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.link_libc = if (use_c_libs_option == true) true else false,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "zig_squashfs", .module = mod },
|
.{ .name = "zig_squashfs", .module = mod },
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
exe_mod.addOptions("config", unsquashfs_options);
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = exe_mod,
|
||||||
|
});
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
zig test -lc -lzstd src/test.zig
|
||||||
+33
-11
@@ -4,7 +4,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const File = std.fs.File;
|
||||||
|
|
||||||
const DecompMgr = @import("decomp.zig");
|
const Decomp = @import("decomp.zig");
|
||||||
const ExtractionOptions = @import("options.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const InodeRef = Inode.Ref;
|
const InodeRef = Inode.Ref;
|
||||||
@@ -40,7 +40,7 @@ super: Superblock,
|
|||||||
|
|
||||||
setup: bool = false,
|
setup: bool = false,
|
||||||
|
|
||||||
decomp: DecompMgr = undefined,
|
decomp: Decomp.DecompFn,
|
||||||
|
|
||||||
frag_table: Table(FragEntry) = undefined,
|
frag_table: Table(FragEntry) = undefined,
|
||||||
id_table: Table(u16) = undefined,
|
id_table: Table(u16) = undefined,
|
||||||
@@ -72,6 +72,13 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u
|
|||||||
// .fixed_buf = fixed_buf,
|
// .fixed_buf = fixed_buf,
|
||||||
.thread_count = threads,
|
.thread_count = threads,
|
||||||
.fil = .init(fil, offset),
|
.fil = .init(fil, offset),
|
||||||
|
.decomp = switch (super.compression) {
|
||||||
|
.gzip => Decomp.gzipDecompress,
|
||||||
|
.lzma => Decomp.lzmaDecompress,
|
||||||
|
.xz => Decomp.xzDecompress,
|
||||||
|
.zstd => Decomp.zstdDecompress,
|
||||||
|
else => return error.UnsupportedCompressionType,
|
||||||
|
},
|
||||||
|
|
||||||
.super = super,
|
.super = super,
|
||||||
};
|
};
|
||||||
@@ -79,7 +86,6 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u
|
|||||||
pub fn deinit(self: *Archive) void {
|
pub fn deinit(self: *Archive) void {
|
||||||
// self.parent_alloc.free(self.fixed_buf);
|
// self.parent_alloc.free(self.fixed_buf);
|
||||||
if (self.setup) {
|
if (self.setup) {
|
||||||
self.decomp.deinit();
|
|
||||||
self.frag_table.deinit();
|
self.frag_table.deinit();
|
||||||
self.export_table.deinit();
|
self.export_table.deinit();
|
||||||
self.id_table.deinit();
|
self.id_table.deinit();
|
||||||
@@ -92,10 +98,9 @@ pub fn allocator(self: *Archive) std.mem.Allocator {
|
|||||||
|
|
||||||
fn setupValues(self: *Archive) !void {
|
fn setupValues(self: *Archive) !void {
|
||||||
const alloc = self.allocator();
|
const alloc = self.allocator();
|
||||||
self.decomp = try .init(alloc, self.super.compression, self.super.block_size, self.thread_count);
|
self.frag_table = try .init(alloc, self.fil, self.decomp, self.super.frag_start, self.super.frag_count);
|
||||||
self.frag_table = try .init(alloc, self.fil, &self.decomp, self.super.frag_start, self.super.frag_count);
|
self.id_table = try .init(alloc, self.fil, self.decomp, self.super.id_start, self.super.id_count);
|
||||||
self.id_table = try .init(alloc, self.fil, &self.decomp, self.super.id_start, self.super.id_count);
|
self.export_table = try .init(alloc, self.fil, self.decomp, self.super.export_start, self.super.inode_count);
|
||||||
self.export_table = try .init(alloc, self.fil, &self.decomp, self.super.export_start, self.super.inode_count);
|
|
||||||
self.setup = true;
|
self.setup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +126,7 @@ pub fn inode(self: *Archive, num: u32) !Inode {
|
|||||||
pub fn root(self: *Archive) !SfsFile {
|
pub fn root(self: *Archive) !SfsFile {
|
||||||
if (!self.setup) try self.setupValues();
|
if (!self.setup) try self.setupValues();
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
|
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
||||||
return .init(self, in, "");
|
return .init(self, in, "");
|
||||||
@@ -136,7 +141,24 @@ pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
|||||||
|
|
||||||
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
if (!self.setup) try self.setupValues();
|
if (!self.setup) try self.setupValues();
|
||||||
var root_fil = try self.root();
|
var alloc = self.allocator();
|
||||||
defer root_fil.deinit();
|
var ext_path: []u8 = undefined;
|
||||||
return root_fil.extract(path, options);
|
if (std.fs.cwd().statFile(path)) |stat| {
|
||||||
|
if (stat.kind == .directory) {
|
||||||
|
ext_path = @constCast(path);
|
||||||
|
} else return error.ExtractionPathExists;
|
||||||
|
} else |err| {
|
||||||
|
if (err == error.FileNotFound) {
|
||||||
|
ext_path = @constCast(path);
|
||||||
|
} else {
|
||||||
|
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
||||||
|
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
||||||
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
|
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
||||||
|
try in.extractTo(self, ext_path, options);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Writer = std.Io.Writer;
|
const Writer = std.Io.Writer;
|
||||||
|
|
||||||
|
const config = @import("config");
|
||||||
const squashfs = @import("zig_squashfs");
|
const squashfs = @import("zig_squashfs");
|
||||||
|
|
||||||
//TODO: Add more options
|
//TODO: Add more options
|
||||||
@@ -8,8 +9,11 @@ const help_mgs =
|
|||||||
\\Usage: unsquashfs [options] <archive>
|
\\Usage: unsquashfs [options] <archive>
|
||||||
\\
|
\\
|
||||||
\\Options:
|
\\Options:
|
||||||
\\ -o <offset> Start reading the archive at the given offset.
|
|
||||||
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
||||||
|
\\
|
||||||
|
\\ -o <offset> Start reading the archive at the given offset.
|
||||||
|
\\
|
||||||
|
\\ --version Display the version
|
||||||
;
|
;
|
||||||
|
|
||||||
const errors = error{InvalidArguments};
|
const errors = error{InvalidArguments};
|
||||||
@@ -24,6 +28,10 @@ pub fn main() !void {
|
|||||||
var out = stdout.writer(&[0]u8{});
|
var out = stdout.writer(&[0]u8{});
|
||||||
defer out.interface.flush() catch {};
|
defer out.interface.flush() catch {};
|
||||||
try handleArgs(alloc, &out.interface);
|
try handleArgs(alloc, &out.interface);
|
||||||
|
if (archive.len == 0) {
|
||||||
|
try out.interface.print("You must provide a squashfs archive\n", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, try std.Thread.getCpuCount(), 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, try std.Thread.getCpuCount(), 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||||
@@ -34,6 +42,7 @@ pub fn main() !void {
|
|||||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
||||||
var args = try std.process.argsWithAllocator(alloc);
|
var args = try std.process.argsWithAllocator(alloc);
|
||||||
defer args.deinit();
|
defer args.deinit();
|
||||||
|
_ = args.next(); // args[0] is the application launch command.
|
||||||
while (args.next()) |arg| {
|
while (args.next()) |arg| {
|
||||||
if (std.mem.eql(u8, arg, "-o")) {
|
if (std.mem.eql(u8, arg, "-o")) {
|
||||||
const nxt = args.next();
|
const nxt = args.next();
|
||||||
@@ -45,6 +54,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
try out.print("-o must be followed by a number\n", .{});
|
try out.print("-o must be followed by a number\n", .{});
|
||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
};
|
};
|
||||||
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-d")) {
|
} else if (std.mem.eql(u8, arg, "-d")) {
|
||||||
const nxt = args.next();
|
const nxt = args.next();
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
if (nxt == null or nxt.?.len == 0) {
|
||||||
@@ -52,6 +62,13 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
}
|
}
|
||||||
extLoc = nxt.?;
|
extLoc = nxt.?;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "--version")) {
|
||||||
|
_ = try out.write("v");
|
||||||
|
try config.version_string.format(out);
|
||||||
|
_ = try out.write("\n");
|
||||||
|
std.process.cleanExit();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (archive.len > 0) {
|
if (archive.len > 0) {
|
||||||
try out.print("you can only provide one file at a time\n", .{});
|
try out.print("you can only provide one file at a time\n", .{});
|
||||||
|
|||||||
+137
-196
@@ -1,21 +1,15 @@
|
|||||||
//! Decompression manager. Can decompress either from an Io.Reader or from a byte slice.
|
//! Implementations for decompression.
|
||||||
|
//! TODO: change to vtable interface to allow for shared decompressors for better performance/resource usage.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const compress = std.compress;
|
|
||||||
const Reader = std.Io.Reader;
|
const Reader = std.Io.Reader;
|
||||||
const Thread = std.Thread;
|
const builtin = @import("builtin");
|
||||||
const Futex = Thread.Futex;
|
|
||||||
const Mutex = Thread.Mutex;
|
|
||||||
const Condition = Thread.Condition;
|
|
||||||
const Node = std.DoublyLinkedList.Node;
|
|
||||||
|
|
||||||
const Atomic = std.atomic.Value(u32);
|
const config = if (builtin.is_test) .{ .use_c_libs = true } else @import("config");
|
||||||
|
|
||||||
const DecompError = error{
|
const c = @cImport({
|
||||||
ThreadClosed,
|
@cInclude("zstd.h");
|
||||||
LzoUnsupported,
|
});
|
||||||
Lz4Unsupported,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const CompressionType = enum(u16) {
|
pub const CompressionType = enum(u16) {
|
||||||
gzip = 1,
|
gzip = 1,
|
||||||
@@ -26,193 +20,140 @@ pub const CompressionType = enum(u16) {
|
|||||||
zstd,
|
zstd,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DecompThread = struct {
|
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
||||||
mgr: *DecompMgr,
|
|
||||||
|
|
||||||
/// Current thread status & signal value via Futex.
|
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
||||||
/// 0 - Unstarted, 1 - Waiting, 2 - Working, 3 - Closed,
|
|
||||||
status: Atomic = .{ .raw = 0 },
|
|
||||||
thr: Thread = undefined,
|
|
||||||
node: Node = .{},
|
|
||||||
buf: []u8,
|
|
||||||
|
|
||||||
dat: []u8 = &[0]u8{},
|
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
rdr: ?*Reader = null,
|
var rdr: Reader = .fixed(in);
|
||||||
res: []u8 = &[0]u8{},
|
const buf = try alloc.alloc(u8, out.len);
|
||||||
res_size: anyerror!usize = 0,
|
defer alloc.free(buf);
|
||||||
|
var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, buf);
|
||||||
|
return decomp.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
|
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
|
_ = alloc;
|
||||||
|
_ = in;
|
||||||
|
_ = out;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(mgr: *DecompMgr) !DecompThread {
|
pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma;
|
||||||
return .{
|
|
||||||
.mgr = mgr,
|
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
.buf = switch (mgr.comp_type) {
|
var rdr: Reader = .fixed(in);
|
||||||
.gzip => try mgr.alloc.alloc(u8, compress.flate.max_window_len),
|
var decomp = try std.compress.lzma.decompress(alloc, rdr.adaptToOldInterface());
|
||||||
.zstd => try mgr.alloc.alloc(u8, compress.zstd.default_window_len + compress.zstd.block_size_max),
|
return decomp.read(out);
|
||||||
.lzma, .xz => &[0]u8{},
|
}
|
||||||
else => unreachable,
|
fn cLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
},
|
_ = alloc;
|
||||||
|
_ = in;
|
||||||
|
_ = out;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const xzDecompress = if (config.use_c_libs) cXz else zigXz;
|
||||||
|
|
||||||
|
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
var decomp = try std.compress.xz.decompress(alloc, rdr.adaptToOldInterface());
|
||||||
|
return decomp.read(out);
|
||||||
|
}
|
||||||
|
fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
|
_ = alloc;
|
||||||
|
_ = in;
|
||||||
|
_ = out;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const zstdDecompress = if (config.use_c_libs) cZstd else zigZstd;
|
||||||
|
|
||||||
|
pub fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
const buf = try alloc.alloc(u8, std.compress.zstd.default_window_len + std.compress.zstd.block_size_max);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
var decomp = std.compress.zstd.Decompress.init(&rdr, buf, .{});
|
||||||
|
return decomp.reader.readSliceShort(out) catch |err| {
|
||||||
|
return decomp.err orelse err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn cZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
|
_ = alloc;
|
||||||
|
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
|
||||||
|
if (c.ZSTD_isError(res) == 0) return res;
|
||||||
|
return switch (c.ZSTD_getErrorCode(res)) {
|
||||||
|
c.ZSTD_error_prefix_unknown => cZstdError.PrefixUnknown,
|
||||||
|
c.ZSTD_error_version_unsupported => cZstdError.VersionUnsupported,
|
||||||
|
c.ZSTD_error_frameParameter_unsupported => cZstdError.FrameParameterUnsupported,
|
||||||
|
c.ZSTD_error_frameParameter_windowTooLarge => cZstdError.FrameParameterWindowTooLarge,
|
||||||
|
c.ZSTD_error_corruption_detected => cZstdError.CorruptionDetected,
|
||||||
|
c.ZSTD_error_checksum_wrong => cZstdError.ChecksumWrong,
|
||||||
|
c.ZSTD_error_literals_headerWrong => cZstdError.LiteralsHeaderWrong,
|
||||||
|
c.ZSTD_error_dictionary_corrupted => cZstdError.DictionaryCorrupted,
|
||||||
|
c.ZSTD_error_dictionary_wrong => cZstdError.DictionaryWrong,
|
||||||
|
c.ZSTD_error_dictionaryCreation_failed => cZstdError.DictionaryCreationFailed,
|
||||||
|
c.ZSTD_error_parameter_unsupported => cZstdError.ParameterUnsupported,
|
||||||
|
c.ZSTD_error_parameter_combination_unsupported => cZstdError.ParameterCombinationUnsupported,
|
||||||
|
c.ZSTD_error_parameter_outOfBound => cZstdError.ParameterOutOfBound,
|
||||||
|
c.ZSTD_error_tableLog_tooLarge => cZstdError.TableLogTooLarge,
|
||||||
|
c.ZSTD_error_maxSymbolValue_tooLarge => cZstdError.MaxSymbolValueTooLarge,
|
||||||
|
c.ZSTD_error_maxSymbolValue_tooSmall => cZstdError.MaxSymbolValueTooSmall,
|
||||||
|
c.ZSTD_error_cannotProduce_uncompressedBlock => cZstdError.CannotProduceUncompressedBlock,
|
||||||
|
c.ZSTD_error_stabilityCondition_notRespected => cZstdError.StabilityConditionNotRespected,
|
||||||
|
c.ZSTD_error_stage_wrong => cZstdError.StageWrong,
|
||||||
|
c.ZSTD_error_init_missing => cZstdError.InitMissing,
|
||||||
|
c.ZSTD_error_memory_allocation => cZstdError.MemoryAllocation,
|
||||||
|
c.ZSTD_error_workSpace_tooSmall => cZstdError.WorkSpaceTooSmall,
|
||||||
|
c.ZSTD_error_dstSize_tooSmall => cZstdError.DstSizeTooSmall,
|
||||||
|
c.ZSTD_error_srcSize_wrong => cZstdError.SrcSizeWrong,
|
||||||
|
c.ZSTD_error_dstBuffer_null => cZstdError.DstBufferNull,
|
||||||
|
c.ZSTD_error_noForwardProgress_destFull => cZstdError.NoForwardProgressDestFull,
|
||||||
|
c.ZSTD_error_noForwardProgress_inputEmpty => cZstdError.NoForwardProgressInputEmpty,
|
||||||
|
c.ZSTD_error_frameIndex_tooLarge => cZstdError.FrameIndexTooLarge,
|
||||||
|
c.ZSTD_error_seekableIO => cZstdError.SeekableIo,
|
||||||
|
c.ZSTD_error_dstBuffer_wrong => cZstdError.DstBufferWrong,
|
||||||
|
c.ZSTD_error_srcBuffer_wrong => cZstdError.SrcBufferWrong,
|
||||||
|
c.ZSTD_error_sequenceProducer_failed => cZstdError.SequenceProducerFailed,
|
||||||
|
c.ZSTD_error_externalSequences_invalid => cZstdError.ExternalSequencesInvalid,
|
||||||
|
c.ZSTD_error_maxCode => cZstdError.MaxCode,
|
||||||
|
else => cZstdError.Generic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(self: *DecompThread) void {
|
pub const cZstdError = error{
|
||||||
if (self.status.raw == 0) return;
|
Generic,
|
||||||
while (self.status.raw == 2) Futex.wait(&self.status, 2);
|
PrefixUnknown,
|
||||||
self.status.store(3, .release);
|
VersionUnsupported,
|
||||||
Futex.wake(&self.status, 1);
|
FrameParameterUnsupported,
|
||||||
self.thr.join();
|
FrameParameterWindowTooLarge,
|
||||||
self.mgr.alloc.free(self.buf);
|
CorruptionDetected,
|
||||||
}
|
ChecksumWrong,
|
||||||
|
LiteralsHeaderWrong,
|
||||||
pub fn submitData(self: *DecompThread, dat: []u8, res: []u8) anyerror!usize {
|
DictionaryCorrupted,
|
||||||
if (self.status.raw == 3) return DecompError.ThreadClosed;
|
DictionaryWrong,
|
||||||
if (self.status.raw == 0) {
|
DictionaryCreationFailed,
|
||||||
self.thr = try .spawn(.{}, thread, .{self});
|
ParameterUnsupported,
|
||||||
}
|
ParameterCombinationUnsupported,
|
||||||
self.dat = dat;
|
ParameterOutOfBound,
|
||||||
defer self.dat = &[0]u8{};
|
TableLogTooLarge,
|
||||||
self.res = res;
|
MaxSymbolValueTooLarge,
|
||||||
self.status.raw = 2;
|
MaxSymbolValueTooSmall,
|
||||||
while (self.status.raw == 2) Futex.wait(&self.status, 2);
|
CannotProduceUncompressedBlock,
|
||||||
return self.res_size;
|
StabilityConditionNotRespected,
|
||||||
}
|
StageWrong,
|
||||||
pub fn submitReader(self: *DecompThread, rdr: *Reader, res: []u8) anyerror!usize {
|
InitMissing,
|
||||||
if (self.status.raw == 3) return DecompError.ThreadClosed;
|
MemoryAllocation,
|
||||||
if (self.status.raw == 0) {
|
WorkSpaceTooSmall,
|
||||||
self.thr = try .spawn(.{}, thread, .{self});
|
DstSizeTooSmall,
|
||||||
}
|
SrcSizeWrong,
|
||||||
self.rdr = rdr;
|
DstBufferNull,
|
||||||
defer self.rdr = null;
|
NoForwardProgressDestFull,
|
||||||
self.res = res;
|
NoForwardProgressInputEmpty,
|
||||||
self.status.store(2, .release);
|
FrameIndexTooLarge,
|
||||||
Futex.wake(&self.status, 1);
|
SeekableIo,
|
||||||
while (self.status.raw == 2) Futex.wait(&self.status, 2);
|
DstBufferWrong,
|
||||||
return self.res_size;
|
SrcBufferWrong,
|
||||||
}
|
SequenceProducerFailed,
|
||||||
|
ExternalSequencesInvalid,
|
||||||
pub fn thread(self: *DecompThread) void {
|
MaxCode,
|
||||||
const comp_type = self.mgr.comp_type;
|
|
||||||
while (self.status.raw != 3) {
|
|
||||||
while (self.status.raw == 1) Futex.wait(&self.status, 1);
|
|
||||||
if (self.status.raw == 3) return;
|
|
||||||
var dat_rdr: Reader = .fixed(self.dat);
|
|
||||||
var rdr: *Reader = if (self.rdr != null) self.rdr.? else &dat_rdr;
|
|
||||||
self.res_size = blk: switch (comp_type) {
|
|
||||||
.gzip => {
|
|
||||||
var decomp_rdr = compress.flate.Decompress.init(rdr, .zlib, self.buf);
|
|
||||||
break :blk decomp_rdr.reader.readSliceShort(self.res) catch |err| {
|
|
||||||
break :blk decomp_rdr.err orelse err;
|
|
||||||
};
|
};
|
||||||
},
|
|
||||||
.lzma => {
|
|
||||||
var decomp_rdr = compress.lzma.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| {
|
|
||||||
break :blk err;
|
|
||||||
};
|
|
||||||
break :blk decomp_rdr.read(self.res);
|
|
||||||
},
|
|
||||||
.xz => {
|
|
||||||
var decomp_rdr = compress.xz.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| {
|
|
||||||
break :blk err;
|
|
||||||
};
|
|
||||||
break :blk decomp_rdr.read(self.res);
|
|
||||||
},
|
|
||||||
.zstd => {
|
|
||||||
var decomp_rdr = compress.zstd.Decompress.init(rdr, self.buf, .{});
|
|
||||||
break :blk decomp_rdr.reader.readSliceShort(self.res) catch |err| {
|
|
||||||
break :blk decomp_rdr.err orelse err;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
const orig = self.status.swap(1, .release);
|
|
||||||
Futex.wake(&self.status, 1);
|
|
||||||
if (orig == 3) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const DecompMgr = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
comp_type: CompressionType,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
threads: []DecompThread,
|
|
||||||
queue: std.DoublyLinkedList = .{},
|
|
||||||
mut: Mutex = .{},
|
|
||||||
cond: Condition = .{},
|
|
||||||
to_start: usize,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, comp_type: CompressionType, block_size: u32, threads: usize) !DecompMgr {
|
|
||||||
return switch (comp_type) {
|
|
||||||
.lzo => DecompError.LzoUnsupported,
|
|
||||||
.lz4 => DecompError.Lz4Unsupported,
|
|
||||||
else => .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.comp_type = comp_type,
|
|
||||||
.block_size = block_size,
|
|
||||||
.threads = try alloc.alloc(DecompThread, threads),
|
|
||||||
.to_start = threads,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: DecompMgr) void {
|
|
||||||
for (self.threads[self.to_start..]) |*t| {
|
|
||||||
t.close();
|
|
||||||
}
|
|
||||||
self.alloc.free(self.threads);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decompSlice(self: *DecompMgr, dat: []u8, res: []u8) !usize {
|
|
||||||
self.mut.lock();
|
|
||||||
var thr: *DecompThread = undefined;
|
|
||||||
var node = self.queue.popFirst();
|
|
||||||
if (self.node != null) {
|
|
||||||
self.mut.unlock();
|
|
||||||
thr = @fieldParentPtr("node", node.?);
|
|
||||||
} else blk: {
|
|
||||||
defer self.mut.unlock();
|
|
||||||
if (self.to_start > 0) {
|
|
||||||
self.threads[self.to_start - 1] = .init(self);
|
|
||||||
thr = &self.threads[self.to_start - 1];
|
|
||||||
self.to_start -= 1;
|
|
||||||
break :blk;
|
|
||||||
}
|
|
||||||
while (node == null) {
|
|
||||||
self.cond.wait(&self.mut);
|
|
||||||
node = self.queue.popFirst();
|
|
||||||
}
|
|
||||||
thr = @fieldParentPtr("node", node.?);
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
self.queue.append(&thr.node);
|
|
||||||
self.cond.signal();
|
|
||||||
}
|
|
||||||
return thr.submitData(dat, res);
|
|
||||||
}
|
|
||||||
pub fn decompReader(self: *DecompMgr, rdr: *Reader, res: []u8) !usize {
|
|
||||||
self.mut.lock();
|
|
||||||
var thr: *DecompThread = undefined;
|
|
||||||
var node = self.queue.popFirst();
|
|
||||||
if (node != null) {
|
|
||||||
self.mut.unlock();
|
|
||||||
thr = @fieldParentPtr("node", node.?);
|
|
||||||
} else blk: {
|
|
||||||
defer self.mut.unlock();
|
|
||||||
if (self.to_start > 0) {
|
|
||||||
self.threads[self.to_start - 1] = try .init(self);
|
|
||||||
thr = &self.threads[self.to_start - 1];
|
|
||||||
self.to_start -= 1;
|
|
||||||
break :blk;
|
|
||||||
}
|
|
||||||
while (node == null) {
|
|
||||||
self.cond.wait(&self.mut);
|
|
||||||
node = self.queue.popFirst();
|
|
||||||
}
|
|
||||||
thr = @fieldParentPtr("node", node.?);
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
self.queue.append(&thr.node);
|
|
||||||
self.cond.signal();
|
|
||||||
}
|
|
||||||
return thr.submitReader(rdr, res);
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-7
@@ -30,19 +30,16 @@ pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry {
|
|||||||
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
|
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
|
||||||
var hdr: Header = undefined;
|
var hdr: Header = undefined;
|
||||||
var raw: RawEntry = undefined;
|
var raw: RawEntry = undefined;
|
||||||
var out: std.ArrayList(Entry) = .empty;
|
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 100); // Start out with a decent capacity instead of needing to allocate per header.
|
||||||
errdefer {
|
errdefer out.deinit(alloc);
|
||||||
for (out.items) |i|
|
|
||||||
i.deinit(alloc);
|
|
||||||
out.deinit(alloc);
|
|
||||||
}
|
|
||||||
while (cur_red < size) {
|
while (cur_red < size) {
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
cur_red += @sizeOf(Header);
|
cur_red += @sizeOf(Header);
|
||||||
try out.ensureUnusedCapacity(alloc, hdr.num + 1);
|
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||||
for (0..hdr.count + 1) |_| {
|
for (0..hdr.count + 1) |_| {
|
||||||
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
||||||
const name = try alloc.alloc(u8, raw.name_size + 1);
|
const name = try alloc.alloc(u8, raw.name_size + 1);
|
||||||
|
errdefer alloc.free(name);
|
||||||
try rdr.readSliceEndian(u8, name, .little);
|
try rdr.readSliceEndian(u8, name, .little);
|
||||||
const val = out.addOneAssumeCapacity();
|
const val = out.addOneAssumeCapacity();
|
||||||
val.* = .{
|
val.* = .{
|
||||||
|
|||||||
+5
-299
@@ -42,7 +42,7 @@ pub fn init(archive: *Archive, inode: Inode, name: []const u8) !SfsFile {
|
|||||||
}
|
}
|
||||||
pub fn fromEntry(archive: *Archive, entry: DirEntry) !SfsFile {
|
pub fn fromEntry(archive: *Archive, entry: DirEntry) !SfsFile {
|
||||||
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, &archive.decomp);
|
var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, archive.decomp);
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
||||||
errdefer inode.deinit(archive.allocator());
|
errdefer inode.deinit(archive.allocator());
|
||||||
@@ -56,28 +56,7 @@ pub fn deinit(self: SfsFile) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn getEntries(self: SfsFile) ![]DirEntry {
|
fn getEntries(self: SfsFile) ![]DirEntry {
|
||||||
if (!self.isDir()) return FileError.NotDirectory;
|
return self.inode.dirEntries(self.archive);
|
||||||
var block_start: u32 = undefined;
|
|
||||||
var block_offset: u16 = undefined;
|
|
||||||
var size: u32 = undefined;
|
|
||||||
switch (self.inode.data) {
|
|
||||||
.dir => |d| {
|
|
||||||
block_start = d.block_start;
|
|
||||||
block_offset = d.block_offset;
|
|
||||||
size = d.size;
|
|
||||||
},
|
|
||||||
.ext_dir => |d| {
|
|
||||||
block_start = d.block_start;
|
|
||||||
block_offset = d.block_offset;
|
|
||||||
size = d.size;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
var rdr = try self.archive.fil.readerAt(self.archive.super.dir_start + block_start, &[0]u8{});
|
|
||||||
const alloc = self.archive.allocator();
|
|
||||||
var meta: MetadataReader = .init(alloc, &rdr.interface, &self.archive.decomp);
|
|
||||||
try meta.interface.discardAll(block_offset);
|
|
||||||
return DirEntry.readDir(alloc, &meta.interface, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ownerUid(self: SfsFile) !u16 {
|
pub fn ownerUid(self: SfsFile) !u16 {
|
||||||
@@ -99,33 +78,7 @@ pub fn isRegular(self: SfsFile) bool {
|
|||||||
/// The returned DataReader will no longer work if the File's deinit function is called
|
/// The returned DataReader will no longer work if the File's deinit function is called
|
||||||
/// or, more specifically, it's inode's deinit function is called.
|
/// or, more specifically, it's inode's deinit function is called.
|
||||||
pub fn dataReader(self: SfsFile) !DataReader {
|
pub fn dataReader(self: SfsFile) !DataReader {
|
||||||
if (!self.isRegular()) return FileError.NotRegularFile;
|
return self.inode.dataReader(self.archive);
|
||||||
var frag_idx: u32 = undefined;
|
|
||||||
var frag_offset: u32 = undefined;
|
|
||||||
var size: u64 = undefined;
|
|
||||||
var blocks: []BlockSize = undefined;
|
|
||||||
var start: u64 = undefined;
|
|
||||||
switch (self.inode.data) {
|
|
||||||
.file => |f| {
|
|
||||||
frag_idx = f.frag_idx;
|
|
||||||
frag_offset = f.frag_block_offset;
|
|
||||||
size = f.size;
|
|
||||||
blocks = f.block_sizes;
|
|
||||||
start = f.block_start;
|
|
||||||
},
|
|
||||||
.ext_file => |f| {
|
|
||||||
frag_idx = f.frag_idx;
|
|
||||||
frag_offset = f.frag_block_offset;
|
|
||||||
size = f.size;
|
|
||||||
blocks = f.block_sizes;
|
|
||||||
start = f.block_start;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
var out: DataReader = .init(self.archive, blocks, start, size);
|
|
||||||
if (frag_idx != 0xFFFFFFFF)
|
|
||||||
out.addFragment(try self.archive.frag(frag_idx), frag_offset);
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isDir(self: SfsFile) bool {
|
pub fn isDir(self: SfsFile) bool {
|
||||||
@@ -245,255 +198,8 @@ pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !vo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
||||||
var pool: std.Thread.Pool = undefined;
|
//TODO: switch to threaded version.
|
||||||
try pool.init(.{ .allocator = alloc, .n_jobs = 16 });
|
return self.inode.extractTo(self.archive, path, options);
|
||||||
var wg: WaitGroup = .{};
|
|
||||||
defer pool.deinit();
|
|
||||||
var err: ?anyerror = null;
|
|
||||||
wg.start();
|
|
||||||
self.extractReal(ext_path, options, &pool, &wg, &err, null);
|
|
||||||
wg.wait();
|
|
||||||
if (err != null) return err.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ParentInfo = struct {
|
|
||||||
sfs_fil: SfsFile,
|
|
||||||
path: []const u8,
|
|
||||||
mut: *Mutex,
|
|
||||||
dir_wg: *WaitGroup,
|
|
||||||
parent_wg: *WaitGroup,
|
|
||||||
options: ExtractionOptions,
|
|
||||||
err: *?anyerror,
|
|
||||||
|
|
||||||
fn finish(self: *const ParentInfo) void {
|
|
||||||
self.mut.lock();
|
|
||||||
if (!self.dir_wg.isDone()) {
|
|
||||||
self.mut.unlock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mut.unlock();
|
|
||||||
std.debug.print("finishing dir {}: {s}\n", .{ self.sfs_fil.inode.hdr.num, self.sfs_fil.name });
|
|
||||||
self.sfs_fil.archive.allocator().destroy(self.mut);
|
|
||||||
self.sfs_fil.archive.allocator().destroy(self.dir_wg);
|
|
||||||
defer self.parent_wg.finish();
|
|
||||||
var fil = std.fs.cwd().openFile(self.path, .{}) catch |err| {
|
|
||||||
std.log.err("Error opening folder {s} to set permissions: {}\n", .{ self.path, err });
|
|
||||||
self.err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer fil.close();
|
|
||||||
self.sfs_fil.setPerm(fil, self.options) catch |err| {
|
|
||||||
std.log.err("Error setting permissions to {s}: {}\n", .{ self.path, err });
|
|
||||||
self.err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn extractReal(self: SfsFile, path: []const u8, options: ExtractionOptions, pol: *std.Thread.Pool, wg: *WaitGroup, out_err: *?anyerror, parent: ?ParentInfo) void {
|
|
||||||
std.log.info("Extracting {s} (inode {}) to {s}\n", .{ self.name, self.inode.hdr.num, path });
|
|
||||||
defer {
|
|
||||||
if (parent != null) {
|
|
||||||
parent.?.finish();
|
|
||||||
self.archive.allocator().free(path);
|
|
||||||
self.deinit();
|
|
||||||
} else {
|
|
||||||
wg.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (out_err.* != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (self.inode.hdr.inode_type) {
|
|
||||||
.file, .ext_file => {
|
|
||||||
var fil = std.fs.cwd().createFile(path, .{}) catch |err| {
|
|
||||||
std.log.err("Error creating {s}: {}\n", .{ path, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer fil.close();
|
|
||||||
var dat_rdr = self.dataReader() catch |err| {
|
|
||||||
std.log.err("Error getting data reader for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer dat_rdr.deinit();
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
|
||||||
_ = dat_rdr.interface.streamRemaining(&wrt.interface) catch |err| {
|
|
||||||
std.log.err("Error writing data for {s} (inode {}) to {s}: {}\n", .{ self.name, self.inode.hdr.num, path, err });
|
|
||||||
out_err.* = wrt.err orelse err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
wrt.interface.flush() catch |err| {
|
|
||||||
std.log.err("Error flushing data for {s} (inode {}) to {s}: {}\n", .{ self.name, self.inode.hdr.num, path, err });
|
|
||||||
out_err.* = wrt.err orelse err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.setPerm(fil, options) catch |err| {
|
|
||||||
std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.symlink, .ext_symlink => {
|
|
||||||
//TODO: deal with dereference symlink options
|
|
||||||
const target_path = self.symlinkPath() catch |err| {
|
|
||||||
std.log.err("Error getting symlink target path for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
std.fs.cwd().symLink(target_path, path, .{}) catch |err| {
|
|
||||||
std.log.err("Error creating {s}: {}\n", .{ path, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// self.setPerm(fil, options) catch |err| {
|
|
||||||
// std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err });
|
|
||||||
// out_err.* = err;
|
|
||||||
// return;
|
|
||||||
// };
|
|
||||||
},
|
|
||||||
.block_dev,
|
|
||||||
.char_dev,
|
|
||||||
.fifo,
|
|
||||||
.ext_block_dev,
|
|
||||||
.ext_char_dev,
|
|
||||||
.ext_fifo,
|
|
||||||
=> {
|
|
||||||
var mode: u32 = undefined;
|
|
||||||
var fil_dev: u32 = 0;
|
|
||||||
switch (self.inode.hdr.inode_type) {
|
|
||||||
.block_dev, .ext_block_dev => {
|
|
||||||
mode = std.posix.DT.BLK;
|
|
||||||
fil_dev = self.devNum() catch |err| {
|
|
||||||
std.log.err("Error getting device number for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.char_dev, .ext_char_dev => {
|
|
||||||
mode = std.posix.DT.CHR;
|
|
||||||
fil_dev = self.devNum() catch |err| {
|
|
||||||
std.log.err("Error getting device number for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
else => mode = std.posix.DT.FIFO,
|
|
||||||
}
|
|
||||||
const res = std.os.linux.mknod(@ptrCast(path), mode, fil_dev);
|
|
||||||
if (res != 0) {
|
|
||||||
std.log.err("Error creating device file at {s} with code {}\n", .{ path, res });
|
|
||||||
out_err.* = error.MknodError;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fil = std.fs.cwd().openFile(path, .{}) catch |err| {
|
|
||||||
std.log.err("Error openning {s} to set permissions: {}\n", .{ path, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer fil.close();
|
|
||||||
self.setPerm(fil, options) catch |err| {
|
|
||||||
std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.dir, .ext_dir => {
|
|
||||||
std.debug.print("starting dir {}: {s}\n", .{ self.inode.hdr.num, self.name });
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind != .directory) {
|
|
||||||
std.log.err("{s} exists and is not a folder\n", .{path});
|
|
||||||
out_err.* = FileError.ExtractionPathExists;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
std.fs.cwd().makeDir(path) catch |err_2| {
|
|
||||||
std.log.err("Error creating {s}: {}\n", .{ path, err_2 });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
std.log.err("Error checking if {s} exists: {}\n", .{ path, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var dir_wg: *WaitGroup = self.archive.allocator().create(WaitGroup) catch |err| {
|
|
||||||
std.log.err("Error allocating waitgroup for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const parent_info: ParentInfo = .{
|
|
||||||
.sfs_fil = self,
|
|
||||||
.path = path,
|
|
||||||
.mut = self.archive.allocator().create(Mutex) catch |err| {
|
|
||||||
std.log.err("Error allocating mutex for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.dir_wg = dir_wg,
|
|
||||||
.parent_wg = wg,
|
|
||||||
.options = options,
|
|
||||||
.err = out_err,
|
|
||||||
};
|
|
||||||
var iter: Iterator = self.iterate() catch |err| {
|
|
||||||
std.log.err("Error getting iterator for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer iter.deinit();
|
|
||||||
const path_has_end_sep = path[path.len - 1] == '/';
|
|
||||||
while (true) {
|
|
||||||
const iter_fil = iter.next() catch |err| {
|
|
||||||
std.log.err("Error getting next iterator value {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
if (iter_fil == null) break;
|
|
||||||
const fil = iter_fil.?;
|
|
||||||
dir_wg.start();
|
|
||||||
var path_len = path.len + fil.name.len;
|
|
||||||
if (!path_has_end_sep) path_len += 1;
|
|
||||||
var new_path = self.archive.allocator().alloc(u8, path_len) catch |err| {
|
|
||||||
std.log.err("Error allocating subpath for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
|
|
||||||
out_err.* = err;
|
|
||||||
dir_wg.finish();
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
@memcpy(new_path[0..path.len], path);
|
|
||||||
@memcpy(new_path[new_path.len - fil.name.len ..], fil.name);
|
|
||||||
if (!path_has_end_sep) new_path[path.len] = '/';
|
|
||||||
if (fil.isDir()) {
|
|
||||||
fil.extractReal(new_path, options, pol, wg, out_err, parent_info);
|
|
||||||
} else {
|
|
||||||
pol.spawn(extractReal, .{
|
|
||||||
fil,
|
|
||||||
new_path,
|
|
||||||
options,
|
|
||||||
pol,
|
|
||||||
wg,
|
|
||||||
out_err,
|
|
||||||
parent_info,
|
|
||||||
}) catch |err| {
|
|
||||||
std.log.err("Error starting sub-file extraction thread: {}\n", .{err});
|
|
||||||
out_err.* = err;
|
|
||||||
dir_wg.finish();
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.socket, .ext_socket => {
|
|
||||||
std.log.info("Ignoring socket file {s} (inode {})\n", .{ self.name, self.inode.hdr.num });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setPerm(self: SfsFile, fil: File, options: ExtractionOptions) !void {
|
|
||||||
if (!options.ignoreOwner) try fil.chmod(self.inode.hdr.permissions);
|
|
||||||
if (!options.ignorePermissions) try fil.chown(try self.ownerUid(), try self.ownerGid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function.
|
/// Utility function.
|
||||||
|
|||||||
+222
@@ -2,10 +2,17 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Reader = std.Io.Reader;
|
const Reader = std.Io.Reader;
|
||||||
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
const Pool = std.Thread.Pool;
|
||||||
|
|
||||||
|
const Archive = @import("archive.zig");
|
||||||
|
const DirEntry = @import("dir_entry.zig");
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
const dir = @import("inode_data/dir.zig");
|
const dir = @import("inode_data/dir.zig");
|
||||||
const file = @import("inode_data/file.zig");
|
const file = @import("inode_data/file.zig");
|
||||||
const misc = @import("inode_data/misc.zig");
|
const misc = @import("inode_data/misc.zig");
|
||||||
|
const DataReader = @import("util/data.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
pub const Ref = packed struct {
|
pub const Ref = packed struct {
|
||||||
block_offset: u16,
|
block_offset: u16,
|
||||||
@@ -94,3 +101,218 @@ pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
|||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the data reader for a file inode.
|
||||||
|
pub fn dataReader(self: Inode, archive: *Archive) !DataReader {
|
||||||
|
return switch (self.hdr.inode_type) {
|
||||||
|
.file => readerFromData(archive, self.data.file),
|
||||||
|
.ext_file => readerFromData(archive, self.data.ext_file),
|
||||||
|
else => error.NotRegularFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn readerFromData(archive: *Archive, data: anytype) !DataReader {
|
||||||
|
var out: DataReader = .init(archive, data.block_sizes, data.block_start, data.size);
|
||||||
|
if (data.frag_idx != 0xFFFFFFFF)
|
||||||
|
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the directory entries for a directory inode.
|
||||||
|
pub fn dirEntries(self: Inode, archive: *Archive) ![]DirEntry {
|
||||||
|
return switch (self.hdr.inode_type) {
|
||||||
|
.dir => entriesFromData(archive, self.data.dir),
|
||||||
|
.ext_dir => entriesFromData(archive, self.data.ext_dir),
|
||||||
|
else => error.NotDirectory,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn entriesFromData(archive: *Archive, data: anytype) ![]DirEntry {
|
||||||
|
var rdr = try archive.fil.readerAt(archive.super.dir_start + data.block_start, &[0]u8{});
|
||||||
|
const alloc = archive.allocator();
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
|
try meta.interface.discardAll(data.block_offset);
|
||||||
|
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extractTo(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
switch (self.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
// Removing any trailing separators since that's the easiest path forward.
|
||||||
|
if (path[path.len - 1] == '/') return self.extractTo(archive, path[0 .. path.len - 1], options);
|
||||||
|
std.fs.cwd().makeDir(path) catch |err| {
|
||||||
|
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
|
||||||
|
};
|
||||||
|
var alloc = archive.allocator();
|
||||||
|
const entries = try self.dirEntries(archive);
|
||||||
|
defer {
|
||||||
|
for (entries) |entry| entry.deinit(alloc);
|
||||||
|
alloc.free(entries);
|
||||||
|
}
|
||||||
|
for (entries) |entry| {
|
||||||
|
var new_path = try alloc.alloc(u8, path.len + 1 + entry.name.len);
|
||||||
|
@memcpy(new_path[0..path.len], path);
|
||||||
|
@memcpy(new_path[path.len + 1 ..], entry.name);
|
||||||
|
new_path[path.len] = '/';
|
||||||
|
defer alloc.free(new_path);
|
||||||
|
|
||||||
|
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
|
var inode: Inode = try read(alloc, &meta.interface, archive.super.block_size);
|
||||||
|
defer inode.deinit(alloc);
|
||||||
|
try inode.extractTo(archive, new_path, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.file, .ext_file => try self.extractRegFile(archive, path, options),
|
||||||
|
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||||
|
else => try self.extractDevice(archive, path, options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Perms = struct {
|
||||||
|
path: []const u8,
|
||||||
|
owner: u16,
|
||||||
|
perm: u16,
|
||||||
|
mod_time: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn extractToThreaded(inode: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, threads: usize) !void {
|
||||||
|
_ = archive;
|
||||||
|
_ = path;
|
||||||
|
_ = options;
|
||||||
|
_ = threads;
|
||||||
|
switch (inode.hdr.inode_type) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract threadedly the inode to the path.
|
||||||
|
fn extractThread(inode: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, wg: *WaitGroup, pool: *Pool, perms: ?*std.ArrayList(Perms)) !void {
|
||||||
|
_ = pool;
|
||||||
|
_ = perms;
|
||||||
|
_ = archive;
|
||||||
|
switch (inode.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
//TOOD
|
||||||
|
return error.TODO;
|
||||||
|
},
|
||||||
|
.file, .ext_file => {
|
||||||
|
//TOOD
|
||||||
|
return error.TODO;
|
||||||
|
},
|
||||||
|
.symlink, .ext_symlink => {
|
||||||
|
defer wg.finish();
|
||||||
|
try inode.extractSymlink(path);
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
defer wg.finish();
|
||||||
|
try inode.extractDevice(path, options.ignore_permissions);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates and writes the inode file contents to the given path.
|
||||||
|
/// Optionally set owner & permissions.
|
||||||
|
///
|
||||||
|
/// Assumes the inode is a file or ext_file type.
|
||||||
|
fn extractRegFile(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var fil = try std.fs.cwd().createFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
var buf: [8192]u8 = undefined;
|
||||||
|
var wrt = fil.writer(&buf);
|
||||||
|
var dat_rdr = try self.dataReader(archive);
|
||||||
|
defer dat_rdr.deinit();
|
||||||
|
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
||||||
|
try wrt.interface.flush();
|
||||||
|
// updateTime is in nanoseconds (a billionth of a second). mod_time is in seconds.
|
||||||
|
// TODO: fix
|
||||||
|
// try fil.updateTimes(self.hdr.mod_time, self.hdr.mod_time);
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.chmod(self.hdr.permissions);
|
||||||
|
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
if (!options.ignore_xattr) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// TODO: not implemented
|
||||||
|
/// Extract the inode file contents to the given path.
|
||||||
|
/// The extraction will be done threaded using pool for threads and will call wg.finish() when done.
|
||||||
|
///
|
||||||
|
/// Optionally set owner & permissions.
|
||||||
|
/// Assumes the inode is a file or ext_file type.
|
||||||
|
fn extractRegFileThreaded(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, pool: *Pool, wg: *WaitGroup) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = archive;
|
||||||
|
_ = path;
|
||||||
|
_ = options;
|
||||||
|
_ = pool;
|
||||||
|
_ = wg;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
/// Creates the symlink described by the inode.
|
||||||
|
///
|
||||||
|
/// Assumes the inode is a symlink or ext_symlink type.
|
||||||
|
fn extractSymlink(self: Inode, path: []const u8) !void {
|
||||||
|
const target = switch (self.data) {
|
||||||
|
.symlink => |s| s.target,
|
||||||
|
.ext_symlink => |s| s.target,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
try std.fs.cwd().symLink(target, path, .{});
|
||||||
|
}
|
||||||
|
/// Creates the device described by the inode.
|
||||||
|
///
|
||||||
|
/// Optionally set owner & permissions.
|
||||||
|
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
|
||||||
|
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var mode: u32 = undefined;
|
||||||
|
var dev: u32 = 0;
|
||||||
|
switch (self.data) {
|
||||||
|
.char_dev => |d| {
|
||||||
|
mode = std.posix.S.IFCHR;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.ext_char_dev => |d| {
|
||||||
|
mode = std.posix.S.IFCHR;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.block_dev => |d| {
|
||||||
|
mode = std.posix.S.IFBLK;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.ext_block_dev => |d| {
|
||||||
|
mode = std.posix.S.IFBLK;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.fifo, .ext_fifo => mode = std.posix.S.IFIFO,
|
||||||
|
.socket, .ext_socket => mode = std.posix.S.IFSOCK,
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
const res: std.os.linux.E = @enumFromInt(std.os.linux.mknod(@ptrCast(path), mode, dev));
|
||||||
|
switch (res) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
|
||||||
|
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
|
||||||
|
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
|
||||||
|
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
|
||||||
|
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
|
||||||
|
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
|
||||||
|
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
|
||||||
|
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
|
||||||
|
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
|
||||||
|
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
|
||||||
|
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
|
||||||
|
else => return blk: {
|
||||||
|
std.debug.print("unhandled mknod result: {}\n", .{res});
|
||||||
|
break :blk std.fs.Dir.MakeError.Unexpected;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var fil = try std.fs.cwd().openFile(path, .{});
|
||||||
|
// updateTime is in nanoseconds (a billionth of a second). mod_time is in seconds.
|
||||||
|
// TODO: fix
|
||||||
|
// try fil.updateTimes(self.hdr.mod_time, self.hdr.mod_time);
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.chmod(self.hdr.permissions);
|
||||||
|
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
if (!options.ignore_xattr) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+15
-10
@@ -5,16 +5,21 @@ const Writer = std.Io.Writer;
|
|||||||
|
|
||||||
const ExtractionOptions = @This();
|
const ExtractionOptions = @This();
|
||||||
|
|
||||||
/// Don't set the file's permissions after extraction
|
/// Don't set the file's owner & permissions after extraction
|
||||||
ignorePermissions: bool = false,
|
ignore_permissions: bool = false,
|
||||||
/// Don't set the file's owner after extraction.
|
/// Don't set xattr values. Currently xattrs are never set anyway.
|
||||||
ignoreOwner: bool = false,
|
ignore_xattr: bool = false,
|
||||||
/// Replace symlinks with their target.
|
/// Replace symlinks with their target.
|
||||||
dereferenceSymlinks: bool = false,
|
dereference_symlinks: bool = false,
|
||||||
|
/// Verbose logging. If true, verbose_writer must be set
|
||||||
log_level: std.log.Level = .err,
|
verbose: bool = false,
|
||||||
// /// If options verbose and verboseWriter not set, logs are printed to stdout.
|
/// Where to print verbose log.
|
||||||
// verboseWriter: ?*Writer = null,
|
verbose_writer: ?*Writer = null,
|
||||||
|
|
||||||
pub const Default: ExtractionOptions = .{};
|
pub const Default: ExtractionOptions = .{};
|
||||||
pub const VerboseDefault: ExtractionOptions = .{ .log_level = .debug };
|
pub fn VerboseDefault(wrt: *Writer) ExtractionOptions {
|
||||||
|
return .{
|
||||||
|
.verbose = true,
|
||||||
|
.verbose_writer = wrt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Mutex = std.Thread.Mutex;
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
const DecompMgr = @import("decomp.zig");
|
const DecompFn = @import("decomp.zig").DecompFn;
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ pub fn Table(T: anytype) type {
|
|||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
fil: OffsetFile,
|
fil: OffsetFile,
|
||||||
decomp: *DecompMgr,
|
decomp: DecompFn,
|
||||||
tab_start: u64,
|
tab_start: u64,
|
||||||
|
|
||||||
tab: std.AutoHashMap(u32, []T),
|
tab: std.AutoHashMap(u32, []T),
|
||||||
@@ -26,7 +26,7 @@ pub fn Table(T: anytype) type {
|
|||||||
|
|
||||||
mut: Mutex = .{},
|
mut: Mutex = .{},
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *DecompMgr, tab_start: u64, values: u32) !Self {
|
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.fil = fil,
|
.fil = fil,
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ test "ExtractSingleFile" {
|
|||||||
defer sfs.deinit();
|
defer sfs.deinit();
|
||||||
var test_fil = try sfs.open(TestFile);
|
var test_fil = try sfs.open(TestFile);
|
||||||
defer test_fil.deinit();
|
defer test_fil.deinit();
|
||||||
try test_fil.extract(TestFileExtractLocation, .VerboseDefault);
|
try test_fil.extract(TestFileExtractLocation, .Default);
|
||||||
//TODO: validate extracted file.
|
//TODO: validate extracted file.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ test "ExtractCompleteArchive" {
|
|||||||
defer fil.close();
|
defer fil.close();
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||||
defer sfs.deinit();
|
defer sfs.deinit();
|
||||||
try sfs.extract(TestFullExtractLocation, .VerboseDefault);
|
try sfs.extract(TestFullExtractLocation, .Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||||
|
|||||||
+11
-10
@@ -7,7 +7,7 @@ const Limit = std.Io.Limit;
|
|||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
const Archive = @import("../archive.zig");
|
||||||
const FragEntry = Archive.FragEntry;
|
const FragEntry = Archive.FragEntry;
|
||||||
const DecompMgr = @import("../decomp.zig");
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||||
const OffsetFile = @import("offset_file.zig");
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ const DataReader = @This();
|
|||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
fil: OffsetFile,
|
fil: OffsetFile,
|
||||||
decomp: *DecompMgr,
|
decomp: DecompFn,
|
||||||
block_size: u32,
|
block_size: u32,
|
||||||
|
|
||||||
blocks: []BlockSize,
|
blocks: []BlockSize,
|
||||||
@@ -33,7 +33,7 @@ pub fn init(archive: *Archive, blocks: []BlockSize, start: u64, size: u64) DataR
|
|||||||
return .{
|
return .{
|
||||||
.alloc = archive.allocator(),
|
.alloc = archive.allocator(),
|
||||||
.fil = archive.fil,
|
.fil = archive.fil,
|
||||||
.decomp = &archive.decomp,
|
.decomp = archive.decomp,
|
||||||
.block_size = archive.super.block_size,
|
.block_size = archive.super.block_size,
|
||||||
.blocks = blocks,
|
.blocks = blocks,
|
||||||
.size = size,
|
.size = size,
|
||||||
@@ -91,11 +91,11 @@ fn advance(self: *DataReader) !void {
|
|||||||
}
|
}
|
||||||
const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size);
|
const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size);
|
||||||
defer self.alloc.free(tmp_buf);
|
defer self.alloc.free(tmp_buf);
|
||||||
var limit_rdr = Reader.limited(&rdr.interface, @enumFromInt(self.frag.?.size.size), tmp_buf);
|
try rdr.interface.readSliceAll(tmp_buf);
|
||||||
const needed_block = try self.alloc.alloc(u8, self.frag_offset + cur_block_size);
|
const needed_block = try self.alloc.alloc(u8, self.block_size);
|
||||||
defer self.alloc.free(needed_block);
|
defer self.alloc.free(needed_block);
|
||||||
_ = try self.decomp.decompReader(&limit_rdr.interface, needed_block);
|
_ = try self.decomp(self.alloc, tmp_buf, needed_block);
|
||||||
@memcpy(self.interface.buffer, needed_block[self.frag_offset..]);
|
@memcpy(self.interface.buffer, needed_block[self.frag_offset .. self.frag_offset + cur_block_size]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const block = self.blocks[self.block_idx];
|
const block = self.blocks[self.block_idx];
|
||||||
@@ -109,9 +109,10 @@ fn advance(self: *DataReader) !void {
|
|||||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
try rdr.interface.readSliceAll(self.interface.buffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var buf: [8192]u8 = undefined; //TODO: possibly change for better performance/memory usage. Might need to be a full block in size.
|
const tmp_buf = try self.alloc.alloc(u8, block.size);
|
||||||
var limit_rdr = Reader.limited(&rdr.interface, @enumFromInt(block.size), &buf);
|
defer self.alloc.free(tmp_buf);
|
||||||
_ = try self.decomp.decompReader(&limit_rdr.interface, self.interface.buffer);
|
try rdr.interface.readSliceAll(tmp_buf);
|
||||||
|
_ = try self.decomp(self.alloc, tmp_buf, self.interface.buffer);
|
||||||
}
|
}
|
||||||
/// Does not guarentee that data currently in the buffer is retained.
|
/// Does not guarentee that data currently in the buffer is retained.
|
||||||
fn resizeBuffer(self: *DataReader, size: usize) !void {
|
fn resizeBuffer(self: *DataReader, size: usize) !void {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const Writer = std.Io.Writer;
|
|||||||
const Limit = std.Io.Limit;
|
const Limit = std.Io.Limit;
|
||||||
const StreamError = std.Io.Reader.StreamError;
|
const StreamError = std.Io.Reader.StreamError;
|
||||||
|
|
||||||
const DecompMgr = @import("../decomp.zig");
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
|
|
||||||
const BlockHeader = packed struct {
|
const BlockHeader = packed struct {
|
||||||
size: u15,
|
size: u15,
|
||||||
@@ -15,14 +15,14 @@ const This = @This();
|
|||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
rdr: *Reader,
|
rdr: *Reader,
|
||||||
decomp: *DecompMgr,
|
decomp: DecompFn,
|
||||||
|
|
||||||
buf: [8192]u8 = undefined,
|
buf: [8192]u8 = undefined,
|
||||||
|
|
||||||
interface: Reader,
|
interface: Reader,
|
||||||
err: ?anyerror = null,
|
err: ?anyerror = null,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *DecompMgr) This {
|
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: DecompFn) This {
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.rdr = rdr,
|
.rdr = rdr,
|
||||||
@@ -51,8 +51,8 @@ fn advance(self: *This) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var tmp_buf: [8192]u8 = undefined;
|
var tmp_buf: [8192]u8 = undefined;
|
||||||
var limit_rdr = self.rdr.limited(@enumFromInt(hdr.size), &tmp_buf);
|
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
|
||||||
self.interface.end = try self.decomp.decompReader(&limit_rdr.interface, &self.buf);
|
self.interface.end = try self.decomp(self.alloc, tmp_buf[0..hdr.size], &self.buf);
|
||||||
self.interface.buffer = self.buf[0..self.interface.end];
|
self.interface.buffer = self.buf[0..self.interface.end];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user