From c9499251f8f1da6113c0d0ba9f20447119e1ec04 Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Thu, 5 Mar 2026 12:20:30 -0600 Subject: [PATCH] Moved lookup tables into separate struct to fix some race conditions Fixed lingering issues due to zero work size InodeFinish Fixed xattrs not applying due to the keys sometimes not being null-terminated. Updated performance numbers --- README.md | 6 +-- build.zig | 12 ++--- src/archive.zig | 32 +++++-------- src/inode.zig | 32 ++++++------- src/{table.zig => tables.zig} | 37 +++++++++++++-- src/util/data.zig | 2 +- src/util/data_threaded.zig | 2 +- src/util/extract.zig | 87 +++++++++++++++++++++++------------ src/util/inode_finish.zig | 11 +++-- src/xattr.zig | 21 +++++---- 10 files changed, 148 insertions(+), 94 deletions(-) rename src/{table.zig => tables.zig} (64%) diff --git a/README.md b/README.md index 7ec974f..53c64bb 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Most features are present except for the following: This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) and with `--release=fast`. -* Under ideal circumstances, my library is ~60% slower (.12s vs .19s). +* Under ideal circumstances, my library is ~70% slower (.12s vs .20s). * Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances. * Performance improvements/regressions will be common. I'm still learning Zig. @@ -45,8 +45,8 @@ Example Times: * *unsquashfs, multi-threaded*: .12s * *unsquashfs, single-threaded*: .13s -* *C-libs, single-threaded*: .56s -* *C-libs, multi-threaded*: .19s +* *C-libs, single-threaded*: .45s +* *C-libs, multi-threaded*: .20s * *Zig-libs, single-threaded*: 5.78s * *Zig-libs, multi-threaded*: 1.08s diff --git a/build.zig b/build.zig index d27f8f9..eec0868 100644 --- a/build.zig +++ b/build.zig @@ -21,12 +21,12 @@ pub fn build(b: *std.Build) !void { }); mod.addOptions("config", zig_squashfs_options); if (use_c_libs_option == true) { - mod.linkSystemLibrary("zlib", .{}); - mod.linkSystemLibrary("lzma", .{}); + mod.linkSystemLibrary("zlib", .{ .preferred_link_mode = .static }); + mod.linkSystemLibrary("lzma", .{ .preferred_link_mode = .static }); if (allow_lzo == true) - mod.linkSystemLibrary("minilzo", .{}); - mod.linkSystemLibrary("lz4", .{}); - mod.linkSystemLibrary("zstd", .{}); + mod.linkSystemLibrary("minilzo", .{ .preferred_link_mode = .static }); + mod.linkSystemLibrary("lz4", .{ .preferred_link_mode = .static }); + mod.linkSystemLibrary("zstd", .{ .preferred_link_mode = .static }); } var version = version_string_option orelse "0.0.0-testing"; @@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void { const exe = b.addExecutable(.{ .name = "unsquashfs", .root_module = exe_mod, - .use_llvm = true, + // .use_llvm = true, This can be needed to properly debug }); const lib = b.addLibrary(.{ diff --git a/src/archive.zig b/src/archive.zig index c19c635..f053e0b 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -12,7 +12,7 @@ const InodeRef = Inode.Ref; const BlockSize = @import("inode_data/file.zig").BlockSize; const SfsFile = @import("file.zig"); const Superblock = @import("super.zig").Superblock; -const Table = @import("table.zig").Table; +const Tables = @import("tables.zig"); const MetadataReader = @import("util/metadata.zig"); const OffsetFile = @import("util/offset_file.zig"); const XattrTable = @import("xattr.zig"); @@ -22,14 +22,6 @@ const config = if (builtin.is_test) .{ .allow_lzo = false, } else @import("config"); -/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry. -/// The offset into the block and fragment size is stored in the file's inode. -pub const FragEntry = packed struct { - start: u64, - size: BlockSize, - _: u32, -}; - const Archive = @This(); alloc: std.mem.Allocator, @@ -39,10 +31,7 @@ decomp: Decomp.DecompFn, super: Superblock, -frag_table: Table(FragEntry), -id_table: Table(u16), -export_table: Table(InodeRef), -xattr_table: XattrTable, +tables: ?Tables = null, /// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage. pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive { @@ -64,19 +53,16 @@ pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive { .fil = off_fil, .decomp = decomp, .super = super, - .frag_table = try .init(alloc, off_fil, decomp, super.frag_start, super.frag_count), - .id_table = try .init(alloc, off_fil, decomp, super.id_start, super.id_count), - .export_table = try .init(alloc, off_fil, decomp, super.export_start, super.inode_count), - .xattr_table = try .init(alloc, off_fil, decomp, super.xattr_start), }; } pub fn deinit(self: *Archive) void { - self.frag_table.deinit(); - self.export_table.deinit(); - self.id_table.deinit(); + if (self.tables != null) + self.tables.?.deinit(); } pub fn inode(self: *Archive, alloc: std.mem.Allocator, num: u32) !Inode { + if (self.tables == null) + self.tables = try .init(alloc, self); const ref = try self.export_table.get(num - 1); var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{}); var meta: MetadataReader = .init(alloc, &rdr.interface, &self.decomp); @@ -85,6 +71,8 @@ pub fn inode(self: *Archive, alloc: std.mem.Allocator, num: u32) !Inode { } pub fn root(self: *Archive, alloc: std.mem.Allocator) !SfsFile { + if (self.tables == null) + self.tables = try .init(alloc, self); var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{}); var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp); try meta.interface.discardAll(self.super.root_ref.block_offset); @@ -93,12 +81,14 @@ pub fn root(self: *Archive, alloc: std.mem.Allocator) !SfsFile { } pub fn open(self: *Archive, alloc: std.mem.Allocator, path: []const u8) !SfsFile { + if (self.tables == null) + self.tables = try .init(alloc, self); var root_fil = try self.root(alloc); defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit(); return root_fil.open(path); } -pub fn extract(self: *Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void { +pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void { var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{}); var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp); try meta.interface.discardAll(self.super.root_ref.block_offset); diff --git a/src/inode.zig b/src/inode.zig index cb24c4b..58687b8 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -12,6 +12,7 @@ const ExtractionOptions = @import("options.zig"); const dir = @import("inode_data/dir.zig"); const file = @import("inode_data/file.zig"); const misc = @import("inode_data/misc.zig"); +const Tables = @import("tables.zig"); const DataReader = @import("util/data.zig"); const ThreadedDataReader = @import("util/data_threaded.zig"); const InodeExtract = @import("util/extract.zig"); @@ -96,7 +97,7 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode { }, }; } -pub fn readFromEntry(alloc: std.mem.Allocator, archive: *Archive, entry: DirEntry) !Inode { +pub fn readFromEntry(alloc: std.mem.Allocator, archive: Archive, entry: DirEntry) !Inode { var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{}); var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp); try meta.interface.discardAll(entry.block_offset); @@ -114,17 +115,17 @@ pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { } /// Get the data reader for a file inode. -pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !DataReader { +pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive, tables: *Tables) !DataReader { return switch (self.hdr.inode_type) { - .file => readerFromData(alloc, archive, self.data.file), - .ext_file => readerFromData(alloc, archive, self.data.ext_file), + .file => readerFromData(alloc, archive, tables, self.data.file), + .ext_file => readerFromData(alloc, archive, tables, self.data.ext_file), else => error.NotRegularFile, }; } -fn readerFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !DataReader { - var out: DataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size); +fn readerFromData(alloc: std.mem.Allocator, archive: Archive, tables: *Tables, data: anytype) !DataReader { + var out: DataReader = .init(alloc, archive, data.block_sizes, data.block_start, data.size); if (data.frag_idx != 0xFFFFFFFF) - out.addFragment(try archive.frag_table.get(data.frag_idx), data.frag_block_offset); + out.addFragment(try tables.frag_table.get(data.frag_idx), data.frag_block_offset); return out; } @@ -157,33 +158,32 @@ pub fn xattrIdx(self: Inode) u32 { /// Applies the Inode's metadata to the given File. /// Mod time is always set, but permissions and xattrs are set based on the given ExtractionOptions. -pub fn setMetadata(self: Inode, alloc: std.mem.Allocator, archive: *Archive, fil: std.fs.File, options: ExtractionOptions) !void { +pub fn setMetadata(self: Inode, alloc: std.mem.Allocator, tables: *Tables, fil: std.fs.File, options: ExtractionOptions) !void { const time = @as(i128, self.hdr.mod_time) * 1000000000; try fil.updateTimes(time, time); if (!options.ignore_permissions) { try fil.chmod(self.hdr.permissions); - try fil.chown(try archive.id_table.get(self.hdr.uid_idx), try archive.id_table.get(self.hdr.gid_idx)); + try fil.chown(try tables.id_table.get(self.hdr.uid_idx), try tables.id_table.get(self.hdr.gid_idx)); } if (!options.ignore_xattr) { const idx = self.xattrIdx(); if (idx == 0xFFFFFFFF) return; - const xattrs = try archive.xattr_table.get(alloc, idx); + const xattrs = try tables.xattr_table.get(alloc, idx); defer alloc.free(xattrs); for (xattrs) |kv| { - const res = std.os.linux.fsetxattr(fil.handle, @ptrCast(kv.key), @ptrCast(kv.value), kv.value.len, 0); - alloc.free(kv.key); - alloc.free(kv.value); + const res = std.os.linux.fsetxattr(fil.handle, kv.key[0.. :0], kv.value.ptr, kv.value.len, 0); if (res != 0) { if (options.verbose) options.verbose_writer.?.print("fsetxattr has result of: {}\n", .{res}) catch {}; - //TODO: Currently this seems a bit flakey, so we just ignore the result... for now. - // return error.SetXattr; + return error.SetXattr; } + alloc.free(kv.key); + alloc.free(kv.value); } } } /// Extract the inode to the given path. -pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void { +pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void { return InodeExtract.extractTo(alloc, self, archive, path, options); } diff --git a/src/table.zig b/src/tables.zig similarity index 64% rename from src/table.zig rename to src/tables.zig index c98e85d..700d3b6 100644 --- a/src/table.zig +++ b/src/tables.zig @@ -1,14 +1,45 @@ const std = @import("std"); const Mutex = std.Thread.Mutex; +const Archive = @import("archive.zig"); const DecompFn = @import("decomp.zig").DecompFn; +const BlockSize = @import("inode_data/file.zig").BlockSize; +const InodeRef = @import("inode.zig").Ref; +const Superblock = @import("super.zig").Superblock; const MetadataReader = @import("util/metadata.zig"); const OffsetFile = @import("util/offset_file.zig"); +const XattrTable = @import("xattr.zig"); -const TableError = error{ - InvalidIndex, +/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry. +/// The offset into the block and fragment size is stored in the file's inode. +pub const FragEntry = packed struct { + start: u64, + size: BlockSize, + _: u32, }; +const Tables = @This(); + +frag_table: Table(FragEntry), +id_table: Table(u16), +export_table: Table(InodeRef), +xattr_table: XattrTable, + +pub fn init(alloc: std.mem.Allocator, archive: Archive) !Tables { + return .{ + .frag_table = try .init(alloc, archive.fil, archive.decomp, archive.super.frag_start, archive.super.frag_count), + .id_table = try .init(alloc, archive.fil, archive.decomp, archive.super.id_start, archive.super.id_count), + .export_table = try .init(alloc, archive.fil, archive.decomp, archive.super.export_start, archive.super.inode_count), + .xattr_table = try .init(alloc, archive.fil, archive.decomp, archive.super.xattr_start), + }; +} +pub fn deinit(self: *Tables) void { + self.frag_table.deinit(); + self.id_table.deinit(); + self.export_table.deinit(); + self.xattr_table.deinit(); +} + /// A two-layer metadata table. pub fn Table(T: anytype) type { return struct { @@ -47,7 +78,7 @@ pub fn Table(T: anytype) type { } pub fn get(self: *Self, idx: u32) !T { - if (idx >= self.values) return TableError.InvalidIndex; + if (idx >= self.values) return error.InvalidIndex; const block_num = idx / VALS_PER_BLOCK; const idx_offset = idx - (block_num * VALS_PER_BLOCK); if (self.tab.contains(block_num)) { diff --git a/src/util/data.zig b/src/util/data.zig index 177e7c1..0c1d43e 100644 --- a/src/util/data.zig +++ b/src/util/data.zig @@ -6,9 +6,9 @@ const Writer = std.Io.Writer; const Limit = std.Io.Limit; const Archive = @import("../archive.zig"); -const FragEntry = Archive.FragEntry; const DecompFn = @import("../decomp.zig").DecompFn; const BlockSize = @import("../inode_data/file.zig").BlockSize; +const FragEntry = @import("../tables.zig").FragEntry; const OffsetFile = @import("offset_file.zig"); const DataReader = @This(); diff --git a/src/util/data_threaded.zig b/src/util/data_threaded.zig index 784ed5d..280586a 100644 --- a/src/util/data_threaded.zig +++ b/src/util/data_threaded.zig @@ -8,9 +8,9 @@ const WaitGroup = std.Thread.WaitGroup; const Pool = std.Thread.Pool; const Archive = @import("../archive.zig"); -const FragEntry = Archive.FragEntry; const DecompFn = @import("../decomp.zig").DecompFn; const BlockSize = @import("../inode_data/file.zig").BlockSize; +const FragEntry = @import("../tables.zig").FragEntry; const InodeFinish = @import("inode_finish.zig"); const OffsetFile = @import("offset_file.zig"); diff --git a/src/util/extract.zig b/src/util/extract.zig index 20ea222..285997c 100644 --- a/src/util/extract.zig +++ b/src/util/extract.zig @@ -7,6 +7,7 @@ const Archive = @import("../archive.zig"); const DirEntry = @import("../dir_entry.zig"); const Inode = @import("../inode.zig"); const ExtractionOptions = @import("../options.zig"); +const Tables = @import("../tables.zig"); const InodeFinish = @import("inode_finish.zig"); const FinishUnion = InodeFinish.FinishUnion; const ThreadedDataReader = @import("data_threaded.zig"); @@ -17,7 +18,7 @@ const STACK_ALLOC_SIZE = 1024 * 1024; pub fn extractTo( allocator: Allocator, inode: Inode, - archive: *Archive, + archive: Archive, path: []const u8, options: ExtractionOptions, ) !void { @@ -27,11 +28,15 @@ pub fn extractTo( var stack_alloc = std.heap.stackFallback(STACK_ALLOC_SIZE, allocator); var arena: std.heap.ArenaAllocator = .init(stack_alloc.get()); defer arena.deinit(); - if (options.threads <= 1) - return extractSingleThread(arena.allocator(), inode, archive, path, options); + if (options.threads <= 1) { + const alloc = arena.allocator(); + var tables: Tables = try .init(alloc, archive); + return extractSingleThread(arena.allocator(), inode, archive, &tables, path, options); + } var thread_alloc = std.heap.ThreadSafeAllocator{ .child_allocator = arena.allocator() }; const alloc = thread_alloc.allocator(); + var tables: Tables = try .init(alloc, archive); var pool_alloc = std.heap.stackFallback(10 * 1024, alloc); var pool: Pool = undefined; @@ -44,6 +49,7 @@ pub fn extractTo( alloc, inode, archive, + &tables, path, options, &pool, @@ -57,7 +63,8 @@ pub fn extractTo( fn extractSingleThread( alloc: Allocator, inode: Inode, - archive: *Archive, + archive: Archive, + tables: *Tables, path: []const u8, options: ExtractionOptions, ) !void { @@ -73,37 +80,38 @@ fn extractSingleThread( // otherwise be more conscientious about freeing memory. // For now, this is good enough. - const entries = try inode.dirEntries(alloc, archive.*); + const entries = try inode.dirEntries(alloc, archive); for (entries) |ent| { const sub_inode: Inode = try .readFromEntry(alloc, archive, ent); const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", ent.name }); - try extractSingleThread(alloc, sub_inode, archive, new_path, options); + try extractSingleThread(alloc, sub_inode, archive, tables, new_path, options); } const fil = try std.fs.cwd().openFile(path, .{}); defer fil.close(); - try inode.setMetadata(alloc, archive, fil, options); + try inode.setMetadata(alloc, tables, fil, options); }, .file, .ext_file => { var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true }); defer fil.close(); var wrt = fil.writer(&[0]u8{}); - var dat_rdr = try inode.dataReader(alloc, archive); + var dat_rdr = try inode.dataReader(alloc, archive, tables); defer dat_rdr.deinit(); _ = try dat_rdr.interface.streamRemaining(&wrt.interface); try wrt.interface.flush(); - try inode.setMetadata(alloc, archive, fil, options); + try inode.setMetadata(alloc, tables, fil, options); }, .symlink, .ext_symlink => return extractSymlink(inode, path), - else => return extractDeviceAndIPC(inode, alloc, archive, path, options), + else => return extractDeviceAndIPC(inode, alloc, tables, path, options), } } fn extractMultiThread( alloc: Allocator, inode: Inode, - archive: *Archive, + archive: Archive, + tables: *Tables, path: []const u8, options: ExtractionOptions, pool: *Pool, @@ -130,17 +138,22 @@ fn extractMultiThread( // otherwise be more conscientious about freeing memory. // For now, this is good enough. - const entries = inode.dirEntries(alloc, archive.*) catch |res_err| { + const entries = inode.dirEntries(alloc, archive) catch |res_err| { err.* = res_err; fin.finish(); return; }; + if (entries.len == 0) { + fin.finish(); + return; + } + var dir_fin = InodeFinish.create( alloc, inode, path, - archive, + tables, options, fin, err, @@ -153,21 +166,24 @@ fn extractMultiThread( }; for (entries) |ent| { - if (ent.inode_type == .dir) + if (ent.inode_type == .dir) { extractEntry( alloc, ent, archive, + tables, path, options, pool, .{ .fin = dir_fin }, err, ); + continue; + } pool.spawn( extractEntry, - .{ alloc, ent, archive, path, options, pool, FinishUnion{ .fin = dir_fin }, err }, + .{ alloc, ent, archive, tables, path, options, pool, FinishUnion{ .fin = dir_fin }, err }, ) catch |res_err| { err.* = res_err; dir_fin.finish(); @@ -184,23 +200,32 @@ fn extractMultiThread( return; }; - var data_rdr = threadedDataReader(inode, alloc, archive) catch |res_err| { + var data_rdr = threadedDataReader(inode, alloc, archive, tables) catch |res_err| { if (options.verbose) options.verbose_writer.?.print("Can't create data reader for inode #{} (extracting to {s}): {}\n", .{ inode.hdr.num, path, res_err }) catch {}; err.* = res_err; fin.finish(); return; }; + if (data_rdr == null) { + inode.setMetadata(alloc, tables, fil, options) catch |res_err| { + if (options.verbose) + options.verbose_writer.?.print("Can't set metadata to {s}: {}\n", .{ path, res_err }) catch {}; + err.* = res_err; + }; + fin.finish(); + return; + } const file_fin = InodeFinish.create( alloc, inode, path, - archive, + tables, options, fin, err, fil, - data_rdr.num_blocks, + data_rdr.?.num_blocks, ) catch |res_err| { if (options.verbose) options.verbose_writer.?.print("Can't create callback for inode #{} (extracting to {s}): {}\n", .{ inode.hdr.num, path, res_err }) catch {}; @@ -209,7 +234,7 @@ fn extractMultiThread( return; }; - data_rdr.extractThreaded(fil, pool, file_fin); + data_rdr.?.extractThreaded(fil, pool, file_fin); }, .symlink, .ext_symlink => { extractSymlink(inode, path) catch |res_err| { @@ -218,7 +243,7 @@ fn extractMultiThread( fin.finish(); }, else => { - extractDeviceAndIPC(inode, alloc, archive, path, options) catch |res_err| { + extractDeviceAndIPC(inode, alloc, tables, path, options) catch |res_err| { err.* = res_err; }; fin.finish(); @@ -229,7 +254,8 @@ fn extractMultiThread( fn extractEntry( alloc: Allocator, ent: DirEntry, - archive: *Archive, + archive: Archive, + tables: *Tables, path: []const u8, options: ExtractionOptions, pool: *Pool, @@ -247,21 +273,22 @@ fn extractEntry( fin.finish(); return; }; - extractMultiThread(alloc, inode, archive, new_path, options, pool, fin, err); + extractMultiThread(alloc, inode, archive, tables, new_path, options, pool, fin, err); } /// Get a threaded data reader for a file inode. -fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !ThreadedDataReader { +fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive, tables: *Tables) !?ThreadedDataReader { return switch (self.hdr.inode_type) { - .file => threadedReaderFromData(alloc, archive, self.data.file), - .ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file), + .file => threadedReaderFromData(alloc, archive, tables, self.data.file), + .ext_file => threadedReaderFromData(alloc, archive, tables, self.data.ext_file), else => error.NotRegularFile, }; } -fn threadedReaderFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !ThreadedDataReader { - var out: ThreadedDataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size); +fn threadedReaderFromData(alloc: std.mem.Allocator, archive: Archive, tables: *Tables, data: anytype) !?ThreadedDataReader { + if (data.block_sizes.len == 0 and data.frag_idx == 0xFFFFFFFF) return null; + var out: ThreadedDataReader = .init(alloc, archive, data.block_sizes, data.block_start, data.size); if (data.frag_idx != 0xFFFFFFFF) - out.addFragment(try archive.frag_table.get(data.frag_idx), data.frag_block_offset); + out.addFragment(try tables.frag_table.get(data.frag_idx), data.frag_block_offset); return out; } @@ -277,7 +304,7 @@ fn extractSymlink(self: Inode, path: []const u8) !void { } /// Creates the device described by the inode. /// Sets metadata. -fn extractDeviceAndIPC(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void { +fn extractDeviceAndIPC(self: Inode, alloc: std.mem.Allocator, tables: *Tables, path: []const u8, options: ExtractionOptions) !void { var mode: u32 = undefined; var dev: u32 = 0; switch (self.data) { @@ -322,5 +349,5 @@ fn extractDeviceAndIPC(self: Inode, alloc: std.mem.Allocator, archive: *Archive, } var fil = try std.fs.cwd().openFile(path, .{}); defer fil.close(); - try self.setMetadata(alloc, archive, fil, options); + try self.setMetadata(alloc, tables, fil, options); } diff --git a/src/util/inode_finish.zig b/src/util/inode_finish.zig index c6e57fc..c16dfd2 100644 --- a/src/util/inode_finish.zig +++ b/src/util/inode_finish.zig @@ -5,6 +5,7 @@ const Mutex = std.Thread.Mutex; const Archive = @import("../archive.zig"); const Inode = @import("../inode.zig"); const ExtractionOptions = @import("../options.zig"); +const Tables = @import("../tables.zig"); const InodeFinish = @This(); @@ -28,7 +29,7 @@ alloc: std.mem.Allocator, inode: Inode, path: []const u8, -archive: *Archive, +tables: *Tables, options: ExtractionOptions, parent_finish: FinishUnion, fil: ?std.fs.File, @@ -41,13 +42,15 @@ pub fn create( alloc: std.mem.Allocator, inode: Inode, path: []const u8, - archive: *Archive, + tables: *Tables, options: ExtractionOptions, parent_finish: FinishUnion, out_err: *?anyerror, fil: ?std.fs.File, work_size: usize, ) !*InodeFinish { + if (work_size == 0) + return error.InvalidWorkSize; const out = try alloc.create(InodeFinish); errdefer alloc.destroy(out); out.* = .{ @@ -55,7 +58,7 @@ pub fn create( .inode = inode, .path = path, - .archive = archive, + .tables = tables, .options = options, .parent_finish = parent_finish, .out_err = out_err, @@ -89,7 +92,7 @@ pub fn finish(self: *InodeFinish) void { return; }; defer self.fil.?.close(); - self.inode.setMetadata(self.alloc, self.archive, self.fil.?, self.options) catch |err| { + self.inode.setMetadata(self.alloc, self.tables, self.fil.?, self.options) catch |err| { if (self.options.verbose) self.options.verbose_writer.?.print("Error setting metadata to {s}: {}\n", .{ self.path, err }) catch {}; self.out_err.* = err; diff --git a/src/xattr.zig b/src/xattr.zig index 1bffd00..e5203b7 100644 --- a/src/xattr.zig +++ b/src/xattr.zig @@ -1,7 +1,7 @@ const std = @import("std"); const DecompFn = @import("decomp.zig").DecompFn; -const Table = @import("table.zig").Table; +const Table = @import("tables.zig").Table; const MetadataReader = @import("util/metadata.zig"); const OffsetFile = @import("util/offset_file.zig"); @@ -30,7 +30,7 @@ const KeyRaw = packed struct { }; pub const KeyValue = struct { - key: []u8, + key: [:0]u8, value: []u8, }; @@ -62,7 +62,7 @@ pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, table_s .table = try .init(alloc, fil, decomp, table_start + 16, info.count), }; } -pub fn deinit(self: XattrTable) void { +pub fn deinit(self: *XattrTable) void { self.table.deinit(); } @@ -80,19 +80,22 @@ pub fn get(self: *XattrTable, alloc: std.mem.Allocator, idx: u32) ![]KeyValue { switch (key_raw.type.prefix) { .user => { - kv.key = try alloc.alloc(u8, key_raw.name_size + 5); + kv.key = @ptrCast(try alloc.alloc(u8, key_raw.name_size + 5 + 1)); @memcpy(kv.key[0..5], "user."); - try meta.interface.readSliceAll(kv.key[5..]); + try meta.interface.readSliceAll(kv.key[5 .. kv.key.len - 1]); + kv.key[kv.key.len - 1] = 0; }, .security => { - kv.key = try alloc.alloc(u8, key_raw.name_size + 9); + kv.key = @ptrCast(try alloc.alloc(u8, key_raw.name_size + 9 + 1)); @memcpy(kv.key[0..9], "security."); - try meta.interface.readSliceAll(kv.key[9..]); + try meta.interface.readSliceAll(kv.key[9 .. kv.key.len - 1]); + kv.key[kv.key.len - 1] = 0; }, .trusted => { - kv.key = try alloc.alloc(u8, key_raw.name_size + 8); + kv.key = @ptrCast(try alloc.alloc(u8, key_raw.name_size + 8 + 1)); @memcpy(kv.key[0..8], "trusted."); - try meta.interface.readSliceAll(kv.key[8..]); + try meta.interface.readSliceAll(kv.key[8 .. kv.key.len - 1]); + kv.key[kv.key.len - 1] = 0; }, } if (key_raw.type.out_of_line) {