Remove DecompMgr in favor of a much simpler fn ptr.

Moved more functionality to Inode instead of File.
Started doing some optimization around allocation.
Slight rework of ExtractionOptions.
This commit is contained in:
Caleb J. Gardner
2026-02-07 05:04:22 -06:00
parent a316ba569f
commit 75502da1d0
10 changed files with 299 additions and 539 deletions
+1 -1
View File
@@ -4,4 +4,4 @@ This is my experiments to learn Zig. Might amount to something. Might not.
## Current State
Kinda works as a library, but currently has known memory leaks. `unsquashfs` is missing a lot of features (and will probably never match the official unsquashfs). Extraction is stupidly slow and uses too many resources.
Kinda works as a library, but currently has known memory leaks. `unsquashfs` is missing a lot of features (and will probably never match the official unsquashfs). Extraction is stupidly slow and uses too many resources. Only properly work on Linux, any other OSes probably won't work fully.
+13 -8
View File
@@ -4,7 +4,7 @@
const std = @import("std");
const File = std.fs.File;
const DecompMgr = @import("decomp.zig");
const Decomp = @import("decomp.zig");
const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig");
const InodeRef = Inode.Ref;
@@ -40,7 +40,7 @@ super: Superblock,
setup: bool = false,
decomp: DecompMgr = undefined,
decomp: Decomp.DecompFn,
frag_table: Table(FragEntry) = undefined,
id_table: Table(u16) = undefined,
@@ -72,6 +72,13 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u
// .fixed_buf = fixed_buf,
.thread_count = threads,
.fil = .init(fil, offset),
.decomp = switch (super.compression) {
.gzip => Decomp.gzipDecompress,
.lzma => Decomp.lzmaDecompress,
.xz => Decomp.xzDecompress,
.zstd => Decomp.zstdDecompress,
else => return error.UnsupportedCompressionType,
},
.super = super,
};
@@ -79,7 +86,6 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u
pub fn deinit(self: *Archive) void {
// self.parent_alloc.free(self.fixed_buf);
if (self.setup) {
self.decomp.deinit();
self.frag_table.deinit();
self.export_table.deinit();
self.id_table.deinit();
@@ -92,10 +98,9 @@ pub fn allocator(self: *Archive) std.mem.Allocator {
fn setupValues(self: *Archive) !void {
const alloc = self.allocator();
self.decomp = try .init(alloc, self.super.compression, self.super.block_size, self.thread_count);
self.frag_table = try .init(alloc, self.fil, &self.decomp, self.super.frag_start, self.super.frag_count);
self.id_table = try .init(alloc, self.fil, &self.decomp, self.super.id_start, self.super.id_count);
self.export_table = try .init(alloc, self.fil, &self.decomp, self.super.export_start, self.super.inode_count);
self.frag_table = try .init(alloc, self.fil, self.decomp, self.super.frag_start, self.super.frag_count);
self.id_table = try .init(alloc, self.fil, self.decomp, self.super.id_start, self.super.id_count);
self.export_table = try .init(alloc, self.fil, self.decomp, self.super.export_start, self.super.inode_count);
self.setup = true;
}
@@ -121,7 +126,7 @@ pub fn inode(self: *Archive, num: u32) !Inode {
pub fn root(self: *Archive) !SfsFile {
if (!self.setup) try self.setupValues();
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
try meta.interface.discardAll(self.super.root_ref.block_offset);
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
return .init(self, in, "");
+23 -197
View File
@@ -1,21 +1,5 @@
//! Decompression manager. Can decompress either from an Io.Reader or from a byte slice.
const std = @import("std");
const compress = std.compress;
const Reader = std.Io.Reader;
const Thread = std.Thread;
const Futex = Thread.Futex;
const Mutex = Thread.Mutex;
const Condition = Thread.Condition;
const Node = std.DoublyLinkedList.Node;
const Atomic = std.atomic.Value(u32);
const DecompError = error{
ThreadClosed,
LzoUnsupported,
Lz4Unsupported,
};
pub const CompressionType = enum(u16) {
gzip = 1,
@@ -26,193 +10,35 @@ pub const CompressionType = enum(u16) {
zstd,
};
pub const DecompThread = struct {
mgr: *DecompMgr,
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
/// Current thread status & signal value via Futex.
/// 0 - Unstarted, 1 - Waiting, 2 - Working, 3 - Closed,
status: Atomic = .{ .raw = 0 },
thr: Thread = undefined,
node: Node = .{},
buf: []u8,
// pub const DecompressError = error{
// ReadFailed,
// anyerror,
// };
dat: []u8 = &[0]u8{},
rdr: ?*Reader = null,
res: []u8 = &[0]u8{},
res_size: anyerror!usize = 0,
pub fn init(mgr: *DecompMgr) !DecompThread {
return .{
.mgr = mgr,
.buf = switch (mgr.comp_type) {
.gzip => try mgr.alloc.alloc(u8, compress.flate.max_window_len),
.zstd => try mgr.alloc.alloc(u8, compress.zstd.default_window_len + compress.zstd.block_size_max),
.lzma, .xz => &[0]u8{},
else => unreachable,
},
};
pub fn gzipDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
_ = alloc;
var rdr: Reader = .fixed(in);
var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, &[0]u8{});
return decomp.reader.readSliceShort(out);
}
pub fn close(self: *DecompThread) void {
if (self.status.raw == 0) return;
while (self.status.raw == 2) Futex.wait(&self.status, 2);
self.status.store(3, .release);
Futex.wake(&self.status, 1);
self.thr.join();
self.mgr.alloc.free(self.buf);
pub fn lzmaDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
var rdr: Reader = .fixed(in);
var decomp = try std.compress.lzma.decompress(alloc, rdr.adaptToOldInterface());
return decomp.read(out);
}
pub fn submitData(self: *DecompThread, dat: []u8, res: []u8) anyerror!usize {
if (self.status.raw == 3) return DecompError.ThreadClosed;
if (self.status.raw == 0) {
self.thr = try .spawn(.{}, thread, .{self});
}
self.dat = dat;
defer self.dat = &[0]u8{};
self.res = res;
self.status.raw = 2;
while (self.status.raw == 2) Futex.wait(&self.status, 2);
return self.res_size;
}
pub fn submitReader(self: *DecompThread, rdr: *Reader, res: []u8) anyerror!usize {
if (self.status.raw == 3) return DecompError.ThreadClosed;
if (self.status.raw == 0) {
self.thr = try .spawn(.{}, thread, .{self});
}
self.rdr = rdr;
defer self.rdr = null;
self.res = res;
self.status.store(2, .release);
Futex.wake(&self.status, 1);
while (self.status.raw == 2) Futex.wait(&self.status, 2);
return self.res_size;
pub fn xzDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
var rdr: Reader = .fixed(in);
var decomp = try std.compress.xz.decompress(alloc, rdr.adaptToOldInterface());
return decomp.read(out);
}
pub fn thread(self: *DecompThread) void {
const comp_type = self.mgr.comp_type;
while (self.status.raw != 3) {
while (self.status.raw == 1) Futex.wait(&self.status, 1);
if (self.status.raw == 3) return;
var dat_rdr: Reader = .fixed(self.dat);
var rdr: *Reader = if (self.rdr != null) self.rdr.? else &dat_rdr;
self.res_size = blk: switch (comp_type) {
.gzip => {
var decomp_rdr = compress.flate.Decompress.init(rdr, .zlib, self.buf);
break :blk decomp_rdr.reader.readSliceShort(self.res) catch |err| {
break :blk decomp_rdr.err orelse err;
};
},
.lzma => {
var decomp_rdr = compress.lzma.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| {
break :blk err;
};
break :blk decomp_rdr.read(self.res);
},
.xz => {
var decomp_rdr = compress.xz.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| {
break :blk err;
};
break :blk decomp_rdr.read(self.res);
},
.zstd => {
var decomp_rdr = compress.zstd.Decompress.init(rdr, self.buf, .{});
break :blk decomp_rdr.reader.readSliceShort(self.res) catch |err| {
break :blk decomp_rdr.err orelse err;
};
},
else => unreachable,
};
const orig = self.status.swap(1, .release);
Futex.wake(&self.status, 1);
if (orig == 3) return;
}
}
};
const DecompMgr = @This();
alloc: std.mem.Allocator,
comp_type: CompressionType,
block_size: u32,
threads: []DecompThread,
queue: std.DoublyLinkedList = .{},
mut: Mutex = .{},
cond: Condition = .{},
to_start: usize,
pub fn init(alloc: std.mem.Allocator, comp_type: CompressionType, block_size: u32, threads: usize) !DecompMgr {
return switch (comp_type) {
.lzo => DecompError.LzoUnsupported,
.lz4 => DecompError.Lz4Unsupported,
else => .{
.alloc = alloc,
.comp_type = comp_type,
.block_size = block_size,
.threads = try alloc.alloc(DecompThread, threads),
.to_start = threads,
},
};
}
pub fn deinit(self: DecompMgr) void {
for (self.threads[self.to_start..]) |*t| {
t.close();
}
self.alloc.free(self.threads);
}
pub fn decompSlice(self: *DecompMgr, dat: []u8, res: []u8) !usize {
self.mut.lock();
var thr: *DecompThread = undefined;
var node = self.queue.popFirst();
if (self.node != null) {
self.mut.unlock();
thr = @fieldParentPtr("node", node.?);
} else blk: {
defer self.mut.unlock();
if (self.to_start > 0) {
self.threads[self.to_start - 1] = .init(self);
thr = &self.threads[self.to_start - 1];
self.to_start -= 1;
break :blk;
}
while (node == null) {
self.cond.wait(&self.mut);
node = self.queue.popFirst();
}
thr = @fieldParentPtr("node", node.?);
}
defer {
self.queue.append(&thr.node);
self.cond.signal();
}
return thr.submitData(dat, res);
}
pub fn decompReader(self: *DecompMgr, rdr: *Reader, res: []u8) !usize {
self.mut.lock();
var thr: *DecompThread = undefined;
var node = self.queue.popFirst();
if (node != null) {
self.mut.unlock();
thr = @fieldParentPtr("node", node.?);
} else blk: {
defer self.mut.unlock();
if (self.to_start > 0) {
self.threads[self.to_start - 1] = try .init(self);
thr = &self.threads[self.to_start - 1];
self.to_start -= 1;
break :blk;
}
while (node == null) {
self.cond.wait(&self.mut);
node = self.queue.popFirst();
}
thr = @fieldParentPtr("node", node.?);
}
defer {
self.queue.append(&thr.node);
self.cond.signal();
}
return thr.submitReader(rdr, res);
pub fn zstdDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
_ = alloc;
var rdr: Reader = .fixed(in);
var decomp = std.compress.zstd.Decompress.init(&rdr, &[0]u8{}, .{});
return decomp.reader.readSliceShort(out);
}
+9 -8
View File
@@ -30,19 +30,20 @@ pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry {
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
var hdr: Header = undefined;
var raw: RawEntry = undefined;
var out: std.ArrayList(Entry) = .empty;
errdefer {
for (out.items) |i|
i.deinit(alloc);
out.deinit(alloc);
}
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 25); // Start out with capacity instead of needing to allocate per header.
errdefer out.deinit(alloc);
while (cur_red < size) {
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
cur_red += @sizeOf(Header);
try out.ensureUnusedCapacity(alloc, hdr.num + 1);
for (0..hdr.count + 1) |_| {
const count = hdr.count + 1;
if (out.capacity < count) {
// Make sure we have at least 25 capacity past current count.
try out.ensureUnusedCapacity(alloc, ((count % 25) + 2) * 25);
}
for (0..count) |_| {
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);
const val = out.addOneAssumeCapacity();
val.* = .{
+4 -298
View File
@@ -56,28 +56,7 @@ pub fn deinit(self: SfsFile) void {
}
fn getEntries(self: SfsFile) ![]DirEntry {
if (!self.isDir()) return FileError.NotDirectory;
var block_start: u32 = undefined;
var block_offset: u16 = undefined;
var size: u32 = undefined;
switch (self.inode.data) {
.dir => |d| {
block_start = d.block_start;
block_offset = d.block_offset;
size = d.size;
},
.ext_dir => |d| {
block_start = d.block_start;
block_offset = d.block_offset;
size = d.size;
},
else => unreachable,
}
var rdr = try self.archive.fil.readerAt(self.archive.super.dir_start + block_start, &[0]u8{});
const alloc = self.archive.allocator();
var meta: MetadataReader = .init(alloc, &rdr.interface, &self.archive.decomp);
try meta.interface.discardAll(block_offset);
return DirEntry.readDir(alloc, &meta.interface, size);
return self.inode.dirEntries(self.archive);
}
pub fn ownerUid(self: SfsFile) !u16 {
@@ -99,33 +78,7 @@ pub fn isRegular(self: SfsFile) bool {
/// The returned DataReader will no longer work if the File's deinit function is called
/// or, more specifically, it's inode's deinit function is called.
pub fn dataReader(self: SfsFile) !DataReader {
if (!self.isRegular()) return FileError.NotRegularFile;
var frag_idx: u32 = undefined;
var frag_offset: u32 = undefined;
var size: u64 = undefined;
var blocks: []BlockSize = undefined;
var start: u64 = undefined;
switch (self.inode.data) {
.file => |f| {
frag_idx = f.frag_idx;
frag_offset = f.frag_block_offset;
size = f.size;
blocks = f.block_sizes;
start = f.block_start;
},
.ext_file => |f| {
frag_idx = f.frag_idx;
frag_offset = f.frag_block_offset;
size = f.size;
blocks = f.block_sizes;
start = f.block_start;
},
else => unreachable,
}
var out: DataReader = .init(self.archive, blocks, start, size);
if (frag_idx != 0xFFFFFFFF)
out.addFragment(try self.archive.frag(frag_idx), frag_offset);
return out;
return self.inode.dataReader(self.archive);
}
pub fn isDir(self: SfsFile) bool {
@@ -245,255 +198,8 @@ pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !vo
}
}
defer if (ext_path.len > path.len) alloc.free(ext_path);
var pool: std.Thread.Pool = undefined;
try pool.init(.{ .allocator = alloc, .n_jobs = 16 });
var wg: WaitGroup = .{};
defer pool.deinit();
var err: ?anyerror = null;
wg.start();
self.extractReal(ext_path, options, &pool, &wg, &err, null);
wg.wait();
if (err != null) return err.?;
}
const ParentInfo = struct {
sfs_fil: SfsFile,
path: []const u8,
mut: *Mutex,
dir_wg: *WaitGroup,
parent_wg: *WaitGroup,
options: ExtractionOptions,
err: *?anyerror,
fn finish(self: *const ParentInfo) void {
self.mut.lock();
if (!self.dir_wg.isDone()) {
self.mut.unlock();
return;
}
self.mut.unlock();
std.debug.print("finishing dir {}: {s}\n", .{ self.sfs_fil.inode.hdr.num, self.sfs_fil.name });
self.sfs_fil.archive.allocator().destroy(self.mut);
self.sfs_fil.archive.allocator().destroy(self.dir_wg);
defer self.parent_wg.finish();
var fil = std.fs.cwd().openFile(self.path, .{}) catch |err| {
std.log.err("Error opening folder {s} to set permissions: {}\n", .{ self.path, err });
self.err.* = err;
return;
};
defer fil.close();
self.sfs_fil.setPerm(fil, self.options) catch |err| {
std.log.err("Error setting permissions to {s}: {}\n", .{ self.path, err });
self.err.* = err;
return;
};
}
};
fn extractReal(self: SfsFile, path: []const u8, options: ExtractionOptions, pol: *std.Thread.Pool, wg: *WaitGroup, out_err: *?anyerror, parent: ?ParentInfo) void {
std.log.info("Extracting {s} (inode {}) to {s}\n", .{ self.name, self.inode.hdr.num, path });
defer {
if (parent != null) {
parent.?.finish();
self.archive.allocator().free(path);
self.deinit();
} else {
wg.finish();
}
}
if (out_err.* != null) {
return;
}
switch (self.inode.hdr.inode_type) {
.file, .ext_file => {
var fil = std.fs.cwd().createFile(path, .{}) catch |err| {
std.log.err("Error creating {s}: {}\n", .{ path, err });
out_err.* = err;
return;
};
defer fil.close();
var dat_rdr = self.dataReader() catch |err| {
std.log.err("Error getting data reader for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
out_err.* = err;
return;
};
defer dat_rdr.deinit();
var wrt = fil.writer(&[0]u8{});
_ = dat_rdr.interface.streamRemaining(&wrt.interface) catch |err| {
std.log.err("Error writing data for {s} (inode {}) to {s}: {}\n", .{ self.name, self.inode.hdr.num, path, err });
out_err.* = wrt.err orelse err;
return;
};
wrt.interface.flush() catch |err| {
std.log.err("Error flushing data for {s} (inode {}) to {s}: {}\n", .{ self.name, self.inode.hdr.num, path, err });
out_err.* = wrt.err orelse err;
return;
};
self.setPerm(fil, options) catch |err| {
std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err });
out_err.* = err;
return;
};
},
.symlink, .ext_symlink => {
//TODO: deal with dereference symlink options
const target_path = self.symlinkPath() catch |err| {
std.log.err("Error getting symlink target path for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
out_err.* = err;
return;
};
std.fs.cwd().symLink(target_path, path, .{}) catch |err| {
std.log.err("Error creating {s}: {}\n", .{ path, err });
out_err.* = err;
return;
};
// self.setPerm(fil, options) catch |err| {
// std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err });
// out_err.* = err;
// return;
// };
},
.block_dev,
.char_dev,
.fifo,
.ext_block_dev,
.ext_char_dev,
.ext_fifo,
=> {
var mode: u32 = undefined;
var fil_dev: u32 = 0;
switch (self.inode.hdr.inode_type) {
.block_dev, .ext_block_dev => {
mode = std.posix.DT.BLK;
fil_dev = self.devNum() catch |err| {
std.log.err("Error getting device number for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
out_err.* = err;
return;
};
},
.char_dev, .ext_char_dev => {
mode = std.posix.DT.CHR;
fil_dev = self.devNum() catch |err| {
std.log.err("Error getting device number for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err });
out_err.* = err;
return;
};
},
else => mode = std.posix.DT.FIFO,
}
const res = std.os.linux.mknod(@ptrCast(path), mode, fil_dev);
if (res != 0) {
std.log.err("Error creating device file at {s} with code {}\n", .{ path, res });
out_err.* = error.MknodError;
return;
}
const fil = std.fs.cwd().openFile(path, .{}) catch |err| {
std.log.err("Error openning {s} to set permissions: {}\n", .{ path, err });
out_err.* = err;
return;
};
defer fil.close();
self.setPerm(fil, options) catch |err| {
std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err });
out_err.* = err;
return;
};
},
.dir, .ext_dir => {
std.debug.print("starting dir {}: {s}\n", .{ self.inode.hdr.num, self.name });
if (std.fs.cwd().statFile(path)) |stat| {
if (stat.kind != .directory) {
std.log.err("{s} exists and is not a folder\n", .{path});
out_err.* = FileError.ExtractionPathExists;
return;
}
} else |err| {
if (err == error.FileNotFound) {
std.fs.cwd().makeDir(path) catch |err_2| {
std.log.err("Error creating {s}: {}\n", .{ path, err_2 });
out_err.* = err;
return;
};
} else {
std.log.err("Error checking if {s} exists: {}\n", .{ path, err });
out_err.* = err;
return;
}
}
var dir_wg: *WaitGroup = self.archive.allocator().create(WaitGroup) catch |err| {
std.log.err("Error allocating waitgroup for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
out_err.* = err;
return;
};
const parent_info: ParentInfo = .{
.sfs_fil = self,
.path = path,
.mut = self.archive.allocator().create(Mutex) catch |err| {
std.log.err("Error allocating mutex for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
out_err.* = err;
return;
},
.dir_wg = dir_wg,
.parent_wg = wg,
.options = options,
.err = out_err,
};
var iter: Iterator = self.iterate() catch |err| {
std.log.err("Error getting iterator for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
out_err.* = err;
return;
};
defer iter.deinit();
const path_has_end_sep = path[path.len - 1] == '/';
while (true) {
const iter_fil = iter.next() catch |err| {
std.log.err("Error getting next iterator value {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
out_err.* = err;
break;
};
if (iter_fil == null) break;
const fil = iter_fil.?;
dir_wg.start();
var path_len = path.len + fil.name.len;
if (!path_has_end_sep) path_len += 1;
var new_path = self.archive.allocator().alloc(u8, path_len) catch |err| {
std.log.err("Error allocating subpath for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err });
out_err.* = err;
dir_wg.finish();
break;
};
@memcpy(new_path[0..path.len], path);
@memcpy(new_path[new_path.len - fil.name.len ..], fil.name);
if (!path_has_end_sep) new_path[path.len] = '/';
if (fil.isDir()) {
fil.extractReal(new_path, options, pol, wg, out_err, parent_info);
} else {
pol.spawn(extractReal, .{
fil,
new_path,
options,
pol,
wg,
out_err,
parent_info,
}) catch |err| {
std.log.err("Error starting sub-file extraction thread: {}\n", .{err});
out_err.* = err;
dir_wg.finish();
break;
};
}
}
},
.socket, .ext_socket => {
std.log.info("Ignoring socket file {s} (inode {})\n", .{ self.name, self.inode.hdr.num });
},
}
}
fn setPerm(self: SfsFile, fil: File, options: ExtractionOptions) !void {
if (!options.ignoreOwner) try fil.chmod(self.inode.hdr.permissions);
if (!options.ignorePermissions) try fil.chown(try self.ownerUid(), try self.ownerGid());
//TODO: switch to threaded version.
return self.inode.extractTo(self.archive, path, options);
}
/// Utility function.
+217
View File
@@ -2,10 +2,17 @@
const std = @import("std");
const Reader = std.Io.Reader;
const WaitGroup = std.Thread.WaitGroup;
const Pool = std.Thread.Pool;
const Archive = @import("archive.zig");
const DirEntry = @import("dir_entry.zig");
const ExtractionOptions = @import("options.zig");
const dir = @import("inode_data/dir.zig");
const file = @import("inode_data/file.zig");
const misc = @import("inode_data/misc.zig");
const DataReader = @import("util/data.zig");
const MetadataReader = @import("util/metadata.zig");
pub const Ref = packed struct {
block_offset: u16,
@@ -94,3 +101,213 @@ pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
else => {},
}
}
/// Get the data reader for a file inode.
pub fn dataReader(self: Inode, archive: *Archive) !DataReader {
return switch (self.hdr.inode_type) {
.file => readerFromData(archive, self.data.file),
.ext_file => readerFromData(archive, self.data.ext_file),
else => error.NotRegularFile,
};
}
fn readerFromData(archive: *Archive, data: anytype) !DataReader {
var out: DataReader = .init(archive, data.block_sizes, data.block_start, data.size);
if (data.frag_idx != 0xFFFFFFFF)
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
return out;
}
/// Get the directory entries for a directory inode.
pub fn dirEntries(self: Inode, archive: *Archive) ![]DirEntry {
return switch (self.hdr.inode_type) {
.dir => entriesFromData(archive, self.data.dir),
.ext_dir => entriesFromData(archive, self.data.ext_dir),
else => error.NotDirectory,
};
}
fn entriesFromData(archive: *Archive, data: anytype) ![]DirEntry {
var rdr = try archive.fil.readerAt(archive.super.dir_start + data.block_start, &[0]u8{});
const alloc = archive.allocator();
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
try meta.interface.discardAll(data.block_offset);
return DirEntry.readDir(alloc, &meta.interface, data.size);
}
pub fn extractTo(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
switch (self.hdr.inode_type) {
.dir, .ext_dir => {
// Removing any trailing separators since that's the easiest path forward.
if (path[path.len - 1] == '/') return self.extractTo(archive, path[0 .. path.len - 1], options);
var alloc = archive.allocator();
const entries = try self.dirEntries(archive);
defer {
for (entries) |entry| entry.deinit(alloc);
alloc.free(entries);
}
for (entries) |entry| {
var new_path = try alloc.alloc(u8, path.len + 1 + entry.name.len);
@memcpy(new_path[0..path.len], path);
@memcpy(new_path[path.len + 1 ..], entry.name);
new_path[path.len] = '/';
defer alloc.free(new_path);
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
try meta.interface.discardAll(entry.block_offset);
var inode: Inode = try read(alloc, &meta.interface, archive.super.block_size);
defer inode.deinit(alloc);
try inode.extractTo(archive, new_path, options);
}
},
.file, .ext_file => try self.extractRegFile(archive, path, options),
.symlink, .ext_symlink => try self.extractSymlink(path),
else => try self.extractDevice(archive, path, options),
}
}
const Perms = struct {
path: []const u8,
owner: u16,
perm: u16,
mod_time: u32,
};
pub fn extractToThreaded(inode: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, threads: usize) !void {
_ = archive;
_ = path;
_ = options;
_ = threads;
switch (inode.hdr.inode_type) {}
}
/// Extract threadedly the inode to the path.
fn extractThread(inode: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, wg: *WaitGroup, pool: *Pool, perms: ?*std.ArrayList(Perms)) !void {
_ = pool;
_ = perms;
_ = archive;
switch (inode.hdr.inode_type) {
.dir, .ext_dir => {
//TOOD
return error.TODO;
},
.file, .ext_file => {
//TOOD
return error.TODO;
},
.symlink, .ext_symlink => {
defer wg.finish();
try inode.extractSymlink(path);
},
else => {
defer wg.finish();
try inode.extractDevice(path, options.ignore_permissions);
},
}
}
/// Creates and writes the inode file contents to the given path.
/// Optionally set owner & permissions.
///
/// Assumes the inode is a file or ext_file type.
fn extractRegFile(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
var fil = try std.fs.cwd().createFile(path, .{});
defer fil.close();
var buf: [8192]u8 = undefined;
var wrt = fil.writer(&buf);
var dat_rdr = try self.dataReader(archive);
defer dat_rdr.deinit();
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
try wrt.interface.flush();
// updateTime is in nanoseconds (a billionth of a second). mod_time is in seconds.
try fil.updateTimes(self.hdr.mod_time * (10 ^ 9), self.hdr.mod_time * (10 ^ 9));
if (!options.ignore_permissions) {
try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
}
if (!options.ignore_xattr) {
// TODO
}
}
/// TODO: not implemented
/// Extract the inode file contents to the given path.
/// The extraction will be done threaded using pool for threads and will call wg.finish() when done.
///
/// Optionally set owner & permissions.
/// Assumes the inode is a file or ext_file type.
fn extractRegFileThreaded(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, pool: *Pool, wg: *WaitGroup) !void {
_ = self;
_ = archive;
_ = path;
_ = options;
_ = pool;
_ = wg;
return error.TODO;
}
/// Creates the symlink described by the inode.
///
/// Assumes the inode is a symlink or ext_symlink type.
fn extractSymlink(self: Inode, path: []const u8) !void {
const target = switch (self.data) {
.symlink => |s| s.target,
.ext_symlink => |s| s.target,
else => unreachable,
};
try std.fs.cwd().symLink(target, path, .{});
}
/// Creates the device described by the inode.
///
/// Optionally set owner & permissions.
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
var mode: u32 = undefined;
var dev: u32 = 0;
switch (self.data) {
.char_dev => |d| {
mode = std.posix.S.IFCHR;
dev = d.dev;
},
.ext_char_dev => |d| {
mode = std.posix.S.IFCHR;
dev = d.dev;
},
.block_dev => |d| {
mode = std.posix.S.IFBLK;
dev = d.dev;
},
.ext_block_dev => |d| {
mode = std.posix.S.IFBLK;
dev = d.dev;
},
.fifo, .ext_fifo => mode = std.posix.S.IFIFO,
.socket, .ext_socket => mode = std.posix.S.IFSOCK,
else => unreachable,
}
const res: std.os.linux.E = @enumFromInt(std.os.linux.mknod(@ptrCast(path), mode, dev));
switch (res) {
.SUCCESS => {},
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
else => return blk: {
std.debug.print("unhandled mknod result: {}\n", .{res});
break :blk std.fs.Dir.MakeError.Unexpected;
},
}
var fil = try std.fs.cwd().openFile(path, .{});
// updateTime is in nanoseconds (a billionth of a second). mod_time is in seconds.
try fil.updateTimes(self.hdr.mod_time * (10 ^ 9), self.hdr.mod_time * (10 ^ 9));
if (!options.ignore_permissions) {
try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
}
if (!options.ignore_xattr) {
// TODO
}
}
+15 -10
View File
@@ -5,16 +5,21 @@ const Writer = std.Io.Writer;
const ExtractionOptions = @This();
/// Don't set the file's permissions after extraction
ignorePermissions: bool = false,
/// Don't set the file's owner after extraction.
ignoreOwner: bool = false,
/// Don't set the file's owner & permissions after extraction
ignore_permissions: bool = false,
/// Don't set xattr values. Currently xattrs are never set anyway.
ignore_xattr: bool = false,
/// Replace symlinks with their target.
dereferenceSymlinks: bool = false,
log_level: std.log.Level = .err,
// /// If options verbose and verboseWriter not set, logs are printed to stdout.
// verboseWriter: ?*Writer = null,
dereference_symlinks: bool = false,
/// Verbose logging. If true, verbose_writer must be set
verbose: bool = false,
/// Where to print verbose log.
verbose_writer: ?*Writer = null,
pub const Default: ExtractionOptions = .{};
pub const VerboseDefault: ExtractionOptions = .{ .log_level = .debug };
pub fn VerboseDefault(wrt: *Writer) ExtractionOptions {
return .{
.verbose = true,
.verbose_writer = wrt,
};
}
+3 -3
View File
@@ -1,7 +1,7 @@
const std = @import("std");
const Mutex = std.Thread.Mutex;
const DecompMgr = @import("decomp.zig");
const DecompFn = @import("decomp.zig").DecompFn;
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
@@ -18,7 +18,7 @@ pub fn Table(T: anytype) type {
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *DecompMgr,
decomp: DecompFn,
tab_start: u64,
tab: std.AutoHashMap(u32, []T),
@@ -26,7 +26,7 @@ pub fn Table(T: anytype) type {
mut: Mutex = .{},
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *DecompMgr, tab_start: u64, values: u32) !Self {
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
return .{
.alloc = alloc,
.fil = fil,
+8 -8
View File
@@ -7,7 +7,7 @@ const Limit = std.Io.Limit;
const Archive = @import("../archive.zig");
const FragEntry = Archive.FragEntry;
const DecompMgr = @import("../decomp.zig");
const DecompFn = @import("../decomp.zig").DecompFn;
const BlockSize = @import("../inode_data/file.zig").BlockSize;
const OffsetFile = @import("offset_file.zig");
@@ -15,7 +15,7 @@ const DataReader = @This();
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *DecompMgr,
decomp: DecompFn,
block_size: u32,
blocks: []BlockSize,
@@ -33,7 +33,7 @@ pub fn init(archive: *Archive, blocks: []BlockSize, start: u64, size: u64) DataR
return .{
.alloc = archive.allocator(),
.fil = archive.fil,
.decomp = &archive.decomp,
.decomp = archive.decomp,
.block_size = archive.super.block_size,
.blocks = blocks,
.size = size,
@@ -91,10 +91,10 @@ fn advance(self: *DataReader) !void {
}
const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size);
defer self.alloc.free(tmp_buf);
var limit_rdr = Reader.limited(&rdr.interface, @enumFromInt(self.frag.?.size.size), tmp_buf);
try rdr.interface.readSliceAll(tmp_buf);
const needed_block = try self.alloc.alloc(u8, self.frag_offset + cur_block_size);
defer self.alloc.free(needed_block);
_ = try self.decomp.decompReader(&limit_rdr.interface, needed_block);
_ = try self.decomp(self.alloc, tmp_buf, needed_block);
@memcpy(self.interface.buffer, needed_block[self.frag_offset..]);
return;
}
@@ -109,9 +109,9 @@ fn advance(self: *DataReader) !void {
try rdr.interface.readSliceAll(self.interface.buffer);
return;
}
var buf: [8192]u8 = undefined; //TODO: possibly change for better performance/memory usage. Might need to be a full block in size.
var limit_rdr = Reader.limited(&rdr.interface, @enumFromInt(block.size), &buf);
_ = try self.decomp.decompReader(&limit_rdr.interface, self.interface.buffer);
const tmp_buf = try self.alloc.alloc(u8, block.size);
defer self.alloc.free(tmp_buf);
_ = try self.decomp(self.alloc, tmp_buf, self.interface.buffer);
}
/// Does not guarentee that data currently in the buffer is retained.
fn resizeBuffer(self: *DataReader, size: usize) !void {
+5 -5
View File
@@ -4,7 +4,7 @@ const Writer = std.Io.Writer;
const Limit = std.Io.Limit;
const StreamError = std.Io.Reader.StreamError;
const DecompMgr = @import("../decomp.zig");
const DecompFn = @import("../decomp.zig").DecompFn;
const BlockHeader = packed struct {
size: u15,
@@ -15,14 +15,14 @@ const This = @This();
alloc: std.mem.Allocator,
rdr: *Reader,
decomp: *DecompMgr,
decomp: DecompFn,
buf: [8192]u8 = undefined,
interface: Reader,
err: ?anyerror = null,
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *DecompMgr) This {
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: DecompFn) This {
return .{
.alloc = alloc,
.rdr = rdr,
@@ -51,8 +51,8 @@ fn advance(self: *This) !void {
return;
}
var tmp_buf: [8192]u8 = undefined;
var limit_rdr = self.rdr.limited(@enumFromInt(hdr.size), &tmp_buf);
self.interface.end = try self.decomp.decompReader(&limit_rdr.interface, &self.buf);
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
self.interface.end = try self.decomp(self.alloc, tmp_buf[0..hdr.size], &self.buf);
self.interface.buffer = self.buf[0..self.interface.end];
}