Files
zig-squashfs/src/archive.zig
T
Caleb J. Gardner 9a710b98cf Moved a lot of things to have explicit alloc arguments
Changed Table to be stateless
2026-02-15 10:31:42 -06:00

114 lines
4.3 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();
fil: OffsetFile,
super: Superblock,
decomp: Decomp.DecompFn,
frag_table: Table(FragEntry),
id_table: Table(u16),
export_table: Table(InodeRef),
/// Begin reading a squashfs archive from the given File at the given offset.
pub fn init(fil: File, offset: u64) !Archive {
var super: Superblock = undefined;
_ = try fil.pread(@ptrCast(&super), offset);
try super.validate();
const decomp: Decomp.DecompFn = 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,
};
const offset_fil: OffsetFile = .init(fil, offset);
return .{
.fil = offset_fil,
.super = super,
.decomp = decomp,
.frag_table = .init(offset_fil, decomp, super.frag_start, super.frag_count),
.id_table = .init(offset_fil, decomp, super.id_start, super.id_count),
.export_table = .init(offset_fil, decomp, super.export_start, super.inode_count),
};
}
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.alloc, &rdr.interface, &self.decomp);
try meta.interface.discardAll(ref.block_offset);
return try .read(self.alloc, &meta.interface, self.super.block_size);
}
pub fn root(self: Archive, alloc: std.mem.Allocator) !SfsFile {
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
try meta.interface.discardAll(self.super.root_ref.block_offset);
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
return .init(alloc, self, in, "");
}
pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !SfsFile {
var root_fil = try self.root(alloc);
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
return root_fil.open(alloc, path);
}
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
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(alloc, &rdr.interface, self.decomp);
try meta.interface.discardAll(self.super.root_ref.block_offset);
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
try in.extractTo(alloc, self, ext_path, options);
}