//! 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); }