A BUNCH OF STUFF

Metadata reading
Directory reading
Decompile cache
Actual SfsFile implementation
Finished Inode implementation
Actually build unsquashfs
This commit is contained in:
Caleb Gardner
2026-05-29 06:20:06 -05:00
parent 2c47c7492e
commit 2cb0863cc1
7 changed files with 851 additions and 31 deletions
+23 -1
View File
@@ -36,7 +36,7 @@ pub fn build(b: *std.Build) !void {
const deps = try getDependencies(b, optimize, target, allow_lzo); const deps = try getDependencies(b, optimize, target, allow_lzo);
const lib = b.addLibrary(.{ const lib = b.addLibrary(.{
.name = "unsquashfs", .name = "squashfs",
.use_llvm = debug, .use_llvm = debug,
.version = version, .version = version,
.root_module = b.createModule(.{ .root_module = b.createModule(.{
@@ -54,6 +54,23 @@ pub fn build(b: *std.Build) !void {
for (deps) |d| for (deps) |d|
lib.root_module.linkLibrary(d); lib.root_module.linkLibrary(d);
b.installArtifact(lib);
const exe = b.addExecutable(.{
.name = "unsquashfs",
.use_llvm = debug,
.version = version,
.root_module = b.createModule(.{
.optimize = optimize,
.target = target,
.root_source_file = b.path("src/bin/unsquashfs.zig"),
.valgrind = debug,
}),
});
exe.root_module.linkLibrary(lib);
b.installArtifact(exe);
const lib_test = b.addTest(.{ const lib_test = b.addTest(.{
.name = "squashfs-test", .name = "squashfs-test",
.root_module = lib.root_module, .root_module = lib.root_module,
@@ -66,9 +83,14 @@ pub fn build(b: *std.Build) !void {
.name = "squashfs-check", .name = "squashfs-check",
.root_module = lib.root_module, .root_module = lib.root_module,
}); });
const exe_check = b.addLibrary(.{
.name = "unsquashfs-check",
.root_module = exe.root_module,
});
const check = b.step("check", "Check if squashfs compiles"); const check = b.step("check", "Check if squashfs compiles");
check.dependOn(&lib_check.step); check.dependOn(&lib_check.step);
check.dependOn(&exe_check.step);
} }
fn getDependencies(b: *Build, optimize: std.builtin.OptimizeMode, target: Build.ResolvedTarget, allow_lzo: bool) ![]*Build.Step.Compile { fn getDependencies(b: *Build, optimize: std.builtin.OptimizeMode, target: Build.ResolvedTarget, allow_lzo: bool) ![]*Build.Step.Compile {
+79 -13
View File
@@ -4,28 +4,83 @@ const File = Io.File;
const MemoryMap = File.MemoryMap; const MemoryMap = File.MemoryMap;
const Decomp = @import("decomp.zig"); const Decomp = @import("decomp.zig");
const DecompCache = @import("decomp_cache.zig");
const ExtractionOptions = @import("options.zig"); const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const SfsFile = @import("file.zig"); const SfsFile = @import("file.zig");
const Archive = @This(); const Archive = @This();
map: MemoryMap, const CACHE_MEM_MAX = 1024 * 1024 * 1024;
decomp: Decomp.Fn, super: Superblock,
pub fn init(io: Io, fil: File) !Archive { cache: DecompCache,
return initAdvanced(io, fil, 0);
pub fn init(alloc: std.mem.Allocator, io: Io, fil: File) !Archive {
return initAdvanced(alloc, io, fil, 0, 0);
}
pub fn initAdvanced(alloc: std.mem.Allocator, io: Io, fil: File, offset: u64, cache_memory_max: u64) !Archive {
var rdr = fil.reader(io, &[0]u8{});
try rdr.seekTo(offset);
var super: Superblock = undefined;
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
try super.validate();
const map = try fil.createMemoryMap(io, .{
.offset = offset,
.len = super.size,
.protection = .{ .read = true },
});
return .{
.super = super,
.cache = .init(
alloc,
map,
super.compression,
if (cache_memory_max != 0)
cache_memory_max
else
@min(CACHE_MEM_MAX, (try std.process.totalSystemMemory()) / 2),
),
};
} }
pub fn initAdvanced(io: Io, fil: File, offset: u64) !Archive {}
pub fn deinit(self: *Archive, io: Io) void { pub fn deinit(self: *Archive, io: Io) void {
self.map.destroy(io); self.cache.deinit(io);
} }
pub fn root(self: Archive, alloc: std.mem.Allocator) !SfsFile {} pub fn root(self: *Archive, alloc: std.mem.Allocator, io: Io) !SfsFile {
pub fn open(self: Archive, alloc: std.mem.Allocator, filepath: []const u8) !SfsFile {} const inode: Inode = try .initRef(
alloc,
io,
&self.cache,
self.super.inode_start,
self.super.block_size,
self.super.root_ref,
);
return .init(alloc, self, inode, "");
}
pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile {
const path = std.mem.trim(u8, filepath, "/");
pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {} var root_file = try self.root(alloc, io);
if (path.len == 0 or path[0] == '.') return root_file;
defer root_file.deinit();
return root_file.open(alloc, io, filepath);
}
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {
_ = self;
_ = alloc;
_ = io;
_ = ext_loc;
_ = options;
return error.TODO;
}
// Superblock // Superblock
@@ -37,7 +92,9 @@ pub const Superblock = extern struct {
frag_count: u32, frag_count: u32,
compression: Decomp.Enum, compression: Decomp.Enum,
block_log: u16, block_log: u16,
flags: packed struct(u16) {}, flags: packed struct(u16) {
TODO: u16,
},
id_count: u16, id_count: u16,
ver_maj: u16, ver_maj: u16,
ver_min: u16, ver_min: u16,
@@ -49,6 +106,15 @@ pub const Superblock = extern struct {
dir_start: u64, dir_start: u64,
frag_start: u64, frag_start: u64,
export_start: u64, export_start: u64,
pub fn validate(self: Superblock) !void {
if (self.magic != std.mem.readInt(u32, "hsqs", .little))
return error.BadMagic;
if (self.ver_maj != 4 or self.ver_min != 0)
return error.InvalidVersion;
if (self.block_log != std.math.log2(self.block_size))
return error.BadBlockLog;
}
}; };
// Test // Test
@@ -61,7 +127,7 @@ test "Basics" {
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io); defer archive_file.close(io);
var arc: Archive = .init(io, archive_file); var arc: Archive = try .init(io, archive_file);
defer arc.deinit(io); defer arc.deinit(io);
var root_file = try arc.root(alloc); var root_file = try arc.root(alloc);
@@ -77,7 +143,7 @@ test "SingleFileExtraction" {
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io); defer archive_file.close(io);
var arc: Archive = .init(io, archive_file); var arc: Archive = try .init(io, archive_file);
defer arc.deinit(io); defer arc.deinit(io);
var ext_file = try arc.open(alloc, TestFile); var ext_file = try arc.open(alloc, TestFile);
@@ -94,7 +160,7 @@ test "FullExtraction" {
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io); defer archive_file.close(io);
var arc: Archive = .init(io, archive_file); var arc: Archive = try .init(io, archive_file);
defer arc.deinit(io); defer arc.deinit(io);
try arc.extract(alloc, io, TestFullExtractLocation, .default); try arc.extract(alloc, io, TestFullExtractLocation, .default);
+116
View File
@@ -0,0 +1,116 @@
const std = @import("std");
const Io = std.Io;
const File = Io.File;
const MemoryMap = File.MemoryMap;
const Atomic = std.atomic.Value;
const Decomp = @import("decomp.zig");
const DecompCache = @This();
alloc: std.mem.Allocator,
map: MemoryMap,
decomp_fn: Decomp.Fn,
cache: std.AutoHashMap(u64, Cache),
mut: std.Io.RwLock = .init,
cond: std.Io.Condition = .init,
max_mem: u64,
cur_mem: u64 = 0,
pub fn init(alloc: std.mem.Allocator, map: MemoryMap, compression: Decomp.Enum, max_mem: u64) DecompCache {
return .{
.alloc = alloc,
.map = map,
.decomp_fn = try Decomp.DecompFn(compression),
.cache = .init(alloc),
.max_mem = max_mem,
};
}
pub fn deinit(self: *DecompCache, io: Io) void {
self.mut.lockUncancelable(io);
var iter = self.cache.valueIterator();
while (iter.next()) |v|
self.alloc.free(v.data);
self.cache.deinit();
}
pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_size: u32) ![]u8 {
{
try self.mut.lockShared(io);
defer self.mut.unlockShared(io);
const cache = self.cache.getPtr(offset);
if (cache != null) {
_ = cache.?.usage.fetchAdd(1, .acquire);
return cache.?.data;
}
}
try self.mut.lock(io);
defer self.mut.unlock(io);
const cache = try self.cache.getOrPut(offset);
if (cache.found_existing) {
_ = cache.?.usage.fetchAdd(1, .acquire);
return cache.?.data;
}
errdefer self.cache.removeByPtr(cache.key_ptr);
try self.ensureSpace(io, max_size);
var out = try self.alloc.alloc(u8, max_size);
errdefer self.alloc.free(out);
const decomp_size = try self.decomp_fn(self.alloc, self.map.memory[offset..][0..compressed_size], out);
if (decomp_size != max_size) {
if (!self.alloc.resize(out, decomp_size)) {
const new_out = try self.alloc.alloc(u8, decomp_size);
@memcpy(new_out, out[0..decomp_size]);
out = new_out;
} else {
out.len = decomp_size;
}
}
self.cur_mem += decomp_size;
cache.value_ptr.data = out;
_ = cache.value_ptr.usage.fetchAdd(1, .acquire);
return out;
}
pub fn finished(self: *DecompCache, io: Io, offset: u64) void {
const cache = self.cache.getPtr(offset);
if (cache == null) {
std.debug.print("Finished using cache, but cache does not exist: {}\n", .{offset});
return;
}
const use = cache.?.usage.fetchSub(1, .acquire);
if (use == 0)
self.cond.broadcast(io);
}
fn ensureSpace(self: *DecompCache, io: Io, size: u64) !void {
while (self.cur_mem + size > self.max_mem) {
var iter = self.cache.valueIterator();
while (iter.next()) |cache| {
if (cache.usage.load(.unordered) == 0) {
self.alloc.free(cache.data);
self.cur_mem -= cache.data.len;
if (self.cur_mem + size <= self.max_mem) return;
}
}
if (self.cur_mem + size <= self.max_mem) return;
try self.cond.wait(io, self.mut.mutex);
}
}
// Types
const Cache = struct {
data: []u8,
usage: Atomic(u32),
};
+81
View File
@@ -0,0 +1,81 @@
const std = @import("std");
const Io = std.Io;
const Reader = Io.Reader;
const Inode = @import("inode.zig");
const Directory = @This();
entries: []Entry,
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u32) !Directory {
if (size <= 3) return .{ .entries = &[0]Entry{} };
var entries: std.ArrayList(Entry) = try .initCapacity(alloc, 50);
errdefer {
for (entries.items) |ent|
ent.deinit(alloc);
entries.deinit(alloc);
}
var read: u32 = 3;
while (read < size) {
var hdr: Header = undefined;
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
read += @sizeOf(Header);
try entries.ensureUnusedCapacity(alloc, hdr.count + 1);
for (0..hdr.count + 1) |_| {
var raw: RawEntry = undefined;
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
const name = try alloc.alloc(u8, raw.name_size + 1);
errdefer alloc.free(name);
try rdr.readSliceEndian(u8, name, .little);
entries.appendAssumeCapacity(.{
.inode_num = if (raw.inode_num_offset > 0)
hdr.inode_num + @abs(raw.inode_num_offset)
else
hdr.inode_num - @abs(raw.inode_num_offset),
.block_start = hdr.block_start,
.block_offset = raw.block_offset,
.type = raw.type,
.name = name,
});
}
}
return entries.toOwnedSlice(alloc);
}
pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
for (self.entries) |entry|
entry.deinit(alloc);
alloc.free(self.entries);
}
// Types
pub const Entry = struct {
inode_num: u32,
block_start: u32,
block_offset: u16,
type: Inode.Enum,
name: []const u8,
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
};
const Header = extern struct {
count: u32,
block_start: u32,
inode_num: u32,
};
const RawEntry = extern struct {
block_offset: u16,
inode_num_offset: i16,
type: Inode.Enum,
name_size: u16,
};
+88 -3
View File
@@ -1,7 +1,92 @@
const SfsFile = @This();
alloc: std.mem.Allocator,
archive: *Archive,
inode: Inode,
name: []const u8,
/// The given allocator must have been used to create the Inode and name.
pub fn init(alloc: std.mem.Allocator, archive: *Archive, inode: Inode, name: []const u8) SfsFile {
return .{
.alloc = alloc,
.archive = archive,
.inode = inode,
.name = name,
};
}
pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry: Directory.Entry) !SfsFile {
const new_name = try alloc.alloc(u8, entry.name.len);
defer alloc.free(new_name);
@memcpy(new_name, entry.name);
return .{
.alloc = alloc,
.archive = archive,
.inode = try .initDirEntry(
alloc,
io,
archive.cache,
archive.super.inode_start,
archive.super.block_size,
entry,
),
.name = new_name,
};
}
/// Creates a new copy of the given SfsFile using the given allocator
pub fn copy(self: SfsFile, alloc: std.mem.Allocator) !SfsFile {
const new_name = try alloc(u8, self.name.len);
errdefer alloc.free(new_name);
return .{
.alloc = alloc,
.archive = self.archive,
.inode = try self.inode.copy(alloc),
.name = new_name,
};
}
pub fn deinit(self: SfsFile) void {
self.inode.deinit(self.alloc);
self.alloc.free(self.name);
}
/// Attempts to open the filepath if the SfsFile is a directory.
/// If the given path refers to itself (such as "" or "."), a copied SfsFile is returned.
pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile {
const path = std.mem.trim(u8, filepath, "/");
const first_element: []const u8 = std.mem.sliceTo(path, '/');
const dir: Directory = try self.inode.directory(alloc, io, self.archive.cache, self.archive.super.dir_start);
defer dir.deinit(alloc);
var cur_slice = dir.entries;
var idx: usize = undefined;
while (cur_slice.len > 0) {
idx = cur_slice.len / 2;
switch (std.mem.order(u8, first_element, cur_slice[idx].name)) {
.eq => break,
.lt => cur_slice = cur_slice[0..idx],
.gt => cur_slice = cur_slice[idx..],
}
} else {
return error.NotFound;
}
if (first_element.len == path) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
if (cur_slice[idx].type != .dir) return error.NotFound;
const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
defer tmp_file.deinit();
return tmp_file.open(alloc, io, path[first_element.len..]);
}
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Archive = @import("archive.zig");
const Directory = @import("directory.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
name: []const u8,
inode: Inode,
+355 -14
View File
@@ -1,10 +1,109 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io;
const Reader = Io.Reader;
const DecompCache = @import("decomp_cache.zig");
const Directory = @import("directory.zig");
const MetadataReader = @import("meta_rdr.zig");
const Inode = @This(); const Inode = @This();
hdr: Header, hdr: Header,
data: Data, data: Data,
/// Read an inode given an inode Ref.
pub fn initRef(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, ref: Ref) !Inode {
var meta: MetadataReader = .init(io, cache, inode_start + ref.block_start);
defer meta.deinit(io);
try meta.interface.discardAll(ref.block_offset);
return .init(alloc, &meta.interface, block_size);
}
pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, entry: Directory.Entry) !Inode {
var meta: MetadataReader = .init(io, cache, inode_start + entry.block_start);
defer meta.deinit(io);
try meta.interface.discardAll(entry.block_offset);
return .init(alloc, &meta.interface, block_size);
}
/// Read the inode from the given Reader.
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
var hdr: Header = undefined;
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
const data: Data = switch (hdr.type) {
.dir => .{ .dir = try .init(rdr) },
.file => .{ .file = try .init(alloc, rdr, block_size) },
.symlink => .{ .symlink = try .init(alloc, rdr) },
.block_dev => .{ .block_dev = try .init(rdr) },
.char_dev => .{ .char_dev = try .init(rdr) },
.fifo => .{ .fifo = try .init(rdr) },
.socket => .{ .socket = try .init(rdr) },
.ext_dir => .{ .ext_dir = try .init(rdr) },
.ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = try .init(alloc, rdr) },
.ext_block_dev => .{ .ext_block_dev = try .init(rdr) },
.ext_char_dev => .{ .ext_char_dev = try .init(rdr) },
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
.ext_socket => .{ .ext_socket = try .init(rdr) },
};
return .{
.hdr = hdr,
.data = data,
};
}
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
var new_inode = self;
switch (new_inode.data) {
.file => |*f| {
if (f.blocks.len > 0) {
f.blocks = try alloc.alloc(DataBlock, f.blocks.len);
@memcpy(f.blocks, self.data.file.blocks);
}
},
.ext_file => |*f| {
if (f.blocks.len > 0) {
f.blocks = try alloc.alloc(DataBlock, f.blocks.len);
@memcpy(f.blocks, self.data.ext_file.blocks);
}
},
.symlink => |*s| {
s.target = try alloc.alloc(u8, s.target.len);
@memcpy(s.target, self.data.symlink.target);
},
.ext_symlink => |*s| {
s.target = try alloc.alloc(u8, s.target.len);
@memcpy(s.target, self.data.ext_symlink.target);
},
}
return new_inode;
}
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) {
.file => |f| f.deinit(alloc),
.ext_file => |f| f.deinit(alloc),
.symlink => |s| s.deinit(alloc),
.ext_symlink => |s| s.deinit(alloc),
else => {},
}
}
// Utility functions
pub fn directory(self: Inode, alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64) !Directory {
return switch (self.data) {
.dir => |d| readDirectory(alloc, io, cache, dir_start, d),
.ext_dir => |d| readDirectory(alloc, io, cache, dir_start, d),
else => error.NotDirectory,
};
}
fn readDirectory(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64, d: anytype) !Directory {
var meta: MetadataReader = .init(io, cache, dir_start + d.block_start);
defer meta.deinit(io);
try meta.interface.discardAll(d.block_offset);
return .init(alloc, &meta.interface, d.size);
}
// Types // Types
pub const Ref = packed struct(u64) { pub const Ref = packed struct(u64) {
@@ -40,18 +139,260 @@ pub const Header = extern struct {
}; };
pub const Data = union(Enum) { pub const Data = union(Enum) {
dir: , dir: Dir,
file: , file: File,
symlink: , symlink: Symlink,
block_dev: , block_dev: Device,
char_dev: , char_dev: Device,
fifo: , fifo: IPC,
socket: , socket: IPC,
ext_dir: , ext_dir: ExtDir,
ext_file: , ext_file: ExtFile,
ext_symlink: , ext_symlink: ExtSymlink,
ext_block_dev: , ext_block_dev: ExtDevice,
ext_char_dev: , ext_char_dev: ExtDevice,
ext_fifo: , ext_fifo: ExtIPC,
ext_socket: , ext_socket: ExtIPC,
};
pub const DataBlock = packed struct(u32) {
size: u24,
uncompressed: bool,
_: u7,
};
const Dir = extern struct {
block_start: u32,
hard_links: u32,
size: u16,
block_offset: u16,
parent: u32,
const Self = @This();
fn init(rdr: *Reader) !Self {
var dir: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
return dir;
}
};
const ExtDir = extern struct {
hard_links: u32,
size: u32,
block_start: u32,
parent: u32,
idx_count: u16,
block_offset: u16,
xattr_idx: u32,
// []DirIndex
const Self = @This();
fn init(rdr: *Reader) !Self {
var dir: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
return dir;
}
};
const File = struct {
data_start: u32,
frag_idx: u32,
frag_offset: u32,
size: u32,
blocks: []DataBlock,
const Raw = extern struct {
data_start: u32,
frag_idx: u32,
frag_offset: u32,
size: u32,
};
const Self = @This();
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self {
var raw: Raw = undefined;
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
var blocks_num = raw.size / block_size;
if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0)
blocks_num += 1;
const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num);
errdefer alloc.free(blocks);
try rdr.readSliceEndian(DataBlock, blocks, .little);
return .{
.data_start = raw.data_start,
.frag_idx = raw.frag_idx,
.frag_offset = raw.frag_offset,
.size = raw.size,
.blocks = blocks,
};
}
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
alloc.free(self.blocks);
}
};
const ExtFile = struct {
data_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_offset: u32,
xattr_idx: u32,
blocks: []DataBlock,
const Raw = extern struct {
data_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_offset: u32,
xattr_idx: u32,
};
const Self = @This();
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self {
var raw: Raw = undefined;
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
var blocks_num = raw.size / block_size;
if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0)
blocks_num += 1;
const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num);
errdefer alloc.free(blocks);
try rdr.readSliceEndian(DataBlock, blocks, .little);
return .{
.data_start = raw.data_start,
.size = raw.size,
.sparse = raw.sparse,
.hard_links = raw.hard_links,
.frag_idx = raw.frag_idx,
.frag_offset = raw.frag_offset,
.xattr_idx = raw.xattr_idx,
.blocks = blocks,
};
}
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
alloc.free(self.blocks);
}
};
const Symlink = struct {
hard_links: u32,
target: []const u8,
const Raw = extern struct {
hard_links: u32,
target_size: u32,
};
const Self = @This();
pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self {
var raw: Raw = undefined;
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
const target = try alloc.alloc(u8, raw.target_size);
try rdr.readSliceEndian(u8, target, .little);
return .{
.hard_links = raw.hard_links,
.target = target,
};
}
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
alloc.free(self.target);
}
};
const ExtSymlink = struct {
hard_links: u32,
xattr_idx: u32,
target: []const u8,
const Raw = extern struct {
hard_links: u32,
target_size: u32,
};
const Self = @This();
pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self {
var raw: Raw = undefined;
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
const target = try alloc.alloc(u8, raw.target_size);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
var xattr_idx: u32 = undefined;
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
return .{
.hard_links = raw.hard_links,
.target = target,
.xattr_idx = xattr_idx,
};
}
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
alloc.free(self.target);
}
};
const Device = extern struct {
hard_links: u32,
device: u32,
const Self = @This();
fn init(rdr: *Reader) !Self {
var dir: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
return dir;
}
};
const ExtDevice = extern struct {
hard_links: u32,
device: u32,
xattr_idx: u32,
const Self = @This();
fn init(rdr: *Reader) !Self {
var dir: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
return dir;
}
};
const IPC = extern struct {
hard_links: u32,
const Self = @This();
fn init(rdr: *Reader) !Self {
var dir: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
return dir;
}
};
const ExtIPC = extern struct {
hard_links: u32,
xattr_idx: u32,
const Self = @This();
fn init(rdr: *Reader) !Self {
var dir: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
return dir;
}
}; };
+109
View File
@@ -0,0 +1,109 @@
const std = @import("std");
const Io = std.Io;
const Reader = Io.Reader;
const Writer = Io.Writer;
const Limit = Io.Limit;
const DecompCache = @import("decomp_cache.zig");
const MetadataReader = @This();
io: Io,
cache: *DecompCache,
cur_offset: u64 = 0,
next_offset: u64,
interface: Reader = .{
.buffer = &[0]u8{},
.end = 0,
.seek = 0,
.vtable = &.{
.stream = stream,
.discard = discard,
.readVec = readVec,
},
},
pub fn init(io: Io, cache: *DecompCache, start: u64) MetadataReader {
return .{
.io = io,
.cache = cache,
.next_offset = start,
};
}
pub fn deinit(self: *MetadataReader, io: Io) void {
self.cache.finished(io, self.cur_offset);
}
fn advance(self: *MetadataReader) !void {
self.cache.finished(self.io, self.cur_offset);
self.interface.seek = 0;
errdefer self.interface.end = 0;
const hdr: Header = @bitCast(std.mem.readInt(u16, self.cache.map.memory[self.next_offset..][0..2], .little));
self.cur_offset = self.next_offset + 2;
self.next_offset = self.cur_offset + hdr.size;
if (hdr.uncompressed) {
self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..hdr.size];
self.interface.end = hdr.size;
return;
}
self.interface.buffer = try self.cache.get(self.io, self.cur_offset, hdr.size, 8192);
self.interface.end = self.interface.buffer.len;
}
fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
if (r.seek >= r.end) {
const self: *MetadataReader = @fieldParentPtr("interface", r);
self.advance() catch |err| {
std.debug.print("error advancing metadata reader: {}\n", .{err});
return Reader.Error.ReadFailed;
};
}
const to_write = @min(r.end - r.seek, @intFromEnum(limit));
const wrote = try w.write(r.buffer[r.seek..][0..to_write]);
r.seek += wrote;
return wrote;
}
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
if (r.seek >= r.end) {
const self: *MetadataReader = @fieldParentPtr("interface", r);
self.advance() catch |err| {
std.debug.print("error advancing metadata reader: {}\n", .{err});
return Reader.Error.ReadFailed;
};
}
const to_discard = @min(r.end - r.seek, @intFromEnum(limit));
r.seek += to_discard;
return to_discard;
}
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
if (r.seek >= r.end) {
const self: *MetadataReader = @fieldParentPtr("interface", r);
self.advance() catch |err| {
std.debug.print("error advancing metadata reader: {}\n", .{err});
return Reader.Error.ReadFailed;
};
}
var total: usize = 0;
for (vec) |v| {
const to_copy = @min(r.end - r.seek, v.len);
@memcpy(v[0..to_copy], r.buffer[r.seek..][0..to_copy]);
r.seek += to_copy;
total += to_copy;
if (r.seek >= r.end)
break;
}
return total;
}
// Types
const Header = packed struct(u16) {
size: u15,
uncompressed: bool,
};