From 5c14b7db48db3a197e17bb6a42654219abb43eca Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Wed, 9 Jul 2025 06:42:02 -0500 Subject: [PATCH] Try 4, lol. --- build.zig | 3 +- src/bin/unsquashfs.zig | 3 + src/decompress.zig | 85 -------- src/directory.zig | 66 ------ src/file.zig | 361 --------------------------------- src/fragment.zig | 26 --- src/inode.zig | 85 ++++++++ src/inode/dir.zig | 40 ++-- src/inode/file.zig | 87 ++++---- src/inode/inode.zig | 103 ---------- src/inode/misc.zig | 85 ++++++-- src/inode/sym.zig | 48 ----- src/reader.zig | 142 ++----------- src/reader/metadata.zig | 43 ++++ src/readers/data_extractor.zig | 202 ------------------ src/readers/data_reader.zig | 164 --------------- src/readers/file_holder.zig | 90 -------- src/readers/metadata.zig | 77 ------- src/root.zig | 1 - src/superblock.zig | 82 +++++--- src/table.zig | 56 ----- src/zig_unsquashfs.zig | 203 ------------------ 22 files changed, 331 insertions(+), 1721 deletions(-) create mode 100644 src/bin/unsquashfs.zig delete mode 100644 src/decompress.zig delete mode 100644 src/directory.zig delete mode 100644 src/file.zig delete mode 100644 src/fragment.zig create mode 100644 src/inode.zig delete mode 100644 src/inode/inode.zig delete mode 100644 src/inode/sym.zig create mode 100644 src/reader/metadata.zig delete mode 100644 src/readers/data_extractor.zig delete mode 100644 src/readers/data_reader.zig delete mode 100644 src/readers/file_holder.zig delete mode 100644 src/readers/metadata.zig delete mode 100644 src/table.zig delete mode 100644 src/zig_unsquashfs.zig diff --git a/build.zig b/build.zig index 070544a..c682bc7 100644 --- a/build.zig +++ b/build.zig @@ -23,10 +23,11 @@ pub fn build(b: *std.Build) !void { }); const exe_mod = b.createModule(.{ - .root_source_file = b.path("src/zig_unsquashfs.zig"), + .root_source_file = b.path("src/bin/unsquashfs.zig"), .target = target, .optimize = optimize, }); + exe_mod.addImport("squashfs", lib_mod); exe_mod.addOptions("config", opt); const exe = b.addExecutable(.{ .linkage = .static, diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig new file mode 100644 index 0000000..66a7fbc --- /dev/null +++ b/src/bin/unsquashfs.zig @@ -0,0 +1,3 @@ +const import = @import("std"); + +pub fn main() void {} diff --git a/src/decompress.zig b/src/decompress.zig deleted file mode 100644 index d35a355..0000000 --- a/src/decompress.zig +++ /dev/null @@ -1,85 +0,0 @@ -const std = @import("std"); -const io = std.io; -const compress = std.compress; - -const DecompressError = error{ - LzoUnsupported, - Lz4Unsupported, -}; - -pub const DecompressType = enum(u16) { - zlib = 1, - lzma, - lzo, - xz, - lz4, - zstd, - - pub fn decompress(self: DecompressType, alloc: std.mem.Allocator, rdr: io.AnyReader) !std.ArrayList(u8) { - var out = std.ArrayList(u8).init(alloc); - errdefer out.deinit(); - switch (self) { - .zlib => try compress.zlib.decompress(rdr, out.writer()), - .lzma => { - var decomp = try compress.lzma.decompress(alloc, rdr); - defer decomp.deinit(); - try decomp.reader().readAllArrayList(&out, 1024 * 1024); - }, - .lzo => return DecompressError.LzoUnsupported, - .xz => { - var decomp = try compress.xz.decompress(alloc, rdr); - defer decomp.deinit(); - try decomp.reader().readAllArrayList(&out, 1024 * 1024); - }, - .lz4 => return DecompressError.Lz4Unsupported, - .zstd => { - const buf = try alloc.alloc(u8, compress.zstd.DecompressorOptions.default_window_buffer_len); - defer alloc.free(buf); - var decomp = compress.zstd.decompressor(rdr, .{ - .window_buffer = buf, - }); - try decomp.reader().readAllArrayList(&out, 1024 * 1024); - }, - } - return out; - } - - pub fn decompressTo(self: DecompressType, alloc: std.mem.Allocator, rdr: io.AnyReader, writer: io.AnyWriter) anyerror!void { - const buf_size: usize = 8192; - switch (self) { - .zlib => try compress.zlib.decompress(rdr, writer), - .lzma => { - var decomp = try compress.lzma.decompress(alloc, rdr); - defer decomp.deinit(); - var buf: [buf_size]u8 = undefined; - var red = try decomp.read(&buf); - while (red > 0) : (red = try decomp.read(&buf)) { - _ = try writer.writeAll(&buf); - } - }, - .lzo => return DecompressError.LzoUnsupported, - .xz => { - var decomp = try compress.xz.decompress(alloc, rdr); - defer decomp.deinit(); - var buf: [buf_size]u8 = undefined; - var red = try decomp.read(&buf); - while (red > 0) : (red = try decomp.read(&buf)) { - _ = try writer.writeAll(&buf); - } - }, - .lz4 => return DecompressError.Lz4Unsupported, - .zstd => { - const window_buf = try alloc.alloc(u8, compress.zstd.DecompressorOptions.default_window_buffer_len); - defer alloc.free(window_buf); - var decomp = compress.zstd.decompressor(rdr, .{ - .window_buffer = window_buf, - }); - var buf: [buf_size]u8 = undefined; - var red = try decomp.read(&buf); - while (red > 0) : (red = try decomp.read(&buf)) { - _ = try writer.writeAll(&buf); - } - }, - } - } -}; diff --git a/src/directory.zig b/src/directory.zig deleted file mode 100644 index 9ed1fad..0000000 --- a/src/directory.zig +++ /dev/null @@ -1,66 +0,0 @@ -const std = @import("std"); -const io = std.io; - -const InodeType = @import("inode/inode.zig").InodeType; - -const DirHeader = extern struct { - count: u32, - inode_block_start: u32, - inode_num: u32, -}; - -const RawDirEntryStart = packed struct { - inode_block_offset: u16, - /// Difference from the current DirHeader inode_num - inode_num_difference: i16, - /// Extended inodes will be their basic type. - inode_type: InodeType, - name_size: u16, -}; - -pub const DirEntry = struct { - block_start: u32, - offset: u16, - inode_num: u32, - name: []const u8, - - fn init(alloc: std.mem.Allocator, hdr: DirHeader, rdr: io.AnyReader) !DirEntry { - const raw = try rdr.readStruct(RawDirEntryStart); - const name = try alloc.alloc(u8, raw.name_size + 1); - errdefer alloc.free(name); - _ = try rdr.read(name); - return .{ - .block_start = hdr.inode_block_start, - .offset = raw.inode_block_offset, - .inode_num = if (raw.inode_num_difference > 0) - hdr.inode_num + @abs(raw.inode_num_difference) - else - hdr.inode_num - @abs(raw.inode_num_difference), - .name = name, - }; - } - - pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void { - alloc.free(self.name); - } -}; - -pub fn readDirectory(alloc: std.mem.Allocator, rdr: io.AnyReader, size: u64) !std.StringHashMap(DirEntry) { - var out: std.StringHashMap(DirEntry) = .init(alloc); - errdefer out.deinit(); - var red_size: u64 = 3; - var hdr: DirHeader = undefined; - while (red_size < size) { - hdr = try rdr.readStruct(DirHeader); - red_size += 12; - var i: u32 = 0; - try out.ensureUnusedCapacity(hdr.count + 1); - while (i <= hdr.count) : (i += 1) { - var tmp: DirEntry = try .init(alloc, hdr, rdr); - errdefer tmp.deinit(alloc); - out.putAssumeCapacity(tmp.name, tmp); - red_size += 8 + tmp.name.len; - } - } - return out; -} diff --git a/src/file.zig b/src/file.zig deleted file mode 100644 index a685797..0000000 --- a/src/file.zig +++ /dev/null @@ -1,361 +0,0 @@ -const std = @import("std"); -const io = std.io; -const fs = std.fs; -const builtin = @import("builtin"); - -const inode = @import("inode/inode.zig"); -const directory = @import("directory.zig"); - -const Reader = @import("reader.zig").Reader; -const DirEntry = @import("directory.zig").DirEntry; -const DataReader = @import("readers/data_reader.zig").DataReader; -const DataExtractor = @import("readers/data_extractor.zig").DataExtractor; -const MetadataReader = @import("readers/metadata.zig").MetadataReader; - -/// A file or directory inside of a squashfs. -/// Make sure to call deinit(); -pub const File = struct { - name: []const u8, - inode: inode.Inode, - parent_path: []const u8, - - dirEntries: ?std.StringHashMap(DirEntry) = null, - data_rdr: ?DataReader = null, - - pub const FileError = error{ - NotDirectory, - NotNormalFile, - NotSymlink, - NotFound, - }; - - fn fromDirEntry(rdr: *Reader, ent: DirEntry, parent_path: []const u8) !File { - var offset_rdr = rdr.holder.readerAt(ent.block_start + rdr.super.inode_table_start); - var meta_rdr: MetadataReader = .init( - rdr.alloc, - rdr.super.decomp, - offset_rdr.any(), - ); - defer meta_rdr.deinit(); - try meta_rdr.skip(ent.offset); - const name = try rdr.alloc.alloc(u8, ent.name.len); - errdefer rdr.alloc.free(name); - @memcpy(name, ent.name); - var out: File = .{ - .name = name, - .inode = try .init( - rdr.alloc, - meta_rdr.any(), - rdr.super.block_size, - ), - .parent_path = parent_path, - }; - switch (out.inode.header.inode_type) { - .file, .ext_file => { - out.data_rdr = try .init(&out, rdr); - }, - else => {}, - } - return out; - } - - pub fn file_path(self: File, alloc: std.mem.Allocator) ![]u8 { - if (self.parent_path.len == 0) { - const out = try alloc.alloc(u8, self.name.len); - @memcpy(out, self.name); - return out; - } - return std.mem.concat(alloc, u8, &[3][]const u8{ self.parent_path, "/", self.name }); - } - - pub fn uid(self: File, rdr: *Reader) !u32 { - return rdr.id_table.getValue(rdr, self.inode.header.uid_idx); - } - - pub fn gid(self: File, rdr: *Reader) !u32 { - return rdr.id_table.getValue(rdr, self.inode.header.gid_idx); - } - - pub fn deinit(self: *File, alloc: std.mem.Allocator) void { - self.inode.deinit(); - alloc.free(self.name); - alloc.free(self.parent_path); - if (self.data_rdr != null) self.data_rdr.?.deinit(); - if (self.dirEntries != null) { - var iter = self.dirEntries.?.iterator(); - while (iter.next()) |ent| { - ent.value_ptr.deinit(alloc); - } - self.dirEntries.?.deinit(); - } - } - - pub fn isDir(self: File) bool { - return switch (self.inode.header.inode_type) { - .dir, .ext_dir => true, - else => false, - }; - } - - /// If the File is a directory, tries to return the file at path. - /// An empty path returns itself. - pub fn open(self: *File, rdr: *Reader, path: []const u8) !File { - return self.realOpen(rdr, path, true); - } - - fn realOpen(self: *File, rdr: *Reader, path: []const u8, first: bool) (FileError || anyerror)!File { - const clean_path: []const u8 = std.mem.trim(u8, path, "/"); - if (clean_path.len == 0) { - return self.*; - } - defer if (!first) self.deinit(rdr.alloc); - switch (self.inode.header.inode_type) { - .dir, .ext_dir => {}, - else => return FileError.NotDirectory, - } - try self.readDirEntries(rdr); - const split_idx = std.mem.indexOf(u8, clean_path, "/") orelse clean_path.len; - const name = clean_path[0..split_idx]; - const ent = self.dirEntries.?.get(name); - if (ent == null) { - return FileError.NotFound; - } - var fil = try fromDirEntry(rdr, ent.?, try self.file_path(rdr.alloc)); - return fil.realOpen(rdr, clean_path[split_idx..], false); - } - - /// If the File is a symlink, returns the symlink's target path. - pub fn symPath(self: File) (FileError || anyerror)![]const u8 { - return switch (self.inode.data) { - .sym => |s| s.target, - .ext_sym => |s| s.target, - else => FileError.NotSymlink, - }; - } - - /// If the File is a directory, returns an iterator that iterates over it's children. - pub fn iterator(self: *File, rdr: *Reader) (FileError || anyerror)!FileIterator { - switch (self.inode.header.inode_type) { - .dir, .ext_dir => {}, - else => return FileError.NotDirectory, - } - try self.readDirEntries(rdr); - var files = try rdr.alloc.alloc(File, self.dirEntries.?.count()); - errdefer rdr.alloc.free(files); - var dirEntryIter = self.dirEntries.?.valueIterator(); - var i: u32 = 0; - while (dirEntryIter.next()) |ent| : (i += 1) { - files[i] = try .fromDirEntry(rdr, ent.*, try self.file_path(rdr.alloc)); - } - return .{ - .alloc = rdr.alloc, - .files = files, - }; - } - - fn readDirEntries(self: *File, rdr: *Reader) (FileError || anyerror)!void { - if (self.dirEntries != null) return; - var block_start: u32 = 0; - var offset: u16 = 0; - var siz: u32 = 0; - switch (self.inode.data) { - .dir => |d| { - block_start = d.block_start; - offset = d.offset; - siz = d.size; - }, - .ext_dir => |d| { - block_start = d.block_start; - offset = d.offset; - siz = d.size; - }, - else => return FileError.NotDirectory, - } - var offset_rdr = rdr.holder.readerAt(rdr.super.dir_table_start + block_start); - var meta_rdr: MetadataReader = .init( - rdr.alloc, - rdr.super.decomp, - offset_rdr.any(), - ); - defer meta_rdr.deinit(); - try meta_rdr.skip(offset); - self.dirEntries = try directory.readDirectory(rdr.alloc, meta_rdr.any(), siz); - } - - pub fn size(self: File) u64 { - return switch (self.inode.data) { - .file => |f| f.size, - .ext_file => |f| f.size, - else => 0, - }; - } - - /// If the file is a normal file, reads it's data. - pub fn read(self: *File, bytes: []u8) (FileError || anyerror)!usize { - if (self.data_rdr == null) { - return FileError.NotNormalFile; - } - return self.data_rdr.?.read(bytes); - } - - const FileReader = io.GenericReader(*File, (FileError || anyerror), read); - - pub fn reader(self: *File) FileReader { - return .{ - .context = self, - }; - } - - fn extractor(self: *File, rdr: *Reader) !DataExtractor { - return .init(self, rdr); - } - - pub const ExtractConfig = struct { - /// The amount of worker threads to spawn. Defaults to your cpu core count. - thread_count: u16, - /// The maximum amount of additional memory this extraction will use. - /// Default is 1GB or a quarter of your system memory, whichever is smaller. - /// Actually memory usage will be higher, as this does not account of vaious metadata (such as file names). - max_mem: u64, - deref_sym: bool = false, - unbreak_sym: bool = false, - verbose: bool = false, - pub fn init() !ExtractConfig { - const sys_mem = try std.process.totalSystemMemory(); - return .{ - .thread_count = @truncate(try std.Thread.getCpuCount()), - .max_mem = @min(sys_mem / 4, 1024 * 1024 * 1024), - }; - } - }; - - pub const ExtractError = error{ - FileExists, - }; - - /// Extract's the File to the path. - pub fn extract(self: *File, rdr: *Reader, config: ExtractConfig, path: []const u8) (ExtractError || anyerror)!void { - var pol: std.Thread.Pool = undefined; - try pol.init(.{ - .allocator = rdr.alloc, - .n_jobs = config.thread_count, - }); - defer pol.deinit(); - return self.extractReal(rdr, config, &pol, path, true); - } - - fn extractReal(self: *File, rdr: *Reader, config: ExtractConfig, pool: *std.Thread.Pool, path: []const u8, first: bool) (ExtractError || anyerror)!void { - const real_path = std.mem.trimRight(u8, path, "/"); - var exists = true; - var stat: ?fs.File.Stat = null; - if (fs.cwd().statFile(real_path)) |s| { - stat = s; - } else |err| { - if (err == fs.File.OpenError.FileNotFound) { - exists = false; - } else return err; - } - switch (self.inode.header.inode_type) { - .dir, .ext_dir => { - if (!exists) { - fs.cwd().makeDir(real_path) catch |err| { - if (config.verbose) - std.log.err("error creating directory {s}: {any}", .{ real_path, err }); - return err; - }; - } - var iter = try self.iterator(rdr); - defer iter.deinit(); - while (iter.next()) |f| { - const extr_path = try std.mem.concat(rdr.alloc, u8, &[3][]const u8{ real_path, "/", f.name }); - defer rdr.alloc.free(extr_path); - try f.extractReal(rdr, config, pool, extr_path, false); - } - }, - .file, .ext_file => { - if ((!first and exists) or - (first and exists and stat.?.kind != .directory)) return ExtractError.FileExists; - var extr_path: []u8 = undefined; - if (first and exists and stat.?.kind == .directory) { - extr_path = try std.mem.concat(rdr.alloc, u8, &[3][]const u8{ real_path, "/", self.name }); - } else { - extr_path = try rdr.alloc.alloc(u8, real_path.len); - @memcpy(extr_path, real_path); - } - defer rdr.alloc.free(extr_path); - var fil = fs.cwd().createFile(extr_path, .{}) catch |err| { - if (config.verbose) - std.log.err("error creating file {s}: {any}", .{ extr_path, err }); - return err; - }; - defer fil.close(); - if (config.thread_count > 1 and self.size() > rdr.super.block_size) { - var ext = try self.extractor(rdr); - defer ext.deinit(); - ext.writeToFile(pool, &fil) catch |err| { - if (config.verbose) - std.log.err("error writing file {s}: {any}", .{ self.name, err }); - return err; - }; - } else { - var buf = [1]u8{0} ** 8192; - var total_red: u64 = 0; - while (total_red < self.size()) { - const red = try self.read(&buf); - total_red += red; - } - } - }, - .sym, .ext_sym => { - //TODO: unbreak symlinks & dereference symlinks - if (exists) return ExtractError.FileExists; - fs.cwd().symLink(try self.symPath(), real_path, .{}) catch |err| { - if (config.verbose) - std.log.err("error creating symlink {s}: {any}", .{ self.name, err }); - return err; - }; - }, - .block, .ext_block, .char, .ext_char, .fifo, .ext_fifo => { - if (exists) return ExtractError.FileExists; - comptime if (builtin.os.tag != .linux) return; - const mode: u32 = switch (self.inode.header.inode_type) { - .block, .ext_block => std.posix.S.IFBLK, - .char, .ext_char => std.posix.S.IFCHR, - .fifo, .ext_fifo => std.posix.S.IFIFO, - else => unreachable, - }; - const dev = switch (self.inode.data) { - .block, .char => |b| b.device, - .ext_block, .ext_char => |b| b.device, - .fifo, .ext_fifo => 0, - else => unreachable, - }; - _ = std.os.linux.mknod(@ptrCast(real_path), mode, dev); - }, - .sock, .ext_sock => {}, //TODO - } - //TODO: permissions - } -}; - -const FileIterator = struct { - alloc: std.mem.Allocator, - files: []File, - - curIndex: u32 = 0, - - pub fn next(self: *FileIterator) ?*File { - if (self.curIndex >= self.files.len) return null; - defer self.curIndex += 1; - return &self.files[self.curIndex]; - } - pub fn reset(self: *FileIterator) void { - self.curIndex = 0; - } - pub fn deinit(self: *FileIterator) void { - for (self.files) |*f| { - f.deinit(self.alloc); - } - self.alloc.free(self.files); - } -}; diff --git a/src/fragment.zig b/src/fragment.zig deleted file mode 100644 index 8ac1336..0000000 --- a/src/fragment.zig +++ /dev/null @@ -1,26 +0,0 @@ -const std = @import("std"); - -const BlockSize = @import("inode/file.zig").BlockSize; -const Reader = @import("reader.zig").Reader; - -pub const FragEntry = packed struct { - start: u64, - size: BlockSize, - _: u32, - - pub fn getData(self: FragEntry, rdr: *Reader, offset: u32, frag_size: u32) ![]u8 { - var offset_rdr = rdr.holder.readerAt(self.start); - if (self.size.not_compressed) { - const buf = try rdr.alloc.alloc(u8, frag_size); - _ = try offset_rdr.read(buf); - return buf; - } - var limit_rdr = std.io.limitedReader(offset_rdr, self.size.size); - var decomp = try rdr.super.decomp.decompress(rdr.alloc, limit_rdr.reader().any()); - var frag_all = try decomp.toOwnedSlice(); - defer rdr.alloc.free(frag_all); - const out = try rdr.alloc.alloc(u8, frag_size); - @memcpy(out, frag_all[offset .. offset + frag_size]); - return out; - } -}; diff --git a/src/inode.zig b/src/inode.zig new file mode 100644 index 0000000..321d344 --- /dev/null +++ b/src/inode.zig @@ -0,0 +1,85 @@ +const std = @import("std"); + +const dir = @import("inode/dir.zig"); +const file = @import("inode/file.zig"); +const misc = @import("inode/misc.zig"); + +pub const Ref = packed struct { + offset: u16, + block: u32, + _: u16, +}; + +const Type = enum(u16) { + dir = 1, + file, + symlink, + block_dev, + char_dev, + fifo, + socket, + ext_dir, + ext_file, + ext_symlink, + ext_block_dev, + ext_char_dev, + ext_fifo, + ext_socket, +}; + +const Header = packed struct { + type: Type, + perm: u16, + uid_idx: u16, + gid_idx: u16, + mod_time: u32, + num: u32, +}; + +const Data = union(enum) { + dir: dir.Dir, + file: file.File, + symlink: misc.Symlink, + block_dev: misc.Dev, + char_dev: misc.Dev, + fifo: misc.IPC, + socket: misc.IPC, + ext_dir: dir.ExtDir, + ext_file: file.ExtFile, + ext_symlink: misc.ExtSymlink, + ext_block_dev: misc.ExtDev, + ext_char_dev: misc.ExtDev, + ext_fifo: misc.ExtIPC, + ext_socket: misc.ExtIPC, +}; + +const Self = @This(); + +hdr: Header, +data: Data, + +pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !Self { + std.debug.assert(std.meta.hasFn(@TypeOf(rdr), "read")); + var hdr: Header = undefined; + _ = try rdr.read(std.mem.asBytes(&hdr)); + const data = switch (hdr.type) { + .dir => .{ .dir = .init(rdr) }, + .file => .{ .file = .init(rdr, alloc, block_size) }, + .symlink => .{ .symlink = .init(rdr, alloc) }, + .block_dev => .{ .block_dev = .init(rdr) }, + .char_dev => .{ .char_dev = .init(rdr) }, + .fifo => .{ .fifo = .init(rdr) }, + .socket => .{ .socket = .init(rdr) }, + .ext_dir => .{ .ext_dir = .init(rdr) }, + .ext_file => .{ .ext_file = .init(rdr, alloc, block_size) }, + .ext_symlink => .{ .ext_symlink = .init(rdr, alloc) }, + .ext_block_dev => .{ .ext_block_dev = .init(rdr) }, + .ext_char_dev => .{ .ext_char_dev = .init(rdr) }, + .ext_fifo => .{ .ext_fifo = .init(rdr) }, + .ext_socket => .{ .ext_socket = .init(rdr) }, + }; + return .{ + .hdr = hdr, + .data = data, + }; +} diff --git a/src/inode/dir.zig b/src/inode/dir.zig index b1afc21..8234393 100644 --- a/src/inode/dir.zig +++ b/src/inode/dir.zig @@ -1,37 +1,31 @@ -const io = @import("std").io; +const std = @import("std"); -pub const DirInode = packed struct { - block_start: u32, - hard_links: u32, - /// Note: size is 3 larger then the actual size, due to "." and ".." +pub const Dir = packed struct { + block: u32, + hard_link: u32, size: u16, offset: u16, parent_num: u32, - pub fn init(rdr: io.AnyReader) !DirInode { - return rdr.readStruct(DirInode); + pub fn init(rdr: anytype) !Dir { + const out: Dir = undefined; + _ = rdr.read(std.mem.asBytes(&out)); + return out; } }; -const DirIndex = struct { - offset: u32, - block_start: u32, - name_size: u32, - name: []const u8, -}; - -pub const ExtDirInode = packed struct { - hard_links: u32, - /// Note: size is 3 larger then the actual size, due to "." and ".." +pub const ExtDir = packed struct { + hard_link: u32, size: u32, - block_start: u32, + block: u32, parent_num: u32, - index_count: u16, + idx_count: u16, offset: u16, - xattr_inx: u32, - // TODO: possibly also read dir indexes. Maybe relagate to function... + xattr_idx: u32, - pub fn init(rdr: io.AnyReader) !ExtDirInode { - return rdr.readStruct(ExtDirInode); + pub fn init(rdr: anytype) !ExtDir { + const out: ExtDir = undefined; + _ = rdr.read(std.mem.asBytes(&out)); + return out; } }; diff --git a/src/inode/file.zig b/src/inode/file.zig index bd86862..5b464a5 100644 --- a/src/inode/file.zig +++ b/src/inode/file.zig @@ -1,76 +1,71 @@ const std = @import("std"); -const io = std.io; pub const BlockSize = packed struct { size: u24, - not_compressed: bool, + uncompressed: bool, _: u7, }; -pub const FileInode = struct { - data_start: u32, +pub const File = struct { + block: u32, frag_idx: u32, - frag_offset: u32, + offset: u32, size: u32, - blocks: []const BlockSize, + block_sizes: []BlockSize, - pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !FileInode { - var fixed_buf: [16]u8 = undefined; - _ = try rdr.readAll(&fixed_buf); - const frag_idx = std.mem.bytesToValue(u32, fixed_buf[4..8]); - const size = std.mem.bytesToValue(u32, fixed_buf[12..16]); - var block_num = size / block_size; - if (frag_idx == 0xFFFFFFFF and size % block_size > 0) { - block_num += 1; + pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !File { + var fixed: [16]u8 = undefined; + _ = try rdr.read(&fixed); + const frag_idx = std.mem.readInt(u32, fixed[4..8], .little); + const size = std.mem.readInt(u32, fixed[12..16], .little); + var blocks: u32 = size / block_size; + if (size % block_size > 0 and frag_idx != 0xffffffff) { + blocks += 1; } - const blocks = try alloc.alloc(BlockSize, block_num); - _ = try rdr.readAll(@ptrCast(blocks)); + const block_sizes = alloc.alloc(BlockSize, blocks); + errdefer alloc.free(block_sizes); + _ = try rdr.read(std.mem.sliceAsBytes(block_sizes)); return .{ - .data_start = std.mem.bytesToValue(u32, fixed_buf[0..4]), + .block = std.mem.readInt(u32, fixed[0..4], .little), .frag_idx = frag_idx, - .frag_offset = std.mem.bytesToValue(u32, fixed_buf[8..12]), + .offset = std.mem.readInt(u32, fixed[8..12], .little), .size = size, - .blocks = blocks, + .block_sizes = block_sizes, }; } - pub fn deinit(self: FileInode, alloc: std.mem.Allocator) void { - alloc.free(self.blocks); - } }; -pub const ExtFileInode = struct { - data_start: u64, +pub const ExtFile = struct { + block: u64, size: u64, sparse: u64, - hard_links: u32, + hard_link: u32, frag_idx: u32, - frag_offset: u32, + offset: u32, xattr_idx: u32, - blocks: []const BlockSize, + block_sizes: []BlockSize, - pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !ExtFileInode { - var fixed_buf = [1]u8{0} ** 40; - _ = try rdr.readAll(&fixed_buf); - const size = std.mem.bytesToValue(u64, fixed_buf[8..16]); - const frag_idx = std.mem.bytesToValue(u32, fixed_buf[28..32]); - var block_num = size / block_size; - if (frag_idx == 0xFFFFFFFF and size % block_size > 0) { - block_num += 1; + pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !ExtFile { + var fixed: [40]u8 = undefined; + _ = try rdr.read(&fixed); + const size = std.mem.readInt(u64, fixed[8..16], .little); + const frag_idx = std.mem.readInt(u32, fixed[28..32], .little); + var blocks: u32 = size / block_size; + if (size % block_size > 0 and frag_idx != 0xffffffff) { + blocks += 1; } - const blocks = try alloc.alloc(BlockSize, block_num); - _ = try rdr.readAll(@ptrCast(blocks)); + const block_sizes = alloc.alloc(BlockSize, blocks); + errdefer alloc.free(block_sizes); + _ = try rdr.read(std.mem.sliceAsBytes(block_sizes)); return .{ - .data_start = std.mem.bytesToValue(u64, fixed_buf[0..8]), + .block = std.mem.readInt(u64, fixed[0..8], .little), .size = size, - .sparse = std.mem.bytesToValue(u64, fixed_buf[16..24]), - .hard_links = std.mem.bytesToValue(u32, fixed_buf[24..28]), + .sparse = std.mem.readInt(u64, fixed[16..24], .little), + .hard_link = std.mem.readInt(u32, fixed[24..28], .little), .frag_idx = frag_idx, - .frag_offset = std.mem.bytesToValue(u32, fixed_buf[32..36]), - .xattr_idx = std.mem.bytesToValue(u32, fixed_buf[36..40]), - .blocks = blocks, + .offset = std.mem.readInt(u32, fixed[32..36], .little), + .xattr_idx = std.mem.readInt(u32, fixed[36..40], .little), + .block_sizes = blocks, }; } - pub fn deinit(self: ExtFileInode, alloc: std.mem.Allocator) void { - alloc.free(self.blocks); - } }; diff --git a/src/inode/inode.zig b/src/inode/inode.zig deleted file mode 100644 index a67a0c6..0000000 --- a/src/inode/inode.zig +++ /dev/null @@ -1,103 +0,0 @@ -const std = @import("std"); -const io = std.io; - -pub const InodeRef = packed struct { - offset: u16, - block_start: u32, - _: u16 = 0, - - pub fn init(block_start: u32, offset: u16) InodeRef { - return .{ - .offset = offset, - .block_start = block_start, - }; - } -}; - -pub const InodeType = enum(u16) { - dir = 1, - file, - sym, - block, - char, - fifo, - sock, - ext_dir, - ext_file, - ext_sym, - ext_block, - ext_char, - ext_fifo, - ext_sock, -}; - -const dir = @import("dir.zig"); -const file = @import("file.zig"); -const sym = @import("sym.zig"); -const misc = @import("misc.zig"); - -pub const InodeData = union(enum) { - dir: dir.DirInode, - file: file.FileInode, - sym: sym.SymInode, - block: misc.DeviceInode, - char: misc.DeviceInode, - fifo: misc.IPCInode, - sock: misc.IPCInode, - ext_dir: dir.ExtDirInode, - ext_file: file.ExtFileInode, - ext_sym: sym.ExtSymInode, - ext_block: misc.ExtDeviceInode, - ext_char: misc.ExtDeviceInode, - ext_fifo: misc.ExtIPCInode, - ext_sock: misc.ExtIPCInode, -}; - -pub const InodeHeader = packed struct { - inode_type: InodeType, - perm: u16, - uid_idx: u16, - gid_idx: u16, - mod_time: u32, - num: u32, -}; - -pub const Inode = struct { - alloc: std.mem.Allocator, - header: InodeHeader, - data: InodeData, - - pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !Inode { - const hdr = try rdr.readStruct(InodeHeader); - const data: InodeData = switch (hdr.inode_type) { - .dir => .{ .dir = try .init(rdr) }, - .file => .{ .file = try .init(alloc, rdr, block_size) }, - .sym => .{ .sym = try .init(alloc, rdr) }, - .block => .{ .block = try .init(rdr) }, - .char => .{ .char = try .init(rdr) }, - .fifo => .{ .fifo = try .init(rdr) }, - .sock => .{ .sock = try .init(rdr) }, - .ext_dir => .{ .ext_dir = try .init(rdr) }, - .ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) }, - .ext_sym => .{ .ext_sym = try .init(alloc, rdr) }, - .ext_block => .{ .ext_block = try .init(rdr) }, - .ext_char => .{ .ext_char = try .init(rdr) }, - .ext_fifo => .{ .ext_fifo = try .init(rdr) }, - .ext_sock => .{ .ext_sock = try .init(rdr) }, - }; - return .{ - .alloc = alloc, - .header = hdr, - .data = data, - }; - } - pub fn deinit(self: Inode) void { - switch (self.data) { - .file => |d| d.deinit(self.alloc), - .sym => |d| d.deinit(self.alloc), - .ext_file => |d| d.deinit(self.alloc), - .ext_sym => |d| d.deinit(self.alloc), - else => {}, - } - } -}; diff --git a/src/inode/misc.zig b/src/inode/misc.zig index 940a0a0..134f9de 100644 --- a/src/inode/misc.zig +++ b/src/inode/misc.zig @@ -1,38 +1,87 @@ const std = @import("std"); -const io = std.io; -pub const DeviceInode = packed struct { - hard_links: u32, - device: u32, +pub const Symlink = struct { + hard_link: u32, + // size: u32, + target: []const u8, - pub fn init(rdr: io.AnyReader) !DeviceInode { - return rdr.readStruct(DeviceInode); + pub fn init(rdr: anytype, alloc: std.mem.Allocator) !Symlink { + var fixed: [8]u8 = undefined; + _ = try rdr.read(&fixed); + const size = std.mem.readInt(u32, fixed[4..8], .little); + const target = alloc.alloc(u8, size); + errdefer alloc.free(target); + _ = try rdr.read(target); + return .{ + .hard_link = std.mem.readInt(u32, fixed[0..4], .little), + .target = target, + }; } }; -pub const ExtDeviceInode = packed struct { - hard_links: u32, +pub const ExtSymlink = struct { + hard_link: u32, + // size: u32, + target: []const u8, + xattr_idx: u32, + + pub fn init(rdr: anytype, alloc: std.mem.Allocator) !ExtSymlink { + var fixed: [8]u8 = undefined; + _ = try rdr.read(&fixed); + const size = std.mem.readInt(u32, fixed[4..8], .little); + const target = alloc.alloc(u8, size); + errdefer alloc.free(target); + _ = try rdr.read(target); + var xattr_idx: u32 = 0; + _ = try rdr.read(std.mem.asBytes(&xattr_idx)); + return .{ + .hard_link = std.mem.readInt(u32, fixed[0..4], .little), + .target = target, + .xattr_idx = xattr_idx, + }; + } +}; + +pub const Dev = packed struct { + hard_link: u32, + device: u32, + + pub fn init(rdr: anytype) !Dev { + const out: Dev = undefined; + _ = try rdr.read(std.mem.asBytes(&out)); + return out; + } +}; + +pub const ExtDev = packed struct { + hard_link: u32, device: u32, xattr_idx: u32, - pub fn init(rdr: io.AnyReader) !ExtDeviceInode { - return rdr.readStruct(ExtDeviceInode); + pub fn init(rdr: anytype) !ExtDev { + const out: ExtDev = undefined; + _ = try rdr.read(std.mem.asBytes(&out)); + return out; } }; -pub const IPCInode = packed struct { - hard_links: u32, +pub const IPC = packed struct { + hard_link: u32, - pub fn init(rdr: io.AnyReader) !IPCInode { - return rdr.readStruct(IPCInode); + pub fn init(rdr: anytype) !IPC { + const out: IPC = undefined; + _ = try rdr.read(std.mem.asBytes(&out)); + return out; } }; -pub const ExtIPCInode = packed struct { - hard_links: u32, +pub const ExtIPC = packed struct { + hard_link: u32, xattr_idx: u32, - pub fn init(rdr: io.AnyReader) !ExtIPCInode { - return rdr.readStruct(ExtIPCInode); + pub fn init(rdr: anytype) !ExtIPC { + const out: ExtIPC = undefined; + _ = try rdr.read(std.mem.asBytes(&out)); + return out; } }; diff --git a/src/inode/sym.zig b/src/inode/sym.zig deleted file mode 100644 index 8f68c85..0000000 --- a/src/inode/sym.zig +++ /dev/null @@ -1,48 +0,0 @@ -const std = @import("std"); -const io = std.io; - -pub const SymInode = struct { - hard_links: u32, - size: u32, - target: []const u8, - - pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader) !SymInode { - var fixed_buf = [_]u8{0} ** 8; - _ = try rdr.readAll(@ptrCast(&fixed_buf)); - const size = std.mem.bytesToValue(u32, fixed_buf[4..]); - const target = try alloc.alloc(u8, size); - _ = try rdr.readAll(target); - return .{ - .hard_links = std.mem.bytesToValue(u32, fixed_buf[0..4]), - .size = size, - .target = target, - }; - } - pub fn deinit(self: SymInode, alloc: std.mem.Allocator) void { - alloc.free(self.target); - } -}; - -pub const ExtSymInode = struct { - hard_links: u32, - size: u32, - target: []const u8, - xattr_idx: u32, - - pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader) !ExtSymInode { - var fixed_buf = [_]u8{0} ** 8; - _ = try rdr.readAll(&fixed_buf); - const size = std.mem.bytesToValue(u32, fixed_buf[4..]); - const target = try alloc.alloc(u8, size); - _ = try rdr.readAll(target); - return .{ - .hard_links = std.mem.bytesToValue(u32, fixed_buf[0..4]), - .size = size, - .target = target, - .xattr_idx = try rdr.readInt(u32, std.builtin.Endian.little), - }; - } - pub fn deinit(self: ExtSymInode, alloc: std.mem.Allocator) void { - alloc.free(self.target); - } -}; diff --git a/src/reader.zig b/src/reader.zig index b71bb9d..a21312a 100644 --- a/src/reader.zig +++ b/src/reader.zig @@ -1,139 +1,25 @@ const std = @import("std"); -const inode = @import("inode/inode.zig"); - -const Table = @import("table.zig").Table; -const FileHolder = @import("readers/file_holder.zig").FileHolder; const Superblock = @import("superblock.zig").Superblock; -const File = @import("file.zig").File; -const MetadataReader = @import("readers/metadata.zig").MetadataReader; -const DirEntry = @import("directory.zig").DirEntry; -const FragEntry = @import("fragment.zig").FragEntry; -/// A squashfs archive reader. Make sure to call deinit(). -/// For most actions, you'll want to use Reader.root. -pub const Reader = struct { - alloc: std.mem.Allocator, - holder: FileHolder, - super: Superblock, - root: File, +pub fn Reader(comptime T: type) type { + std.debug.assert(std.meta.hasFn(T, "pread")); - frag_table: Table(FragEntry), - export_table: Table(inode.InodeRef), - id_table: Table(u32), + return struct { + const Self = @This(); - pub fn init(alloc: std.mem.Allocator, filepath: []const u8, offset: u64) !Reader { - var holder: FileHolder = try .init(filepath, offset); - const super: Superblock = try holder.reader().readStruct(Superblock); - try super.validate(); - var out: Reader = .{ - .alloc = alloc, - .holder = holder, - .super = super, - .root = undefined, - .frag_table = undefined, - .export_table = undefined, - .id_table = undefined, - }; - out.frag_table = .init( - &out, - super.frag_table_start, - super.frag_count, - ); - out.export_table = .init( - &out, - super.export_table_start, - super.inode_count, - ); - out.id_table = .init( - &out, - super.id_table_start, - super.id_count, - ); - out.root = try out.rootFile(); - return out; - } - pub fn deinit(self: *Reader) void { - self.frag_table.deinit(self.alloc); - self.export_table.deinit(self.alloc); - self.id_table.deinit(self.alloc); - self.root.deinit(self.alloc); - self.holder.deinit(); - } + alloc: std.mem.Allocator, + rdr: T, - pub fn open(self: *Reader, path: []const u8) !File { - return self.root.open(self, path); - } + super: Superblock = undefined, - fn rootFile(self: *Reader) !File { - var offset_rdr = self.holder.readerAt(self.super.root_ref.block_start + self.super.inode_table_start); - var meta_rdr: MetadataReader = .init( - self.alloc, - self.super.decomp, - offset_rdr.any(), - ); - defer meta_rdr.deinit(); - try meta_rdr.skip(self.super.root_ref.offset); - return .{ - .name = "", - .inode = try .init( - self.alloc, - meta_rdr.any(), - self.super.block_size, - ), - .parent_path = "", - }; - } -}; - -const test_sfs_path = "testing/LinuxPATest.sfs"; - -test "root iter" { - var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0); - defer rdr.deinit(); - var rootIter = try rdr.root.iterator(&rdr); - defer rootIter.deinit(); - while (rootIter.next()) |f| { - std.debug.print("{s}\n", .{f.name}); - } -} - -test "extract single file" { - const sfs_file_path = "PortableApps/Cool_Retro_Term-dac2b4f-x86_64.AppImage"; - const extract_path = "testing/Cool_Retro_Term-dac2b4f-x86_64.AppImage"; - std.fs.cwd().deleteFile(extract_path) catch |err| { - if (err != std.fs.Dir.DeleteFileError.FileNotFound) { - return err; + pub fn init(alloc: std.mem.Allocator, rdr: T) Self { + const out = Self{ + .alloc = alloc, + .rdr = rdr, + }; + _ = try rdr.pread(std.mem.asBytes(&out.super), 0); + return out; } }; - var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0); - defer rdr.deinit(); - var fil = try rdr.open(sfs_file_path); - defer fil.deinit(std.testing.allocator); - try fil.extract(&rdr, try .init(), extract_path); -} - -test "extract single directory" { - const sfs_file_path = "Documents"; - const extract_path = "testing/Documents"; - try std.fs.cwd().deleteTree(extract_path); - var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0); - defer rdr.deinit(); - var fil = try rdr.open(sfs_file_path); - defer fil.deinit(std.testing.allocator); - var config: File.ExtractConfig = try .init(); - config.verbose = true; - try fil.extract(&rdr, config, extract_path); -} - -test "full extract" { - const extract_path = "testing/testExtract"; - std.fs.cwd().deleteTree(extract_path) catch |err| { - if (err != std.fs.Dir.DeleteFileError.FileNotFound) { - return err; - } - }; - var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0); - defer rdr.deinit(); - try rdr.root.extract(&rdr, try .init(), extract_path); } diff --git a/src/reader/metadata.zig b/src/reader/metadata.zig new file mode 100644 index 0000000..f468717 --- /dev/null +++ b/src/reader/metadata.zig @@ -0,0 +1,43 @@ +const std = @import("std"); + +const Compression = @import("../superblock.zig").Compression; + +const MetaHeader = packed struct { + size: u15, + uncompressed: bool, +}; + +pub fn MetadataReader(comptime T: type) type { + return struct { + const Self = @This(); + + alloc: std.mem.Allocator, + comp: Compression, + rdr: T, + + block: [8192]u8 = undefined, + block_size: usize = 0, + block_offset: u32 = 0, + + pub fn init(alloc: std.mem.Allocator, comp: Compression, rdr: T) !Self { + var out: Self = .{ + .alloc = alloc, + .comp = comp, + .rdr = rdr, + }; + try out.readNextBlock(); + return out; + } + + fn readNextBlock(self: *Self) !void { + const hdr: MetaHeader = undefined; + _ = try self.rdr.read(std.mem.asBytes(hdr)); + self.block_size = try self.comp.decompress( + 8192, + self.alloc, + std.io.limitedReader(self.rdr, hdr.size), + self.block, + ); + } + }; +} diff --git a/src/readers/data_extractor.zig b/src/readers/data_extractor.zig deleted file mode 100644 index b746a40..0000000 --- a/src/readers/data_extractor.zig +++ /dev/null @@ -1,202 +0,0 @@ -const std = @import("std"); -const fs = std.fs; -const io = std.io; - -const File = @import("../file.zig").File; -const Reader = @import("../reader.zig").Reader; -const BlockSize = @import("../inode/file.zig").BlockSize; -const DecompressionType = @import("../decompress.zig").DecompressType; -const FileHolder = @import("../readers/file_holder.zig").FileHolder; -const FileOffsetWriter = @import("../readers/file_holder.zig").FileOffsetWriter; -const DataReader = @import("data_reader.zig").DataReader; -const Config = @import("../file.zig").Config; - -/// A specialized File data reader that's meant to write all of it's data at once. -/// Can be re-used freely until deinit() is called. -pub const DataExtractor = struct { - alloc: std.mem.Allocator, - decomp: DecompressionType, - holder: *FileHolder, - block_size: u32, - file_size: u64, - sizes: []BlockSize, - block_offset: []u64, - frag_data: ?[]u8 = null, - - pub fn init(fil: *File, reader: *Reader) !DataExtractor { - var data_start: u64 = 0; - var sizes: []BlockSize = undefined; - var file_size: u64 = 0; - var frag_idx: u32 = 0; - var frag_offset: u32 = 0; - switch (fil.inode.data) { - .file => |f| { - data_start = f.data_start; - sizes = try reader.alloc.alloc(BlockSize, f.blocks.len); - @memcpy(sizes, f.blocks); - file_size = f.size; - frag_idx = f.frag_idx; - frag_offset = f.frag_offset; - }, - .ext_file => |f| { - data_start = f.data_start; - sizes = try reader.alloc.alloc(BlockSize, f.blocks.len); - @memcpy(sizes, f.blocks); - file_size = f.size; - frag_idx = f.frag_idx; - frag_offset = f.frag_offset; - }, - else => return File.FileError.NotNormalFile, - } - var out: DataExtractor = .{ - .alloc = reader.alloc, - .decomp = reader.super.decomp, - .holder = &reader.holder, - .block_size = reader.super.block_size, - .file_size = file_size, - .sizes = sizes, - .block_offset = try reader.alloc.alloc(u64, sizes.len), - }; - errdefer out.deinit(); - var offset: u64 = data_start; - for (0.., out.block_offset) |i, _| { - out.block_offset[i] = offset; - offset += out.sizes[i].size; - } - if (frag_idx != 0xFFFFFFFF) { - const frag_ent = try reader.frag_table.getValue(reader, frag_idx); - out.frag_data = try frag_ent.getData(reader, frag_offset, @truncate(file_size % reader.super.block_size)); - } - return out; - } - - pub fn deinit(self: *DataExtractor) void { - self.alloc.free(self.sizes); - self.alloc.free(self.block_offset); - if (self.frag_data != null) self.alloc.free(self.frag_data.?); - } - - fn processBlockToFile(self: *DataExtractor, wg: *std.Thread.WaitGroup, errs: *MutexList, block_ind: usize, fil: *fs.File) void { - defer wg.finish(); - if (self.sizes[block_ind].not_compressed) { - @branchHint(.unlikely); - if (self.sizes[block_ind].size == 0) { - if (block_ind == self.sizes.len - 1) { - fil.pwriteAll(&[1]u8{0}, self.file_size - 1) catch |err| { - std.debug.print("yo1\n", .{}); - errs.append(err) catch {}; - }; - } else { - fil.pwriteAll(&[1]u8{0}, ((block_ind + 1) * self.block_size) - 1) catch |err| { - std.debug.print("yo2\n", .{}); - errs.append(err) catch {}; - }; - } - return; - } - const dat = self.alloc.alloc(u8, self.sizes[block_ind].size) catch |err| { - errs.append(err) catch {}; - return; - }; - defer self.alloc.free(dat); - _ = self.holder.file.preadAll(dat, self.block_offset[block_ind]) catch |err| { - errs.append(err) catch {}; - return; - }; - fil.pwriteAll(dat, block_ind * self.block_size) catch |err| { - errs.append(err) catch {}; - }; - } else { - @branchHint(.likely); - const offset_rdr = self.holder.readerAt(self.block_offset[block_ind]); - var fil_wrtr: FileOffsetWriter = .init(fil, block_ind * self.block_size); - var limit = std.io.limitedReader(offset_rdr, self.sizes[block_ind].size); - self.decomp.decompressTo( - self.alloc, - limit.reader().any(), - fil_wrtr.any(), - ) catch |err| { - errs.append(err) catch {}; - }; - } - } - - fn fragmentToFile(self: *DataExtractor, wg: *std.Thread.WaitGroup, errs: *MutexList, fil: *fs.File) void { - defer wg.finish(); - fil.pwriteAll(self.frag_data.?, self.block_size * self.sizes.len) catch |err| { - errs.append(err) catch {}; - }; - } - - /// Write the data completely to the given file. - /// Ignores the file's current offset and writes from the beginning of the file. - /// Returns the amount of bytes written. - /// - /// Optimized for lower memory usage by using File.pwrite. - pub fn writeToFile(self: *DataExtractor, pool: *std.Thread.Pool, fil: *fs.File) !void { - var wg: std.Thread.WaitGroup = .{}; - var errs: MutexList = .init(self.alloc); - defer errs.deinit(); - for (0..self.sizes.len) |i| { - wg.start(); - try pool.spawn(processBlockToFile, .{ self, &wg, &errs, i, fil }); - } - if (self.frag_data != null) { - wg.start(); - try pool.spawn(fragmentToFile, .{ self, &wg, &errs, fil }); - } - wg.wait(); - if (errs.list.items.len > 0) { - //TODO: better handle all the errors - return errs.list.items[0]; - } - } - - // fn processBlock(self: *DataExtractor, errs: std.ArrayList(anyerror), data_out: std.AutoHashMap([]u8), block_ind: u32) void { - // const offset_rdr = self.holder.readerAt(self.block_offset[block_ind]); - // const out = self.decomp.decompress( - // self.alloc, - // std.io.limitedReader(offset_rdr, self.sizes[block_ind].size), - // ) catch |err| { - // errs.append(err); - // return; - // }; - // data_out.put(block_ind, ) - // } - - // Write the data completely to the given writer. - // Returns the amount of bytes written. - // - // To write data in order, some data may end up cached temporarily. - // pub fn writeToWriter(self: DataExtractor, pool: *std.Thread.Pool, writer: io.AnyWriter) !void { - // const wg: std.Thread.WaitGroup = .{}; - // const errs: std.ArrayList(anyerror) = .init(self.alloc); - // const data: std.AutoHashMap(u32, []u8) = .init(self.alloc); - // const cond: std.Thread. = .{}; - // defer errs.deinit(); - // for (0..self.sizes.len) |i| { - // pool.spawnWg(&wg, processBlock, .{ &self, i, fil }); - // } - // wg.wait(); - // } -}; - -const MutexList = struct { - list: std.ArrayList(anyerror), - mut: std.Thread.Mutex = .{}, - - fn init(alloc: std.mem.Allocator) MutexList { - return .{ - .list = .init(alloc), - }; - } - fn deinit(self: *MutexList) void { - self.list.deinit(); - } - - fn append(self: *MutexList, err: anyerror) !void { - self.mut.lock(); - defer self.mut.unlock(); - try self.list.append(err); - } -}; diff --git a/src/readers/data_reader.zig b/src/readers/data_reader.zig deleted file mode 100644 index 5f9e1d4..0000000 --- a/src/readers/data_reader.zig +++ /dev/null @@ -1,164 +0,0 @@ -const std = @import("std"); -const io = std.io; -const fs = std.fs; - -const File = @import("../file.zig").File; -const Reader = @import("../reader.zig").Reader; -const BlockSize = @import("../inode/file.zig").BlockSize; -const DecompressionType = @import("../decompress.zig").DecompressType; -const FileOffsetReader = @import("../readers/file_holder.zig").FileOffsetReader; -const FragEntry = @import("../fragment.zig").FragEntry; - -const DataReaderError = error{ - EOF, -}; - -pub const DataReader = struct { - alloc: std.mem.Allocator, - decomp: DecompressionType, - rdr: FileOffsetReader, - block_size: u32, - file_size: u64, - sizes: []BlockSize, - frag_data: ?[]u8 = null, - - next_block_num: u32 = 0, - cur_bloc: []u8 = &[0]u8{}, - cur_offset: u32 = 0, - - pub fn init(fil: *File, reader: *Reader) !DataReader { - var data_start: u64 = 0; - var sizes: []BlockSize = undefined; - var file_size: u64 = 0; - var frag_idx: u32 = 0; - var frag_offset: u32 = 0; - switch (fil.inode.data) { - .file => |f| { - sizes = try reader.alloc.alloc(BlockSize, f.blocks.len); - @memcpy(sizes, f.blocks); - data_start = f.data_start; - file_size = f.size; - frag_idx = f.frag_idx; - frag_offset = f.frag_offset; - }, - .ext_file => |f| { - sizes = try reader.alloc.alloc(BlockSize, f.blocks.len); - @memcpy(sizes, f.blocks); - data_start = f.data_start; - file_size = f.size; - frag_idx = f.frag_idx; - frag_offset = f.frag_offset; - }, - else => return File.FileError.NotNormalFile, - } - var out: DataReader = .{ - .alloc = reader.alloc, - .decomp = reader.super.decomp, - .rdr = reader.holder.readerAt(data_start), - .block_size = reader.super.block_size, - .file_size = file_size, - .sizes = sizes, - }; - errdefer out.deinit(); - if (frag_idx != 0xFFFFFFFF) { - const frag_ent = try reader.frag_table.getValue(reader, frag_idx); - out.frag_data = try frag_ent.getData(reader, frag_offset, @truncate(file_size % reader.super.block_size)); - } - return out; - } - pub fn fromFragEntry(reader: *Reader, ent: FragEntry) !DataReader { - const size = try reader.alloc.alloc(BlockSize, 1); - size[0] = ent.size; - return .{ - .alloc = reader.alloc, - .decomp = reader.super.decomp, - .rdr = reader.holder.readerAt(ent.start), - .block_size = reader.super.block_size, - .sizes = size, - }; - } - - pub fn deinit(self: *DataReader) void { - self.alloc.free(self.sizes); - if (self.cur_bloc.len > 0) self.alloc.free(self.cur_bloc); - if (self.frag_data != null) self.alloc.free(self.frag_data.?); - } - - pub fn skip(self: *DataReader, offset: u32) !void { - var cur_skip: u32 = 0; - var to_skip: u32 = 0; - while (cur_skip < offset) { - if (self.cur_offset >= self.cur_bloc.len) try self.readNextBlock(); - to_skip = @min(offset - cur_skip, self.cur_bloc.len - self.cur_offset); - cur_skip += to_skip; - self.cur_offset += to_skip; - } - } - - fn readNextBlock(self: *DataReader) !void { - defer self.next_block_num += 1; - if (self.next_block_num == self.sizes.len and self.frag_data != null) { - try self.sizeBlock(self.frag_data.?.len); - @memcpy(self.cur_bloc, self.frag_data.?); - return; - } else if (self.next_block_num >= self.sizes.len) { - return DataReaderError.EOF; - } - const siz = self.sizes[self.next_block_num]; - if (siz.size == 0) { - if (self.next_block_num == self.sizes.len) { - try self.sizeBlock(@truncate(self.file_size % self.block_size)); - } else { - try self.sizeBlock(self.block_size); - } - @memset(self.cur_bloc, 0); - return; - } - if (siz.not_compressed) { - try self.sizeBlock(siz.size); - _ = try self.rdr.any().readAll(self.cur_bloc); - } else { - self.alloc.free(self.cur_bloc); - var limit = std.io.limitedReader(self.rdr, siz.size); - var dat = try self.decomp.decompress(self.alloc, limit.reader().any()); - self.cur_bloc = try dat.toOwnedSlice(); - } - } - - fn sizeBlock(self: *DataReader, size: usize) !void { - if (!self.alloc.resize(self.cur_bloc, size)) { - self.alloc.free(self.cur_bloc); - self.cur_bloc = try self.alloc.alloc(u8, size); - } - } - - pub fn read(self: *DataReader, bytes: []u8) !usize { - var cur_read: usize = 0; - var to_read: usize = 0; - while (cur_read < bytes.len) { - if (self.cur_offset >= self.cur_bloc.len) { - self.readNextBlock() catch |err| { - if (err == DataReaderError.EOF) return cur_read; - return err; - }; - } - to_read = @min(bytes.len - cur_read, self.cur_bloc.len - self.cur_offset); - @memcpy(bytes[cur_read .. cur_read + to_read], self.cur_bloc[self.cur_offset .. @as(usize, self.cur_offset) + to_read]); - self.cur_offset += @truncate(to_read); - cur_read += to_read; - } - return cur_read; - } - - pub fn any(self: *DataReader) io.AnyReader { - return .{ - .context = @ptrCast(self), - .readFn = readOpaque, - }; - } - - fn readOpaque(context: *const anyopaque, bytes: []u8) !usize { - var self: *DataReader = @constCast(@ptrCast(@alignCast(context))); - return self.read(bytes); - } -}; diff --git a/src/readers/file_holder.zig b/src/readers/file_holder.zig deleted file mode 100644 index 703a02a..0000000 --- a/src/readers/file_holder.zig +++ /dev/null @@ -1,90 +0,0 @@ -const std = @import("std"); -const fs = std.fs; -const io = std.io; - -const File = std.fs.File; - -pub const FileHolder = struct { - file: File, - offset: u64, - - pub fn init(path: []const u8, offset: u64) !FileHolder { - return .{ - .file = try fs.cwd().openFile(path, .{ .mode = .read_write }), - .offset = offset, - }; - } - pub fn deinit(self: FileHolder) void { - self.file.close(); - } - - pub fn reader(self: *FileHolder) File.Reader { - return self.file.reader(); - } - pub fn readerAt(self: *FileHolder, offset: u64) FileOffsetReader { - return .{ - .file = &self.file, - .offset = self.offset + offset, - }; - } - - // pub fn writerAt(self: *FileHolder, offset: u64) FileOffsetWriter { - // return .{ - // .file = &self.file, - // .offset = self.offset + offset, - // }; - // } -}; - -pub const FileOffsetWriter = struct { - file: *File, - offset: u64, - - pub fn init(fil: *File, init_offset: u64) FileOffsetWriter { - return .{ - .file = fil, - .offset = init_offset, - }; - } - - pub const Error = fs.File.PWriteError; - - pub fn write(self: *FileOffsetWriter, bytes: []const u8) !usize { - try self.file.pwriteAll(bytes, self.offset); - self.offset += bytes.len; - return bytes.len; - } - pub fn any(self: *FileOffsetWriter) io.AnyWriter { - return .{ - .context = @ptrCast(self), - .writeFn = writeOpaque, - }; - } - fn writeOpaque(context: *const anyopaque, bytes: []const u8) anyerror!usize { - var rdr: *FileOffsetWriter = @constCast(@ptrCast(@alignCast(context))); - return try rdr.write(bytes); - } -}; - -pub const FileOffsetReader = struct { - file: *File, - offset: u64, - - pub const Error = fs.File.PReadError; - - pub fn read(self: *FileOffsetReader, bytes: []u8) !usize { - const red = try self.file.preadAll(bytes, self.offset); - self.offset += red; - return red; - } - pub fn any(self: *FileOffsetReader) io.AnyReader { - return .{ - .context = @ptrCast(self), - .readFn = readOpaque, - }; - } - fn readOpaque(context: *const anyopaque, bytes: []u8) !usize { - var rdr: *FileOffsetReader = @constCast(@ptrCast(@alignCast(context))); - return try rdr.read(bytes); - } -}; diff --git a/src/readers/metadata.zig b/src/readers/metadata.zig deleted file mode 100644 index 33d87ab..0000000 --- a/src/readers/metadata.zig +++ /dev/null @@ -1,77 +0,0 @@ -const std = @import("std"); -const io = std.io; - -const DecompressType = @import("../decompress.zig").DecompressType; - -const MetadataHeader = packed struct { - size: u15, - not_compressed: bool, -}; - -pub const MetadataReader = struct { - alloc: std.mem.Allocator, - decomp: DecompressType, - reader: io.AnyReader, - block: []u8 = &[0]u8{}, - offset: u32 = 0, - - pub fn init(alloc: std.mem.Allocator, decomp: DecompressType, rdr: io.AnyReader) MetadataReader { - return .{ - .alloc = alloc, - .decomp = decomp, - .reader = rdr, - }; - } - pub fn deinit(self: *MetadataReader) void { - self.alloc.free(self.block); - } - - pub fn skip(self: *MetadataReader, offset: u16) !void { - var cur_skip: u32 = 0; - var to_skip: u32 = 0; - while (cur_skip < offset) { - if (self.offset >= self.block.len) try self.readNextBlock(); - to_skip = @min(offset - cur_skip, self.block.len - self.offset); - cur_skip += to_skip; - self.offset += to_skip; - } - } - - fn readNextBlock(self: *MetadataReader) !void { - self.offset = 0; - if (self.block.len > 0) self.alloc.free(self.block); - const hdr = try self.reader.readStruct(MetadataHeader); - if (hdr.not_compressed) { - self.block = try self.alloc.alloc(u8, hdr.size); - _ = try self.reader.readAll(self.block); - } else { - var limit = std.io.limitedReader(self.reader, hdr.size); - var dat = try self.decomp.decompress(self.alloc, limit.reader().any()); - self.block = try dat.toOwnedSlice(); - } - } - - pub fn any(self: *MetadataReader) io.AnyReader { - return .{ - .context = @ptrCast(self), - .readFn = readOpaque, - }; - } - - pub fn read(self: *MetadataReader, bytes: []u8) !usize { - var cur_read: usize = 0; - var to_read: usize = 0; - while (cur_read < bytes.len) { - if (self.offset >= self.block.len) try self.readNextBlock(); - to_read = @min(bytes.len - cur_read, self.block.len - self.offset); - @memcpy(bytes[cur_read .. cur_read + to_read], self.block[self.offset .. @as(usize, self.offset) + to_read]); - self.offset += @truncate(to_read); - cur_read += to_read; - } - return cur_read; - } - fn readOpaque(context: *const anyopaque, bytes: []u8) !usize { - var rdr: *MetadataReader = @constCast(@ptrCast(@alignCast(context))); - return rdr.read(bytes); - } -}; diff --git a/src/root.zig b/src/root.zig index b64c197..e69de29 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1 +0,0 @@ -pub const Reader = @import("reader.zig").Reader; diff --git a/src/superblock.zig b/src/superblock.zig index b1d255a..c17db3c 100644 --- a/src/superblock.zig +++ b/src/superblock.zig @@ -1,10 +1,4 @@ -const math = @import("std").math; - -const SuperblockError = error{ - InvalidMagic, - InvalidBlockLog, - InvalidVersion, -}; +const std = @import("std"); pub const Superblock = packed struct { magic: u32, @@ -12,28 +6,70 @@ pub const Superblock = packed struct { mod_time: u32, block_size: u32, frag_count: u32, - decomp: @import("decompress.zig").DecompressType, + comp: Compression, block_log: u16, - flags: u16, + flags: packed struct { + _: u4, + id_uncomp: bool, + comp_options: bool, + no_xattr: bool, + xattr_uncomp: bool, + has_export: bool, + de_dupe: bool, + frag_always: bool, + no_frag: bool, + frag_uncomp: bool, + check: bool, + data_uncomp: bool, + inode_uncomp: bool, + }, id_count: u16, ver_maj: u16, ver_min: u16, - root_ref: @import("inode/inode.zig").InodeRef, + root_ref: u64, size: u64, - id_table_start: u64, - xattr_table_start: u64, - inode_table_start: u64, - dir_table_start: u64, - frag_table_start: u64, - export_table_start: u64, + id_start: u64, + xattr_start: u64, + inode_start: u64, + dir_start: u64, + frag_start: u64, + export_start: u64, +}; - pub fn validate(self: Superblock) SuperblockError!void { - if (self.magic != 0x73717368) { - return SuperblockError.InvalidMagic; - } else if (self.block_log != math.log2(self.block_size)) { - return SuperblockError.InvalidBlockLog; - } else if (self.ver_maj != 4 or self.ver_min != 0) { - return SuperblockError.InvalidVersion; +const DecompressError = error{ + LzoUnavailable, + Lz4Unavailable, +}; + +pub const Compression = enum(u16) { + gzip = 1, + lzma, + lzo, + xz, + lz4, + zstd, + + pub fn decompress(self: Compression, comptime max_size: u16, alloc: std.mem.Allocator, source: anytype, dest: *[max_size]u8) !usize { + switch (self) { + .gzip => { + const decomp = std.compress.zlib.decompressor(source); + return decomp.read(dest); + }, + .lzma => { + const decomp = try std.compress.lzma.decompress(alloc, source); + return decomp.read(dest); + }, + .lzo => return DecompressError.LzoUnavailable, + .xz => { + const decomp = try std.compress.xz.decompress(alloc, source); + return decomp.read(dest); + }, + .lz4 => return DecompressError.Lz4Unavailable, + .zstd => { + const window: [@min(std.compress.zstd.DecompressorOptions.default_window_buffer_len, max_size)]u8 = undefined; + const decomp = std.compress.zstd.decompressor(source, .{ .window_buffer = window }); + return decomp.read(dest); + }, } } }; diff --git a/src/table.zig b/src/table.zig deleted file mode 100644 index e3acab4..0000000 --- a/src/table.zig +++ /dev/null @@ -1,56 +0,0 @@ -const std = @import("std"); - -const Reader = @import("reader.zig").Reader; -const DecompressType = @import("decompress.zig").DecompressType; -const FileHolder = @import("readers/file_holder.zig").FileHolder; -const FileOffsetReader = @import("readers/file_holder.zig").FileOffsetReader; -const MetadataReader = @import("readers/metadata.zig").MetadataReader; - -const TableError = error{InvalidIndex}; - -/// A lazily read squashfs table. -pub fn Table( - comptime T: type, -) type { - return struct { - decomp: DecompressType, - table: []T = &[0]T{}, - offset: u64, - item_count: u32, - - pub fn init(read: *Reader, offset: u64, item_count: u32) Self { - return .{ - .decomp = read.super.decomp, - .offset = offset, - .item_count = item_count, - }; - } - pub fn deinit(self: *Self, alloc: std.mem.Allocator) void { - if (self.table.len != 0) alloc.free(self.table); - } - - pub fn getValue(self: *Self, read: *Reader, i: u64) !T { - if (i >= self.item_count) return TableError.InvalidIndex; - if (self.table.len > i) return self.table[i]; - var offset_rdr: FileOffsetReader = undefined; - var meta_rdr: MetadataReader = undefined; - var meta_buf: [8]u8 = [1]u8{0} ** 8; - const meta_offset = std.mem.bytesAsValue(u64, &meta_buf); - var to_read: u32 = 0; - while (self.table.len <= i) { - _ = try read.holder.file.preadAll(&meta_buf, self.offset); - self.offset += 8; - offset_rdr = read.holder.readerAt(meta_offset.*); - meta_rdr = .init(read.alloc, self.decomp, offset_rdr.any()); - defer meta_rdr.deinit(); - to_read = @min(self.item_count - self.table.len, comptime 8192 / @sizeOf(T)); - const alloc_size = self.table.len + to_read; - if (self.table.len != 0) read.alloc.free(self.table); - self.table = try read.alloc.alloc(T, alloc_size); - _ = try meta_rdr.any().readAll(@ptrCast(self.table[self.table.len - to_read ..])); - } - return self.table[i]; - } - const Self: type = @This(); - }; -} diff --git a/src/zig_unsquashfs.zig b/src/zig_unsquashfs.zig deleted file mode 100644 index acab20c..0000000 --- a/src/zig_unsquashfs.zig +++ /dev/null @@ -1,203 +0,0 @@ -const std = @import("std"); -const config = @import("config"); - -const File = @import("file.zig").File; -const Reader = @import("reader.zig").Reader; -const ExtractConfig = @import("file.zig").File.ExtractConfig; - -const stdout = std.io.getStdOut(); - -var extr_files: std.ArrayList([]const u8) = undefined; -var offset: u64 = 0; -var verbose: bool = false; -var unbreak: bool = false; -var deref: bool = false; -var processors: u16 = 0; -var list: ListTypes = .None; - -var filename: []const u8 = ""; -var extr_location: []const u8 = ""; - -const ListTypes = enum { - None, - List, - ListAttr, - ListNumeric, -}; - -fn help() !void { - const help_msg = - \\Basic Usage: zig-unsquashfs [Options] SQUASHFS_FILE EXTRACT_LOCATION - \\ - \\General options: - \\ -e Path to a file or directory inside the archive to extract instead of the whole archive. - \\ Can be given multiple times. - \\ -o Skip before reading from the archive. - \\ -v Verbose output. - \\ --help Prints this help message. - \\ -h Same as --help - \\ - \\Extraction options: - \\ --unbreak-symlinks Attempt extract symlink targets along with symlinks. Will not place files outside of the extraction location. - \\ -us Same as --unbreak-symlinks - \\ --deref-symlinks Replace symlink files with their target. - \\ -ds Same as --deref-symlinks - \\ -p <#> Use at most # of processors. Defaults to logical core count. - \\ - \\Listing Options: - \\ -l List files instead of extracting. When used, you do not need to specify an extraction location. - \\ -ll Like -l, but with file attributes. - \\ -lln Like -ll, but with numeric uids and gids. - \\ - \\Other: - \\ --version Print version number. - \\ - ; - _ = try stdout.writeAll(help_msg); -} - -pub fn main() !void { - var alloc: std.heap.GeneralPurposeAllocator(.{}) = .init; - extr_files = .init(alloc.allocator()); - defer extr_files.deinit(); - var args = std.process.argsWithAllocator(alloc.allocator()) catch { - _ = try stdout.writeAll("Unable allocate memory"); - return; - }; - defer args.deinit(); - _ = args.next(); - while (args.next()) |arg| { - if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { - try help(); - return; - } else if (std.mem.eql(u8, arg, "-v")) { - verbose = true; - } else if (std.mem.eql(u8, arg, "--unbreak-symlinks") or std.mem.eql(u8, arg, "-us")) { - unbreak = true; - } else if (std.mem.eql(u8, arg, "--deref-symlinks") or std.mem.eql(u8, arg, "-ds")) { - deref = true; - } else if (std.mem.eql(u8, arg, "-l")) { - list = .List; - } else if (std.mem.eql(u8, arg, "-ll")) { - list = .ListAttr; - } else if (std.mem.eql(u8, arg, "-lln")) { - list = .ListNumeric; - } else if (std.mem.eql(u8, arg, "-e")) { - const next = args.next(); - if (next == null) { - _ = try stdout.writeAll("path required after -e\n"); - return; - } - try extr_files.append(next.?); - } else if (std.mem.eql(u8, arg, "-o")) { - const next = args.next(); - if (next == null) { - _ = try stdout.writeAll("offset required after -o\n"); - return; - } - offset = try std.fmt.parseInt(u64, next.?, 10); - } else if (std.mem.eql(u8, arg, "-p")) { - const next = args.next(); - if (next == null) { - _ = try stdout.writeAll("number required after -p\n"); - return; - } - processors = try std.fmt.parseInt(u16, next.?, 10); - } else if (std.mem.eql(u8, arg, "--version")) { - try config.version.format("", .{}, stdout.writer()); - _ = try stdout.write("\n"); - return; - } else if (filename.len == 0) { - filename = arg; - } else if (extr_location.len == 0) { - extr_location = arg; - } else { - _ = try stdout.writeAll("invalid or too many arguments\n"); - return; - } - } - if (filename.len == 0) { - _ = try stdout.writeAll("no archive given\n"); - return; - } - if (list == .None and extr_location.len == 0) { - _ = try stdout.writeAll("no extract location given\n"); - return; - } - var rdr = Reader.init( - alloc.allocator(), - filename, - offset, - ) catch |err| { - try std.fmt.format(stdout.writer(), "Error opening {s} as squashfs: {any}\n", .{ filename, err }); - return; - }; - defer rdr.deinit(); - switch (list) { - .None => { - var conf = ExtractConfig.init() catch |err| { - try std.fmt.format(stdout.writer(), "Error getting system info: {any}\n", .{err}); - return; - }; - conf.deref_sym = deref; - conf.unbreak_sym = unbreak; - conf.verbose = verbose; - if (extr_files.items.len == 0) { - rdr.root.extract(&rdr, conf, extr_location) catch |err| { - try std.fmt.format(stdout.writer(), "Error extracting archive: {any}\n", .{err}); - return; - }; - } else { - for (extr_files.items) |path| { - var fil = rdr.root.open(&rdr, path) catch |err| { - try std.fmt.format(stdout.writer(), "Error extracting {s}: {any}\n", .{ path, err }); - return; - }; - defer fil.deinit(alloc.allocator()); - fil.extract(&rdr, conf, extr_location) catch |err| { - try std.fmt.format(stdout.writer(), "Error extracting {s}: {any}\n", .{ path, err }); - return; - }; - } - } - }, - else => { - if (extr_files.items.len == 0) { - try printFile(alloc.allocator(), &rdr, &rdr.root); - } else { - for (extr_files.items) |path| { - var fil = rdr.root.open(&rdr, path) catch |err| { - try std.fmt.format(stdout.writer(), "Error finding {s}: {any}\n", .{ path, err }); - return; - }; - defer fil.deinit(alloc.allocator()); - try printFile(alloc.allocator(), &rdr, &fil); - } - } - }, - } -} - -fn printFile(alloc: std.mem.Allocator, rdr: *Reader, f: *File) anyerror!void { - const pth = try f.file_path(alloc); - defer alloc.free(pth); - if (list == .List) { - try std.fmt.format(stdout.writer(), "{s}\n", .{pth}); - if (f.isDir()) { - try printDir(alloc, rdr, f); - } - return; - } - try std.fmt.format(stdout.writer(), "{s} {d}/{d} {d} {s}\n", .{ "tmp-perm", try f.uid(rdr), try f.gid(rdr), f.size(), pth }); - if (f.isDir()) { - try printDir(alloc, rdr, f); - } -} - -fn printDir(alloc: std.mem.Allocator, rdr: *Reader, f: *File) anyerror!void { - var iter = try f.iterator(rdr); - defer iter.deinit(); - while (iter.next()) |fil| { - try printFile(alloc, rdr, fil); - } -}