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
+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.