diff --git a/src/lookup_table.zig b/src/lookup_table.zig index 3147ced..29db239 100644 --- a/src/lookup_table.zig +++ b/src/lookup_table.zig @@ -2,8 +2,8 @@ const std = @import("std"); const Io = std.Io; const Decompressor = @import("util/decompressor.zig"); -const OffsetFile = @import("util/offset_file.zig"); const MetadataReader = @import("util/metadata.zig"); +const OffsetFile = @import("util/offset_file.zig"); pub fn lookupValue(comptime T: anytype, alloc: std.mem.Allocator, io: Io, decomp: *Decompressor, file: OffsetFile, table_start: u64, idx: u16) !T { const T_PER_BLOCK: u16 = 8192 / @sizeOf(T); @@ -49,6 +49,10 @@ pub fn CachedTable(comptime T: anytype) type { .table = .init(alloc), }; } + pub fn deinit(self: *Table, io: Io) void { + self.mut.lockUncancelable(io); + self.table.deinit(); + } pub fn get(self: *Table, io: Io, idx: u32) !T { const block = idx / T_PER_BLOCK; diff --git a/src/util/metadata.zig b/src/util/metadata.zig index 485c65c..c354066 100644 --- a/src/util/metadata.zig +++ b/src/util/metadata.zig @@ -17,26 +17,28 @@ alloc: std.mem.Allocator, rdr: *Reader, decomp: *const Decompressor, +cur_block_start: u32 = 0, +next_start_start: u32 = 0, buf: [8192]u8 = undefined, -interface: Reader, err: ?anyerror = null, +interface: Reader = .{ + .buffer = &[0]u8{}, + .end = 0, + .seek = 0, + .vtable = &.{ + .stream = stream, + .discard = discard, + .readVec = readVec, + // TODO: Potentially add rebase so that we can guarentee that self.block_start & interface.seek is correct. + }, +}, -pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *const Decompressor) This { +pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *const Decompressor) This {] return .{ .alloc = alloc, .rdr = rdr, .decomp = decomp, - .interface = .{ - .buffer = &[0]u8{}, - .end = 0, - .seek = 0, - .vtable = &.{ - .stream = stream, - .discard = discard, - .readVec = readVec, - }, - }, }; } @@ -44,6 +46,8 @@ fn advance(self: *This) !void { self.interface.seek = 0; var hdr: BlockHeader = undefined; try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little); + self.cur_block_start = self.next_start_start; + self.next_start_start += hdr.size; if (hdr.uncompressed) { try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little); self.interface.end = hdr.size; diff --git a/src/xattr_table.zig b/src/xattr_table.zig index 9333789..0164726 100644 --- a/src/xattr_table.zig +++ b/src/xattr_table.zig @@ -1,18 +1,282 @@ const std = @import("std"); +const Io = std.Io; +const InodeRef = @import("inode.zig").Ref; const LookupTable = @import("lookup_table.zig"); const Decompressor = @import("util/decompressor.zig"); +const MetadataReader = @import("util/metadata.zig"); const OffsetFile = @import("util/offset_file.zig"); -const XattrTable = @This(); +const XattrCachedTable = @This(); + +alloc: std.mem.Allocator, + +fil: OffsetFile, +decomp: *const Decompressor, + +kv_start: u64, + +table: LookupTable.CachedTable(TableValue), +value_cache: std.AutoHashMap(InodeRef, []const u8), +value_mut: Io.Mutex, + +pub fn init(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, xattr_start: u64) !XattrCachedTable { + var rdr = try fil.readerAt(io, xattr_start, &[0]u8{}); + + var start: u64 = undefined; + try rdr.interface.readSliceEndian(u64, @ptrCast(&start), .little); + var num: u32 = undefined; + try rdr.interface.readSliceEndian(u32, @ptrCast(&num), .little); + + return .{ + .alloc = alloc, + + .fil = fil, + .decomp = decomp, + + .kv_start = start, + + .table = .init(alloc, fil, xattr_start + 16, num), + .value_cache = .init(alloc), + }; +} +pub fn deinit(self: *XattrCachedTable) void { + self.table.deinit(); + self.value_cache.deinit(); +} + +pub fn get(self: *XattrCachedTable, alloc: std.mem.Allocator, io: Io, idx: u32) ![]XattrSemiOwned { + const lookup = try self.table.get(io, idx); + + var rdr = try self.fil.readerAt(io, self.kv_start + lookup.ref.block_start, &[0]u8{}); + var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp); + try meta.interface.discardAll(lookup.ref.block_offset); + + const out = try alloc.alloc(XattrSemiOwned, lookup.count); + errdefer alloc.free(out); + + for (0..lookup.count) |i| { + const key_entry: KeyEntry = undefined; + try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little); + + const key = switch (key_entry.type.namespace) { + .user => blk: { + const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 5); + errdefer alloc.free(tmp); + try meta.interface.readSliceEndian(u8, tmp[5 .. tmp.len - 1], .little); + @memset(tmp[0..5], "user."); + break :blk tmp; + }, + .trusted => blk: { + const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 8); + errdefer alloc.free(tmp); + try meta.interface.readSliceEndian(u8, tmp[8 .. tmp.len - 1], .little); + @memset(tmp[0..8], "trusted."); + break :blk tmp; + }, + .security => blk: { + const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 9); + errdefer alloc.free(tmp); + try meta.interface.readSliceEndian(u8, tmp[9 .. tmp.len - 1], .little); + @memset(tmp[0..9], "security."); + break :blk tmp; + }, + }; + key[key.len - 1] = 0; + errdefer alloc.free(key); + + if (key_entry.type.out_of_line) { + const value: ValueOutOfLineEntry = undefined; + try meta.interface.readSliceEndian(ValueOutOfLineEntry, @ptrCast(&value), .little); + + out[i] = .{ + .key = key, + .value = try self.valueAt(io, value.ref), + }; + continue; + } + const val_ref: InodeRef = .{ .block_start = meta.cur_block_start, .block_offset = meta.interface.seek }; + + try self.value_mut.lock(io); + defer self.value_mut.unlock(io); + if (self.value_cache.contains(val_ref)) { + out[i] = .{ + .key = key, + .value = try self.valueAt(io, val_ref), + }; + continue; + } + + var val_size: u32 = undefined; + try meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little); + + const val = try self.alloc.alloc(u8, val_size); + try meta.interface.readSliceEndian(u8, val, .little); + + try self.value_cache.put(val_ref, val); + out[i] = .{ + .key = key, + .value = val, + }; + } + return out; +} + +fn valueAt(self: *XattrCachedTable, io: Io, ref: InodeRef) ![]const u8 { + try self.value_mut.lock(io); + defer self.value_mut.unlock(io); + + if (self.value_cache.contains(ref)) return self.value_cache.get(ref).?; + + var rdr = try self.fil.readerAt(io, self.kv_start + ref.block_start, &[0]u8{}); + var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp); + try meta.interface.discardAll(ref.block_offset); + + var val_size: u32 = undefined; + try meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little); + + const val = try self.alloc.alloc(u8, val_size); + errdefer self.alloc.free(val); + try meta.interface.readSliceEndian(u8, val, .little); + + try self.value_cache.put(ref, val); + return val; +} // Types -pub const Xattr = struct { +/// An Xattr return value where the reciever only owns the key value. +pub const XattrSemiOwned = struct { key: [:0]const u8, value: []const u8, + + pub fn deinit(self: XattrSemiOwned, alloc: std.mem.Allocator) void { + alloc.free(self.key); + } +}; +/// An Xattr return value where the reciever owns both the key & value. +pub const XattrOwned = struct { + key: [:0]const u8, + value: []const u8, + + pub fn deinit(self: XattrSemiOwned, alloc: std.mem.Allocator) void { + alloc.free(self.key); + alloc.free(self.value); + } +}; + +const TableValue = extern struct { + ref: InodeRef, + count: u32, + size: u32, +}; + +const KeyEntry = extern struct { + type: XattrPrefix, + name_size: u16, +}; +const ValueOutOfLineEntry = extern struct { + _: u32, + ref: InodeRef, +}; + +const XattrPrefix = packed struct(u16) { + namespace: enum(u8) { + user, + trusted, + security, + + fn prefixSize(self: @This()) u16 { + return switch (self) { + .user => 5, + .trusted => 8, + .security => 9, + }; + } + }, + out_of_line: bool, + _: u7, }; // Stateless -pub fn statelessLookup() ![]Xattr {} +pub fn statelessLookup(alloc: std.mem.Allocator, io: Io, decomp: *const Decompressor, fil: OffsetFile, table_start: u64, idx: u16) ![]XattrOwned { + var rdr = try fil.readerAt(io, table_start, &[0]u8{}); + + var kv_start: u64 = undefined; + try rdr.interface.readSliceEndian(u64, @ptrCast(&kv_start), .little); + + const lookup = try LookupTable.lookupValue(TableValue, alloc, io, decomp, fil, table_start + 16, idx); + + rdr = try fil.readerAt(io, kv_start + lookup.ref.block_start, &[0]u8{}); + var meta: MetadataReader = .init(alloc, &rdr.interface, decomp); + try meta.interface.discardAll(lookup.ref.block_offset); + + const out = try alloc.alloc(XattrOwned, lookup.count); + errdefer alloc.free(out); + + for (0..lookup.count) |i| { + const key_entry: KeyEntry = undefined; + try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little); + + const key = switch (key_entry.type.namespace) { + .user => blk: { + const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 5); + errdefer alloc.free(tmp); + try meta.interface.readSliceEndian(u8, tmp[5 .. tmp.len - 1], .little); + @memset(tmp[0..5], "user."); + break :blk tmp; + }, + .trusted => blk: { + const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 8); + errdefer alloc.free(tmp); + try meta.interface.readSliceEndian(u8, tmp[8 .. tmp.len - 1], .little); + @memset(tmp[0..8], "trusted."); + break :blk tmp; + }, + .security => blk: { + const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 9); + errdefer alloc.free(tmp); + try meta.interface.readSliceEndian(u8, tmp[9 .. tmp.len - 1], .little); + @memset(tmp[0..9], "security."); + break :blk tmp; + }, + }; + key[key.len - 1] = 0; + errdefer alloc.free(key); + + if (key_entry.type.out_of_line) { + const value: ValueOutOfLineEntry = undefined; + try meta.interface.readSliceEndian(ValueOutOfLineEntry, @ptrCast(&value), .little); + + var ool_rdr = try fil.readerAt(io, kv_start + value.ref.block_start, &[0]u8{}); + var ool_meta: MetadataReader = .init(alloc, &ool_rdr.interface, decomp); + try ool_meta.interface.discardAll(value.ref.block_offset); + + var val_size: u32 = undefined; + try ool_meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little); + + const val = try alloc.alloc(u8, val_size); + errdefer alloc.free(val); + try ool_meta.interface.readSliceEndian(u8, val, .little); + + out[i] = .{ + .key = key, + .value = val, + }; + continue; + } + + var val_size: u32 = undefined; + try meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little); + + const val = try alloc.alloc(u8, val_size); + try meta.interface.readSliceEndian(u8, val, .little); + + out[i] = .{ + .key = key, + .value = val, + }; + } + return out; +}