From ab606bdfa5458124a48fc48a4280e34e082a2552 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Fri, 1 May 2026 07:05:52 -0500 Subject: [PATCH] Fished decompression (maybe) Added the necessary skeleton functions to re-add test --- build.zig | 28 ++++--------- src/archive.zig | 68 +++++++++++++++++++++++++++++- src/bin/unsquashfs.zig | 2 +- src/decomp/lzma.zig | 77 ++++++++++++++++++++++++++++++++++ src/decomp/xz.zig | 77 ++++++++++++++++++++++++++++++++++ src/decomp/zlib.zig | 88 +++++++++++++++++++++++---------------- src/decomp/zstd.zig | 73 ++++++++++++++++++++++++++++++++ src/file.zig | 48 +++++++++++++++++++++ src/inode.zig | 39 ++++++++++------- src/test.zig | 86 ++++++++++++++++++++++++++++++++++++++ src/util/decompressor.zig | 4 +- src/util/metadata.zig | 12 +++--- src/util/misc.zig | 25 +++++++++++ 13 files changed, 547 insertions(+), 80 deletions(-) create mode 100644 src/decomp/lzma.zig create mode 100644 src/decomp/xz.zig create mode 100644 src/decomp/zstd.zig create mode 100644 src/file.zig create mode 100644 src/util/misc.zig diff --git a/build.zig b/build.zig index 1a17b2c..9437a6b 100644 --- a/build.zig +++ b/build.zig @@ -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); } diff --git a/src/archive.zig b/src/archive.zig index 2c5b1cc..5717c46 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -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, diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig index a3591fb..6e4d810 100644 --- a/src/bin/unsquashfs.zig +++ b/src/bin/unsquashfs.zig @@ -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 { diff --git a/src/decomp/lzma.zig b/src/decomp/lzma.zig new file mode 100644 index 0000000..63ae5b2 --- /dev/null +++ b/src/decomp/lzma.zig @@ -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; +} diff --git a/src/decomp/xz.zig b/src/decomp/xz.zig new file mode 100644 index 0000000..dfa7c75 --- /dev/null +++ b/src/decomp/xz.zig @@ -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; +} diff --git a/src/decomp/zlib.zig b/src/decomp/zlib.zig index a8f123f..7907883 100644 --- a/src/decomp/zlib.zig +++ b/src/decomp/zlib.zig @@ -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); } diff --git a/src/decomp/zstd.zig b/src/decomp/zstd.zig new file mode 100644 index 0000000..7700044 --- /dev/null +++ b/src/decomp/zstd.zig @@ -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); +} diff --git a/src/file.zig b/src/file.zig new file mode 100644 index 0000000..0c92802 --- /dev/null +++ b/src/file.zig @@ -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; +} diff --git a/src/inode.zig b/src/inode.zig index 759cb6c..bcbe5d8 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -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 diff --git a/src/test.zig b/src/test.zig index e69de29..4e4d06c 100644 --- a/src/test.zig +++ b/src/test.zig @@ -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, +}; diff --git a/src/util/decompressor.zig b/src/util/decompressor.zig index 8b0ad00..ff4a491 100644 --- a/src/util/decompressor.zig +++ b/src/util/decompressor.zig @@ -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); } diff --git a/src/util/metadata.zig b/src/util/metadata.zig index 56e22b0..63c0955 100644 --- a/src/util/metadata.zig +++ b/src/util/metadata.zig @@ -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| { diff --git a/src/util/misc.zig b/src/util/misc.zig new file mode 100644 index 0000000..6666caa --- /dev/null +++ b/src/util/misc.zig @@ -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); +}