From 2e33e19cdde965aacb5def447e5e4ffff745f655 Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Wed, 2 Sep 2026 10:53:01 -0500 Subject: [PATCH] Added lookup Tables Simplified Inode Data types & union Started work on decompression --- src/archive.zig | 5 +- src/inode.zig | 243 ++++++++++++++++++++++---------------- src/table.zig | 80 +++++++++++++ src/utils/data_reader.zig | 4 +- src/utils/decompress.zig | 148 +++++++++++++++++++++++ 5 files changed, 373 insertions(+), 107 deletions(-) create mode 100644 src/table.zig create mode 100644 src/utils/decompress.zig diff --git a/src/archive.zig b/src/archive.zig index 002313d..1087897 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -43,10 +43,11 @@ pub fn root(self: *Archive, alloc: std.mem.Allocator) !File { .data = self.map.memory, .super = self.super, - .inode = try .fromRef( + .inode = try .readLocation( alloc, self.map.memory, - self.root_inode_ref, + self.root_inode_ref.start, + self.root_inode_ref.offset, self.super, ), .name = "", diff --git a/src/inode.zig b/src/inode.zig index cdf9fe3..4ce9a57 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -11,9 +11,9 @@ const Inode = @This(); header: Header, data: Data, -pub fn fromRef(alloc: std.mem.Allocator, data: []u8, ref: Reference, super: Super) !Inode { - var meta: MetadataReader = .init(alloc, data[super.inode_table_start + ref.start ..], super.decomp_fn); - try meta.interface.discardAll(ref.offset); +pub fn readLocation(alloc: std.mem.Allocator, data: []u8, start: u32, offset: u16, super: Super) !Inode { + var meta: MetadataReader = .init(alloc, data[super.inode_table_start + start ..], super.decomp_fn); + try meta.interface.discardAll(offset); return read(alloc, &meta.interface, super.block_size); } @@ -23,20 +23,15 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, block_size: u32) !Inode { return .{ .header = hdr, .data = switch (hdr.type) { - .dir => .{ .dir = try .read(rdr) }, - .file => .{ .file = try .read(rdr, alloc, block_size) }, - .symlink => .{ .symlink = try .read(rdr, alloc) }, - .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(rdr, alloc, block_size) }, - .ext_symlink => .{ .ext_symlink = try .read(rdr, alloc) }, - .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) }, + .dir => .{ .dir = try .readBasic(rdr) }, + .ext_dir => .{ .dir = try .readExt(rdr) }, + .file => .{ .file = try .readBasic(rdr, alloc, block_size) }, + .ext_file => .{ .file = try .readExt(rdr, alloc, block_size) }, + .symlink, .ext_symlink => .{ .symlink = try .read(rdr, alloc) }, + .block_dev, .char_dev => .{ .dev = try .readBasic(rdr) }, + .ext_block_dev, .ext_char_dev => .{ .dev = try .readExt(rdr) }, + .fifo, .socket => .{ .fifo = try .readBasic(rdr) }, + .ext_fifo, .ext_socket => .{ .fifo = try .readExt(rdr) }, }, }; } @@ -56,9 +51,7 @@ pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode { pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { switch (self.data) { .file => |f| alloc.free(f.blocks), - .ext_file => |f| alloc.free(f.blocks), .symlink => |s| alloc.free(s.target), - .ext_symlink => |s| alloc.free(s.target), else => {}, } } @@ -103,111 +96,135 @@ pub const Header = extern struct { inode_num: u32, }; -pub const Data = union(Type) { +pub const Data = union(enum) { dir: Dir, file: File, symlink: Symlink, - block_dev: Dev, - char_dev: Dev, + dev: Dev, fifo: Fifo, - socket: Fifo, - ext_dir: ExtDir, - ext_file: ExtFile, - ext_symlink: Symlink, - ext_block_dev: ExtDev, - ext_char_dev: ExtDev, - ext_fifo: ExtFifo, - ext_socket: ExtFifo, }; // Inode data types -pub const Dir = extern struct { - start: u32, - hard_links: u32, - size: u16, - offset: u16, - parent_inode_num: u32, +pub const Dir = struct { + //RegularDir structure: + // start: u32, + // hard_links: u32, + // size: u16, + // offset: u16, + // parent_inode_num: u32, + // ExtDir structure + // hard_links: u32, + // size: u32, + // start: u32, + // parent_inode_num: u32, + // idx_count: u16, + // offset: u16, + // xattr_idx: u32, + // dir_indexes: []DirIndex, - pub fn read(rdr: *Io.Reader) !Dir { - return util.readValueRdr(Dir, rdr); - } -}; -pub const ExtDir = extern struct { hard_links: u32, size: u32, start: u32, - parent_inode_num: u32, - idx_count: u16, offset: u16, - xattr_idx: u32, + xattr_idx: ?u32, - pub fn read(rdr: *Io.Reader) !ExtDir { - return util.readValueRdr(ExtDir, rdr); + pub fn readBasic(rdr: *Io.Reader) !Dir { + var raw: [12]u8 = undefined; + try rdr.readSliceAll(&raw); + + return .{ + .start = util.readValue(u32, raw[0..4]), + .hard_links = util.readValue(u32, raw[4..8]), + .size = util.readValue(u16, raw[8..10]), + .offset = util.readValue(u16, raw[10..12]), + .xattr_idx = null, + }; + } + pub fn readExt(rdr: *Io.Reader) !Dir { + var raw: [24]u8 = undefined; + try rdr.readSliceAll(&raw); + + return .{ + .hard_links = util.readValue(u32, raw[0..4]), + .size = util.readValue(u32, raw[4..8]), + .start = util.readValue(u32, raw[8..12]), + .offset = util.readValue(u16, raw[18..20]), + .xattr_idx = util.readValue(u32, raw[20..24]), + }; } }; pub const File = struct { - start: u32, + //RegularFile structure: + // start: u32, + // frag_idx: u32, + // frag_offset: u32, + // size: u32, + // block_sizes: []BlockSize, + //ExtFile structure: + // start: u64, + // size: u64, + // sparse: u64, + // hard_links: u32, + // frag_idx: u32, + // frag_offset: u32, + // xattr_idx: u32, + // block_sizes: []BlockSize, + + start: u64, frag_idx: u32, frag_offset: u32, - size: u32, + size: u64, blocks: []BlockSize, + xattr_idx: ?u32, - pub fn read(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File { + pub fn readBasic(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File { var raw: [16]u8 = undefined; try rdr.readSliceAll(&raw); - const frag_idx = std.mem.readInt(u32, raw[4..8], .little); - const size = std.mem.readInt(u32, raw[12..16], .little); + const frag_idx = util.readValue(u32, raw[4..8]); + const size = util.readValue(u32, raw[12..16]); - var num = size / block_size; - if (frag_idx == 0xFFFFFFFF and size % block_size > 0) - num += 1; + const block_num = if (frag_idx == 0xFFFFFFFF) + try std.math.divCeil(usize, size, block_size) + else + size / block_size; - const blocks = try alloc.alloc(BlockSize, num); - try rdr.readSliceEndian(BlockSize, blocks, .little); + const sizes = try alloc.alloc(BlockSize, block_num); + try rdr.readSliceEndian(BlockSize, sizes, .little); return .{ - .start = std.mem.readInt(u32, raw[0..4], .little), + .start = util.readValue(u32, raw[0..4]), .frag_idx = frag_idx, - .frag_offset = std.mem.readInt(u32, raw[8..12], .little), + .frag_offset = util.readValue(u32, raw[8..12]), .size = size, - .blocks = blocks, + .blocks = sizes, + .xattr_idx = null, }; } -}; -pub const ExtFile = struct { - start: u64, - size: u64, - hard_links: u32, - frag_idx: u32, - frag_offset: u32, - xattr_idx: u32, - blocks: []BlockSize, - - pub fn read(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !ExtFile { + pub fn readExt(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File { var raw: [40]u8 = undefined; try rdr.readSliceAll(&raw); - const frag_idx = std.mem.readInt(u32, raw[28..32], .little); - const size = std.mem.readInt(u64, raw[8..16], .little); + const frag_idx = util.readValue(u32, raw[28..32]); + const size = util.readValue(u64, raw[8..16]); - var num = size / block_size; - if (frag_idx == 0xFFFFFFFF and size % block_size > 0) - num += 1; + const block_num = if (frag_idx == 0xFFFFFFFF) + try std.math.divCeil(usize, size, block_size) + else + size / block_size; - const blocks = try alloc.alloc(BlockSize, num); - try rdr.readSliceEndian(BlockSize, blocks, .little); + const sizes = try alloc.alloc(BlockSize, block_num); + try rdr.readSliceEndian(BlockSize, sizes, .little); return .{ - .start = std.mem.readInt(u64, raw[0..8], .little), + .start = util.readValue(u64, raw[0..8]), .size = size, - .hard_links = std.mem.readInt(u32, raw[24..28], .little), .frag_idx = frag_idx, - .frag_offset = std.mem.readInt(u32, raw[32..36], .little), - .xattr_idx = std.mem.readInt(u32, raw[36..], .little), - .blocks = blocks, + .frag_offset = util.readValue(u32, raw[32..36]), + .blocks = sizes, + .xattr_idx = util.readValue(u32, raw[36..40]), }; } }; @@ -215,6 +232,7 @@ pub const ExtFile = struct { pub const Symlink = struct { hard_links: u32, target: []const u8, + // xattr_idx is ignored on extended symlinks as you can't apply them to symlinks anyway. pub fn read(rdr: *Io.Reader, alloc: std.mem.Allocator) !Symlink { var raw: [8]u8 = undefined; @@ -232,36 +250,55 @@ pub const Symlink = struct { } }; -pub const Dev = extern struct { +pub const Dev = struct { hard_links: u32, device: u32, + // xattr_idx omitted on basic device inodes. + xattr_idx: ?u32, - pub fn read(rdr: *Io.Reader) !Dev { - return util.readValueRdr(Dev, rdr); + pub fn readBasic(rdr: *Io.Reader) !Dev { + var raw: [8]u8 = undefined; + try rdr.readSliceAll(&raw); + + return .{ + .hard_links = util.readValue(u32, raw[0..4]), + .device = util.readValue(u32, raw[4..8]), + .xattr_idx = null, + }; + } + pub fn readExt(rdr: *Io.Reader) !Dev { + var raw: [12]u8 = undefined; + try rdr.readSliceAll(&raw); + + return .{ + .hard_links = util.readValue(u32, raw[0..4]), + .device = util.readValue(u32, raw[4..8]), + .xattr_idx = util.readValue(u32, raw[8..12]), + }; } }; -pub const ExtDev = extern struct { + +pub const Fifo = struct { hard_links: u32, - device: u32, - xattr_idx: u32, + // Xattr_idx omitted on basic fifo inodes. + xattr_idx: ?u32, - pub fn read(rdr: *Io.Reader) !ExtDev { - return util.readValueRdr(ExtDev, rdr); - } -}; - -pub const Fifo = extern struct { - hard_links: u32, + pub fn readBasic(rdr: *Io.Reader) !Fifo { + var raw: [4]u8 = undefined; + try rdr.readSliceAll(&raw); - pub fn read(rdr: *Io.Reader) !Fifo { - return util.readValueRdr(Fifo, rdr); + return .{ + .hard_links = util.readValue(u32, raw[0..4]), + .xattr_idx = null, + }; } -}; -pub const ExtFifo = extern struct { - hard_links: u32, - xattr_idx: u32, + pub fn readExt(rdr: *Io.Reader) !Fifo { + var raw: [8]u8 = undefined; + try rdr.readSliceAll(&raw); - pub fn read(rdr: *Io.Reader) !ExtFifo { - return util.readValueRdr(ExtFifo, rdr); + return .{ + .hard_links = util.readValue(u32, raw[0..4]), + .xattr_idx = util.readValue(u32, raw[4..8]), + }; } }; diff --git a/src/table.zig b/src/table.zig new file mode 100644 index 0000000..f38b144 --- /dev/null +++ b/src/table.zig @@ -0,0 +1,80 @@ +const std = @import("std"); +const Io = std.Io; + +const util = @import("utils/util.zig"); + +const MetadataReader = @import("utils/meta.zig"); +const Decomp = @import("decomp.zig"); + +pub fn Table(comptime T: type) type { + return struct { + const Self = @This(); + + const ITEMS_PER_BLOCK = 8192 / @sizeOf(T); + + alloc: std.mem.Allocator, + + data: []u8, + decomp: Decomp.Fn, + + start: u64, + count: u32, + block_count: u32, + + table: std.hash_map.AutoHashMapUnmanaged(u32, []T) = .empty, + mut: Io.Mutex = .init, + + pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, start: u64, count: u32) !Self { + return .{ + .alloc = alloc, + + .data = data, + .decomp = decomp, + + .start = start, + .count = count, + .block_count = std.math.divCeil(u32, count, ITEMS_PER_BLOCK), + }; + } + pub fn deinit(self: *Self) void { + var iter = self.table.valueIterator(); + while (iter.next()) |block| + self.alloc.free(block); + + self.table.deinit(self.alloc); + } + + fn getBlock(self: *Self, io: Io, idx: u32) ![]T { + const init_get = self.table.get(idx); + if (init_get != null) return init_get.?; + + self.mut.lock(io); + defer self.mut.unlock(io); + + const start = util.readValue(u64, self.data[self.start + (idx * 8) ..][0..8]); + + const len = if (idx == self.block_count - 1) self.count % ITEMS_PER_BLOCK else ITEMS_PER_BLOCK; + + const new_block = try self.alloc.alloc(T, len); + errdefer self.alloc.free(new_block); + + var meta: MetadataReader = .init(self.alloc, self.data[start..], self.decomp); + + try meta.interface.readSliceEndian(T, new_block, .little); + + try self.table.put(self.alloc, idx, new_block); + + return new_block; + } + + pub fn get(self: *Self, io: Io, idx: u32) !T { + if (idx >= self.count) return error.InvalidIndex; + + const block_idx = idx / ITEMS_PER_BLOCK; + + const block = self.getBlock(io, block_idx); + + return block[idx % ITEMS_PER_BLOCK]; + } + }; +} diff --git a/src/utils/data_reader.zig b/src/utils/data_reader.zig index 25a5581..5b30003 100644 --- a/src/utils/data_reader.zig +++ b/src/utils/data_reader.zig @@ -31,11 +31,11 @@ interface: Io.Reader = .{ }, }, -pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, block_size: u32, start: u64, size: u64, blocks: []BlockSize) DataReader { +pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, block_size: u32, size: u64, blocks: []BlockSize) DataReader { return .{ .alloc = alloc, - .data = data[start..], + .data = data, .decomp = decomp, .block_size = block_size, diff --git a/src/utils/decompress.zig b/src/utils/decompress.zig new file mode 100644 index 0000000..c4e4b73 --- /dev/null +++ b/src/utils/decompress.zig @@ -0,0 +1,148 @@ +const std = @import("std"); +const Io = std.Io; + +const Options = @import("../options.zig"); +const Inode = @import("../inode.zig"); +const Decomp = @import("../decomp.zig"); +const Directory = @import("../dir.zig"); +const MetadataReader = @import("meta.zig"); +const DataReader = @import("data_reader.zig"); +const Super = @import("../archive.zig").Super; + +pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp.Fn, super: Super, inode: Inode, filepath: []const u8, options: Options) !void { + const path = std.mem.trim(u8, filepath, "/"); + + var pool: std.ArrayList(InodeAndPath) = .initBuffer(&.{.{ .inode = inode, .path = path }}); + defer { + while (pool.pop()) |in| + in.deinit(in); + pool.deinit(alloc); + } + + var dirs: std.ArrayList(InodeAndPath) = .empty; + defer { + while (dirs.pop()) |dir| + alloc.free(dir.path); + dirs.deinit(alloc); + } + + // TODO: Use options + _ = options; + + while (pool.pop()) |in| { + switch (in.inode.data) { + .dir => |d| { + try Io.Dir.cwd().createDirPath(io, in.path); + + if (d.size <= 3) { + defer if (in.path.len != path.len) + in.deinit(alloc); + // TODO: apply permissions & xattrs immediately. + continue; + } + + var meta: MetadataReader = .init(alloc, data[d.start], decomp); + try meta.interface.discardAll(d.offset); + + var dir: Directory = .read(alloc, &meta.interface, d.size); + defer dir.deinit(alloc); + + for (dir.entries) |entry| { + const new_inode: Inode = try .readLocation(alloc, data, entry.start, entry.offset, super); + + const new_path = std.mem.concat(alloc, u8, &.{ in.path, "/", entry.name }) catch |err| { + new_inode.deinit(alloc); + return err; + }; + + try pool.append(alloc, .{ .inode = new_inode, .path = new_path }); + } + + try dirs.append(alloc, in); + }, + .file => |f| { + defer if (in.path.len != path.len) + in.deinit(alloc); + + var atomic = try Io.Dir.cwd().createFileAtomic(io, in.path, .{}); + defer atomic.deinit(io); + + var rdr: DataReader = .init(alloc, data[f.start..], decomp, super.block_size, f.size, f.blocks); + defer rdr.deinit(); + + if (f.frag_idx != 0xFFFFFFFF) { + //TODO: fragment + } + + var writer = atomic.file.writer(io, &[0]u8{}); // TODO: find a resonable buffer size. + try rdr.interface.streamRemaining(&writer.interface); + + // TODO: apply permissions & xattrs + + try atomic.link(io); + }, + .symlink => |s| { + defer if (in.path.len != path.len) + in.deinit(alloc); + + try Io.Dir.cwd().symLink(io, s.target, in.path, .{}); + }, + .dev => |d| { + defer if (in.path.len != path.len) + in.deinit(alloc); + + const mode = switch (in.inode.header.type) { + .char_dev, .ext_char_dev => std.os.linux.DT.CHR, + .block_dev, .ext_block_dev => std.os.linux.DT.BLK, + else => unreachable, + }; + + const sent_path = try alloc.dupeSentinel(u8, in.path, 0); + + const res = std.os.linux.mknod(sent_path, mode, d.device); + alloc.free(sent_path); + if (res != 0) + return error.MkNodError; + + // TODO: apply permissions & xattrs + }, + .fifo => |f| { + _ = f; + + defer if (in.path.len != path.len) + in.deinit(alloc); + + const mode = switch (in.inode.header.type) { + .fifo, .ext_fifo => std.os.linux.DT.FIFO, + .socket, .ext_socket => std.os.linux.DT.SOCK, + else => unreachable, + }; + + const sent_path = try alloc.dupeSentinel(u8, in.path, 0); + + const res = std.os.linux.mknod(sent_path, mode, 0); + alloc.free(sent_path); + if (res != 0) + return error.MkNodError; + + // TODO: apply permissions & xattrs + }, + } + } + + while (dirs.pop()) |dir| { + defer if (dir.path.len != path.len) + alloc.free(dir.path); + // TODO: apply permissions & xattrs + } +} +const InodeAndPath = struct { + inode: Inode, + path: []const u8, + + fn deinit(self: InodeAndPath, alloc: std.mem.Allocator) void { + self.inode.deinit(alloc); + alloc.free(self.path); + } +}; +pub fn multi() !void {}