From 68d1a9d26dec0e63e001d4d2ce1af183096ddd97 Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Tue, 8 Sep 2026 21:37:30 -0500 Subject: [PATCH] Renamed Table.zig to Lookup.Zig Added Lookup.Value (reason for above) Added FragCache Small tweaks & fixes (I forgot about) --- src/archive.zig | 18 +++-- src/decomp.zig | 4 +- src/dir.zig | 6 +- src/file.zig | 34 ++++------ src/frag_cache.zig | 72 ++++++++++++++++++++ src/inode.zig | 4 +- src/{table.zig => lookup.zig} | 84 +++++++++++++++--------- src/options.zig | 3 + src/tests.zig | 21 +++++- src/utils/data_reader.zig | 2 +- src/utils/decompress.zig | 119 +++++++++++++++++++++++++--------- 11 files changed, 265 insertions(+), 102 deletions(-) create mode 100644 src/frag_cache.zig rename src/{table.zig => lookup.zig} (63%) diff --git a/src/archive.zig b/src/archive.zig index 1087897..89e9c8e 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -7,6 +7,7 @@ const File = @import("file.zig"); const Inode = @import("inode.zig"); const Decomp = @import("decomp.zig"); const Options = @import("options.zig"); +const Decompress = @import("utils/decompress.zig"); const Archive = @This(); @@ -60,12 +61,17 @@ pub fn open(self: *Archive, alloc: std.mem.Allocator, filepath: []const u8) !Fil return root.open(alloc, filepath); } pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: Options) !void { - _ = self; - _ = alloc; - _ = io; - _ = ext_loc; - _ = options; - return error.TODO; + const root_inode: Inode = try .readLocation( + alloc, + self.map.memory, + self.root_inode_ref.start, + self.root_inode_ref.offset, + self.super, + ); + + // TODO: use single & multi respectively. + + return Decompress.single(alloc, io, self.map.memory, self.super, root_inode, ext_loc, options); } // Superblock diff --git a/src/decomp.zig b/src/decomp.zig index fe5d85a..79f3506 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -140,7 +140,9 @@ fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize { } fn cZstd(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize { const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len); - if (c.ZSTD_isError(res) == 1) + if (c.ZSTD_isError(res) == 1) { + std.debug.print("{s}\n", .{c.ZSTD_getErrorName(res)}); return Error.ReadFailed; + } return res; } diff --git a/src/dir.zig b/src/dir.zig index ed085f7..b713f5a 100644 --- a/src/dir.zig +++ b/src/dir.zig @@ -21,7 +21,7 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, size: u32) !Directory { try out.ensureUnusedCapacity(alloc, hdr.count + 1); - for (hdr.count + 1) |_| { + for (0..hdr.count + 1) |_| { const raw: RawEntry = try utils.readValueRdr(RawEntry, rdr); const name = try alloc.alloc(u8, raw.name_size + 1); @@ -36,11 +36,11 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, size: u32) !Directory { .name = name, }; - red += @sizeOf(RawEntry + raw.name_size + 1); + red += @sizeOf(RawEntry) + raw.name_size + 1; } } - return out.toOwnedSlice(alloc); + return .{ .entries = try out.toOwnedSlice(alloc) }; } pub fn deinit(self: Directory, alloc: std.mem.Allocator) void { for (self.entries) |entry| diff --git a/src/file.zig b/src/file.zig index 03d24a7..682cdf0 100644 --- a/src/file.zig +++ b/src/file.zig @@ -73,27 +73,13 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File { if (path.len == 0 or (path.len == 1 and path[0] == '.')) return self.copy(alloc); - const first_element: []u8 = std.mem.sliceTo(path, '/'); + const first_element: []const u8 = std.mem.sliceTo(path, '/'); const final = first_element.len == path.len; - var dir_size: u32 = undefined; - var dir_rdr: MetadataReader = undefined; + var dir_rdr: MetadataReader = .init(alloc, self.data[self.inode.data.dir.start + self.super.dir_table_start ..], self.super.decomp_fn); + try dir_rdr.interface.discardAll(self.inode.data.dir.offset); - switch (self.inode.data) { - .dir => |d| { - dir_rdr = .init(alloc, self.data[d.block..], self.super.decomp_fn); - try dir_rdr.interface.discardAll(d.offset); - - dir_size = d.size; - }, - .ext_dir => |d| { - dir_rdr = .init(alloc, self.data[d.block..], self.super.decomp_fn); - try dir_rdr.interface.discardAll(d.offset); - - dir_size = d.size; - }, - else => unreachable, - } + const dir_size = self.inode.data.dir.size; const entry: Directory.Entry = blk: { var dir: Directory = try .read(alloc, &dir_rdr.interface, dir_size); @@ -101,17 +87,18 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File { var entries = dir.entries; - while (entries.len > 0) { + while (entries.len > 1) { const idx = entries.len / 2; const val = entries[idx]; switch (std.mem.order(u8, first_element, val.name)) { .eq => { if (final) { - const inode: Inode = try .fromRef( + const inode: Inode = try .readLocation( alloc, self.data, - .{ .start = val.start, .offset = val.offset }, + val.start, + val.offset, self.super, ); errdefer inode.deinit(alloc); @@ -144,10 +131,11 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File { .data = self.data, .super = self.super, - .inode = try .fromRef( + .inode = try .readLocation( alloc, self.data, - .{ .start = entry.start, .offset = entry.offset }, + entry.start, + entry.offset, self.super, ), .name = "", diff --git a/src/frag_cache.zig b/src/frag_cache.zig new file mode 100644 index 0000000..b42ae8d --- /dev/null +++ b/src/frag_cache.zig @@ -0,0 +1,72 @@ +const std = @import("std"); +const Io = std.Io; + +const Lookup = @import("lookup.zig"); +const Decomp = @import("decomp.zig"); +const BlockSize = @import("inode.zig").BlockSize; + +const FragCache = @This(); + +block_size: u32, + +entries: Lookup.Table(Entry), + +cache: std.AutoHashMapUnmanaged(u32, CacheData) = .empty, + +mut: Io.Mutex = .init, + +pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, start: u64, count: u32) !FragCache { + return .{ + .block_size = block_size, + + .entries = try .init(data, decomp, start, count), + }; +} +pub fn deinit(self: *FragCache, alloc: std.mem.Allocator) void { + self.entries.deinit(alloc); + var iter = self.cache.valueIterator(); + while (iter.next()) |cache| { + if (cache.allocd) + alloc.free(cache.data); + } + self.cache.deinit(alloc); +} + +pub fn get(self: *FragCache, alloc: std.mem.Allocator, io: Io, idx: u32) ![]u8 { + const cache = self.cache.get(idx); + if (cache != null) return cache.?.data; + + const entry = try self.entries.get(alloc, io, idx); + + const data = self.entries.data[entry.start..][0..entry.size.size]; + + if (entry.size.uncompressed) + return data; + + try self.mut.lock(io); + defer self.mut.unlock(io); + + const cache_data = try alloc.alloc(u8, self.block_size); + errdefer alloc.free(cache_data); + + _ = try self.entries.decomp(alloc, data, cache_data); + + try self.cache.put(alloc, idx, .{ + .data = cache_data, + .allocd = true, + }); + + return cache_data; +} + +// Types + +pub const Entry = extern struct { + start: u64, + size: BlockSize, + _: u32, +}; +const CacheData = struct { + data: []u8, + allocd: bool, +}; diff --git a/src/inode.zig b/src/inode.zig index 4ce9a57..c70e3c3 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -40,9 +40,7 @@ pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode { switch (new_inode.data) { .file => |*f| f.blocks = try alloc.dupe(BlockSize, f.blocks), - .ext_file => |*f| f.blocks = try alloc.dupe(BlockSize, f.blocks), .symlink => |*f| f.target = try alloc.dupe(u8, f.target), - .ext_symlink => |*f| f.target = try alloc.dupe(u8, f.target), else => {}, } @@ -71,7 +69,7 @@ pub const BlockSize = packed struct(u32) { }; pub const Type = enum(u16) { - dir, + dir = 1, file, symlink, block_dev, diff --git a/src/table.zig b/src/lookup.zig similarity index 63% rename from src/table.zig rename to src/lookup.zig index 0822971..c319518 100644 --- a/src/table.zig +++ b/src/lookup.zig @@ -1,13 +1,26 @@ const std = @import("std"); const Io = std.Io; -const util = @import("utils/util.zig"); - -const MetadataReader = @import("utils/meta.zig"); const Decomp = @import("decomp.zig"); const Reference = @import("inode.zig").Reference; +const MetadataReader = @import("utils/meta.zig"); +const util = @import("utils/util.zig"); -pub fn Lookup(comptime T: type) type { +pub fn Value(comptime T: type, alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64, idx: u32) !T { + const ITEMS_PER_BLOCK = 8192 / @sizeOf(T); + + const block_idx = idx / ITEMS_PER_BLOCK; + const value_idx = idx % ITEMS_PER_BLOCK; + + const start = util.readValue(u64, data[table_start + (block_idx * 8) ..][0..8]); + + var meta: MetadataReader = .init(alloc, data[start..], decomp); + try meta.interface.discardAll(value_idx * @sizeOf(T)); + + return util.readValueRdr(T, &meta.interface); +} + +pub fn Table(comptime T: type) type { return struct { const Self = @This(); @@ -23,10 +36,8 @@ pub fn Lookup(comptime T: type) type { table: std.hash_map.AutoHashMapUnmanaged(u32, []T) = .empty, mut: Io.Mutex = .init, - pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, start: u64, count: u32) !Self { + pub fn init(data: []u8, decomp: Decomp.Fn, start: u64, count: u32) !Self { return .{ - .alloc = alloc, - .data = data, .decomp = decomp, @@ -38,21 +49,26 @@ pub fn Lookup(comptime T: type) type { pub fn deinit(self: *Self, alloc: std.mem.Allocator) void { var iter = self.table.valueIterator(); while (iter.next()) |block| - alloc.free(block); + alloc.free(block.*); self.table.deinit(alloc); } - fn getBlock(self: *Self, alloc: std.mem.Allocator, io: Io, idx: u32) ![]T { - const init_get = self.table.get(idx); - if (init_get != null) return init_get.?; + pub fn get(self: *Self, alloc: std.mem.Allocator, io: Io, idx: u32) !T { + if (idx >= self.count) return error.InvalidIndex; - self.mut.lock(io); + const block_idx = idx / ITEMS_PER_BLOCK; + const value_idx = idx % ITEMS_PER_BLOCK; + + const init_get = self.table.get(block_idx); + if (init_get != null) return init_get.?[value_idx]; + + try self.mut.lock(io); defer self.mut.unlock(io); - const start = util.readValue(u64, self.data[self.start + (idx * 8) ..][0..8]); + const start = util.readValue(u64, self.data[self.start + (block_idx * 8) ..][0..8]); - const len = if (idx == self.block_count - 1) self.count % ITEMS_PER_BLOCK else ITEMS_PER_BLOCK; + const len = if (block_idx == self.block_count - 1) self.count % ITEMS_PER_BLOCK else ITEMS_PER_BLOCK; const new_block = try alloc.alloc(T, len); errdefer alloc.free(new_block); @@ -61,41 +77,31 @@ pub fn Lookup(comptime T: type) type { try meta.interface.readSliceEndian(T, new_block, .little); - try self.table.put(alloc, idx, new_block); + try self.table.put(alloc, block_idx, new_block); - return new_block; - } - - pub fn get(self: *Self, alloc: std.mem.Allocator, io: Io, idx: u32) !T { - if (idx >= self.count) return error.InvalidIndex; - - const block_idx = idx / ITEMS_PER_BLOCK; - - const block = self.getBlock(io, alloc, block_idx); - - return block[idx % ITEMS_PER_BLOCK]; + return new_block[value_idx]; } }; } pub const Xattr = struct { kv_start: u64, - lookup: Lookup(XattrLookup), + lookup: Table(XattrLookup), - pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, start: u64) !Xattr { + pub fn init(data: []u8, decomp: Decomp.Fn, start: u64) !Xattr { const kv_start = util.readValue(u64, data[start..][0..8]); const count = util.readValue(u32, data[start..][8..12]); return .{ .kv_start = kv_start, - .lookup = try .init(alloc, data, decomp, start + 16, count), + .lookup = try .init(data, decomp, start + 16, count), }; } pub fn deinit(self: *Xattr, alloc: std.mem.Allocator) void { self.lookup.deinit(alloc); } - pub fn get(self: *Xattr, alloc: std.mem.Allocator, io: Io, idx: u32) !KV { + pub fn get(self: *Xattr, alloc: std.mem.Allocator, io: Io, idx: u32) ![]KV { const lookup = try self.lookup.get(alloc, io, idx); var meta: MetadataReader = .init(alloc, self.lookup.data[self.kv_start + lookup.ref.start ..], self.lookup.decomp); @@ -120,8 +126,24 @@ pub const Xattr = struct { @memcpy(kv.name[0..prefix.len], prefix); - if (entry.type.out_of_line) {} + var value_meta = if (entry.type.out_of_line) blk: { + const ool_ref = try util.readValueRdr(Reference, &meta.interface); + + var ool_meta: MetadataReader = .init(alloc, self.lookup.data[self.kv_start + ool_ref.start ..], self.lookup.decomp); + try ool_meta.interface.discardAll(ool_ref.offset); + + break :blk ool_meta; + } else meta; + + const value_len = try util.readValueRdr(u32, &value_meta.interface); + + kv.value = try alloc.alloc(u8, value_len); + errdefer alloc.free(kv.value); + + try value_meta.interface.readSliceEndian(u8, kv.value, .little); } + + return kvs; } //types diff --git a/src/options.zig b/src/options.zig index a70d5a2..83268a9 100644 --- a/src/options.zig +++ b/src/options.zig @@ -11,7 +11,10 @@ ignore_xattr: bool = false, dereference_symlink: bool = false, +single_threaded: bool = false, + pub const default: Options = .{}; +pub const single_threaded_default: Options = .{ .single_threaded = true }; pub fn verboseDefault(wrt: *Writer) Options { return .{ .verbose = true, diff --git a/src/tests.zig b/src/tests.zig index a4b765e..6fcc6ba 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -8,6 +8,8 @@ const Archive = @import("archive.zig"); const TEST_ARCHIVE = "testing/LinuxPATest.sfs"; +const OPEN_TEST_LOCATION = "PortableApps/gVimPortable/GVim-v8.2.2965.glibc2.15-x86_64.AppImage"; + test "Open" { var archive_file = try Io.Dir.cwd().openFile(io, TEST_ARCHIVE, .{}); defer archive_file.close(io); @@ -15,8 +17,23 @@ test "Open" { var arc: Archive = try .init(io, archive_file, 0); defer arc.deinit(io); - std.debug.print("{}\n", .{arc.super}); - var root = try arc.root(alloc); defer root.deinit(); + + var test_open = try root.open(alloc, OPEN_TEST_LOCATION); + defer test_open.deinit(); +} + +const FULL_EXTRACT_LOCATION = "testing/TestExtractST"; + +test "FullExtractSingleThreaded" { + Io.Dir.cwd().deleteTree(io, FULL_EXTRACT_LOCATION) catch {}; + + var archive_file = try Io.Dir.cwd().openFile(io, TEST_ARCHIVE, .{}); + defer archive_file.close(io); + + var arc: Archive = try .init(io, archive_file, 0); + defer arc.deinit(io); + + try arc.extract(alloc, io, FULL_EXTRACT_LOCATION, .default); } diff --git a/src/utils/data_reader.zig b/src/utils/data_reader.zig index 5b30003..78da9aa 100644 --- a/src/utils/data_reader.zig +++ b/src/utils/data_reader.zig @@ -31,7 +31,7 @@ interface: Io.Reader = .{ }, }, -pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, block_size: u32, size: u64, blocks: []BlockSize) DataReader { +pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, block_size: u32, size: u64, blocks: []BlockSize) !DataReader { return .{ .alloc = alloc, diff --git a/src/utils/decompress.zig b/src/utils/decompress.zig index 0cc2e5b..59305cc 100644 --- a/src/utils/decompress.zig +++ b/src/utils/decompress.zig @@ -1,26 +1,37 @@ const std = @import("std"); const Io = std.Io; -const Options = @import("../options.zig"); -const Inode = @import("../inode.zig"); +const Super = @import("../archive.zig").Super; const Decomp = @import("../decomp.zig"); const Directory = @import("../dir.zig"); -const MetadataReader = @import("meta.zig"); +const FragCache = @import("../frag_cache.zig"); +const Inode = @import("../inode.zig"); +const Lookup = @import("../lookup.zig"); +const Options = @import("../options.zig"); const DataReader = @import("data_reader.zig"); -const Table = @import("../table.zig"); -const Super = @import("../archive.zig").Super; +const MetadataReader = @import("meta.zig"); -pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp.Fn, super: Super, inode: Inode, filepath: []const u8, options: Options) !void { +// TODO: Add verbose logging. + +pub fn single(alloc: std.mem.Allocator, io: Io, data: []u8, super: Super, inode: Inode, filepath: []const u8, options: Options) !void { const path = std.mem.trim(u8, filepath, "/"); - var pool: std.ArrayList(InodeAndPath) = .initBuffer(&.{.{ + var id_table: Lookup.Table(u16) = try .init(data, super.decomp_fn, super.id_table_start, super.id_count); + defer id_table.deinit(alloc); + var xattr_table: Lookup.Xattr = try .init(data, super.decomp_fn, super.xattr_table_start); + defer xattr_table.deinit(alloc); + var frag_cache: FragCache = try .init(data, super.decomp_fn, super.block_size, super.frag_table_start, super.frag_count); + defer frag_cache.deinit(alloc); + + var pool: std.ArrayList(InodeAndPath) = try .initCapacity(alloc, 15); + pool.appendAssumeCapacity(.{ .inode = inode, .path = path, .root = true, - }}); + }); defer { while (pool.pop()) |in| - in.deinit(in); + in.deinit(alloc); pool.deinit(alloc); } @@ -31,25 +42,28 @@ pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp dirs.deinit(alloc); } - // TODO: Use options - _ = options; + var cwd = Io.Dir.cwd(); while (pool.pop()) |in| { switch (in.inode.data) { .dir => |d| { - try Io.Dir.cwd().createDirPath(io, in.path); + try cwd.createDirPath(io, in.path); if (d.size <= 3) { defer if (in.path.len != path.len) in.deinit(alloc); - // TODO: apply permissions & xattrs immediately. + + var fil = try cwd.openFile(io, in.path, .{}); + defer fil.close(io); + + try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, d.xattr_idx, fil, options); continue; } - var meta: MetadataReader = .init(alloc, data[d.start], decomp); + var meta: MetadataReader = .init(alloc, data[d.start..], super.decomp_fn); try meta.interface.discardAll(d.offset); - var dir: Directory = .read(alloc, &meta.interface, d.size); + var dir: Directory = try .read(alloc, &meta.interface, d.size); defer dir.deinit(alloc); for (dir.entries) |entry| { @@ -73,20 +87,21 @@ pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp defer if (in.path.len != path.len) in.deinit(alloc); - var atomic = try Io.Dir.cwd().createFileAtomic(io, in.path, .{}); + var atomic = try cwd.createFileAtomic(io, in.path, .{}); defer atomic.deinit(io); - var rdr: DataReader = .init(alloc, data[f.start..], decomp, super.block_size, f.size, f.blocks); + var rdr: DataReader = try .init(alloc, data[f.start..], super.decomp_fn, super.block_size, f.size, f.blocks); defer rdr.deinit(); if (f.frag_idx != 0xFFFFFFFF) { - //TODO: fragment + const frag = try frag_cache.get(alloc, io, f.frag_idx); + rdr.addFrag(frag); } + var buf: [1024 * 50]u8 = undefined; // TODO: find a resonable buffer size. + var writer = atomic.file.writer(io, &buf); + _ = try rdr.interface.streamRemaining(&writer.interface); - var writer = atomic.file.writer(io, &[0]u8{}); // TODO: find a resonable buffer size. - try rdr.interface.streamRemaining(&writer.interface); - - // TODO: apply permissions & xattrs + try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, f.xattr_idx, atomic.file, options); try atomic.link(io); }, @@ -94,13 +109,13 @@ pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp defer if (in.path.len != path.len) in.deinit(alloc); - try Io.Dir.cwd().symLink(io, s.target, in.path, .{}); + try cwd.symLink(io, s.target, in.path, .{}); }, .dev => |d| { defer if (in.path.len != path.len) in.deinit(alloc); - const mode = switch (in.inode.header.type) { + const mode: u32 = switch (in.inode.header.type) { .char_dev, .ext_char_dev => std.os.linux.DT.CHR, .block_dev, .ext_block_dev => std.os.linux.DT.BLK, else => unreachable, @@ -113,15 +128,16 @@ pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp if (res != 0) return error.MkNodError; - // TODO: apply permissions & xattrs + var fil = try cwd.openFile(io, in.path, .{}); + defer fil.close(io); + + try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, d.xattr_idx, fil, options); }, .fifo => |f| { - _ = f; - defer if (in.path.len != path.len) in.deinit(alloc); - const mode = switch (in.inode.header.type) { + const mode: u32 = switch (in.inode.header.type) { .fifo, .ext_fifo => std.os.linux.DT.FIFO, .socket, .ext_socket => std.os.linux.DT.SOCK, else => unreachable, @@ -134,7 +150,10 @@ pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp if (res != 0) return error.MkNodError; - // TODO: apply permissions & xattrs + var fil = try cwd.openFile(io, in.path, .{}); + defer fil.close(io); + + try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, f.xattr_idx, fil, options); }, } } @@ -142,10 +161,24 @@ pub fn single(alloc: std.mem.Allocator, io: Io, data: []const u8, decomp: Decomp while (dirs.pop()) |dir| { defer if (dir.path.len != path.len) alloc.free(dir.path); - // TODO: apply permissions & xattrs + + var fil = try cwd.openFile(io, dir.path, .{}); + defer fil.close(io); + + try applyPermissions(alloc, io, &id_table, &xattr_table, dir.inode.header, dir.inode.data.dir.xattr_idx, fil, options); } } -pub fn multi() !void {} +pub fn multi(alloc: std.mem.Allocator, io: Io, data: []const u8, super: Super, inode: Inode, filepath: []const u8, options: Options) !void { + _ = alloc; + _ = io; + _ = data; + _ = super; + _ = inode; + _ = filepath; + _ = options; + + return error.TODO; +} // Utils @@ -161,4 +194,26 @@ const InodeAndPath = struct { } }; -inline fn applyPermissions(alloc: std.mem.Allocator, io: Io, id_table: Table.Lookup(u16), xattr_table: Table.Xattr, inode: Inode, path: []const u8, options: Options) !void {} +fn applyPermissions(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xattr_table: *Lookup.Xattr, header: Inode.Header, xattr_idx: ?u32, fil: Io.File, options: Options) !void { + if (!options.ignore_permissions) { + try fil.setPermissions(io, @enumFromInt(header.permission)); + + try fil.setTimestamps(io, .{ .modify_timestamp = .{ .new = .fromNanoseconds(@as(i96, @intCast(header.mod_time)) * std.time.ns_per_ms) } }); + + try fil.setOwner(io, try id_table.get(alloc, io, header.uid_idx), try id_table.get(alloc, io, header.gid_idx)); + } + if (!options.ignore_xattr and xattr_idx != null) { + const xattrs = try xattr_table.get(alloc, io, xattr_idx.?); + defer { + for (xattrs) |kv| + kv.deinit(alloc); + alloc.free(xattrs); + } + + for (xattrs) |kv| { + const res = std.os.linux.fsetxattr(fil.handle, kv.name, kv.value.ptr, kv.value.len, 0); + if (res != 0) + return error.FSetXattrError; + } + } +}