DataReader!

This commit is contained in:
Caleb J. Gardner
2026-04-08 02:33:58 -05:00
parent 3e97aabe53
commit b111859c4d
4 changed files with 212 additions and 39 deletions
+9 -16
View File
@@ -32,10 +32,7 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, entry: Directory.Entry)
return .{ return .{
.file = archive.file, .file = archive.file,
.super = archive.super, .super = archive.super,
.decomp = .{ .decomp = archive.stateless_decomp.statelessCopy(alloc),
.alloc = alloc,
.vtable = &.{ .stateless = archive.stateless_decomp.vtable.stateless },
},
.name = new_name, .name = new_name,
.inode = try Utils.readInode( .inode = try Utils.readInode(
alloc, alloc,
@@ -63,8 +60,6 @@ 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. /// 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 { 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( var res = try self.inode.findInode(
alloc, alloc,
&self.decomp, &self.decomp,
@@ -81,16 +76,10 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
return .{ return .{
.file = self.file, .file = self.file,
.super = self.super, .super = self.super,
.decomp = .{ .decomp = self.decomp.statelessCopy(alloc),
.alloc = alloc,
.vtable = &.{ .stateless = self.decomp.vtable.stateless },
},
.name = res.name, .name = res.name,
.inode = res.inode, .inode = res.inode,
}; };
},
else => Error.NotDirectory,
}
} }
pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter { pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter {
return .{ return .{
@@ -107,10 +96,14 @@ pub fn isRegularFile(self: File) bool {
else => false, 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 { pub fn dataReader(self: File, alloc: std.mem.Allocator) !DataReader {
if (!self.isRegularFile()) return Error.NotRegularFile; return self.inode.dataReader(
_ = alloc; &self.decomp.statelessCopy(alloc),
return error.TODO; self.file,
self.super.frag_start,
self.super.block_size,
);
} }
// Universal functions // Universal functions
+43 -1
View File
@@ -7,11 +7,14 @@ const Reader = std.Io.Reader;
const Decompressor = @import("decomp.zig"); const Decompressor = @import("decomp.zig");
const Directory = @import("directory.zig"); const Directory = @import("directory.zig");
const FragEntry = @import("archive.zig").FragEntry;
const Dir = @import("inode/dir.zig"); const Dir = @import("inode/dir.zig");
const File = @import("inode/file.zig"); const File = @import("inode/file.zig");
const Misc = @import("inode/misc.zig"); const Misc = @import("inode/misc.zig");
const Sym = @import("inode/sym.zig"); const Sym = @import("inode/sym.zig");
const LookupTable = @import("lookup_table.zig");
const MinimalSuperblock = @import("archive.zig").MinimalSuperblock; const MinimalSuperblock = @import("archive.zig").MinimalSuperblock;
const DataReader = @import("util/data_reader.zig");
const MetadataReader = @import("util/metadata.zig"); const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig"); const OffsetFile = @import("util/offset_file.zig");
@@ -183,7 +186,19 @@ pub const Error = error{
// Utils functions // 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( pub fn findInode(
inode: Inode, inode: Inode,
alloc: std.mem.Allocator, 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..]); 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 { pub fn readDirectory(inode: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64) ![]Directory.Entry {
return switch (inode.data) { return switch (inode.data) {
.dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d), .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); try meta_rdr.interface.discardAll(dat.block_offset);
return Directory.readDirectory(alloc, meta_rdr, dat.size); 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,
);
}
+4 -4
View File
@@ -9,7 +9,7 @@ pub const BlockSize = packed struct {
pub const File = struct { pub const File = struct {
block_start: u32, block_start: u32,
frag_idx: u32, frag_idx: u32,
block_offset: u32, frag_offset: u32,
size: u32, size: u32,
block_sizes: []BlockSize, block_sizes: []BlockSize,
@@ -27,7 +27,7 @@ pub const File = struct {
return .{ return .{
.block_start = std.mem.readVarInt(u32, buf[0..4], .little), .block_start = std.mem.readVarInt(u32, buf[0..4], .little),
.frag_idx = frag_idx, .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, .size = size,
.block_sizes = sizes, .block_sizes = sizes,
}; };
@@ -40,7 +40,7 @@ pub const ExtFile = struct {
sparse: u64, sparse: u64,
hard_links: u32, hard_links: u32,
frag_idx: u32, frag_idx: u32,
block_offset: u32, frag_offset: u32,
xattr_idx: u32, xattr_idx: u32,
block_sizes: []BlockSize, block_sizes: []BlockSize,
@@ -61,7 +61,7 @@ pub const ExtFile = struct {
.sparse = std.mem.readVarInt(u64, buf[16..24], .little), .sparse = std.mem.readVarInt(u64, buf[16..24], .little),
.hard_links = std.mem.readVarInt(u32, buf[24..28], .little), .hard_links = std.mem.readVarInt(u32, buf[24..28], .little),
.frag_idx = frag_idx, .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), .xattr_idx = std.mem.readVarInt(u32, buf[36..40], .little),
.block_sizes = sizes, .block_sizes = sizes,
}; };
+138
View File
@@ -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;
}