185 lines
5.1 KiB
Zig
185 lines
5.1 KiB
Zig
const std = @import("std");
|
|
const Io = std.Io;
|
|
|
|
const DecompTypes = @import("decomp/types.zig");
|
|
const Decompressor = @import("decomp.zig");
|
|
const ExtractionOptions = @import("options.zig");
|
|
const File = @import("file.zig");
|
|
const Inode = @import("inode.zig");
|
|
const BlockSize = @import("inode/file.zig").BlockSize;
|
|
const LookupTable = @import("lookup_table.zig");
|
|
const MetadataReader = @import("util/metadata.zig");
|
|
const OffsetFile = @import("util/offset_file.zig");
|
|
const Utils = @import("util/utils.zig");
|
|
|
|
pub const Error = error{
|
|
BadMagic,
|
|
BadBlockLog,
|
|
BadVersion,
|
|
BadCheck,
|
|
};
|
|
|
|
const Archive = @This();
|
|
|
|
file: OffsetFile,
|
|
|
|
super: Superblock,
|
|
|
|
stateless_decomp: Decompressor,
|
|
|
|
/// Create an Archive from a File.
|
|
pub fn init(io: Io, fil: Io.File, offset: u64) !Archive {
|
|
var super: Superblock = undefined;
|
|
var fil_rdr = fil.reader(io, &[0]u8{});
|
|
if (offset > 0)
|
|
try fil_rdr.seekTo(offset);
|
|
try fil_rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
|
|
try super.validate();
|
|
|
|
return .{
|
|
.file = .{ .fil = fil, .offset = offset },
|
|
.super = super,
|
|
.stateless_decomp = .{ .vtable = &.{ .stateless = try DecompTypes.getStatelessFn(super.compression) } },
|
|
};
|
|
}
|
|
|
|
pub fn root(self: Archive, alloc: std.mem.Allocator) !File {
|
|
return .{
|
|
.file = self.file,
|
|
.super = self.super.toMinimal(),
|
|
.decomp = self.stateless_decomp.statelessCopy(alloc),
|
|
|
|
.inode = try Utils.readInode(
|
|
alloc,
|
|
&self.stateless_decomp,
|
|
self.file,
|
|
self.super.inode_start,
|
|
self.super.block_size,
|
|
self.super.root_ref.block_start,
|
|
self.super.root_ref.block_offset,
|
|
),
|
|
.name = "",
|
|
};
|
|
}
|
|
pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !File {
|
|
if (Utils.pathIsSelf(path)) return self.root(alloc);
|
|
var root_file = self.root(alloc);
|
|
defer root_file.deinit();
|
|
return root_file.open(alloc, path);
|
|
}
|
|
|
|
pub fn fragEntry(self: Archive, idx: u32) !FragEntry {
|
|
return LookupTable.stateless(FragEntry, self.fil, &self.stateless_decomp, self.super.frag_start, idx);
|
|
}
|
|
pub fn id(self: Archive, idx: u32) !u16 {
|
|
return LookupTable.stateless(u16, self.fil, &self.stateless_decomp, self.super.id_start, idx);
|
|
}
|
|
pub fn inode(self: Archive, alloc: std.mem.Allocator, inode_num: u32) !Inode {
|
|
const ref = try LookupTable.stateless(Inode.Ref, self.file, &self.stateless_decomp, self.super.export_start, inode_num - 1);
|
|
return Utils.readInode(
|
|
alloc,
|
|
&self.stateless_decomp,
|
|
self.file,
|
|
self.super.inode_start,
|
|
self.super.block_size,
|
|
ref.block_start,
|
|
ref.block_offset,
|
|
);
|
|
}
|
|
|
|
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
|
_ = self;
|
|
_ = alloc;
|
|
_ = path;
|
|
_ = options;
|
|
return error.TODO;
|
|
}
|
|
|
|
// Superblock
|
|
|
|
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
|
|
|
pub const Superblock = packed struct {
|
|
magic: u32,
|
|
inode_count: u32,
|
|
mod_time: u32,
|
|
block_size: u32,
|
|
frag_count: u32,
|
|
compression: DecompTypes.Enum,
|
|
block_log: u16,
|
|
flags: packed struct {
|
|
inode_uncompressed: bool,
|
|
data_uncompressed: bool,
|
|
check: bool,
|
|
frag_uncompressed: bool,
|
|
fragment_never: bool,
|
|
fragment_always: bool,
|
|
duplicates: bool,
|
|
exportable: bool,
|
|
xattr_uncompressed: bool,
|
|
xattr_never: bool,
|
|
compression_options: bool,
|
|
ids_uncompressed: bool,
|
|
_: u4,
|
|
},
|
|
id_count: u16,
|
|
ver_maj: u16,
|
|
ver_min: u16,
|
|
root_ref: Inode.Ref,
|
|
size: u64,
|
|
id_start: u64,
|
|
xattr_start: u64,
|
|
inode_start: u64,
|
|
dir_start: u64,
|
|
frag_start: u64,
|
|
export_start: u64,
|
|
|
|
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive.
|
|
fn validate(self: Superblock) !void {
|
|
if (self.magic != SQUASHFS_MAGIC)
|
|
return Error.BadMagic;
|
|
if (self.flags.check)
|
|
return Error.BadCheck;
|
|
if (self.ver_maj != 4 or self.ver_min != 0)
|
|
return Error.BadVersion;
|
|
if (std.math.log2(self.block_size) != self.block_log)
|
|
return Error.BadBlockLog;
|
|
}
|
|
|
|
pub fn toMinimal(self: Superblock) MinimalSuperblock {
|
|
return .{
|
|
.inode_count = self.inode_count,
|
|
.block_size = self.block_size,
|
|
.frag_count = self.frag_count,
|
|
.id_count = self.id_count,
|
|
.id_start = self.id_start,
|
|
.xattr_start = self.xattr_start,
|
|
.inode_start = self.inode_start,
|
|
.dir_start = self.dir_start,
|
|
.frag_start = self.frag_start,
|
|
.export_start = self.export_start,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const MinimalSuperblock = struct {
|
|
inode_count: u32,
|
|
block_size: u32,
|
|
frag_count: u32,
|
|
id_count: u16,
|
|
id_start: u64,
|
|
xattr_start: u64,
|
|
inode_start: u64,
|
|
dir_start: u64,
|
|
frag_start: u64,
|
|
export_start: u64,
|
|
};
|
|
|
|
// Frag Entry
|
|
|
|
pub const FragEntry = packed struct {
|
|
block_start: u64,
|
|
size: BlockSize,
|
|
_: u32,
|
|
};
|