diff --git a/src/archive.zig b/src/archive.zig new file mode 100644 index 0000000..67a3ed0 --- /dev/null +++ b/src/archive.zig @@ -0,0 +1,85 @@ +const std = @import("std"); + +const Inode = @import("inode.zig"); + +const Archive = @This(); + +super: Superblock, + +pub fn init(fil: std.fs.File, offset: u64) !Archive { + var super: Superblock = undefined; + var fil_rdr = fil.reader(&[0]u8{}); + try fil_rdr.seekTo(offset); + try fil_rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little); + + return .{ + .super = super, + }; +} + +// Superblock + +const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little); + +const SuperblockError = error{ + InvalidMagic, + InvalidBlockLog, + InvalidVersion, + InvalidCheck, +}; + +/// A squashfs Superblock +pub const Superblock = packed struct { + magic: u32, + inode_count: u32, + mod_time: u32, + block_size: u32, + frag_count: u32, + compression: enum(u16) { + gzip = 1, + lzma, + lzo, + xz, + lz4, + zstd, + }, + 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 SuperblockError.InvalidMagic; + if (self.flags.check) + return SuperblockError.InvalidCheck; + if (self.ver_maj != 4 or self.ver_min != 0) + return SuperblockError.InvalidVersion; + if (std.math.log2(self.block_size) != self.block_log) + return SuperblockError.InvalidBlockLog; + } +}; diff --git a/src/inode.zig b/src/inode.zig new file mode 100644 index 0000000..bdab250 --- /dev/null +++ b/src/inode.zig @@ -0,0 +1,87 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +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 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) }, + }, + }; +} + +// 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, +}; diff --git a/src/inode/dir.zig b/src/inode/dir.zig new file mode 100644 index 0000000..ec8ddab --- /dev/null +++ b/src/inode/dir.zig @@ -0,0 +1,3 @@ +pub const Dir = packed struct {}; + +pub const ExtDir = packed struct {}; diff --git a/src/inode/file.zig b/src/inode/file.zig new file mode 100644 index 0000000..2f5a72f --- /dev/null +++ b/src/inode/file.zig @@ -0,0 +1,3 @@ +pub const File = struct {}; + +pub const ExtFile = struct {}; diff --git a/src/inode/misc.zig b/src/inode/misc.zig new file mode 100644 index 0000000..51166b8 --- /dev/null +++ b/src/inode/misc.zig @@ -0,0 +1,7 @@ +pub const Device = packed struct {}; + +pub const ExtDevice = packed struct {}; + +pub const Ipc = packed struct {}; + +pub const ExtIpc = packed struct {}; diff --git a/src/inode/sym.zig b/src/inode/sym.zig new file mode 100644 index 0000000..5a080ab --- /dev/null +++ b/src/inode/sym.zig @@ -0,0 +1,3 @@ +pub const Symlink = struct {}; + +pub const ExtSymlink = struct {}; diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..78c33c4 --- /dev/null +++ b/src/root.zig @@ -0,0 +1 @@ +pub const Archive = @import("archive.zig"); diff --git a/src/util/offset_file.zig b/src/util/offset_file.zig new file mode 100644 index 0000000..a209b72 --- /dev/null +++ b/src/util/offset_file.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const FileReader = std.fs.File.Reader; + +const OffsetFile = @This(); + +fil: std.fs.File, +offset: u64 = 0, + +pub fn readerAt(self: OffsetFile, offset: u64, buf: []u8) !FileReader { + var rdr = self.fil.reader(buf); + try rdr.seekTo(self.offset + offset); + return rdr; +}