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
This commit is contained in:
@@ -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`.
|
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.
|
* Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances.
|
||||||
* Performance improvements/regressions will be common. I'm still learning Zig.
|
* Performance improvements/regressions will be common. I'm still learning Zig.
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@ Example Times:
|
|||||||
|
|
||||||
* *unsquashfs, multi-threaded*: .12s
|
* *unsquashfs, multi-threaded*: .12s
|
||||||
* *unsquashfs, single-threaded*: .13s
|
* *unsquashfs, single-threaded*: .13s
|
||||||
* *C-libs, single-threaded*: .56s
|
* *C-libs, single-threaded*: .45s
|
||||||
* *C-libs, multi-threaded*: .19s
|
* *C-libs, multi-threaded*: .20s
|
||||||
* *Zig-libs, single-threaded*: 5.78s
|
* *Zig-libs, single-threaded*: 5.78s
|
||||||
* *Zig-libs, multi-threaded*: 1.08s
|
* *Zig-libs, multi-threaded*: 1.08s
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
});
|
});
|
||||||
mod.addOptions("config", zig_squashfs_options);
|
mod.addOptions("config", zig_squashfs_options);
|
||||||
if (use_c_libs_option == true) {
|
if (use_c_libs_option == true) {
|
||||||
mod.linkSystemLibrary("zlib", .{});
|
mod.linkSystemLibrary("zlib", .{ .preferred_link_mode = .static });
|
||||||
mod.linkSystemLibrary("lzma", .{});
|
mod.linkSystemLibrary("lzma", .{ .preferred_link_mode = .static });
|
||||||
if (allow_lzo == true)
|
if (allow_lzo == true)
|
||||||
mod.linkSystemLibrary("minilzo", .{});
|
mod.linkSystemLibrary("minilzo", .{ .preferred_link_mode = .static });
|
||||||
mod.linkSystemLibrary("lz4", .{});
|
mod.linkSystemLibrary("lz4", .{ .preferred_link_mode = .static });
|
||||||
mod.linkSystemLibrary("zstd", .{});
|
mod.linkSystemLibrary("zstd", .{ .preferred_link_mode = .static });
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = version_string_option orelse "0.0.0-testing";
|
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(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "unsquashfs",
|
.name = "unsquashfs",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
.use_llvm = true,
|
// .use_llvm = true, This can be needed to properly debug
|
||||||
});
|
});
|
||||||
|
|
||||||
const lib = b.addLibrary(.{
|
const lib = b.addLibrary(.{
|
||||||
|
|||||||
+11
-21
@@ -12,7 +12,7 @@ const InodeRef = Inode.Ref;
|
|||||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
||||||
const SfsFile = @import("file.zig");
|
const SfsFile = @import("file.zig");
|
||||||
const Superblock = @import("super.zig").Superblock;
|
const Superblock = @import("super.zig").Superblock;
|
||||||
const Table = @import("table.zig").Table;
|
const Tables = @import("tables.zig");
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
const XattrTable = @import("xattr.zig");
|
const XattrTable = @import("xattr.zig");
|
||||||
@@ -22,14 +22,6 @@ const config = if (builtin.is_test) .{
|
|||||||
.allow_lzo = false,
|
.allow_lzo = false,
|
||||||
} else @import("config");
|
} 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();
|
const Archive = @This();
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
@@ -39,10 +31,7 @@ decomp: Decomp.DecompFn,
|
|||||||
|
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
frag_table: Table(FragEntry),
|
tables: ?Tables = null,
|
||||||
id_table: Table(u16),
|
|
||||||
export_table: Table(InodeRef),
|
|
||||||
xattr_table: XattrTable,
|
|
||||||
|
|
||||||
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
/// 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 {
|
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,
|
.fil = off_fil,
|
||||||
.decomp = decomp,
|
.decomp = decomp,
|
||||||
.super = super,
|
.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 {
|
pub fn deinit(self: *Archive) void {
|
||||||
self.frag_table.deinit();
|
if (self.tables != null)
|
||||||
self.export_table.deinit();
|
self.tables.?.deinit();
|
||||||
self.id_table.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inode(self: *Archive, alloc: std.mem.Allocator, num: u32) !Inode {
|
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);
|
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 rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(alloc, &rdr.interface, &self.decomp);
|
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 {
|
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 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);
|
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
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 {
|
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);
|
var root_fil = try self.root(alloc);
|
||||||
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
||||||
return root_fil.open(path);
|
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 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);
|
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
|
|||||||
+16
-16
@@ -12,6 +12,7 @@ const ExtractionOptions = @import("options.zig");
|
|||||||
const dir = @import("inode_data/dir.zig");
|
const dir = @import("inode_data/dir.zig");
|
||||||
const file = @import("inode_data/file.zig");
|
const file = @import("inode_data/file.zig");
|
||||||
const misc = @import("inode_data/misc.zig");
|
const misc = @import("inode_data/misc.zig");
|
||||||
|
const Tables = @import("tables.zig");
|
||||||
const DataReader = @import("util/data.zig");
|
const DataReader = @import("util/data.zig");
|
||||||
const ThreadedDataReader = @import("util/data_threaded.zig");
|
const ThreadedDataReader = @import("util/data_threaded.zig");
|
||||||
const InodeExtract = @import("util/extract.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 rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
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.
|
/// 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) {
|
return switch (self.hdr.inode_type) {
|
||||||
.file => readerFromData(alloc, archive, self.data.file),
|
.file => readerFromData(alloc, archive, tables, self.data.file),
|
||||||
.ext_file => readerFromData(alloc, archive, self.data.ext_file),
|
.ext_file => readerFromData(alloc, archive, tables, self.data.ext_file),
|
||||||
else => error.NotRegularFile,
|
else => error.NotRegularFile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn readerFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !DataReader {
|
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);
|
var out: DataReader = .init(alloc, archive, data.block_sizes, data.block_start, data.size);
|
||||||
if (data.frag_idx != 0xFFFFFFFF)
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,33 +158,32 @@ pub fn xattrIdx(self: Inode) u32 {
|
|||||||
|
|
||||||
/// Applies the Inode's metadata to the given File.
|
/// 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.
|
/// 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;
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
try fil.updateTimes(time, time);
|
try fil.updateTimes(time, time);
|
||||||
if (!options.ignore_permissions) {
|
if (!options.ignore_permissions) {
|
||||||
try fil.chmod(self.hdr.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) {
|
if (!options.ignore_xattr) {
|
||||||
const idx = self.xattrIdx();
|
const idx = self.xattrIdx();
|
||||||
if (idx == 0xFFFFFFFF) return;
|
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);
|
defer alloc.free(xattrs);
|
||||||
for (xattrs) |kv| {
|
for (xattrs) |kv| {
|
||||||
const res = std.os.linux.fsetxattr(fil.handle, @ptrCast(kv.key), @ptrCast(kv.value), kv.value.len, 0);
|
const res = std.os.linux.fsetxattr(fil.handle, kv.key[0.. :0], kv.value.ptr, kv.value.len, 0);
|
||||||
alloc.free(kv.key);
|
|
||||||
alloc.free(kv.value);
|
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
if (options.verbose)
|
if (options.verbose)
|
||||||
options.verbose_writer.?.print("fsetxattr has result of: {}\n", .{res}) catch {};
|
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.
|
/// 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);
|
return InodeExtract.extractTo(alloc, self, archive, path, options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,45 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Mutex = std.Thread.Mutex;
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
|
const Archive = @import("archive.zig");
|
||||||
const DecompFn = @import("decomp.zig").DecompFn;
|
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 MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
const XattrTable = @import("xattr.zig");
|
||||||
|
|
||||||
const TableError = error{
|
/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry.
|
||||||
InvalidIndex,
|
/// 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.
|
/// A two-layer metadata table.
|
||||||
pub fn Table(T: anytype) type {
|
pub fn Table(T: anytype) type {
|
||||||
return struct {
|
return struct {
|
||||||
@@ -47,7 +78,7 @@ pub fn Table(T: anytype) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: *Self, idx: u32) !T {
|
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 block_num = idx / VALS_PER_BLOCK;
|
||||||
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
||||||
if (self.tab.contains(block_num)) {
|
if (self.tab.contains(block_num)) {
|
||||||
+1
-1
@@ -6,9 +6,9 @@ const Writer = std.Io.Writer;
|
|||||||
const Limit = std.Io.Limit;
|
const Limit = std.Io.Limit;
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
const Archive = @import("../archive.zig");
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||||
|
const FragEntry = @import("../tables.zig").FragEntry;
|
||||||
const OffsetFile = @import("offset_file.zig");
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
const DataReader = @This();
|
const DataReader = @This();
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ const WaitGroup = std.Thread.WaitGroup;
|
|||||||
const Pool = std.Thread.Pool;
|
const Pool = std.Thread.Pool;
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
const Archive = @import("../archive.zig");
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||||
|
const FragEntry = @import("../tables.zig").FragEntry;
|
||||||
const InodeFinish = @import("inode_finish.zig");
|
const InodeFinish = @import("inode_finish.zig");
|
||||||
const OffsetFile = @import("offset_file.zig");
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
|
|||||||
+57
-30
@@ -7,6 +7,7 @@ const Archive = @import("../archive.zig");
|
|||||||
const DirEntry = @import("../dir_entry.zig");
|
const DirEntry = @import("../dir_entry.zig");
|
||||||
const Inode = @import("../inode.zig");
|
const Inode = @import("../inode.zig");
|
||||||
const ExtractionOptions = @import("../options.zig");
|
const ExtractionOptions = @import("../options.zig");
|
||||||
|
const Tables = @import("../tables.zig");
|
||||||
const InodeFinish = @import("inode_finish.zig");
|
const InodeFinish = @import("inode_finish.zig");
|
||||||
const FinishUnion = InodeFinish.FinishUnion;
|
const FinishUnion = InodeFinish.FinishUnion;
|
||||||
const ThreadedDataReader = @import("data_threaded.zig");
|
const ThreadedDataReader = @import("data_threaded.zig");
|
||||||
@@ -17,7 +18,7 @@ const STACK_ALLOC_SIZE = 1024 * 1024;
|
|||||||
pub fn extractTo(
|
pub fn extractTo(
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
archive: *Archive,
|
archive: Archive,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
) !void {
|
) !void {
|
||||||
@@ -27,11 +28,15 @@ pub fn extractTo(
|
|||||||
var stack_alloc = std.heap.stackFallback(STACK_ALLOC_SIZE, allocator);
|
var stack_alloc = std.heap.stackFallback(STACK_ALLOC_SIZE, allocator);
|
||||||
var arena: std.heap.ArenaAllocator = .init(stack_alloc.get());
|
var arena: std.heap.ArenaAllocator = .init(stack_alloc.get());
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
if (options.threads <= 1)
|
if (options.threads <= 1) {
|
||||||
return extractSingleThread(arena.allocator(), inode, archive, path, options);
|
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() };
|
var thread_alloc = std.heap.ThreadSafeAllocator{ .child_allocator = arena.allocator() };
|
||||||
const alloc = thread_alloc.allocator();
|
const alloc = thread_alloc.allocator();
|
||||||
|
var tables: Tables = try .init(alloc, archive);
|
||||||
|
|
||||||
var pool_alloc = std.heap.stackFallback(10 * 1024, alloc);
|
var pool_alloc = std.heap.stackFallback(10 * 1024, alloc);
|
||||||
var pool: Pool = undefined;
|
var pool: Pool = undefined;
|
||||||
@@ -44,6 +49,7 @@ pub fn extractTo(
|
|||||||
alloc,
|
alloc,
|
||||||
inode,
|
inode,
|
||||||
archive,
|
archive,
|
||||||
|
&tables,
|
||||||
path,
|
path,
|
||||||
options,
|
options,
|
||||||
&pool,
|
&pool,
|
||||||
@@ -57,7 +63,8 @@ pub fn extractTo(
|
|||||||
fn extractSingleThread(
|
fn extractSingleThread(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
archive: *Archive,
|
archive: Archive,
|
||||||
|
tables: *Tables,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
) !void {
|
) !void {
|
||||||
@@ -73,37 +80,38 @@ fn extractSingleThread(
|
|||||||
// otherwise be more conscientious about freeing memory.
|
// otherwise be more conscientious about freeing memory.
|
||||||
// For now, this is good enough.
|
// For now, this is good enough.
|
||||||
|
|
||||||
const entries = try inode.dirEntries(alloc, archive.*);
|
const entries = try inode.dirEntries(alloc, archive);
|
||||||
for (entries) |ent| {
|
for (entries) |ent| {
|
||||||
const sub_inode: Inode = try .readFromEntry(alloc, archive, ent);
|
const sub_inode: Inode = try .readFromEntry(alloc, archive, ent);
|
||||||
const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", ent.name });
|
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, .{});
|
const fil = try std.fs.cwd().openFile(path, .{});
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
try inode.setMetadata(alloc, archive, fil, options);
|
try inode.setMetadata(alloc, tables, fil, options);
|
||||||
},
|
},
|
||||||
.file, .ext_file => {
|
.file, .ext_file => {
|
||||||
var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true });
|
var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true });
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var wrt = fil.writer(&[0]u8{});
|
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();
|
defer dat_rdr.deinit();
|
||||||
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
||||||
try wrt.interface.flush();
|
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),
|
.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(
|
fn extractMultiThread(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
archive: *Archive,
|
archive: Archive,
|
||||||
|
tables: *Tables,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
pool: *Pool,
|
pool: *Pool,
|
||||||
@@ -130,17 +138,22 @@ fn extractMultiThread(
|
|||||||
// otherwise be more conscientious about freeing memory.
|
// otherwise be more conscientious about freeing memory.
|
||||||
// For now, this is good enough.
|
// 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;
|
err.* = res_err;
|
||||||
fin.finish();
|
fin.finish();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (entries.len == 0) {
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var dir_fin = InodeFinish.create(
|
var dir_fin = InodeFinish.create(
|
||||||
alloc,
|
alloc,
|
||||||
inode,
|
inode,
|
||||||
path,
|
path,
|
||||||
archive,
|
tables,
|
||||||
options,
|
options,
|
||||||
fin,
|
fin,
|
||||||
err,
|
err,
|
||||||
@@ -153,21 +166,24 @@ fn extractMultiThread(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (entries) |ent| {
|
for (entries) |ent| {
|
||||||
if (ent.inode_type == .dir)
|
if (ent.inode_type == .dir) {
|
||||||
extractEntry(
|
extractEntry(
|
||||||
alloc,
|
alloc,
|
||||||
ent,
|
ent,
|
||||||
archive,
|
archive,
|
||||||
|
tables,
|
||||||
path,
|
path,
|
||||||
options,
|
options,
|
||||||
pool,
|
pool,
|
||||||
.{ .fin = dir_fin },
|
.{ .fin = dir_fin },
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
pool.spawn(
|
pool.spawn(
|
||||||
extractEntry,
|
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| {
|
) catch |res_err| {
|
||||||
err.* = res_err;
|
err.* = res_err;
|
||||||
dir_fin.finish();
|
dir_fin.finish();
|
||||||
@@ -184,23 +200,32 @@ fn extractMultiThread(
|
|||||||
return;
|
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)
|
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 {};
|
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;
|
err.* = res_err;
|
||||||
fin.finish();
|
fin.finish();
|
||||||
return;
|
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(
|
const file_fin = InodeFinish.create(
|
||||||
alloc,
|
alloc,
|
||||||
inode,
|
inode,
|
||||||
path,
|
path,
|
||||||
archive,
|
tables,
|
||||||
options,
|
options,
|
||||||
fin,
|
fin,
|
||||||
err,
|
err,
|
||||||
fil,
|
fil,
|
||||||
data_rdr.num_blocks,
|
data_rdr.?.num_blocks,
|
||||||
) catch |res_err| {
|
) catch |res_err| {
|
||||||
if (options.verbose)
|
if (options.verbose)
|
||||||
options.verbose_writer.?.print("Can't create callback for inode #{} (extracting to {s}): {}\n", .{ inode.hdr.num, path, res_err }) catch {};
|
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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
data_rdr.extractThreaded(fil, pool, file_fin);
|
data_rdr.?.extractThreaded(fil, pool, file_fin);
|
||||||
},
|
},
|
||||||
.symlink, .ext_symlink => {
|
.symlink, .ext_symlink => {
|
||||||
extractSymlink(inode, path) catch |res_err| {
|
extractSymlink(inode, path) catch |res_err| {
|
||||||
@@ -218,7 +243,7 @@ fn extractMultiThread(
|
|||||||
fin.finish();
|
fin.finish();
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
extractDeviceAndIPC(inode, alloc, archive, path, options) catch |res_err| {
|
extractDeviceAndIPC(inode, alloc, tables, path, options) catch |res_err| {
|
||||||
err.* = res_err;
|
err.* = res_err;
|
||||||
};
|
};
|
||||||
fin.finish();
|
fin.finish();
|
||||||
@@ -229,7 +254,8 @@ fn extractMultiThread(
|
|||||||
fn extractEntry(
|
fn extractEntry(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
ent: DirEntry,
|
ent: DirEntry,
|
||||||
archive: *Archive,
|
archive: Archive,
|
||||||
|
tables: *Tables,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
pool: *Pool,
|
pool: *Pool,
|
||||||
@@ -247,21 +273,22 @@ fn extractEntry(
|
|||||||
fin.finish();
|
fin.finish();
|
||||||
return;
|
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.
|
/// 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) {
|
return switch (self.hdr.inode_type) {
|
||||||
.file => threadedReaderFromData(alloc, archive, self.data.file),
|
.file => threadedReaderFromData(alloc, archive, tables, self.data.file),
|
||||||
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
|
.ext_file => threadedReaderFromData(alloc, archive, tables, self.data.ext_file),
|
||||||
else => error.NotRegularFile,
|
else => error.NotRegularFile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !ThreadedDataReader {
|
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: Archive, tables: *Tables, data: anytype) !?ThreadedDataReader {
|
||||||
var out: ThreadedDataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
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)
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +304,7 @@ fn extractSymlink(self: Inode, path: []const u8) !void {
|
|||||||
}
|
}
|
||||||
/// Creates the device described by the inode.
|
/// Creates the device described by the inode.
|
||||||
/// Sets metadata.
|
/// 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 mode: u32 = undefined;
|
||||||
var dev: u32 = 0;
|
var dev: u32 = 0;
|
||||||
switch (self.data) {
|
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, .{});
|
var fil = try std.fs.cwd().openFile(path, .{});
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
try self.setMetadata(alloc, archive, fil, options);
|
try self.setMetadata(alloc, tables, fil, options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const Mutex = std.Thread.Mutex;
|
|||||||
const Archive = @import("../archive.zig");
|
const Archive = @import("../archive.zig");
|
||||||
const Inode = @import("../inode.zig");
|
const Inode = @import("../inode.zig");
|
||||||
const ExtractionOptions = @import("../options.zig");
|
const ExtractionOptions = @import("../options.zig");
|
||||||
|
const Tables = @import("../tables.zig");
|
||||||
|
|
||||||
const InodeFinish = @This();
|
const InodeFinish = @This();
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ alloc: std.mem.Allocator,
|
|||||||
|
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
archive: *Archive,
|
tables: *Tables,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
parent_finish: FinishUnion,
|
parent_finish: FinishUnion,
|
||||||
fil: ?std.fs.File,
|
fil: ?std.fs.File,
|
||||||
@@ -41,13 +42,15 @@ pub fn create(
|
|||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
archive: *Archive,
|
tables: *Tables,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
parent_finish: FinishUnion,
|
parent_finish: FinishUnion,
|
||||||
out_err: *?anyerror,
|
out_err: *?anyerror,
|
||||||
fil: ?std.fs.File,
|
fil: ?std.fs.File,
|
||||||
work_size: usize,
|
work_size: usize,
|
||||||
) !*InodeFinish {
|
) !*InodeFinish {
|
||||||
|
if (work_size == 0)
|
||||||
|
return error.InvalidWorkSize;
|
||||||
const out = try alloc.create(InodeFinish);
|
const out = try alloc.create(InodeFinish);
|
||||||
errdefer alloc.destroy(out);
|
errdefer alloc.destroy(out);
|
||||||
out.* = .{
|
out.* = .{
|
||||||
@@ -55,7 +58,7 @@ pub fn create(
|
|||||||
|
|
||||||
.inode = inode,
|
.inode = inode,
|
||||||
.path = path,
|
.path = path,
|
||||||
.archive = archive,
|
.tables = tables,
|
||||||
.options = options,
|
.options = options,
|
||||||
.parent_finish = parent_finish,
|
.parent_finish = parent_finish,
|
||||||
.out_err = out_err,
|
.out_err = out_err,
|
||||||
@@ -89,7 +92,7 @@ pub fn finish(self: *InodeFinish) void {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer self.fil.?.close();
|
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)
|
if (self.options.verbose)
|
||||||
self.options.verbose_writer.?.print("Error setting metadata to {s}: {}\n", .{ self.path, err }) catch {};
|
self.options.verbose_writer.?.print("Error setting metadata to {s}: {}\n", .{ self.path, err }) catch {};
|
||||||
self.out_err.* = err;
|
self.out_err.* = err;
|
||||||
|
|||||||
+12
-9
@@ -1,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const DecompFn = @import("decomp.zig").DecompFn;
|
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 MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ const KeyRaw = packed struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const KeyValue = struct {
|
pub const KeyValue = struct {
|
||||||
key: []u8,
|
key: [:0]u8,
|
||||||
value: []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),
|
.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();
|
self.table.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,19 +80,22 @@ pub fn get(self: *XattrTable, alloc: std.mem.Allocator, idx: u32) ![]KeyValue {
|
|||||||
|
|
||||||
switch (key_raw.type.prefix) {
|
switch (key_raw.type.prefix) {
|
||||||
.user => {
|
.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.");
|
@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 => {
|
.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.");
|
@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 => {
|
.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.");
|
@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) {
|
if (key_raw.type.out_of_line) {
|
||||||
|
|||||||
Reference in New Issue
Block a user