diff --git a/build.zig b/build.zig index d2c0362..133f0db 100644 --- a/build.zig +++ b/build.zig @@ -36,7 +36,7 @@ pub fn build(b: *std.Build) !void { const deps = try getDependencies(b, optimize, target, allow_lzo); const lib = b.addLibrary(.{ - .name = "unsquashfs", + .name = "squashfs", .use_llvm = debug, .version = version, .root_module = b.createModule(.{ @@ -54,6 +54,23 @@ pub fn build(b: *std.Build) !void { for (deps) |d| lib.root_module.linkLibrary(d); + b.installArtifact(lib); + + const exe = b.addExecutable(.{ + .name = "unsquashfs", + .use_llvm = debug, + .version = version, + .root_module = b.createModule(.{ + .optimize = optimize, + .target = target, + .root_source_file = b.path("src/bin/unsquashfs.zig"), + .valgrind = debug, + }), + }); + exe.root_module.linkLibrary(lib); + + b.installArtifact(exe); + const lib_test = b.addTest(.{ .name = "squashfs-test", .root_module = lib.root_module, @@ -66,9 +83,14 @@ pub fn build(b: *std.Build) !void { .name = "squashfs-check", .root_module = lib.root_module, }); + const exe_check = b.addLibrary(.{ + .name = "unsquashfs-check", + .root_module = exe.root_module, + }); const check = b.step("check", "Check if squashfs compiles"); check.dependOn(&lib_check.step); + check.dependOn(&exe_check.step); } fn getDependencies(b: *Build, optimize: std.builtin.OptimizeMode, target: Build.ResolvedTarget, allow_lzo: bool) ![]*Build.Step.Compile { diff --git a/src/archive.zig b/src/archive.zig index e826403..faf161e 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -4,28 +4,83 @@ const File = Io.File; const MemoryMap = File.MemoryMap; const Decomp = @import("decomp.zig"); +const DecompCache = @import("decomp_cache.zig"); const ExtractionOptions = @import("options.zig"); const Inode = @import("inode.zig"); const SfsFile = @import("file.zig"); const Archive = @This(); -map: MemoryMap, +const CACHE_MEM_MAX = 1024 * 1024 * 1024; -decomp: Decomp.Fn, +super: Superblock, -pub fn init(io: Io, fil: File) !Archive { - return initAdvanced(io, fil, 0); +cache: DecompCache, + +pub fn init(alloc: std.mem.Allocator, io: Io, fil: File) !Archive { + return initAdvanced(alloc, io, fil, 0, 0); +} +pub fn initAdvanced(alloc: std.mem.Allocator, io: Io, fil: File, offset: u64, cache_memory_max: u64) !Archive { + var rdr = fil.reader(io, &[0]u8{}); + try rdr.seekTo(offset); + var super: Superblock = undefined; + try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little); + try super.validate(); + + const map = try fil.createMemoryMap(io, .{ + .offset = offset, + .len = super.size, + .protection = .{ .read = true }, + }); + + return .{ + .super = super, + + .cache = .init( + alloc, + map, + super.compression, + if (cache_memory_max != 0) + cache_memory_max + else + @min(CACHE_MEM_MAX, (try std.process.totalSystemMemory()) / 2), + ), + }; } -pub fn initAdvanced(io: Io, fil: File, offset: u64) !Archive {} pub fn deinit(self: *Archive, io: Io) void { - self.map.destroy(io); + self.cache.deinit(io); } -pub fn root(self: Archive, alloc: std.mem.Allocator) !SfsFile {} -pub fn open(self: Archive, alloc: std.mem.Allocator, filepath: []const u8) !SfsFile {} +pub fn root(self: *Archive, alloc: std.mem.Allocator, io: Io) !SfsFile { + const inode: Inode = try .initRef( + alloc, + io, + &self.cache, + self.super.inode_start, + self.super.block_size, + self.super.root_ref, + ); + return .init(alloc, self, inode, ""); +} +pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile { + const path = std.mem.trim(u8, filepath, "/"); -pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {} + var root_file = try self.root(alloc, io); + + if (path.len == 0 or path[0] == '.') return root_file; + + defer root_file.deinit(); + return root_file.open(alloc, io, filepath); +} + +pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void { + _ = self; + _ = alloc; + _ = io; + _ = ext_loc; + _ = options; + return error.TODO; +} // Superblock @@ -37,7 +92,9 @@ pub const Superblock = extern struct { frag_count: u32, compression: Decomp.Enum, block_log: u16, - flags: packed struct(u16) {}, + flags: packed struct(u16) { + TODO: u16, + }, id_count: u16, ver_maj: u16, ver_min: u16, @@ -49,6 +106,15 @@ pub const Superblock = extern struct { dir_start: u64, frag_start: u64, export_start: u64, + + pub fn validate(self: Superblock) !void { + if (self.magic != std.mem.readInt(u32, "hsqs", .little)) + return error.BadMagic; + if (self.ver_maj != 4 or self.ver_min != 0) + return error.InvalidVersion; + if (self.block_log != std.math.log2(self.block_size)) + return error.BadBlockLog; + } }; // Test @@ -61,7 +127,7 @@ test "Basics" { var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); defer archive_file.close(io); - var arc: Archive = .init(io, archive_file); + var arc: Archive = try .init(io, archive_file); defer arc.deinit(io); var root_file = try arc.root(alloc); @@ -77,7 +143,7 @@ test "SingleFileExtraction" { var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); defer archive_file.close(io); - var arc: Archive = .init(io, archive_file); + var arc: Archive = try .init(io, archive_file); defer arc.deinit(io); var ext_file = try arc.open(alloc, TestFile); @@ -94,7 +160,7 @@ test "FullExtraction" { var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); defer archive_file.close(io); - var arc: Archive = .init(io, archive_file); + var arc: Archive = try .init(io, archive_file); defer arc.deinit(io); try arc.extract(alloc, io, TestFullExtractLocation, .default); diff --git a/src/decomp_cache.zig b/src/decomp_cache.zig new file mode 100644 index 0000000..ad1ed0c --- /dev/null +++ b/src/decomp_cache.zig @@ -0,0 +1,116 @@ +const std = @import("std"); +const Io = std.Io; +const File = Io.File; +const MemoryMap = File.MemoryMap; +const Atomic = std.atomic.Value; + +const Decomp = @import("decomp.zig"); + +const DecompCache = @This(); + +alloc: std.mem.Allocator, +map: MemoryMap, +decomp_fn: Decomp.Fn, + +cache: std.AutoHashMap(u64, Cache), +mut: std.Io.RwLock = .init, +cond: std.Io.Condition = .init, + +max_mem: u64, +cur_mem: u64 = 0, + +pub fn init(alloc: std.mem.Allocator, map: MemoryMap, compression: Decomp.Enum, max_mem: u64) DecompCache { + return .{ + .alloc = alloc, + .map = map, + .decomp_fn = try Decomp.DecompFn(compression), + + .cache = .init(alloc), + + .max_mem = max_mem, + }; +} +pub fn deinit(self: *DecompCache, io: Io) void { + self.mut.lockUncancelable(io); + + var iter = self.cache.valueIterator(); + while (iter.next()) |v| + self.alloc.free(v.data); + self.cache.deinit(); +} + +pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_size: u32) ![]u8 { + { + try self.mut.lockShared(io); + defer self.mut.unlockShared(io); + + const cache = self.cache.getPtr(offset); + if (cache != null) { + _ = cache.?.usage.fetchAdd(1, .acquire); + return cache.?.data; + } + } + try self.mut.lock(io); + defer self.mut.unlock(io); + + const cache = try self.cache.getOrPut(offset); + if (cache.found_existing) { + _ = cache.?.usage.fetchAdd(1, .acquire); + return cache.?.data; + } + errdefer self.cache.removeByPtr(cache.key_ptr); + + try self.ensureSpace(io, max_size); + + var out = try self.alloc.alloc(u8, max_size); + errdefer self.alloc.free(out); + + const decomp_size = try self.decomp_fn(self.alloc, self.map.memory[offset..][0..compressed_size], out); + if (decomp_size != max_size) { + if (!self.alloc.resize(out, decomp_size)) { + const new_out = try self.alloc.alloc(u8, decomp_size); + @memcpy(new_out, out[0..decomp_size]); + out = new_out; + } else { + out.len = decomp_size; + } + } + self.cur_mem += decomp_size; + + cache.value_ptr.data = out; + _ = cache.value_ptr.usage.fetchAdd(1, .acquire); + return out; +} +pub fn finished(self: *DecompCache, io: Io, offset: u64) void { + const cache = self.cache.getPtr(offset); + if (cache == null) { + std.debug.print("Finished using cache, but cache does not exist: {}\n", .{offset}); + return; + } + const use = cache.?.usage.fetchSub(1, .acquire); + if (use == 0) + self.cond.broadcast(io); +} + +fn ensureSpace(self: *DecompCache, io: Io, size: u64) !void { + while (self.cur_mem + size > self.max_mem) { + var iter = self.cache.valueIterator(); + while (iter.next()) |cache| { + if (cache.usage.load(.unordered) == 0) { + self.alloc.free(cache.data); + self.cur_mem -= cache.data.len; + + if (self.cur_mem + size <= self.max_mem) return; + } + } + if (self.cur_mem + size <= self.max_mem) return; + try self.cond.wait(io, self.mut.mutex); + } +} + +// Types + +const Cache = struct { + data: []u8, + usage: Atomic(u32), +}; diff --git a/src/directory.zig b/src/directory.zig new file mode 100644 index 0000000..9d2a644 --- /dev/null +++ b/src/directory.zig @@ -0,0 +1,81 @@ +const std = @import("std"); +const Io = std.Io; +const Reader = Io.Reader; + +const Inode = @import("inode.zig"); + +const Directory = @This(); + +entries: []Entry, + +pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u32) !Directory { + if (size <= 3) return .{ .entries = &[0]Entry{} }; + + var entries: std.ArrayList(Entry) = try .initCapacity(alloc, 50); + errdefer { + for (entries.items) |ent| + ent.deinit(alloc); + entries.deinit(alloc); + } + + var read: u32 = 3; + while (read < size) { + var hdr: Header = undefined; + try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + read += @sizeOf(Header); + + try entries.ensureUnusedCapacity(alloc, hdr.count + 1); + for (0..hdr.count + 1) |_| { + var raw: RawEntry = undefined; + try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little); + + const name = try alloc.alloc(u8, raw.name_size + 1); + errdefer alloc.free(name); + try rdr.readSliceEndian(u8, name, .little); + + entries.appendAssumeCapacity(.{ + .inode_num = if (raw.inode_num_offset > 0) + hdr.inode_num + @abs(raw.inode_num_offset) + else + hdr.inode_num - @abs(raw.inode_num_offset), + .block_start = hdr.block_start, + .block_offset = raw.block_offset, + .type = raw.type, + .name = name, + }); + } + } + + return entries.toOwnedSlice(alloc); +} +pub fn deinit(self: Directory, alloc: std.mem.Allocator) void { + for (self.entries) |entry| + entry.deinit(alloc); + alloc.free(self.entries); +} + +// Types + +pub const Entry = struct { + inode_num: u32, + block_start: u32, + block_offset: u16, + type: Inode.Enum, + name: []const u8, + + pub fn deinit(self: Entry, alloc: std.mem.Allocator) void { + alloc.free(self.name); + } +}; + +const Header = extern struct { + count: u32, + block_start: u32, + inode_num: u32, +}; +const RawEntry = extern struct { + block_offset: u16, + inode_num_offset: i16, + type: Inode.Enum, + name_size: u16, +}; diff --git a/src/file.zig b/src/file.zig index 03ad502..9f79e87 100644 --- a/src/file.zig +++ b/src/file.zig @@ -1,7 +1,92 @@ +const SfsFile = @This(); + +alloc: std.mem.Allocator, +archive: *Archive, + +inode: Inode, +name: []const u8, + +/// The given allocator must have been used to create the Inode and name. +pub fn init(alloc: std.mem.Allocator, archive: *Archive, inode: Inode, name: []const u8) SfsFile { + return .{ + .alloc = alloc, + .archive = archive, + + .inode = inode, + .name = name, + }; +} +pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry: Directory.Entry) !SfsFile { + const new_name = try alloc.alloc(u8, entry.name.len); + defer alloc.free(new_name); + @memcpy(new_name, entry.name); + + return .{ + .alloc = alloc, + .archive = archive, + + .inode = try .initDirEntry( + alloc, + io, + archive.cache, + archive.super.inode_start, + archive.super.block_size, + entry, + ), + .name = new_name, + }; +} +/// Creates a new copy of the given SfsFile using the given allocator +pub fn copy(self: SfsFile, alloc: std.mem.Allocator) !SfsFile { + const new_name = try alloc(u8, self.name.len); + errdefer alloc.free(new_name); + + return .{ + .alloc = alloc, + .archive = self.archive, + + .inode = try self.inode.copy(alloc), + .name = new_name, + }; +} +pub fn deinit(self: SfsFile) void { + self.inode.deinit(self.alloc); + self.alloc.free(self.name); +} + +/// Attempts to open the filepath if the SfsFile is a directory. +/// If the given path refers to itself (such as "" or "."), a copied SfsFile is returned. +pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile { + const path = std.mem.trim(u8, filepath, "/"); + + const first_element: []const u8 = std.mem.sliceTo(path, '/'); + + const dir: Directory = try self.inode.directory(alloc, io, self.archive.cache, self.archive.super.dir_start); + defer dir.deinit(alloc); + + var cur_slice = dir.entries; + var idx: usize = undefined; + while (cur_slice.len > 0) { + idx = cur_slice.len / 2; + switch (std.mem.order(u8, first_element, cur_slice[idx].name)) { + .eq => break, + .lt => cur_slice = cur_slice[0..idx], + .gt => cur_slice = cur_slice[idx..], + } + } else { + return error.NotFound; + } + if (first_element.len == path) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]); + if (cur_slice[idx].type != .dir) return error.NotFound; + const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]); + defer tmp_file.deinit(); + + return tmp_file.open(alloc, io, path[first_element.len..]); +} + const std = @import("std"); const Io = std.Io; +const Archive = @import("archive.zig"); +const Directory = @import("directory.zig"); const Inode = @import("inode.zig"); - -name: []const u8, -inode: Inode, diff --git a/src/inode.zig b/src/inode.zig index 6f64e01..7a47617 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -1,10 +1,109 @@ const std = @import("std"); +const Io = std.Io; +const Reader = Io.Reader; + +const DecompCache = @import("decomp_cache.zig"); +const Directory = @import("directory.zig"); +const MetadataReader = @import("meta_rdr.zig"); const Inode = @This(); hdr: Header, data: Data, +/// Read an inode given an inode Ref. +pub fn initRef(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, ref: Ref) !Inode { + var meta: MetadataReader = .init(io, cache, inode_start + ref.block_start); + defer meta.deinit(io); + try meta.interface.discardAll(ref.block_offset); + + return .init(alloc, &meta.interface, block_size); +} +pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, entry: Directory.Entry) !Inode { + var meta: MetadataReader = .init(io, cache, inode_start + entry.block_start); + defer meta.deinit(io); + try meta.interface.discardAll(entry.block_offset); + + return .init(alloc, &meta.interface, block_size); +} +/// Read the inode from the given Reader. +pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode { + var hdr: Header = undefined; + try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + const data: Data = switch (hdr.type) { + .dir => .{ .dir = try .init(rdr) }, + .file => .{ .file = try .init(alloc, rdr, block_size) }, + .symlink => .{ .symlink = try .init(alloc, rdr) }, + .block_dev => .{ .block_dev = try .init(rdr) }, + .char_dev => .{ .char_dev = try .init(rdr) }, + .fifo => .{ .fifo = try .init(rdr) }, + .socket => .{ .socket = try .init(rdr) }, + .ext_dir => .{ .ext_dir = try .init(rdr) }, + .ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) }, + .ext_symlink => .{ .ext_symlink = try .init(alloc, rdr) }, + .ext_block_dev => .{ .ext_block_dev = try .init(rdr) }, + .ext_char_dev => .{ .ext_char_dev = try .init(rdr) }, + .ext_fifo => .{ .ext_fifo = try .init(rdr) }, + .ext_socket => .{ .ext_socket = try .init(rdr) }, + }; + return .{ + .hdr = hdr, + .data = data, + }; +} +pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode { + var new_inode = self; + switch (new_inode.data) { + .file => |*f| { + if (f.blocks.len > 0) { + f.blocks = try alloc.alloc(DataBlock, f.blocks.len); + @memcpy(f.blocks, self.data.file.blocks); + } + }, + .ext_file => |*f| { + if (f.blocks.len > 0) { + f.blocks = try alloc.alloc(DataBlock, f.blocks.len); + @memcpy(f.blocks, self.data.ext_file.blocks); + } + }, + .symlink => |*s| { + s.target = try alloc.alloc(u8, s.target.len); + @memcpy(s.target, self.data.symlink.target); + }, + .ext_symlink => |*s| { + s.target = try alloc.alloc(u8, s.target.len); + @memcpy(s.target, self.data.ext_symlink.target); + }, + } + return new_inode; +} +pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { + switch (self.data) { + .file => |f| f.deinit(alloc), + .ext_file => |f| f.deinit(alloc), + .symlink => |s| s.deinit(alloc), + .ext_symlink => |s| s.deinit(alloc), + else => {}, + } +} + +// Utility functions + +pub fn directory(self: Inode, alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64) !Directory { + return switch (self.data) { + .dir => |d| readDirectory(alloc, io, cache, dir_start, d), + .ext_dir => |d| readDirectory(alloc, io, cache, dir_start, d), + else => error.NotDirectory, + }; +} +fn readDirectory(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64, d: anytype) !Directory { + var meta: MetadataReader = .init(io, cache, dir_start + d.block_start); + defer meta.deinit(io); + try meta.interface.discardAll(d.block_offset); + + return .init(alloc, &meta.interface, d.size); +} + // Types pub const Ref = packed struct(u64) { @@ -40,18 +139,260 @@ pub const Header = extern struct { }; pub const Data = union(Enum) { - dir: , - file: , - symlink: , - block_dev: , - char_dev: , - fifo: , - socket: , - ext_dir: , - ext_file: , - ext_symlink: , - ext_block_dev: , - ext_char_dev: , - ext_fifo: , - ext_socket: , + dir: Dir, + file: File, + symlink: Symlink, + block_dev: Device, + char_dev: Device, + fifo: IPC, + socket: IPC, + ext_dir: ExtDir, + ext_file: ExtFile, + ext_symlink: ExtSymlink, + ext_block_dev: ExtDevice, + ext_char_dev: ExtDevice, + ext_fifo: ExtIPC, + ext_socket: ExtIPC, +}; + +pub const DataBlock = packed struct(u32) { + size: u24, + uncompressed: bool, + _: u7, +}; + +const Dir = extern struct { + block_start: u32, + hard_links: u32, + size: u16, + block_offset: u16, + parent: u32, + + const Self = @This(); + + fn init(rdr: *Reader) !Self { + var dir: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&dir), .little); + return dir; + } +}; +const ExtDir = extern struct { + hard_links: u32, + size: u32, + block_start: u32, + parent: u32, + idx_count: u16, + block_offset: u16, + xattr_idx: u32, + // []DirIndex + + const Self = @This(); + + fn init(rdr: *Reader) !Self { + var dir: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&dir), .little); + return dir; + } +}; + +const File = struct { + data_start: u32, + frag_idx: u32, + frag_offset: u32, + size: u32, + blocks: []DataBlock, + + const Raw = extern struct { + data_start: u32, + frag_idx: u32, + frag_offset: u32, + size: u32, + }; + + const Self = @This(); + + pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self { + var raw: Raw = undefined; + try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little); + + var blocks_num = raw.size / block_size; + if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0) + blocks_num += 1; + + const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num); + errdefer alloc.free(blocks); + + try rdr.readSliceEndian(DataBlock, blocks, .little); + return .{ + .data_start = raw.data_start, + .frag_idx = raw.frag_idx, + .frag_offset = raw.frag_offset, + .size = raw.size, + .blocks = blocks, + }; + } + pub fn deinit(self: File, alloc: std.mem.Allocator) void { + alloc.free(self.blocks); + } +}; +const ExtFile = struct { + data_start: u64, + size: u64, + sparse: u64, + hard_links: u32, + frag_idx: u32, + frag_offset: u32, + xattr_idx: u32, + blocks: []DataBlock, + + const Raw = extern struct { + data_start: u64, + size: u64, + sparse: u64, + hard_links: u32, + frag_idx: u32, + frag_offset: u32, + xattr_idx: u32, + }; + + const Self = @This(); + + pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self { + var raw: Raw = undefined; + try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little); + + var blocks_num = raw.size / block_size; + if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0) + blocks_num += 1; + + const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num); + errdefer alloc.free(blocks); + + try rdr.readSliceEndian(DataBlock, blocks, .little); + return .{ + .data_start = raw.data_start, + .size = raw.size, + .sparse = raw.sparse, + .hard_links = raw.hard_links, + .frag_idx = raw.frag_idx, + .frag_offset = raw.frag_offset, + .xattr_idx = raw.xattr_idx, + .blocks = blocks, + }; + } + pub fn deinit(self: File, alloc: std.mem.Allocator) void { + alloc.free(self.blocks); + } +}; + +const Symlink = struct { + hard_links: u32, + target: []const u8, + + const Raw = extern struct { + hard_links: u32, + target_size: u32, + }; + + const Self = @This(); + + pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self { + var raw: Raw = undefined; + try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little); + + const target = try alloc.alloc(u8, raw.target_size); + try rdr.readSliceEndian(u8, target, .little); + + return .{ + .hard_links = raw.hard_links, + .target = target, + }; + } + pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void { + alloc.free(self.target); + } +}; +const ExtSymlink = struct { + hard_links: u32, + xattr_idx: u32, + target: []const u8, + + const Raw = extern struct { + hard_links: u32, + target_size: u32, + }; + + const Self = @This(); + + pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self { + var raw: Raw = undefined; + try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little); + + const target = try alloc.alloc(u8, raw.target_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 = raw.hard_links, + .target = target, + .xattr_idx = xattr_idx, + }; + } + + pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void { + alloc.free(self.target); + } +}; + +const Device = extern struct { + hard_links: u32, + device: u32, + + const Self = @This(); + + fn init(rdr: *Reader) !Self { + var dir: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&dir), .little); + return dir; + } +}; +const ExtDevice = extern struct { + hard_links: u32, + device: u32, + xattr_idx: u32, + + const Self = @This(); + + fn init(rdr: *Reader) !Self { + var dir: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&dir), .little); + return dir; + } +}; + +const IPC = extern struct { + hard_links: u32, + + const Self = @This(); + + fn init(rdr: *Reader) !Self { + var dir: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&dir), .little); + return dir; + } +}; +const ExtIPC = extern struct { + hard_links: u32, + xattr_idx: u32, + + const Self = @This(); + + fn init(rdr: *Reader) !Self { + var dir: Self = undefined; + try rdr.readSliceEndian(Self, @ptrCast(&dir), .little); + return dir; + } }; diff --git a/src/meta_rdr.zig b/src/meta_rdr.zig new file mode 100644 index 0000000..2bd3273 --- /dev/null +++ b/src/meta_rdr.zig @@ -0,0 +1,109 @@ +const std = @import("std"); +const Io = std.Io; +const Reader = Io.Reader; +const Writer = Io.Writer; +const Limit = Io.Limit; + +const DecompCache = @import("decomp_cache.zig"); + +const MetadataReader = @This(); + +io: Io, +cache: *DecompCache, + +cur_offset: u64 = 0, +next_offset: u64, + +interface: Reader = .{ + .buffer = &[0]u8{}, + .end = 0, + .seek = 0, + .vtable = &.{ + .stream = stream, + .discard = discard, + .readVec = readVec, + }, +}, + +pub fn init(io: Io, cache: *DecompCache, start: u64) MetadataReader { + return .{ + .io = io, + .cache = cache, + + .next_offset = start, + }; +} +pub fn deinit(self: *MetadataReader, io: Io) void { + self.cache.finished(io, self.cur_offset); +} + +fn advance(self: *MetadataReader) !void { + self.cache.finished(self.io, self.cur_offset); + + self.interface.seek = 0; + errdefer self.interface.end = 0; + + const hdr: Header = @bitCast(std.mem.readInt(u16, self.cache.map.memory[self.next_offset..][0..2], .little)); + self.cur_offset = self.next_offset + 2; + self.next_offset = self.cur_offset + hdr.size; + + if (hdr.uncompressed) { + self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..hdr.size]; + self.interface.end = hdr.size; + return; + } + self.interface.buffer = try self.cache.get(self.io, self.cur_offset, hdr.size, 8192); + self.interface.end = self.interface.buffer.len; +} + +fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { + if (r.seek >= r.end) { + const self: *MetadataReader = @fieldParentPtr("interface", r); + self.advance() catch |err| { + std.debug.print("error advancing metadata reader: {}\n", .{err}); + return Reader.Error.ReadFailed; + }; + } + const to_write = @min(r.end - r.seek, @intFromEnum(limit)); + const wrote = try w.write(r.buffer[r.seek..][0..to_write]); + r.seek += wrote; + return wrote; +} +fn discard(r: *Reader, limit: Limit) Reader.Error!usize { + if (r.seek >= r.end) { + const self: *MetadataReader = @fieldParentPtr("interface", r); + self.advance() catch |err| { + std.debug.print("error advancing metadata reader: {}\n", .{err}); + return Reader.Error.ReadFailed; + }; + } + const to_discard = @min(r.end - r.seek, @intFromEnum(limit)); + r.seek += to_discard; + return to_discard; +} +fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize { + if (r.seek >= r.end) { + const self: *MetadataReader = @fieldParentPtr("interface", r); + self.advance() catch |err| { + std.debug.print("error advancing metadata reader: {}\n", .{err}); + return Reader.Error.ReadFailed; + }; + } + var total: usize = 0; + for (vec) |v| { + const to_copy = @min(r.end - r.seek, v.len); + @memcpy(v[0..to_copy], r.buffer[r.seek..][0..to_copy]); + r.seek += to_copy; + total += to_copy; + if (r.seek >= r.end) + break; + } + return total; +} + +// Types + +const Header = packed struct(u16) { + size: u15, + uncompressed: bool, +};