Added xattr support (currently untested)
Use stack fallback allocator on extraction for better performance.
This commit is contained in:
+6
-6
@@ -15,6 +15,7 @@ const Superblock = @import("super.zig").Superblock;
|
||||
const Table = @import("table.zig").Table;
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const OffsetFile = @import("util/offset_file.zig");
|
||||
const XattrTable = @import("xattr.zig");
|
||||
|
||||
const config = if (builtin.is_test) .{
|
||||
.use_c_libs = true,
|
||||
@@ -38,9 +39,10 @@ decomp: Decomp.DecompFn,
|
||||
|
||||
super: Superblock,
|
||||
|
||||
frag_table: Table(FragEntry) = undefined,
|
||||
id_table: Table(u16) = undefined,
|
||||
export_table: Table(InodeRef) = undefined,
|
||||
frag_table: Table(FragEntry),
|
||||
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.
|
||||
pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive {
|
||||
@@ -59,15 +61,13 @@ pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive {
|
||||
};
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
|
||||
.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 {
|
||||
|
||||
+69
-71
@@ -15,6 +15,7 @@ const misc = @import("inode_data/misc.zig");
|
||||
const DataReader = @import("util/data.zig");
|
||||
const ThreadedDataReader = @import("util/data_threaded.zig");
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const XattrTable = @import("xattr.zig");
|
||||
|
||||
pub const Ref = packed struct {
|
||||
block_offset: u16,
|
||||
@@ -154,7 +155,46 @@ fn entriesFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) ![
|
||||
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
||||
}
|
||||
|
||||
/// Extract the inode to the given path. Single threaded.
|
||||
/// Returns the xattr index for the given inode. If the inode isn't an extended variant or doesn't have any, the returned value is the max u32 value (0xFFFFFFFF)
|
||||
pub fn xattrIdx(self: Inode) u32 {
|
||||
return switch (self.data) {
|
||||
.ext_dir => |d| d.xattr_id,
|
||||
.ext_file => |f| f.xattr_idx,
|
||||
.ext_symlink => |s| s.xattr_idx,
|
||||
.ext_block_dev, .ext_char_dev => |d| d.xattr_idx,
|
||||
.ext_fifo, .ext_socket => |i| i.xattr_idx,
|
||||
else => 0xFFFFFFFF,
|
||||
};
|
||||
}
|
||||
|
||||
inline fn setPermissionAndXattr(self: Inode, alloc: std.mem.Allocator, archive: *Archive, 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));
|
||||
}
|
||||
if (!options.ignore_xattr) {
|
||||
const idx = self.xattrIdx();
|
||||
if (idx == 0xFFFFFFFF) return;
|
||||
const xattrs = try archive.xattr_table.get(alloc, idx);
|
||||
defer alloc.free(xattrs);
|
||||
for (xattrs) |kv| {
|
||||
defer {
|
||||
alloc.free(kv.key);
|
||||
alloc.free(kv.value);
|
||||
}
|
||||
const res = std.os.linux.fsetxattr(fil.handle, @ptrCast(kv.key), @ptrCast(kv.value), kv.value.len, 0);
|
||||
if (res != 0) {
|
||||
if (options.verbose)
|
||||
options.verbose_writer.?.print("fsetxattr has result of: {}\n", .{res}) catch {};
|
||||
return error.SetXattr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the inode to the given path.
|
||||
pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
if (options.threads > 1) return self.extractToThreaded(alloc, archive, path, options);
|
||||
switch (self.hdr.inode_type) {
|
||||
@@ -180,42 +220,38 @@ pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path:
|
||||
defer inode.deinit(alloc);
|
||||
try inode.extractTo(alloc, archive, new_path, options);
|
||||
}
|
||||
|
||||
var fil = try std.fs.cwd().openFile(path, .{});
|
||||
defer fil.close();
|
||||
try self.setPermissionAndXattr(alloc, archive, fil, options);
|
||||
},
|
||||
.file, .ext_file => try self.extractRegFile(alloc, archive, path, options),
|
||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||
else => try self.extractDevice(archive, path, options),
|
||||
else => try self.extractDevice(alloc, archive, path, options),
|
||||
}
|
||||
}
|
||||
|
||||
const Parent = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
inode: Inode,
|
||||
path: []const u8,
|
||||
uid: u16,
|
||||
gid: u16,
|
||||
perm: u16,
|
||||
mod_time: u32,
|
||||
|
||||
ignore_permissions: bool,
|
||||
ignore_xattr: bool,
|
||||
archive: *Archive,
|
||||
options: ExtractionOptions,
|
||||
|
||||
wg: WaitGroup = .{},
|
||||
mut: Mutex = .{},
|
||||
|
||||
fn create(alloc: std.mem.Allocator, hdr: Header, archive: *Archive, path: []const u8, options: ExtractionOptions, dir_size: usize) !*Parent {
|
||||
fn create(alloc: std.mem.Allocator, inode: Inode, path: []const u8, archive: *Archive, options: ExtractionOptions, dir_size: usize) !*Parent {
|
||||
const out = try alloc.create(Parent);
|
||||
errdefer alloc.destroy(out);
|
||||
out.* = .{
|
||||
.alloc = alloc,
|
||||
|
||||
.inode = inode,
|
||||
.path = path,
|
||||
.uid = try archive.id_table.get(hdr.uid_idx),
|
||||
.gid = try archive.id_table.get(hdr.gid_idx),
|
||||
.perm = hdr.permissions,
|
||||
.mod_time = hdr.mod_time,
|
||||
|
||||
.ignore_permissions = options.ignore_permissions,
|
||||
.ignore_xattr = options.ignore_xattr,
|
||||
.archive = archive,
|
||||
.options = options,
|
||||
};
|
||||
out.wg.startMany(dir_size);
|
||||
return out;
|
||||
@@ -231,33 +267,21 @@ const Parent = struct {
|
||||
defer p.alloc.destroy(p);
|
||||
var fil = try std.fs.cwd().openFile(p.path, .{});
|
||||
defer fil.close();
|
||||
const time = @as(i128, p.mod_time) * 1000000000;
|
||||
try fil.updateTimes(time, time);
|
||||
if (p.ignore_permissions) {
|
||||
try fil.chmod(p.perm);
|
||||
try fil.chown(p.uid, p.gid);
|
||||
}
|
||||
try p.inode.setPermissionAndXattr(p.alloc, p.archive, fil, p.options);
|
||||
}
|
||||
};
|
||||
|
||||
/// Extract the inode to the given path. Multi-threaded.
|
||||
/// Functions identically to extractTo on all but regular files and directories.
|
||||
///
|
||||
/// If threads <= 1, then this just calls extractTo.
|
||||
fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
// Removing any trailing separators since that's the easiest path forward.
|
||||
if (path[path.len - 1] == '/') return self.extractToThreaded(allocator, archive, path[0 .. path.len - 1], options);
|
||||
|
||||
// Fixed Allocator
|
||||
// const mem_buf = archive.allocator().alloc(u8, 2 * 1024 * 1024 * 1024);
|
||||
// defer archive.allocator().free(mem_buf);
|
||||
// var fixed_alloc: std.heap.FixedBufferAllocator = .init(mem_buf);
|
||||
// const alloc = fixed_alloc.threadSafeAllocator();
|
||||
|
||||
// Arena Allocator
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(allocator);
|
||||
var stack_alloc = std.heap.stackFallback(1024 * 1024, allocator);
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(stack_alloc.get());
|
||||
defer arena_alloc.deinit();
|
||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
||||
const alloc = thread_alloc.allocator();
|
||||
@@ -265,7 +289,7 @@ fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archiv
|
||||
var wg: WaitGroup = .{};
|
||||
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
|
||||
var pool: Pool = undefined;
|
||||
try pool.init(.{ .allocator = allocator, .n_jobs = options.threads - 1 });
|
||||
try pool.init(.{ .allocator = alloc, .n_jobs = options.threads - 1 });
|
||||
defer pool.deinit();
|
||||
var out_err: ?anyerror = null;
|
||||
|
||||
@@ -276,19 +300,16 @@ fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archiv
|
||||
|
||||
var fil = try std.fs.cwd().openFile(path, .{});
|
||||
defer fil.close();
|
||||
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 self.setPermissionAndXattr(alloc, archive, fil, options);
|
||||
},
|
||||
.file, .ext_file => {
|
||||
var pool: Pool = undefined;
|
||||
try pool.init(.{ .allocator = allocator, .n_jobs = options.threads - 1 });
|
||||
defer pool.deinit();
|
||||
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(allocator);
|
||||
// Arena Allocator
|
||||
var stack_alloc = std.heap.stackFallback(1024 * 1024, allocator);
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(stack_alloc.get());
|
||||
defer arena_alloc.deinit();
|
||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
||||
const alloc = thread_alloc.allocator();
|
||||
@@ -297,15 +318,10 @@ fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archiv
|
||||
|
||||
var fil = try std.fs.cwd().openFile(path, .{});
|
||||
defer fil.close();
|
||||
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 self.setPermissionAndXattr(alloc, archive, fil, options);
|
||||
},
|
||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||
else => try self.extractDevice(archive, path, options),
|
||||
else => try self.extractDevice(allocator, archive, path, options),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +390,7 @@ fn extractThread(
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
const p = Parent.create(alloc, self.hdr, archive, path, options, entries.len) catch |err| {
|
||||
const p = Parent.create(alloc, self, path, archive, options, entries.len) catch |err| {
|
||||
out_err.* = err;
|
||||
return;
|
||||
};
|
||||
@@ -422,7 +438,7 @@ fn extractThread(
|
||||
};
|
||||
},
|
||||
else => {
|
||||
self.extractDevice(archive, path, options) catch |err| {
|
||||
self.extractDevice(alloc, archive, path, options) catch |err| {
|
||||
if (options.verbose)
|
||||
options.verbose_writer.?.print("Error extracting device/IPC inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||
out_err.* = err;
|
||||
@@ -443,12 +459,7 @@ fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path
|
||||
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
||||
try wrt.interface.flush();
|
||||
|
||||
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 self.setPermissionAndXattr(alloc, archive, fil, options);
|
||||
}
|
||||
/// Extract the inode file contents to the given path threadedly.
|
||||
/// pool is used to spawn threads.
|
||||
@@ -460,12 +471,7 @@ fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: *Archi
|
||||
var data = try self.threadedDataReader(alloc, archive);
|
||||
try data.extractThreaded(fil, pool);
|
||||
|
||||
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 self.setPermissionAndXattr(alloc, archive, fil, options);
|
||||
}
|
||||
/// Creates the symlink described by the inode.
|
||||
///
|
||||
@@ -482,7 +488,7 @@ fn extractSymlink(self: Inode, path: []const u8) !void {
|
||||
///
|
||||
/// Optionally set owner & permissions.
|
||||
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
|
||||
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
fn extractDevice(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
var mode: u32 = undefined;
|
||||
var dev: u32 = 0;
|
||||
switch (self.data) {
|
||||
@@ -527,13 +533,5 @@ fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: Extr
|
||||
}
|
||||
var fil = try std.fs.cwd().openFile(path, .{});
|
||||
defer fil.close();
|
||||
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));
|
||||
}
|
||||
if (!options.ignore_xattr) {
|
||||
// TODO
|
||||
}
|
||||
try self.setPermissionAndXattr(alloc, archive, fil, options);
|
||||
}
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
const std = @import("std");
|
||||
|
||||
const DecompFn = @import("decomp.zig").DecompFn;
|
||||
const Table = @import("table.zig").Table;
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const OffsetFile = @import("util/offset_file.zig");
|
||||
|
||||
const Ref = packed struct {
|
||||
block_offset: u16,
|
||||
block_start: u32,
|
||||
_: u16,
|
||||
};
|
||||
const Entry = packed struct {
|
||||
ref: Ref,
|
||||
count: u32,
|
||||
size: u32,
|
||||
};
|
||||
const KeyPrefix = enum(u8) {
|
||||
user,
|
||||
trusted,
|
||||
security,
|
||||
};
|
||||
const KeyRaw = packed struct {
|
||||
type: packed struct {
|
||||
prefix: KeyPrefix,
|
||||
out_of_line: bool,
|
||||
_: u7,
|
||||
},
|
||||
name_size: u16,
|
||||
};
|
||||
|
||||
pub const KeyValue = struct {
|
||||
key: []u8,
|
||||
value: []u8,
|
||||
};
|
||||
|
||||
const XattrTable = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
fil: OffsetFile,
|
||||
decomp: DecompFn,
|
||||
|
||||
count: u32,
|
||||
start: u64,
|
||||
|
||||
table: Table(Entry),
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, table_start: u64) !XattrTable {
|
||||
var info = packed struct {
|
||||
start: u64 = undefined,
|
||||
count: u32 = undefined,
|
||||
_: u32 = undefined,
|
||||
}{};
|
||||
var rdr = try fil.readerAt(table_start, &[0]u8{});
|
||||
try rdr.interface.readSliceEndian(@TypeOf(info), @ptrCast(&info), .little);
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.fil = fil,
|
||||
.decomp = decomp,
|
||||
.count = info.count,
|
||||
.start = info.start,
|
||||
.table = try .init(alloc, fil, decomp, table_start + 16, info.count),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: XattrTable) void {
|
||||
self.table.deinit();
|
||||
}
|
||||
|
||||
pub fn get(self: *XattrTable, alloc: std.mem.Allocator, idx: u32) ![]KeyValue {
|
||||
const entry: Entry = try self.table.get(idx);
|
||||
const out = try alloc.alloc(KeyValue, entry.count);
|
||||
|
||||
for (out) |*kv| {
|
||||
var rdr = try self.fil.readerAt(self.start + entry.ref.block_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||
try meta.interface.discardAll(entry.ref.block_offset);
|
||||
|
||||
var key_raw: KeyRaw = undefined;
|
||||
try meta.interface.readSliceEndian(KeyRaw, @ptrCast(&key_raw), .little);
|
||||
|
||||
switch (key_raw.type.prefix) {
|
||||
.user => {
|
||||
kv.key = try alloc.alloc(u8, key_raw.name_size + 5);
|
||||
@memcpy(kv.key[0..5], "user.");
|
||||
try meta.interface.readSliceAll(kv.key[5..]);
|
||||
},
|
||||
.security => {
|
||||
kv.key = try alloc.alloc(u8, key_raw.name_size + 9);
|
||||
@memcpy(kv.key[0..9], "security.");
|
||||
try meta.interface.readSliceAll(kv.key[9..]);
|
||||
},
|
||||
.trusted => {
|
||||
kv.key = try alloc.alloc(u8, key_raw.name_size + 8);
|
||||
@memcpy(kv.key[0..8], "trusted.");
|
||||
try meta.interface.readSliceAll(kv.key[8..]);
|
||||
},
|
||||
}
|
||||
if (key_raw.type.out_of_line) {
|
||||
try meta.interface.discardAll(4);
|
||||
var ref: Ref = undefined;
|
||||
try meta.interface.readSliceEndian(Ref, @ptrCast(&ref), .little);
|
||||
|
||||
rdr = try self.fil.readerAt(self.start + ref.block_start, &[0]u8{});
|
||||
meta = .init(alloc, &rdr.interface, self.decomp);
|
||||
try meta.interface.discardAll(ref.block_offset);
|
||||
}
|
||||
var value_size: u32 = undefined;
|
||||
try meta.interface.readSliceEndian(u32, @ptrCast(&value_size), .little);
|
||||
kv.value = try alloc.alloc(u8, value_size);
|
||||
try meta.interface.readSliceAll(kv.value);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user