169 lines
6.2 KiB
Zig
169 lines
6.2 KiB
Zig
//! A squashfs archive read from a file.
|
|
//! Can be used to directly access File's contents or extract to the filesystem.
|
|
|
|
const std = @import("std");
|
|
const File = std.fs.File;
|
|
const builtin = @import("builtin");
|
|
|
|
const Decomp = @import("decomp.zig");
|
|
const ExtractionOptions = @import("options.zig");
|
|
const Inode = @import("inode.zig");
|
|
const InodeRef = Inode.Ref;
|
|
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
|
const SfsFile = @import("file.zig");
|
|
const Superblock = @import("super.zig").Superblock;
|
|
const Table = @import("table.zig").Table;
|
|
const MetadataReader = @import("util/metadata.zig");
|
|
const OffsetFile = @import("util/offset_file.zig");
|
|
|
|
const config = if (builtin.is_test) .{
|
|
.use_c_libs = true,
|
|
.allow_lzo = false,
|
|
} else @import("config");
|
|
|
|
/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry.
|
|
/// The offset into the block and fragment size is stored in the file's inode.
|
|
pub const FragEntry = packed struct {
|
|
start: u64,
|
|
size: BlockSize,
|
|
_: u32,
|
|
};
|
|
|
|
const Archive = @This();
|
|
|
|
// 4 Gigs
|
|
const DEFAULT_MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
|
|
|
parent_alloc: std.mem.Allocator,
|
|
alloc: std.heap.ThreadSafeAllocator,
|
|
// alloc: std.heap.FixedBufferAllocator,
|
|
// fixed_buf: []u8,
|
|
thread_count: usize,
|
|
|
|
fil: OffsetFile,
|
|
|
|
super: Superblock,
|
|
|
|
setup: bool = false,
|
|
|
|
decomp: Decomp.DecompFn,
|
|
|
|
frag_table: Table(FragEntry) = undefined,
|
|
id_table: Table(u16) = undefined,
|
|
export_table: Table(InodeRef) = undefined,
|
|
|
|
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
|
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
|
return initAdvanced(
|
|
alloc,
|
|
fil,
|
|
0,
|
|
try std.Thread.getCpuCount(),
|
|
);
|
|
}
|
|
/// Create the Archive dictating the amount of threads used for extraction.
|
|
/// If you're planning on only interacting with a small number of files, it should be fine to use few (or one) threads.
|
|
pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: usize) !Archive {
|
|
var super: Superblock = undefined;
|
|
const red = try fil.pread(@ptrCast(&super), offset);
|
|
std.debug.assert(red == @sizeOf(Superblock));
|
|
try super.validate();
|
|
// const fixed_buf = try alloc.alloc(u8, mem);
|
|
return .{
|
|
.parent_alloc = alloc,
|
|
.alloc = .{ .child_allocator = alloc },
|
|
// .fixed_buf = fixed_buf,
|
|
.thread_count = if (threads > 0) threads else try std.Thread.getCpuCount(),
|
|
.fil = .init(fil, offset),
|
|
.decomp = switch (super.compression) {
|
|
.gzip => Decomp.gzipDecompress,
|
|
.lzma => Decomp.lzmaDecompress,
|
|
.xz => Decomp.xzDecompress,
|
|
.zstd => Decomp.zstdDecompress,
|
|
.lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported,
|
|
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
|
},
|
|
|
|
.super = super,
|
|
};
|
|
}
|
|
pub fn deinit(self: *Archive) void {
|
|
// self.parent_alloc.free(self.fixed_buf);
|
|
if (self.setup) {
|
|
self.frag_table.deinit();
|
|
self.export_table.deinit();
|
|
self.id_table.deinit();
|
|
}
|
|
}
|
|
|
|
pub fn allocator(self: *Archive) std.mem.Allocator {
|
|
return self.alloc.allocator();
|
|
}
|
|
|
|
fn setupValues(self: *Archive) !void {
|
|
const alloc = self.allocator();
|
|
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;
|
|
}
|
|
|
|
pub fn id(self: *Archive, idx: u32) !u16 {
|
|
if (!self.setup) try self.setupValues();
|
|
return self.id_table.get(idx);
|
|
}
|
|
|
|
pub fn frag(self: *Archive, idx: u32) !FragEntry {
|
|
if (!self.setup) try self.setupValues();
|
|
return self.frag_table.get(idx);
|
|
}
|
|
|
|
pub fn inode(self: *Archive, num: u32) !Inode {
|
|
if (!self.setup) try self.setupValues();
|
|
const ref = try self.export_table.get(num - 1);
|
|
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
|
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
|
|
try meta.interface.discardAll(ref.block_offset);
|
|
return try .read(self.allocator(), &meta.interface, self.super.block_size);
|
|
}
|
|
|
|
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);
|
|
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, "");
|
|
}
|
|
|
|
pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
|
if (!self.setup) try self.setupValues();
|
|
var root_fil = try self.root();
|
|
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
|
return root_fil.open(path);
|
|
}
|
|
|
|
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
|
if (!self.setup) try self.setupValues();
|
|
var alloc = self.allocator();
|
|
var ext_path: []u8 = undefined;
|
|
if (std.fs.cwd().statFile(path)) |stat| {
|
|
if (stat.kind == .directory) {
|
|
ext_path = @constCast(path);
|
|
} else return error.ExtractionPathExists;
|
|
} else |err| {
|
|
if (err == error.FileNotFound) {
|
|
ext_path = @constCast(path);
|
|
} else {
|
|
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
return err;
|
|
}
|
|
}
|
|
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
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);
|
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
|
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
|
try in.extractToThreaded(self, ext_path, options, self.thread_count);
|
|
}
|