A BUNCH OF STUFF
Metadata reading Directory reading Decompile cache Actual SfsFile implementation Finished Inode implementation Actually build unsquashfs
This commit is contained in:
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
};
|
||||||
@@ -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
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user