From 01d87f6948f961846e4d3b7f685cb90838ef1bf3 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sun, 31 May 2026 05:57:10 -0500 Subject: [PATCH] Xattr table lookup Finished Symlink & mknod inodes --- src/extract.zig | 134 +++++++++++++++++++++++++++++++++++++++++++----- src/lookup.zig | 7 --- src/xattr.zig | 121 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 19 deletions(-) create mode 100644 src/xattr.zig diff --git a/src/extract.zig b/src/extract.zig index 2ce7975..2745ab0 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -37,14 +37,11 @@ pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCac var frag_table: Lookup.Table(Lookup.FragmentEntry) = .init(alloc, cache, super.frag_start, super.frag_count); defer frag_table.deinit(); - var ret_loop = io.async(returnLoop, .{ alloc, &sel, options }); + var ret_loop = io.async(returnLoop, .{ alloc, io, &sel, options }); try extractReal(alloc, io, cache, super, &sel, &frag_table, path, inode, null, false); - ret_loop.await(io) catch |err| { - // TODO: Drain sel - return err; - }; + try ret_loop.await(io); } fn extractReal( @@ -59,7 +56,14 @@ fn extractReal( parent: ?*Atomic(usize), origin: bool, ) Error!void { - try io.checkCancel(); + io.checkCancel() catch |err| { + if (parent != null) _ = parent.?.fetchSub(1, .acquire); + if (!origin) { + alloc.free(path); + inode.deinit(alloc); + } + return err; + }; switch (inode.data) { .dir, .ext_dir => sel.async( @@ -72,7 +76,16 @@ fn extractReal( extractFile, .{ alloc, io, cache, super.block_size, frag_table, path, inode, parent, origin }, ), - else => return error.Canceled, + .symlink, .ext_symlink => sel.async( + .void_ret, + extractSymlink, + .{ alloc, io, path, inode, parent, origin }, + ), + else => sel.async( + .file_ret, + extractNod, + .{ alloc, path, inode, parent, origin }, + ), } } @@ -209,10 +222,96 @@ fn extractFile( return ret; } +fn extractSymlink(alloc: std.mem.Allocator, io: Io, path: []const u8, inode: Inode, parent: ?*Atomic(usize), origin: bool) Error!void { + defer { + if (parent != null) + _ = parent.?.fetchSub(1, .acquire); + if (!origin) { + inode.deinit(alloc); + alloc.free(path); + } + } + + const target = switch (inode.data) { + .symlink => |s| s.target, + .ext_symlink => |s| s.target, + else => unreachable, + }; + + try Io.Dir.cwd().symLink(io, target, path, .{}); +} +fn extractNod(alloc: std.mem.Allocator, path: []const u8, inode: Inode, parent: ?*Atomic(usize), origin: bool) Error!FileReturn { + defer { + if (parent != null) + _ = parent.?.fetchSub(1, .acquire); + if (!origin) inode.deinit(alloc); + } + errdefer if (!origin) alloc.free(path); + + var ret: FileReturn = .{ + .path = path, + .origin = origin, + + .uid_idx = inode.hdr.uid_idx, + .gid_idx = inode.hdr.gid_idx, + .permissions = inode.hdr.permission, + .mod_time = inode.hdr.mod_time, + }; + + const DT = std.os.linux.DT; + + var dev: u32 = 0; + var mode: u32 = undefined; + + switch (inode.data) { + .char_dev => |d| { + dev = d.device; + mode = DT.CHR; + }, + .ext_char_dev => |d| { + dev = d.device; + mode = DT.CHR; + if (d.xattr_idx != 0xFFFFFFFF) + ret.xattr_idx = d.xattr_idx; + }, + .block_dev => |d| { + dev = d.device; + mode = DT.BLK; + }, + .ext_block_dev => |d| { + dev = d.device; + mode = DT.BLK; + if (d.xattr_idx != 0xFFFFFFFF) + ret.xattr_idx = d.xattr_idx; + }, + .fifo => mode = DT.FIFO, + .ext_fifo => |f| { + mode = DT.FIFO; + if (f.xattr_idx != 0xFFFFFFFF) + ret.xattr_idx = f.xattr_idx; + }, + .socket => mode = DT.SOCK, + .ext_socket => |s| { + mode = DT.SOCK; + if (s.xattr_idx != 0xFFFFFFFF) + ret.xattr_idx = s.xattr_idx; + }, + else => unreachable, + } + + const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &.{path}, 0); + defer alloc.free(sentinel_path); + + const res = std.os.linux.mknod(sentinel_path, mode, dev); + if (res != 0) + return Error.MknodError; + + return ret; +} // Loop -fn returnLoop(alloc: std.mem.Allocator, sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void { +fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: Lookup.Table(u16), xattr_table: Lookup.Table(Lookup.XattrEntry), sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void { while (true) { const finished = try sel.await(); @@ -220,7 +319,7 @@ fn returnLoop(alloc: std.mem.Allocator, sel: *Io.Select(ReturnUnion), options: E .dir_ret => |d| { const ret = try d; if (ret.sub_files.load(.unordered) != 0) { - sel.queue.putOne(sel.io, .{ .dir_ret = ret }) catch |err| { + sel.queue.putOne(io, .{ .dir_ret = ret }) catch |err| { if (!ret.origin) alloc.free(ret.path); return err; }; @@ -229,8 +328,18 @@ fn returnLoop(alloc: std.mem.Allocator, sel: *Io.Select(ReturnUnion), options: E if (!ret.origin) alloc.free(ret.path); alloc.destroy(ret.sub_files); - if (!options.ignore_permissions and !options.ignore_xattr) { - // TODO: set permissions & xattr. + if (!options.ignore_permissions and (!options.ignore_xattr and ret.xattr_idx != null)) { + const file = try Io.Dir.cwd().openFile(io, ret.path, .{}); + defer file.close(io); + + if (!options.ignore_permissions) { + try file.setTimestamps(io, .{ + .modify_timestamp = .init(.{ .nanoseconds = @as(i96, @intCast(ret.mod_time)) * std.time.ns_per_s }), + }); + try file.setPermissions(io, @enumFromInt(ret.permissions)); + try file.setOwner(io, try id_table.get(io, ret.uid_idx), try id_table.get(io, ret.gid_idx)); + } + if (!options.ignore_xattr and ret.xattr_idx != null) {} } }, .file_ret => |f| { @@ -256,7 +365,8 @@ const ReturnUnion = union(enum) { void_ret: Error!void, }; -const Error = error{Canceled} || Directory.Error || Io.Dir.CreateFileAtomicError || Io.File.Atomic.LinkError || DataExtractor.Error; +const Error = error{ Canceled, MknodError } || Directory.Error || Io.Dir.CreateFileAtomicError || Io.File.Atomic.LinkError || + DataExtractor.Error || Io.Dir.SymLinkError; const FileReturn = struct { path: []const u8, diff --git a/src/lookup.zig b/src/lookup.zig index 1117fa2..8b9c410 100644 --- a/src/lookup.zig +++ b/src/lookup.zig @@ -2,7 +2,6 @@ const std = @import("std"); const Io = std.Io; const DataBlock = @import("inode.zig").DataBlock; -const InodeRef = @import("inode.zig").Ref; const DecompCache = @import("decomp_cache.zig"); const MetadataReader = @import("meta_rdr.zig"); @@ -106,9 +105,3 @@ pub const FragmentEntry = extern struct { size: DataBlock, _: u32, }; - -pub const XattrEntry = extern struct { - ref: InodeRef, - count: u32, - size: u32, -}; diff --git a/src/xattr.zig b/src/xattr.zig new file mode 100644 index 0000000..d95c248 --- /dev/null +++ b/src/xattr.zig @@ -0,0 +1,121 @@ +const std = @import("std"); +const Io = std.Io; + +const DecompCache = @import("decomp_cache.zig"); +const InodeRef = @import("inode.zig").Ref; +const MetadataReader = @import("meta_rdr.zig"); +const XattrEntryTable = @import("lookup.zig").Table(XattrEntry); + +const XattrTable = @This(); + +table_start: u64, + +table: XattrEntryTable, + +pub fn init(alloc: std.mem.Allocator, cache: *DecompCache, xattr_start: u64) !XattrTable { + const table_start = std.mem.readInt(u64, cache.map.memory[xattr_start..][0..8], .little); + const num = std.mem.readInt(u32, cache.map.memory[xattr_start + 8 ..][0..4], .little); + return .{ + .table_start = table_start, + + .table = .init(alloc, cache, xattr_start + 16, num), + }; +} + +pub fn get(self: XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !XattrKVs { + const entry = try self.table.get(io, idx); + + var meta: MetadataReader = .init(io, self.table.cache, self.table_start + entry.ref.block_start); + defer meta.deinit(io); + try meta.interface.discardAll(entry.ref.block_offset); + + const xattrs = try alloc.alloc(Xattr, entry.count); + errdefer alloc.free(xattrs); + + for (xattrs) |*x| { + var key: KeyEntry = undefined; + try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key), .little); + + key.name_size += switch (key.type.prefix) { + .user => 5, + .trusted => 8, + .security => 9, + }; + x.key = try alloc.allocSentinel(u8, key.name_size, 0); + errdefer alloc.free(x.key); + + switch (key.type.prefix) { + .user => @memcpy(x.key[0..5], "user."), + .trusted => @memcpy(x.key[0..8], "trusted."), + .security => @memcpy(x.key[0..9], "security."), + } + + if (!key.type.out_of_line) { + var size: u32 = undefined; + try meta.interface.readSliceEndian(u32, @ptrCast(&size), .little); + + x.value = try alloc.alloc(u8, size); + errdefer alloc.free(x.value); + + try meta.interface.readSliceEndian(u8, x.value, .little); + continue; + } + try meta.interface.discardAll(4); + + var val_ref: InodeRef = undefined; + try meta.interface.readSliceEndian(InodeRef, @ptrCast(&val_ref), .little); + + var val_meta: MetadataReader = .init(io, self.table.cache, self.table_start + val_ref.block_start); + defer val_meta.deinit(io); + try val_meta.interface.discardAll(val_ref.block_offset); + + var size: u32 = undefined; + try val_meta.interface.readSliceEndian(u32, @ptrCast(&size), .little); + + x.value = try alloc.alloc(u8, size); + errdefer alloc.free(x.value); + + try val_meta.interface.readSliceEndian(u8, x.value, .little); + } + + return .{ .xattrs = xattrs }; +} + +// Types + +pub const XattrKVs = struct { + xattrs: []Xattr, + + pub fn deinit(self: XattrKVs, alloc: std.mem.Allocator) void { + for (self.xattrs) |kv| + kv.deinit(alloc); + alloc.free(self.xattrs); + } +}; +pub const Xattr = struct { + key: [:0]u8, + value: []u8, + + pub fn deinit(self: Xattr, alloc: std.mem.Allocator) void { + alloc.free(self.key); + alloc.free(self.value); + } +}; +const KeyEntry = extern struct { + type: packed struct(u16) { + prefix: enum(u8) { + user, + trusted, + security, + }, + out_of_line: bool, + _: u7, + }, + name_size: u16, +}; + +const XattrEntry = extern struct { + ref: InodeRef, + count: u32, + size: u32, +};