diff --git a/src/file.zig b/src/file.zig index 781607c..7baa55b 100644 --- a/src/file.zig +++ b/src/file.zig @@ -32,10 +32,7 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, entry: Directory.Entry) return .{ .file = archive.file, .super = archive.super, - .decomp = .{ - .alloc = alloc, - .vtable = &.{ .stateless = archive.stateless_decomp.vtable.stateless }, - }, + .decomp = archive.stateless_decomp.statelessCopy(alloc), .name = new_name, .inode = try Utils.readInode( alloc, @@ -63,34 +60,26 @@ pub fn isDir(self: File) bool { } /// Opens a sub-file. If the given path is "" or "." (after trimming /) a copy of the File is returned. pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File { - switch (self.inode.hdr.inode_type) { - .dir, .ext_dir => { - var res = try self.inode.findInode( - alloc, - &self.decomp, - self.file, - self.super.dir_start, - self.super.inode_start, - self.super.block_size, - filepath, - ); - if (res.name.len == 0) { - res.name = try alloc.alloc(u8, self.name.len); - @memcpy(res.name, self.name); - } - return .{ - .file = self.file, - .super = self.super, - .decomp = .{ - .alloc = alloc, - .vtable = &.{ .stateless = self.decomp.vtable.stateless }, - }, - .name = res.name, - .inode = res.inode, - }; - }, - else => Error.NotDirectory, + var res = try self.inode.findInode( + alloc, + &self.decomp, + self.file, + self.super.dir_start, + self.super.inode_start, + self.super.block_size, + filepath, + ); + if (res.name.len == 0) { + res.name = try alloc.alloc(u8, self.name.len); + @memcpy(res.name, self.name); } + return .{ + .file = self.file, + .super = self.super, + .decomp = self.decomp.statelessCopy(alloc), + .name = res.name, + .inode = res.inode, + }; } pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter { return .{ @@ -107,10 +96,14 @@ pub fn isRegularFile(self: File) bool { else => false, }; } +// a std.Io.Reader compatible reader that reads a regular file's data. pub fn dataReader(self: File, alloc: std.mem.Allocator) !DataReader { - if (!self.isRegularFile()) return Error.NotRegularFile; - _ = alloc; - return error.TODO; + return self.inode.dataReader( + &self.decomp.statelessCopy(alloc), + self.file, + self.super.frag_start, + self.super.block_size, + ); } // Universal functions diff --git a/src/inode.zig b/src/inode.zig index 23abaac..c286626 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -7,11 +7,14 @@ const Reader = std.Io.Reader; const Decompressor = @import("decomp.zig"); const Directory = @import("directory.zig"); +const FragEntry = @import("archive.zig").FragEntry; 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 LookupTable = @import("lookup_table.zig"); const MinimalSuperblock = @import("archive.zig").MinimalSuperblock; +const DataReader = @import("util/data_reader.zig"); const MetadataReader = @import("util/metadata.zig"); const OffsetFile = @import("util/offset_file.zig"); @@ -183,7 +186,19 @@ pub const Error = error{ // 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 (""). +// Universal + +pub fn uid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 { + return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.uid_idx); +} +pub fn gid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 { + return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.gid_idx); +} + +// Dir inodes + +/// 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, @@ -279,6 +294,7 @@ inline fn findInodeRaw( return inode.findInode(alloc, decomp, fil, dir_start, inode_start, block_size, path[first_element.len..]); } +/// Get the directory entries for a directory inode. 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), @@ -292,3 +308,29 @@ inline fn readDirRaw(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: try meta_rdr.interface.discardAll(dat.block_offset); return Directory.readDirectory(alloc, meta_rdr, dat.size); } + +// file inodes + +/// Gets the data reader for a file inode. +pub fn dataReader(inode: Inode, decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32) !DataReader { + return switch (inode.data) { + .file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f), + .ext_file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f), + else => Error.NotRegularFile, + }; +} +inline fn dataReaderRaw(decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32, dat: anytype) !DataReader { + return .init( + decomp, + fil, + block_size, + dat.block_sizes, + dat.size, + dat.block_start, + if (dat.frag_idx != 0xFFFFFFFF) + try LookupTable.stateless(FragEntry, fil, decomp, frag_start, dat.frag_idx) + else + null, + dat.frag_offset, + ); +} diff --git a/src/inode/file.zig b/src/inode/file.zig index decb26c..bd635a9 100644 --- a/src/inode/file.zig +++ b/src/inode/file.zig @@ -9,7 +9,7 @@ pub const BlockSize = packed struct { pub const File = struct { block_start: u32, frag_idx: u32, - block_offset: u32, + frag_offset: u32, size: u32, block_sizes: []BlockSize, @@ -27,7 +27,7 @@ pub const File = struct { 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), + .frag_offset = std.mem.readVarInt(u32, buf[8..12], .little), .size = size, .block_sizes = sizes, }; @@ -40,7 +40,7 @@ pub const ExtFile = struct { sparse: u64, hard_links: u32, frag_idx: u32, - block_offset: u32, + frag_offset: u32, xattr_idx: u32, block_sizes: []BlockSize, @@ -61,7 +61,7 @@ pub const ExtFile = struct { .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), + .frag_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/util/data_reader.zig b/src/util/data_reader.zig index e69de29..8d45085 100644 --- a/src/util/data_reader.zig +++ b/src/util/data_reader.zig @@ -0,0 +1,138 @@ +const std = @import("std"); +const Reader = std.Io.Reader; +const Writer = std.Io.Writer; +const Limit = std.Io.Limit; + +const FragEntry = @import("../archive.zig").FragEntry; +const Decompressor = @import("../decomp.zig"); +const BlockSize = @import("../inode/file.zig").BlockSize; +const OffsetFile = @import("offset_file.zig"); + +const DataReader = @This(); + +decomp: *const Decompressor, +file: OffsetFile, +block_size: u32, +blocks: []BlockSize, +size: u64, +frag: ?FragEntry, +frag_offset: u32, + +offset: u64, +idx: usize = 0, +sparse: bool = false, + +interface: Reader, + +pub fn init(decomp: *const Decompressor, file: OffsetFile, block_size: u32, blocks: []BlockSize, size: u64, init_offset: u64, frag: ?FragEntry, frag_offset: u32) DataReader { + return .{ + .decomp = decomp, + .file = file, + .block_size = block_size, + .blocks = blocks, + .size = size, + .frag = frag, + .frag_offset = frag_offset, + + .offset = init_offset, + + .interface = .{ + .buffer = &[1]u8{undefined} ** (1024 * 1024), + .end = 0, + .seek = 0, + .vtable = &.{ .stream = stream, .discard = discard, .readVec = readVec }, + }, + }; +} + +fn numBlocks(self: *DataReader) usize { + return if (self.frag == null) + self.blocks.len + else + self.blocks.len + 1; +} +fn advanceBuffer(self: *DataReader) Reader.Error!void { + if (self.idx >= self.numBlocks()) return Reader.Error.EndOfStream; + defer self.idx += 1; + self.sparse = false; + self.interface.end = 0; // If we error out and the error is ignored, we'll stil end up back here to error again. + self.interface.seek = 0; + if (self.idx == self.blocks.len) { // Fragment + var rdr = self.file.readerAt(self.frag.?.block_start, &[0]u8{}) catch return Reader.Error.ReadFailed; + const size = self.size % self.block_size; + if (self.frag.?.size.uncompressed) { + try rdr.interface.discardAll(self.frag_offset); + try rdr.interface.readSliceAll(self.interface.buffer[0..size]); + self.interface.end = size; + return; + } + const raw_loc = self.interface.buffer.len - self.frag.?.size.size; + try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]); + _ = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch + return Reader.Error.ReadFailed; + @memmove(self.interface.buffer[0..size], self.interface.buffer[self.frag_offset .. self.frag_offset + size]); + self.interface.end = size; + return; + } + const block = self.blocks[self.idx]; + if (block.size == 0) { + self.interface.end = if (self.idx == self.numBlocks() - 1) + self.size % self.block_size + else + self.block_size; + self.sparse = true; + return; + } + defer self.offset += block.size; + var rdr = try self.file.readerAt(self.offset, &[0]u8{}); + if (block.uncompressed) { + try rdr.interface.readSliceAll(self.interface.buffer[0..block.size]); + self.interface.end = block.size; + return; + } + const raw_loc = self.interface.buffer.len - block.size; + try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]); + self.interface.end = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch + return Reader.Error.ReadFailed; +} + +fn stream(r: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize { + var self: *DataReader = @fieldParentPtr("interface", r); + if (r.seek == r.end) try self.advanceBuffer(); + if (limit == .nothing) return 0; + + const to_write = @min(r.end - r.seek, @intFromEnum(limit)); + const wrote = if (self.sparse) + try wrt.splatByte(0, to_write) + else + try wrt.write(r.buffer[r.seek .. r.seek + to_write]); + r.seek += wrote; + return wrote; +} +fn discard(r: *Reader, limit: Limit) Reader.Error!usize { + var self: *DataReader = @fieldParentPtr("interface", r); + if (r.seek == r.end) try self.advanceBuffer(); + if (limit == .nothing) return 0; + + const adv = @min(r.end - r.seek, @intFromEnum(limit)); + r.seek += adv; + return adv; +} +fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize { + var self: *DataReader = @fieldParentPtr("interface", r); + if (r.seek == r.end) try self.advanceBuffer(); + + var wrote: usize = 0; + for (vec) |slice| { + if (r.seek == r.end) break; + const to_copy = @min(r.end - r.seek, slice.len); + if (self.sparse) { + @memset(slice[0..to_copy], 0); + } else { + @memcpy(slice[0..to_copy], r.buffer[r.seek .. r.seek + to_copy]); + } + r.seek += to_copy; + wrote += to_copy; + } + return wrote; +}