Files
zig-squashfs/src/inode.zig
T
2026-05-17 12:32:58 -05:00

338 lines
12 KiB
Zig

//! A file-system object. Represents a File or directory.
const std = @import("std");
const Reader = std.Io.Reader;
const Io = std.Io;
const Archive = @import("archive.zig");
const Decomp = @import("decomp.zig").Decomp;
const DirEntry = @import("directory.zig");
const ExtractionOptions = @import("options.zig");
const FragEntry = @import("frag.zig").FragEntry;
const dir = @import("inode_data/dir.zig");
const file = @import("inode_data/file.zig");
const misc = @import("inode_data/misc.zig");
const LookupTable = @import("lookup_table.zig");
const CachedTable = LookupTable.CachedTable;
const DataExtractor = @import("util/data_extractor.zig");
const DataReader = @import("util/data_reader.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const SharedCache = @import("util/shared_cache.zig");
const XattrTable = @import("xattr_table.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 = try .read(rdr) },
.file => .{ .file = try .read(alloc, rdr, block_size) },
.symlink => .{ .symlink = try .read(alloc, rdr) },
.block_dev => .{ .block_dev = try .read(rdr) },
.char_dev => .{ .char_dev = try .read(rdr) },
.fifo => .{ .fifo = try .read(rdr) },
.socket => .{ .socket = try .read(rdr) },
.ext_dir => .{ .ext_dir = try .read(rdr) },
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
.ext_socket => .{ .ext_socket = try .read(rdr) },
},
};
}
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) {
.file => |d| d.deinit(alloc),
.symlink => |d| d.deinit(alloc),
.ext_file => |d| d.deinit(alloc),
.ext_symlink => |d| d.deinit(alloc),
else => {},
}
}
// Utility Functions
/// Read the directory entries
pub fn readDirectory(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, dir_offset: u64) ![]DirEntry {
return switch (self.data) {
.dir => |d| readDirFromData(alloc, io, fil, decomp, dir_offset, d),
.ext_dir => |d| readDirFromData(alloc, io, fil, decomp, dir_offset, d),
else => Error.NotDirectory,
};
}
fn readDirFromData(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, dir_offset: u64, d: anytype) ![]DirEntry {
var rdr = try fil.readerAt(io, dir_offset + d.block_start, &[0]u8{});
var meta: MetadataReader = .init(alloc, &rdr.interface, decomp);
try meta.interface.discardAll(d.block_offset);
return DirEntry.readDirectory(alloc, &meta.interface, d.size);
}
/// Get a reader for a regular file's data.
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32) !DataReader {
return switch (self.data) {
.file => |f| getReaderFromData(alloc, io, fil, cache, decomp, block_size, f),
.ext_file => |f| getReaderFromData(alloc, io, fil, cache, decomp, block_size, f),
else => Error.NotRegularFile,
};
}
fn getReaderFromData(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32, d: anytype) !DataReader {
const ext: DataReader = .init(alloc, io, fil, cache, decomp, block_size, d.size, d.block_start, d.blocks);
if (d.frag_block_offset == 0xFFFFFFFF) {
// TODO:
return error.TODO;
}
return ext;
}
/// Get an extractor for a regular file's data.
pub fn dataExtractor(self: Inode, fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32) !DataExtractor {
return switch (self.data) {
.file => |f| getExtractorFromData(fil, cache, decomp, block_size, f),
.ext_file => |f| getExtractorFromData(fil, cache, decomp, block_size, f),
else => Error.NotRegularFile,
};
}
fn getExtractorFromData(fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32, d: anytype) !DataExtractor {
const ext: DataExtractor = .init(fil, cache, decomp, block_size, d.size, d.block_start, d.blocks);
if (d.frag_block_offset == 0xFFFFFFFF) {
// TODO:
return error.TODO;
}
return ext;
}
/// Get a symlink's target path
pub fn symlinkTarget(self: Inode) ![]const u8 {
return switch (self.data) {
.symlink => |s| s.target,
.ext_symlink => |s| s.target,
else => Error.NotSymlink,
};
}
/// Get inode's gid
pub fn gid(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, id_table_start: u64) !u16 {
return LookupTable.lookupValue(u16, alloc, io, decomp, fil, id_table_start, self.hdr.gid_idx);
}
/// Get inode's uid
pub fn uid(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, id_table_start: u64) !u16 {
return LookupTable.lookupValue(u16, alloc, io, decomp, fil, id_table_start, self.hdr.uid_idx);
}
/// Get the inode's xattr values as an index into the Archive's xattr table.
/// Returns error.NoXattr if the inode doesn't have extended attributes.
pub fn xattrIndex(self: Inode) !u32 {
const idx = switch (self.data) {
.ext_dir => |e| e.xattr_idx,
.ext_file => |e| e.xattr_idx,
.ext_symlink => |e| e.xattr_idx,
.ext_block_dev => |e| e.xattr_idx,
.ext_char_dev => |e| e.xattr_idx,
.ext_fifo => |e| e.xattr_idx,
.ext_socket => |e| e.xattr_idx,
else => Error.NoXattr,
};
if (idx == 0xFFFFFFFF) return Error.NoXattr;
return idx;
}
// Get an inode's xattr values. If the inode does not have xattr values (including if the inode is not an extended type), an empty slice is returned.
pub fn xattrValues(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, xattr_table_start: u64) ![]XattrTable.XattrOwned {
const idx = self.xattrIndex() catch &[0]XattrTable.XattrOwned{};
return XattrTable.statelessLookup(alloc, io, decomp, fil, xattr_table_start, idx);
}
// Types
pub const Error = error{
NotDirectory,
NotRegularFile,
NotSymlink,
NotExtended,
};
pub const Ref = packed struct(u64) {
block_offset: u16,
block_start: u32,
_: u16 = 0,
};
pub const Type = enum(u16) {
dir = 1,
file,
symlink,
block_dev,
char_dev,
fifo,
socket,
ext_dir,
ext_file,
ext_symlink,
ext_block_dev,
ext_char_dev,
ext_fifo,
ext_socket,
};
pub const Data = union(Type) {
dir: dir.Dir,
file: file.File,
symlink: misc.Symlink,
block_dev: misc.Dev,
char_dev: misc.Dev,
fifo: misc.IPC,
socket: misc.IPC,
ext_dir: dir.ExtDir,
ext_file: file.ExtFile,
ext_symlink: misc.ExtSymlink,
ext_block_dev: misc.ExtDev,
ext_char_dev: misc.ExtDev,
ext_fifo: misc.ExtIPC,
ext_socket: misc.ExtIPC,
};
pub const Header = extern struct {
inode_type: Type,
permissions: u16,
uid_idx: u16,
gid_idx: u16,
mod_time: u32,
num: u32,
};
// Extract
const PathRet = struct {
path: []const u8,
permissions: u16,
uid_idx: u16,
gid_idx: u16,
xattr_idx: ?u32 = null,
};
const ExtractReturnUnion = union(enum) {
path_ret: anyerror!PathRet, // TODO: convert to concrete error type instead of anyerror.
void_ret: anyerror!void,
};
const Tables = struct {
id: LookupTable.CachedTable(u16),
frag: LookupTable.CachedTable(FragEntry),
xattr: XattrTable,
};
pub fn extract(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, super: Archive.Superblock, path: []const u8, options: ExtractionOptions) !void {
var decomp_base: Decomp = switch (super.compression) {
.gzip => .{ .gzip = try .init(alloc, super.block_size) },
.lzma => .{ .lzma = try .init(alloc, super.block_size) },
.xz => .{ .xz = try .init(alloc, super.block_size) },
.zstd => .{ .zstd = try .init(alloc, super.block_size) },
else => unreachable,
};
defer decomp_base.deinit();
const decomp = decomp_base.decompressor();
var frag_table: CachedTable(FragEntry) = .init(alloc, fil, decomp, super.frag_start, super.frag_count);
defer if (!options.ignore_permissions) frag_table.deinit(io);
var sel_buf = [1]ExtractReturnUnion{undefined} ** 10;
var sel: Io.Select(ExtractReturnUnion) = .init(io, &sel_buf);
defer sel.cancelDiscard();
switch (self.hdr.inode_type) {
.file, .ext_file => sel.async(.path_ret, extractFile, .{ self, alloc, io, fil, decomp, &frag_table, super.block_size, path }),
else => return error.TODO,
}
var xattr_table: ?XattrTable = if (!options.ignore_xattr)
try .init(alloc, io, fil, decomp, super.xattr_start)
else
null;
defer if (!options.ignore_xattr) xattr_table.?.deinit(io);
var id_table: ?CachedTable(u16) = if (!options.ignore_xattr)
.init(alloc, fil, decomp, super.id_start, super.id_count)
else
null;
defer if (!options.ignore_xattr) id_table.?.deinit(io);
while (true) {
if (sel.group.token.load(.unordered) == null) break;
const ret = try sel.queue.getOne(io);
switch (ret) {
.void_ret => {
try ret.void_ret;
continue;
},
else => {},
}
const path_ret = try ret.path_ret;
defer if (path_ret.path.len != path.len) alloc.free(path_ret.path);
if (options.ignore_permissions and options.ignore_xattr) continue;
if (options.ignore_permissions and path_ret.xattr_idx == null) continue;
var ret_file = try Io.Dir.cwd().openFile(io, path_ret.path, .{});
defer ret_file.close(io);
if (!options.ignore_permissions) {
try ret_file.setPermissions(io, @enumFromInt(path_ret.permissions));
try ret_file.setOwner(io, try id_table.?.get(io, path_ret.uid_idx), try id_table.?.get(io, path_ret.gid_idx));
}
if (!options.ignore_xattr and path_ret.xattr_idx != null) {
const xattrs = try xattr_table.?.get(alloc, io, path_ret.xattr_idx.?);
defer {
for (xattrs) |x|
alloc.free(x.key);
alloc.free(xattrs);
}
for (xattrs) |x| {
const res = std.os.linux.fsetxattr(ret_file.handle, x.key, x.value.ptr, x.value.len, 0);
if (res != 0)
return error.CannotSetXattr;
}
}
}
}
pub fn extractFile(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, frag: *CachedTable(FragEntry), block_size: u32, path: []const u8) anyerror!PathRet {
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{});
defer atomic.deinit(io);
var ret: PathRet = .{
.gid_idx = self.hdr.gid_idx,
.uid_idx = self.hdr.uid_idx,
.permissions = self.hdr.permissions,
.path = path,
};
const data: DataExtractor = blk: {
switch (self.data) {
.file => |f| {
var data: DataExtractor = .init(fil, decomp, block_size, f.size, f.block_start, f.block_sizes);
if (f.frag_idx != 0xFFFFFFFF)
data.addFrag(f.frag_block_offset, try frag.get(io, f.frag_idx));
break :blk data;
},
.ext_file => |f| {
if (f.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = f.xattr_idx;
var data: DataExtractor = .init(fil, decomp, block_size, f.size, f.block_start, f.block_sizes);
if (f.frag_idx != 0xFFFFFFFF)
data.addFrag(f.frag_block_offset, try frag.get(io, f.frag_idx));
break :blk data;
},
else => unreachable,
}
};
try data.extractAsync(alloc, io, atomic.file);
try atomic.link(io);
return ret;
}