Fished decompression (maybe)

Added the necessary skeleton functions to re-add test
This commit is contained in:
Caleb Gardner
2026-05-01 07:05:52 -05:00
parent 274d088490
commit ab606bdfa5
13 changed files with 547 additions and 80 deletions
+8 -20
View File
@@ -41,11 +41,11 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/bin/unsquashfs.zig"),
.imports = &.{
.{ .name = "zig_squashfs", .module = lib.root_module },
.{ .name = "config", .module = unsquashfs_options.createModule() },
},
}),
.use_llvm = debug,
});
exe.root_module.addOptions("config", unsquashfs_options);
b.installArtifact(lib);
b.installArtifact(exe);
@@ -54,12 +54,8 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.optimize = optimize,
.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 test_step = b.step("test", "Run tests");
@@ -68,21 +64,13 @@ pub fn build(b: *std.Build) !void {
// zls build check steps
const lib_check = b.addLibrary(.{
.name = "squashfs",
.root_module = b.createModule(.{
.optimize = optimize,
.target = target,
.root_source_file = b.path("src/root.zig"),
}),
.root_module = exe.root_module,
});
const exe_check = b.addExecutable(.{
.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");
check.dependOn(&lib_check.step);
// check.dependOn(&exe_check.step);
check.dependOn(&exe_check.step);
}
+67 -1
View File
@@ -1,7 +1,13 @@
const std = @import("std");
const Io = std.Io;
const ExtractionOptions = @import("options.zig");
const File = @import("file.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 Archive = @This();
@@ -9,6 +15,8 @@ const Archive = @This();
file: OffsetFile,
super: Superblock,
stateless_decomp: Decompressor,
pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
var rdr = file.reader(io, &[0]u8{});
try rdr.seekTo(offset);
@@ -17,9 +25,67 @@ pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
return .{
.file = .init(file, offset),
.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
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
@@ -32,7 +98,7 @@ const SuperblockError = error{
};
/// A squashfs Superblock
pub const Superblock = packed struct {
pub const Superblock = packed struct(u768) {
magic: u32,
inode_count: u32,
mod_time: u32,
+1 -1
View File
@@ -63,7 +63,7 @@ pub fn main(init: std.process.Init) !void {
};
if (force)
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 {
+77
View File
@@ -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;
}
+77
View File
@@ -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;
}
+53 -35
View File
@@ -1,55 +1,73 @@
const std = @import("std");
const Reader = std.Io.Reader;
const flate = std.compress.flate;
const Node = std.SinglyLinkedList.Node;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
pub fn Zlib(stateless: bool) type {
return if (stateless)
struct {
const Self = @This();
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 {
const buf = try alloc.alloc(u8, in.len * 2);
defer alloc.free(buf);
return zlibDecomp(buf, in, out);
}
}
else
struct {
const Self = @This();
alloc: std.mem.Allocator,
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 {
return .{
.alloc = alloc,
block_size: u32,
buffers: std.ArrayList([]u8),
buffer_queue: std.SinglyLinkedList,
.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);
}
pub fn init(alloc: std.mem.Allocator, block_size: u32) !Self {
return .{
.alloc = alloc,
.block_size = block_size,
.buffers = try .initCapacity(alloc, 20),
};
}
pub fn deinit(self: Self) void {
for (self.buffers) |buf|
self.alloc.free(buf);
}
};
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 {
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);
}
+73
View File
@@ -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);
}
+48
View File
@@ -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
View File
@@ -12,29 +12,38 @@ const Inode = @This();
hdr: Header,
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;
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
return .{
.hdr = hdr,
.data = switch (hdr.inode_type) {
.dir => .{ .dir = .read(rdr) },
.file => .{ .file = .read(alloc, rdr, block_size) },
.symlink => .{ .symlink = .read(alloc, rdr) },
.block_dev => .{ .block_dev = .read(rdr) },
.char_dev => .{ .char_dev = .read(rdr) },
.fifo => .{ .fifo = .read(rdr) },
.socket => .{ .socket = .read(rdr) },
.ext_dir => .{ .ext_dir = .read(rdr) },
.ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
.ext_block_dev => .{ .ext_block_dev = .read(rdr) },
.ext_char_dev => .{ .ext_char_dev = .read(rdr) },
.ext_fifo => .{ .ext_fifo = .read(rdr) },
.ext_socket => .{ .ext_socket = .read(rdr) },
.dir => .{ .dir = try .read(rdr) },
.file => .{ .file = try .read(alloc, rdr, block_size) },
.symlink => .{ .symlink = try .read(alloc, rdr) },
.block_dev => .{ .block_dev = try .read(rdr) },
.char_dev => .{ .char_dev = try .read(rdr) },
.fifo => .{ .fifo = try .read(rdr) },
.socket => .{ .socket = try .read(rdr) },
.ext_dir => .{ .ext_dir = try .read(rdr) },
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
.ext_socket => .{ .ext_socket = try .read(rdr) },
},
};
}
pub fn deinit(self: 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
+86
View File
@@ -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,
};
+2 -2
View File
@@ -8,9 +8,9 @@ pub const Error = std.Io.Reader.StreamError || std.mem.Allocator.Error;
/// The actual decompression function.
/// If the given decompressor is null, then the decompression should be done "stateless" without lasting allocations.
decomp_fn: *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);
}
+6 -6
View File
@@ -15,14 +15,14 @@ const This = @This();
alloc: std.mem.Allocator,
rdr: *Reader,
decomp: *Decompressor,
decomp: *const Decompressor,
buf: [8192]u8 = undefined,
interface: Reader,
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 .{
.alloc = alloc,
.rdr = rdr,
@@ -68,22 +68,22 @@ fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
self.interface.seek += 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);
if (rdr.end == rdr.seek) self.advance() catch |err| {
self.err = err;
return StreamError.ReadFailed;
return error.ReadFailed;
};
if (@intFromEnum(limit) == 0) return 0;
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
rdr.seek += to_skip;
return to_skip;
}
fn readVec(rdr: *Reader, vec: [][]u8) error{ EndOfStream, ReadFailed }!usize {
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
const self: *This = @fieldParentPtr("interface", rdr);
if (rdr.end == rdr.seek) self.advance() catch |err| {
self.err = err;
return StreamError.ReadFailed;
return error.ReadFailed;
};
var cur_red: usize = 0;
for (vec) |s| {
+25
View File
@@ -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);
}