diff --git a/src/decomp.zig b/src/decomp.zig new file mode 100644 index 0000000..3c3faf8 --- /dev/null +++ b/src/decomp.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +const Error = error{ + OutOfMemory, + EndOfStream, + ReadFailed, + WriteFailed, +}; + +const Decompressor = @This(); + +alloc: std.mem.Allocator = std.heap.page_allocator, +vtable: *struct { + decompress: *const fn (*Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress, + stateless: *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize, +}, + +pub fn decompress(self: *Decompressor, in: []u8, out: []u8) Error!usize { + return self.vtable.decompress(self, in, out); +} + +fn defaultDecompress(self: *Decompressor, in: []u8, out: []u8) Error!usize { + return self.vtable.stateless(self.alloc, in, out); +} diff --git a/src/directory.zig b/src/directory.zig new file mode 100644 index 0000000..380be52 --- /dev/null +++ b/src/directory.zig @@ -0,0 +1,59 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +const InodeType = @import("inode.zig").Type; + +pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) []Entry { + var read: u32 = 3; + var hdr: Header = undefined; + var raw: RawEntry = undefined; + var out: std.ArrayList(Entry) = .initCapacity(alloc, 50); + errdefer { + for (out.items) |i| + alloc.free(i.name); + out.deinit(alloc); + } + while (read < size) { + try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + try out.ensureUnusedCapacity(alloc, hdr.count + 1); + read += @sizeOf(Header); + for (0..hdr.count + 1) |_| { + try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little); + read += @sizeOf(RawEntry) + raw.name_size + 1; + const new = out.addOneAssumeCapacity(); + new.* = .{ + .block_start = hdr.block_start, + .block_offset = raw.block_offset, + .num = @abs(hdr.num + raw.num_offset), + .inode_type = raw.inode_type, + .name = try alloc.alloc(u8, raw.name_size + 1), + }; + try rdr.readSliceEndian(u8, new.name, .little); + } + } + return out.toOwnedSlice(alloc); +} + +// Types + +const Entry = struct { + block_start: u32, + block_offset: u16, + num: u32, + inode_type: InodeType, + name: []u8, +}; + +// extern instead of packed due to alignment issues (packed will read it as 16 bytes instead of 12). +const Header = extern struct { + count: u32, + block_start: u32, + num: u32, +}; + +const RawEntry = packed struct { + block_offset: u16, + num_offset: i16, + inode_type: InodeType, + name_size: u16, +}; diff --git a/src/file.zig b/src/file.zig new file mode 100644 index 0000000..312a350 --- /dev/null +++ b/src/file.zig @@ -0,0 +1,6 @@ +const std = @import("std"); + +const Inode = @import("inode.zig"); + +name: []const u8, +inode: Inode, diff --git a/src/inode.zig b/src/inode.zig index bdab250..7fc5d55 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -34,6 +34,15 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode { }, }; } +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 => {}, + } +} // Types diff --git a/src/inode/dir.zig b/src/inode/dir.zig index ec8ddab..5df8ddd 100644 --- a/src/inode/dir.zig +++ b/src/inode/dir.zig @@ -1,3 +1,35 @@ -pub const Dir = packed struct {}; +const Reader = @import("std").Io.Reader; -pub const ExtDir = packed struct {}; +pub const Dir = packed struct { + block_start: u32, + hard_links: u32, + size: u16, + block_offset: u16, + parent_num: u32, + + const Self = @This(); + + pub fn read(rdr: *Reader) !Self { + var new: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&new), .little); + return new; + } +}; + +pub const ExtDir = packed struct { + hard_links: u32, + size: u32, + block_start: u32, + parent_num: u32, + idx_count: u16, + block_offset: u16, + xattr_idx: u32, + + const Self = @This(); + + pub fn read(rdr: *Reader) !Self { + var new: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&new), .little); + return new; + } +}; diff --git a/src/inode/file.zig b/src/inode/file.zig index 2f5a72f..decb26c 100644 --- a/src/inode/file.zig +++ b/src/inode/file.zig @@ -1,3 +1,69 @@ -pub const File = struct {}; +const std = @import("std"); +const Reader = std.Io.Reader; -pub const ExtFile = struct {}; +pub const BlockSize = packed struct { + size: u31, + uncompressed: bool, +}; + +pub const File = struct { + block_start: u32, + frag_idx: u32, + block_offset: u32, + size: u32, + block_sizes: []BlockSize, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File { + var buf: [16]u8 = undefined; + try rdr.readSliceAll(&buf); + const frag_idx = std.mem.readVarInt(u32, buf[4..8], .little); + const size = std.mem.readVarInt(u32, buf[12..], .little); + const sizes_len = size / block_size; + if (frag_idx != 0xFFFFFFFF and size % block_size > 0) + sizes_len += 1; + const sizes = try alloc.alloc(BlockSize, sizes_len); + errdefer alloc.free(sizes); + try rdr.readSliceEndian(BlockSize, sizes, .little); + return .{ + .block_start = std.mem.readVarInt(u32, buf[0..4], .little), + .frag_idx = frag_idx, + .block_offset = std.mem.readVarInt(u32, buf[8..12], .little), + .size = size, + .block_sizes = sizes, + }; + } +}; + +pub const ExtFile = struct { + block_start: u64, + size: u64, + sparse: u64, + hard_links: u32, + frag_idx: u32, + block_offset: u32, + xattr_idx: u32, + block_sizes: []BlockSize, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File { + var buf: [40]u8 = undefined; + try rdr.readSliceAll(&buf); + const frag_idx = std.mem.readVarInt(u32, buf[28..32], .little); + const size = std.mem.readVarInt(u64, buf[8..16], .little); + const sizes_len = size / block_size; + if (frag_idx != 0xFFFFFFFF and size % block_size > 0) + sizes_len += 1; + const sizes = try alloc.alloc(BlockSize, sizes_len); + errdefer alloc.free(sizes); + try rdr.readSliceEndian(BlockSize, sizes, .little); + return .{ + .block_start = std.mem.readVarInt(u64, buf[0..8], .little), + .size = size, + .sparse = std.mem.readVarInt(u64, buf[16..24], .little), + .hard_links = std.mem.readVarInt(u32, buf[24..28], .little), + .frag_idx = frag_idx, + .block_offset = std.mem.readVarInt(u32, buf[32..36], .little), + .xattr_idx = std.mem.readVarInt(u32, buf[36..40], .little), + .block_sizes = sizes, + }; + } +}; diff --git a/src/inode/misc.zig b/src/inode/misc.zig index 51166b8..4011d04 100644 --- a/src/inode/misc.zig +++ b/src/inode/misc.zig @@ -1,7 +1,53 @@ -pub const Device = packed struct {}; +const Reader = @import("std").Io.Reader; -pub const ExtDevice = packed struct {}; +pub const Device = packed struct { + hard_links: u32, + device: u32, -pub const Ipc = packed struct {}; + const Self = @This(); -pub const ExtIpc = packed struct {}; + pub fn read(rdr: *Reader) !Self { + var new: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&new), .little); + return new; + } +}; + +pub const ExtDevice = packed struct { + hard_links: u32, + device: u32, + xattr_idx: u32, + + const Self = @This(); + + pub fn read(rdr: *Reader) !Self { + var new: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&new), .little); + return new; + } +}; + +pub const Ipc = packed struct { + hard_links: u32, + + const Self = @This(); + + pub fn read(rdr: *Reader) !Self { + var new: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&new), .little); + return new; + } +}; + +pub const ExtIpc = packed struct { + hard_links: u32, + xattr_idx: u32, + + const Self = @This(); + + pub fn read(rdr: *Reader) !Self { + var new: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&new), .little); + return new; + } +}; diff --git a/src/inode/sym.zig b/src/inode/sym.zig index 5a080ab..3d8b4ea 100644 --- a/src/inode/sym.zig +++ b/src/inode/sym.zig @@ -1,3 +1,42 @@ -pub const Symlink = struct {}; +const std = @import("std"); +const Reader = std.Io.Reader; -pub const ExtSymlink = struct {}; +pub const Symlink = struct { + hard_links: u32, + target: []const u8, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink { + var buf: [8]u8 = undefined; + try rdr.readSliceAll(&buf); + const size = std.mem.readVarInt(u32, buf[4..], .little); + const target = try alloc.alloc(u8, size); + errdefer alloc.free(target); + try rdr.readSliceEndian(u8, target, .little); + return .{ + .hard_links = std.mem.readVarInt(u32, buf[0..4], .little), + .target = target, + }; + } +}; + +pub const ExtSymlink = struct { + hard_links: u32, + xattr_idx: u32, + target: []const u8, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink { + var buf: [8]u8 = undefined; + try rdr.readSliceAll(&buf); + const size = std.mem.readVarInt(u32, buf[4..], .little); + const target = try alloc.alloc(u8, size); + errdefer alloc.free(target); + try rdr.readSliceEndian(u8, target, .little); + var xattr_idx: u32 = undefined; + try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little); + return .{ + .hard_links = std.mem.readVarInt(u32, buf[0..4], .little), + .target = target, + .xattr_idx = xattr_idx, + }; + } +}; diff --git a/src/util/metadata.zig b/src/util/metadata.zig new file mode 100644 index 0000000..5e075c5 --- /dev/null +++ b/src/util/metadata.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const Reader = std.Io.Reader; +const Writer = std.Io.Writer; +const Limit = std.Io.Limit; + +const Decompressor = @import("../decomp.zig"); + +const Header = packed struct { + size: u15, + uncompressed: bool, +}; + +const MetadataReader = @This(); + +rdr: *Reader, +decomp: *Decompressor, + +read_buf: [8192]u8 = undefined, +interface: Reader = .{ + .buffer = &([1]u8{undefined} ** 8192), + .end = 0, + .seek = 0, + .vtable = &.{ + .stream = stream, + .discard = discard, + .readVec = readVec, + }, +}, + +pub fn init(rdr: *Reader, decomp: *Decompressor) MetadataReader { + return .{ .rdr = rdr, .decomp = decomp }; +} +fn advanceBuffer(self: *MetadataReader) Reader.Error!void { + self.interface.seek = 0; + var hdr: Header = undefined; + try self.rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + try self.rdr.readSliceAll(self.read_buf[0..hdr.size]); + if (hdr.uncompressed) { + @memcpy(self.interface.buffer[0..hdr.size], self.read_buf[0..hdr.size]); + self.interface.end = hdr.size; + return; + } + self.interface.end = self.decomp.decompress(self.read_buf[0..hdr.size], self.interface.buffer) catch |err| return switch (err) { + error.OutOfMemory => error.ReadFailed, + else => err, + }; +} + +fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize { + var self: *MetadataReader = @fieldParentPtr("interface", rdr); + if (rdr.seek == rdr.end) try self.advanceBuffer(); + const to_write = @min(@intFromEnum(limit), rdr.end - rdr.seek); + const wrote = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_write]); + rdr.seek += wrote; + return wrote; +} +fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize { + var self: *MetadataReader = @fieldParentPtr("interface", rdr); + if (rdr.seek == rdr.end) try self.advanceBuffer(); + const to_adv = @min(@intFromEnum(limit), rdr.end - rdr.seek); + rdr.seek += to_adv; + return to_adv; +} +fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize { + var self: *MetadataReader = @fieldParentPtr("interface", rdr); + if (rdr.seek == rdr.end) try self.advanceBuffer(); + var wrote = 0; + for (vec) |v| { + if (rdr.seek == rdr.end) break; + const to_write = @min(v.len, rdr.end - rdr.seek); + @memcpy(v[0..to_write], rdr.buffer[rdr.seek .. rdr.seek + to_write]); + wrote += to_write; + rdr.seek += to_write; + } + return wrote; +} diff --git a/src/util/offset_file.zig b/src/util/offset_file.zig index a209b72..312ee5c 100644 --- a/src/util/offset_file.zig +++ b/src/util/offset_file.zig @@ -11,3 +11,10 @@ pub fn readerAt(self: OffsetFile, offset: u64, buf: []u8) !FileReader { try rdr.seekTo(self.offset + offset); return rdr; } +pub fn valueAt(self: OffsetFile, comptime T: type, offset: u64) !T { + var rdr = self.fil.reader(&[0]u8{}); + try rdr.seekTo(self.offset + offset); + var new: T = undefined; + try rdr.interface.readSliceEndian(T, @ptrCast(&new), .little); + return new; +}