Files
zig-squashfs/src/inode.zig
T
Caleb J. Gardner 3e97aabe53 Fleshing out more of the library.
Mainly adding functions to File
2026-04-07 15:41:30 -05:00

295 lines
9.1 KiB
Zig

//! This is the raw squashfs representation of a file/directory.
//! Most of the time using File is a better experience and using Inodes directory
//! is only required for more technical use cases.
const std = @import("std");
const Reader = std.Io.Reader;
const Decompressor = @import("decomp.zig");
const Directory = @import("directory.zig");
const Dir = @import("inode/dir.zig");
const File = @import("inode/file.zig");
const Misc = @import("inode/misc.zig");
const Sym = @import("inode/sym.zig");
const MinimalSuperblock = @import("archive.zig").MinimalSuperblock;
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const Inode = @This();
hdr: Header,
data: Data,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
var hdr: Header = undefined;
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
return .{
.hdr = hdr,
.data = switch (hdr.inode_type) {
.dir => .{ .dir = .read(rdr) },
.file => .{ .file = .read(alloc, rdr, block_size) },
.symlink => .{ .symlink = .read(alloc, rdr) },
.block => .{ .block = .read(rdr) },
.char => .{ .char = .read(rdr) },
.fifo => .{ .fifo = .read(rdr) },
.sock => .{ .sock = .read(rdr) },
.ext_dir => .{ .ext_dir = .read(rdr) },
.ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
.ext_block => .{ .ext_block = .read(rdr) },
.ext_char => .{ .ext_char = .read(rdr) },
.ext_fifo => .{ .ext_fifo = .read(rdr) },
.ext_sock => .{ .ext_sock = .read(rdr) },
},
};
}
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) {
.file => |f| alloc.free(f.block_sizes),
.ext_file => |f| alloc.free(f.block_sizes),
.symlink => |s| alloc.free(s.target),
.ext_symlink => |s| alloc.free(s.target),
else => {},
}
}
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
switch (self.data) {
.dir,
.ext_dir,
.block,
.ext_block,
.char,
.ext_char,
.fifo,
.ext_fifo,
.sock,
.ext_sock,
=> return self,
.file => |f| {
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
@memcpy(new_sizes, f.block_sizes);
return .{
.hdr = self.hdr,
.data = .{ .file = .{
.block_start = f.block_start,
.frag_idx = f.frag_idx,
.block_offset = f.block_offset,
.size = f.size,
.block_sizes = new_sizes,
} },
};
},
.ext_file => |f| {
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
@memcpy(new_sizes, f.block_sizes);
return .{
.hdr = self.hdr,
.data = .{ .ext_file = .{
.block_start = self.block_start,
.size = self.size,
.sparse = self.sparse,
.hard_links = self.hard_links,
.frag_idx = self.frag_idx,
.block_offset = self.block_offset,
.xattr_idx = self.xattr_idx,
.block_sizes = new_sizes,
} },
};
},
.symlink => |s| {
const new_target = try alloc.alloc(u8, s.target.len);
@memcpy(new_target, s.target);
return .{
.hdr = self.hdr,
.data = .{ .symlink = .{
.hard_links = s.hard_links,
.target = new_target,
} },
};
},
.ext_symlink => |s| {
const new_target = try alloc.alloc(u8, s.target.len);
@memcpy(new_target, s.target);
return .{
.hdr = self.hdr,
.data = .{ .ext_symlink = .{
.hard_links = s.hard_links,
.xattr_idx = s.xattr_idx,
.target = new_target,
} },
};
},
}
}
// Types
pub const Ref = packed struct {
block_offset: u16,
block_start: u32,
_: u16,
};
pub const Type = enum(u16) {
dir = 1,
file,
symlink,
block,
char,
fifo,
sock,
ext_dir,
ext_file,
ext_symlink,
ext_block,
ext_char,
ext_fifo,
ext_sock,
};
const Header = packed struct {
inode_type: Type,
permission: u16,
uid_idx: u16,
gid_idx: u16,
mod_time: u32,
num: u32,
};
pub const Data = union(Type) {
dir: Dir.Dir,
file: File.File,
symlink: Sym.Symlink,
block: Misc.Device,
char: Misc.Device,
fifo: Misc.Ipc,
sock: Misc.Ipc,
ext_dir: Dir.ExtDir,
ext_file: File.ExtFile,
ext_symlink: Sym.ExtSymlink,
ext_block: Misc.ExtDevice,
ext_char: Misc.ExtDevice,
ext_fifo: Misc.ExtIpc,
ext_sock: Misc.ExtIpc,
};
// Errors
pub const Error = error{
NotDirectory,
NotFound,
NotRegularFile,
};
// Utils functions
/// For directory inodes, tries to find the inode at the given path. Returns both the inode, and it's file name. If the path is empty or "." then a copy of this inode is returned with no name ("").
pub fn findInode(
inode: Inode,
alloc: std.mem.Allocator,
decomp: *const Decompressor,
fil: OffsetFile,
dir_start: u64,
inode_start: u64,
block_size: u32,
filepath: []const u8,
) !struct { inode: Inode, name: []const u8 } {
switch (inode.data) {
.dir => |d| {
const path: []const u8 = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return .{ .inode = inode.copy(alloc), .name = "" };
return findInodeRaw(
alloc,
decomp,
fil,
dir_start,
inode_start,
block_size,
path,
d,
);
},
.ext_dir => |d| {
const path: []const u8 = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return .{ .inode = inode.copy(alloc), .name = "" };
return findInodeRaw(
alloc,
decomp,
fil,
dir_start,
inode_start,
block_size,
path,
d,
);
},
else => return Error.NotDirectory,
}
}
inline fn findInodeRaw(
inode: Inode,
alloc: std.mem.Allocator,
decomp: *const Decompressor,
fil: OffsetFile,
dir_start: u64,
inode_start: u64,
block_size: u32,
path: []const u8,
dat: anytype,
) !struct { inode: Inode, name: []const u8 } {
const first_element: []const u8 = std.mem.sliceTo(path, '/');
const dirs = try readDirRaw(alloc, decomp, fil, dir_start, dat);
defer {
for (dirs) |dir|
dir.deinit(alloc);
alloc.free(dirs);
}
// Directories are stored ASCIIbetically, so we can use binary search.
var cur_slice = dirs;
var idx: usize = 0;
while (cur_slice.len > 0) {
idx = cur_slice.len / 2;
const mid_name = cur_slice[idx].name;
switch (std.mem.order(u8, first_element, mid_name)) {
.gt => {
cur_slice = cur_slice[idx + 1 ..];
continue;
},
.lt => {
cur_slice = cur_slice[0..idx];
continue;
},
.eq => break,
}
} else return Error.NotFound;
const entry = cur_slice[idx];
var rdr = try fil.readerAt(inode_start + entry.block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(entry.block_offset);
const ret_inode: Inode = try .read(alloc, &meta_rdr.interface, block_size);
if (first_element.len == path.len) {
const name_copy = try alloc.alloc(u8, entry.name.len);
@memcpy(name_copy, entry.name.len);
return .{ .inode = ret_inode, .name = name_copy };
}
return inode.findInode(alloc, decomp, fil, dir_start, inode_start, block_size, path[first_element.len..]);
}
pub fn readDirectory(inode: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64) ![]Directory.Entry {
return switch (inode.data) {
.dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
.ext_dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
else => Error.NotDirectory,
};
}
inline fn readDirRaw(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64, dat: anytype) ![]Directory.Entry {
var rdr = try fil.readerAt(dir_start + dat.block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(dat.block_offset);
return Directory.readDirectory(alloc, meta_rdr, dat.size);
}