Fished decompression (maybe)
Added the necessary skeleton functions to re-add test
This commit is contained in:
@@ -41,11 +41,11 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "zig_squashfs", .module = lib.root_module },
|
.{ .name = "zig_squashfs", .module = lib.root_module },
|
||||||
.{ .name = "config", .module = unsquashfs_options.createModule() },
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
.use_llvm = debug,
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
|
exe.root_module.addOptions("config", unsquashfs_options);
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
@@ -54,12 +54,8 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.target = target,
|
.target = target,
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/test.zig"),
|
||||||
}),
|
}),
|
||||||
.test_runner = .{
|
|
||||||
.mode = .simple,
|
|
||||||
.path = b.path("src/test.zig"),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
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");
|
||||||
@@ -68,21 +64,13 @@ 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 = b.createModule(.{
|
.root_module = exe.root_module,
|
||||||
.optimize = optimize,
|
});
|
||||||
.target = target,
|
const exe_check = b.addExecutable(.{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.name = "unsquashfs",
|
||||||
}),
|
.root_module = lib.root_module,
|
||||||
});
|
});
|
||||||
// const exe_check = b.addExecutable(.{
|
|
||||||
// .name = "unsquashfs",
|
|
||||||
// .root_module = b.createModule(.{
|
|
||||||
// .optimize = optimize,
|
|
||||||
// .target = target,
|
|
||||||
// .root_source_file = b.path("src/bin/unsquashfs.zig"),
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-1
@@ -1,7 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const File = @import("file.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
|
const LookupTable = @import("lookup_table.zig");
|
||||||
|
const Decompressor = @import("util/decompressor.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
const Utils = @import("util/misc.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
const Archive = @This();
|
const Archive = @This();
|
||||||
@@ -9,6 +15,8 @@ const Archive = @This();
|
|||||||
file: OffsetFile,
|
file: OffsetFile,
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
|
stateless_decomp: Decompressor,
|
||||||
|
|
||||||
pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
|
pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
|
||||||
var rdr = file.reader(io, &[0]u8{});
|
var rdr = file.reader(io, &[0]u8{});
|
||||||
try rdr.seekTo(offset);
|
try rdr.seekTo(offset);
|
||||||
@@ -17,9 +25,67 @@ pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
|
|||||||
return .{
|
return .{
|
||||||
.file = .init(file, offset),
|
.file = .init(file, offset),
|
||||||
.super = super,
|
.super = super,
|
||||||
|
|
||||||
|
.stateless_decomp = switch (super.compression) {
|
||||||
|
.gzip => @import("decomp/zlib.zig").stateless_decompressor,
|
||||||
|
.lzma => @import("decomp/lzma.zig").stateless_decompressor,
|
||||||
|
.lzo => return error.LzoUnsupported,
|
||||||
|
.xz => @import("decomp/xz.zig").stateless_decompressor,
|
||||||
|
.lz4 => return error.Lz4Unsupported,
|
||||||
|
.zstd => @import("decomp/zstd.zig").stateless_decompressor,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The root folder of the Archive. Used to open other Files.
|
||||||
|
pub fn root(self: Archive, alloc: std.mem.Allocator, io: Io) !File {
|
||||||
|
const root_inode = try Utils.inodeFromRef(
|
||||||
|
alloc,
|
||||||
|
io,
|
||||||
|
self.file,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
self.super.root_ref,
|
||||||
|
);
|
||||||
|
return .init(alloc, root_inode, "");
|
||||||
|
}
|
||||||
|
/// Opens a File within the archive.
|
||||||
|
pub fn open(self: Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||||
|
const root_file = try self.root(alloc, io);
|
||||||
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
|
if (Utils.pathIsSelf(path))
|
||||||
|
return root_file;
|
||||||
|
defer root_file.deinit();
|
||||||
|
return root_file.open(alloc, io, filepath);
|
||||||
|
}
|
||||||
|
/// Extract the entire archive contents to the given directory.
|
||||||
|
pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = io;
|
||||||
|
_ = extract_dir;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inode with the given inode number.
|
||||||
|
/// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Superblock
|
// Superblock
|
||||||
|
|
||||||
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
||||||
@@ -32,7 +98,7 @@ const SuperblockError = error{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A squashfs Superblock
|
/// A squashfs Superblock
|
||||||
pub const Superblock = packed struct {
|
pub const Superblock = packed struct(u768) {
|
||||||
magic: u32,
|
magic: u32,
|
||||||
inode_count: u32,
|
inode_count: u32,
|
||||||
mod_time: u32,
|
mod_time: u32,
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
};
|
};
|
||||||
if (force)
|
if (force)
|
||||||
try Io.Dir.cwd().deleteTree(io, extLoc);
|
try Io.Dir.cwd().deleteTree(io, extLoc);
|
||||||
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
|
try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
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 Self = @This();
|
||||||
|
|
||||||
|
const Buffer = struct {
|
||||||
|
node: Node,
|
||||||
|
buf: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = decomp },
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
block_size: u32,
|
||||||
|
buffers: std.ArrayList(Buffer),
|
||||||
|
buffer_queue: std.SinglyLinkedList,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, block_size: u32) !Self {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.block_size = block_size,
|
||||||
|
.buffers = try .initCapacity(alloc, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
for (self.buffers) |buf|
|
||||||
|
self.alloc.free(buf);
|
||||||
|
self.buffers.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decomp(d: ?*const 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 lzmaDecomp(buf, in, out);
|
||||||
|
}
|
||||||
|
var self: Self = @fieldParentPtr("interface", d.?);
|
||||||
|
const buf_node = self.buffer_queue.popFirst();
|
||||||
|
var buf: *Buffer = undefined;
|
||||||
|
if (buf_node == null) {
|
||||||
|
const new_buf = try self.buffers.addOne(self.alloc);
|
||||||
|
new_buf.* = .{ .{}, try self.alloc.alloc(u8, self.block_size + lzma.block_size_max) };
|
||||||
|
buf = new_buf;
|
||||||
|
} else {
|
||||||
|
buf = @fieldParentPtr("node", buf_node);
|
||||||
|
}
|
||||||
|
defer self.buffer_queue.prepend(&buf.node);
|
||||||
|
return lzmaDecomp(self.alloc, &buf.buf, in, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.*, .{ .allow_incomplete = true }, 3 * 1024 * 1024);
|
||||||
|
defer {
|
||||||
|
buffer.* = d.takeBuffer();
|
||||||
|
d.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stateless
|
||||||
|
|
||||||
|
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*const 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
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 Self = @This();
|
||||||
|
|
||||||
|
const Buffer = struct {
|
||||||
|
node: Node,
|
||||||
|
buf: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = decomp },
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
block_size: u32,
|
||||||
|
buffers: std.ArrayList(Buffer),
|
||||||
|
buffer_queue: std.SinglyLinkedList,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, block_size: u32) !Self {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.block_size = block_size,
|
||||||
|
.buffers = try .initCapacity(alloc, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
for (self.buffers) |buf|
|
||||||
|
self.alloc.free(buf);
|
||||||
|
self.buffers.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decomp(d: ?*const 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 lzmaDecomp(buf, in, out);
|
||||||
|
}
|
||||||
|
var self: Self = @fieldParentPtr("interface", d.?);
|
||||||
|
const buf_node = self.buffer_queue.popFirst();
|
||||||
|
var buf: *Buffer = undefined;
|
||||||
|
if (buf_node == null) {
|
||||||
|
const new_buf = try self.buffers.addOne(self.alloc);
|
||||||
|
new_buf.* = .{ .{}, try self.alloc.alloc(u8, self.block_size + xz.block_size_max) };
|
||||||
|
buf = new_buf;
|
||||||
|
} else {
|
||||||
|
buf = @fieldParentPtr("node", buf_node);
|
||||||
|
}
|
||||||
|
defer self.buffer_queue.prepend(&buf.node);
|
||||||
|
return lzmaDecomp(self.alloc, &buf.buf, in, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn lzmaDecomp(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(_: ?*const 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;
|
||||||
|
}
|
||||||
+47
-29
@@ -1,55 +1,73 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Reader = std.Io.Reader;
|
const Reader = std.Io.Reader;
|
||||||
const flate = std.compress.flate;
|
const flate = std.compress.flate;
|
||||||
|
const Node = std.SinglyLinkedList.Node;
|
||||||
|
|
||||||
const Decompressor = @import("../util/decompressor.zig");
|
const Decompressor = @import("../util/decompressor.zig");
|
||||||
const Error = Decompressor.Error;
|
const Error = Decompressor.Error;
|
||||||
|
|
||||||
pub fn Zlib(stateless: bool) type {
|
const Self = @This();
|
||||||
return if (stateless)
|
|
||||||
struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
interface: Decompressor = .{ .decomp_fn = decomp },
|
const Buffer = struct {
|
||||||
|
node: Node,
|
||||||
|
buf: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
const init: Self = .{};
|
interface: Decompressor = .{ .decomp_fn = decomp },
|
||||||
|
|
||||||
fn decomp(_: *?Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
alloc: std.mem.Allocator,
|
||||||
const buf = try alloc.alloc(u8, in.len * 2);
|
|
||||||
defer alloc.free(buf);
|
|
||||||
return zlibDecomp(buf, in, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
interface: Decompressor = .{ .decomp_fn = decomp },
|
block_size: u32,
|
||||||
|
buffers: std.ArrayList(Buffer),
|
||||||
|
buffer_queue: std.SinglyLinkedList,
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
pub fn init(alloc: std.mem.Allocator, block_size: u32) !Self {
|
||||||
|
|
||||||
block_size: u32,
|
|
||||||
buffers: std.ArrayList([]u8),
|
|
||||||
buffer_queue: std.SinglyLinkedList,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, block_size: u32) !Self {
|
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
|
|
||||||
.block_size = block_size,
|
.block_size = block_size,
|
||||||
.buffers = try .initCapacity(alloc, 20),
|
.buffers = try .initCapacity(alloc, 5),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: Self) void {
|
pub fn deinit(self: Self) void {
|
||||||
for (self.buffers) |buf|
|
for (self.buffers) |buf|
|
||||||
self.alloc.free(buf);
|
self.alloc.free(buf);
|
||||||
|
self.buffers.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decomp(d: ?*const 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 zlibDecomp(buf, in, out);
|
||||||
}
|
}
|
||||||
};
|
var self: Self = @fieldParentPtr("interface", d.?);
|
||||||
|
const buf_node = self.buffer_queue.popFirst();
|
||||||
|
var buf: *Buffer = undefined;
|
||||||
|
if (buf_node == null) {
|
||||||
|
const new_buf = try self.buffers.addOne(self.alloc);
|
||||||
|
new_buf.* = .{ .{}, try self.alloc.alloc(u8, self.block_size) };
|
||||||
|
buf = new_buf;
|
||||||
|
} else {
|
||||||
|
buf = @fieldParentPtr("node", buf_node);
|
||||||
|
}
|
||||||
|
defer self.buffer_queue.prepend(&buf.node);
|
||||||
|
return zlibDecomp(buf.buf, in, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn zlibDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
|
inline fn zlibDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
|
||||||
var rdr: Reader = .fixed(in);
|
var rdr: Reader = .fixed(in);
|
||||||
var decomp = flate.Decompress.init(&rdr, .zlib, buffer);
|
var d = flate.Decompress.init(&rdr, .zlib, buffer);
|
||||||
|
|
||||||
return decomp.reader.readSliceShort(out);
|
return d.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stateless
|
||||||
|
|
||||||
|
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
const buf = try alloc.alloc(u8, in.len * 2);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
return zlibDecomp(buf, in, out);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
const zstd = std.compress.zstd;
|
||||||
|
const Node = std.SinglyLinkedList.Node;
|
||||||
|
|
||||||
|
const Decompressor = @import("../util/decompressor.zig");
|
||||||
|
const Error = Decompressor.Error;
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const Buffer = struct {
|
||||||
|
node: Node,
|
||||||
|
buf: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = decomp },
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
block_size: u32,
|
||||||
|
buffers: std.ArrayList(Buffer),
|
||||||
|
buffer_queue: std.SinglyLinkedList,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, block_size: u32) !Self {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.block_size = block_size,
|
||||||
|
.buffers = try .initCapacity(alloc, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
for (self.buffers) |buf|
|
||||||
|
self.alloc.free(buf);
|
||||||
|
self.buffers.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decomp(d: ?*const 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", d.?);
|
||||||
|
const buf_node = self.buffer_queue.popFirst();
|
||||||
|
var buf: *Buffer = undefined;
|
||||||
|
if (buf_node == null) {
|
||||||
|
const new_buf = try self.buffers.addOne(self.alloc);
|
||||||
|
new_buf.* = .{ .{}, try self.alloc.alloc(u8, self.block_size + zstd.block_size_max) };
|
||||||
|
buf = new_buf;
|
||||||
|
} else {
|
||||||
|
buf = @fieldParentPtr("node", buf_node);
|
||||||
|
}
|
||||||
|
defer self.buffer_queue.prepend(&buf.node);
|
||||||
|
return zstdDecomp(buf.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(in.len) });
|
||||||
|
|
||||||
|
return d.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stateless
|
||||||
|
|
||||||
|
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
const buf = try alloc.alloc(u8, in.len + zstd.block_size_max);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
return zstdDecomp(buf, in, out);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//! An easier to use wrapper around an inode.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
|
||||||
|
const File = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
inode: Inode,
|
||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
/// 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, in: Inode, name: []const u8) !File {
|
||||||
|
const new_name = try alloc.alloc(u8, name.len);
|
||||||
|
@memcpy(new_name, name);
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.inode = in,
|
||||||
|
.name = new_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: File) void {
|
||||||
|
self.alloc.free(self.name);
|
||||||
|
self.inode.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = io;
|
||||||
|
_ = filepath;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = io;
|
||||||
|
_ = filepath;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
+24
-15
@@ -12,29 +12,38 @@ const Inode = @This();
|
|||||||
hdr: Header,
|
hdr: Header,
|
||||||
data: Data,
|
data: Data,
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u16) !Inode {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
||||||
var hdr: Header = undefined;
|
var hdr: Header = undefined;
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
return .{
|
return .{
|
||||||
.hdr = hdr,
|
.hdr = hdr,
|
||||||
.data = switch (hdr.inode_type) {
|
.data = switch (hdr.inode_type) {
|
||||||
.dir => .{ .dir = .read(rdr) },
|
.dir => .{ .dir = try .read(rdr) },
|
||||||
.file => .{ .file = .read(alloc, rdr, block_size) },
|
.file => .{ .file = try .read(alloc, rdr, block_size) },
|
||||||
.symlink => .{ .symlink = .read(alloc, rdr) },
|
.symlink => .{ .symlink = try .read(alloc, rdr) },
|
||||||
.block_dev => .{ .block_dev = .read(rdr) },
|
.block_dev => .{ .block_dev = try .read(rdr) },
|
||||||
.char_dev => .{ .char_dev = .read(rdr) },
|
.char_dev => .{ .char_dev = try .read(rdr) },
|
||||||
.fifo => .{ .fifo = .read(rdr) },
|
.fifo => .{ .fifo = try .read(rdr) },
|
||||||
.socket => .{ .socket = .read(rdr) },
|
.socket => .{ .socket = try .read(rdr) },
|
||||||
.ext_dir => .{ .ext_dir = .read(rdr) },
|
.ext_dir => .{ .ext_dir = try .read(rdr) },
|
||||||
.ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
|
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
|
||||||
.ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
|
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
|
||||||
.ext_block_dev => .{ .ext_block_dev = .read(rdr) },
|
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
|
||||||
.ext_char_dev => .{ .ext_char_dev = .read(rdr) },
|
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
|
||||||
.ext_fifo => .{ .ext_fifo = .read(rdr) },
|
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
|
||||||
.ext_socket => .{ .ext_socket = .read(rdr) },
|
.ext_socket => .{ .ext_socket = try .read(rdr) },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||||
|
switch (self.data) {
|
||||||
|
.file => |d| d.deinit(alloc),
|
||||||
|
.symlink => |d| d.deinit(alloc),
|
||||||
|
.ext_file => |d| d.deinit(alloc),
|
||||||
|
.ext_symlink => |d| d.deinit(alloc),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
const io = std.testing.io;
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
const stuff = @import("builtin");
|
||||||
|
|
||||||
|
const Archive = @import("archive.zig");
|
||||||
|
const Superblock = Archive.Superblock;
|
||||||
|
|
||||||
|
const TestArchive = "testing/LinuxPATest.sfs";
|
||||||
|
|
||||||
|
test "Basics" {
|
||||||
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
var sfs: Archive = try .init(io, fil, 0);
|
||||||
|
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
||||||
|
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
||||||
|
return error.BadSuperblock;
|
||||||
|
}
|
||||||
|
const root_file = try sfs.root(alloc, io);
|
||||||
|
defer root_file.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestFile = "Start.exe";
|
||||||
|
const TestFileExtractLocation = "testing/Start.exe";
|
||||||
|
|
||||||
|
test "ExtractSingleFile" {
|
||||||
|
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);
|
||||||
|
var test_fil = try sfs.open(alloc, io, TestFile);
|
||||||
|
defer test_fil.deinit();
|
||||||
|
try test_fil.extract(alloc, io, TestFileExtractLocation, try .Default());
|
||||||
|
//TODO: validate extracted file.
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestFullExtractLocation = "testing/TestExtract";
|
||||||
|
|
||||||
|
test "ExtractCompleteArchive" {
|
||||||
|
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);
|
||||||
|
try sfs.extract(alloc, io, TestFullExtractLocation, try .Default());
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||||
|
.magic = std.mem.readInt(u32, "hsqs", .little),
|
||||||
|
.inode_count = 2974,
|
||||||
|
.mod_time = 1632696724,
|
||||||
|
.block_size = 131072,
|
||||||
|
.frag_count = 264,
|
||||||
|
.compression = .zstd,
|
||||||
|
.block_log = 17,
|
||||||
|
.flags = .{
|
||||||
|
.inode_uncompressed = false,
|
||||||
|
.data_uncompressed = false,
|
||||||
|
.check = false,
|
||||||
|
.frag_uncompressed = false,
|
||||||
|
.fragment_never = false,
|
||||||
|
.fragment_always = false,
|
||||||
|
.duplicates = true,
|
||||||
|
.exportable = true,
|
||||||
|
.xattr_uncompressed = false,
|
||||||
|
.xattr_never = false,
|
||||||
|
.compression_options = false,
|
||||||
|
.ids_uncompressed = false,
|
||||||
|
._ = 0,
|
||||||
|
},
|
||||||
|
.id_count = 1,
|
||||||
|
.ver_maj = 4,
|
||||||
|
.ver_min = 0,
|
||||||
|
.root_ref = .{
|
||||||
|
.block_offset = 1363,
|
||||||
|
.block_start = 29237,
|
||||||
|
._ = 0,
|
||||||
|
},
|
||||||
|
.size = 106841744,
|
||||||
|
.id_start = 106841632,
|
||||||
|
.xattr_start = 106841720,
|
||||||
|
.inode_start = 106778274,
|
||||||
|
.dir_start = 106807998,
|
||||||
|
.frag_start = 106837747,
|
||||||
|
.export_start = 106841602,
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ pub const Error = std.Io.Reader.StreamError || std.mem.Allocator.Error;
|
|||||||
|
|
||||||
/// The actual decompression function.
|
/// The actual decompression function.
|
||||||
/// If the given decompressor is null, then the decompression should be done "stateless" without lasting allocations.
|
/// If the given decompressor is null, then the decompression should be done "stateless" without lasting allocations.
|
||||||
decomp_fn: *fn (?*Decompressor, std.mem.Allocator, in: []u8, out: []u8) Error!usize,
|
decomp_fn: *const fn (?*const 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 {
|
pub fn Decompress(self: *const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
return self.decomp_fn(self, alloc, in, out);
|
return self.decomp_fn(self, alloc, in, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ const This = @This();
|
|||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
rdr: *Reader,
|
rdr: *Reader,
|
||||||
decomp: *Decompressor,
|
decomp: *const Decompressor,
|
||||||
|
|
||||||
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: *Decompressor) This {
|
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *const Decompressor) This {
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.rdr = rdr,
|
.rdr = rdr,
|
||||||
@@ -68,22 +68,22 @@ fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
|
|||||||
self.interface.seek += wrote;
|
self.interface.seek += wrote;
|
||||||
return wrote;
|
return wrote;
|
||||||
}
|
}
|
||||||
fn discard(rdr: *Reader, limit: Limit) error{ EndOfStream, ReadFailed }!usize {
|
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
||||||
const self: *This = @fieldParentPtr("interface", rdr);
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
self.err = err;
|
self.err = err;
|
||||||
return StreamError.ReadFailed;
|
return error.ReadFailed;
|
||||||
};
|
};
|
||||||
if (@intFromEnum(limit) == 0) return 0;
|
if (@intFromEnum(limit) == 0) return 0;
|
||||||
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||||
rdr.seek += to_skip;
|
rdr.seek += to_skip;
|
||||||
return to_skip;
|
return to_skip;
|
||||||
}
|
}
|
||||||
fn readVec(rdr: *Reader, vec: [][]u8) error{ EndOfStream, ReadFailed }!usize {
|
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||||
const self: *This = @fieldParentPtr("interface", rdr);
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
self.err = err;
|
self.err = err;
|
||||||
return StreamError.ReadFailed;
|
return error.ReadFailed;
|
||||||
};
|
};
|
||||||
var cur_red: usize = 0;
|
var cur_red: usize = 0;
|
||||||
for (vec) |s| {
|
for (vec) |s| {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//! 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, io: Io, file: OffsetFile, decomp: *const Decompressor, inode_start: u64, block_size: u32, ref: Inode.Ref) !Inode {
|
||||||
|
var rdr = try file.readerAt(io, inode_start + ref.block_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, decomp);
|
||||||
|
try meta.interface.discardAll(ref.block_offset);
|
||||||
|
|
||||||
|
return .read(alloc, &meta.interface, block_size);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user