Finished work (theoretically) on single threaded extraction
Started work on Multi-threaded extraction Added decompression cache. Added XattrTable
This commit is contained in:
@@ -15,6 +15,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const build_options = b.addOptions();
|
const build_options = b.addOptions();
|
||||||
build_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
build_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
||||||
build_options.addOption(bool, "allow_lzo", allow_lzo);
|
build_options.addOption(bool, "allow_lzo", allow_lzo);
|
||||||
|
build_options.addOption(std.SemanticVersion, "version", version);
|
||||||
|
|
||||||
const c = b.addTranslateC(.{
|
const c = b.addTranslateC(.{
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
@@ -79,6 +80,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.valgrind = debug,
|
.valgrind = debug,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "squashfs", .module = lib.root_module },
|
.{ .name = "squashfs", .module = lib.root_module },
|
||||||
|
.{ .name = "build_options", .module = build_options.createModule() },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
+32
-18
@@ -1,9 +1,10 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Writer = std.Io.Writer;
|
const Io = std.Io;
|
||||||
|
const Writer = Io.Writer;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const build = @import("build_options");
|
||||||
|
|
||||||
const config = @import("config");
|
const squashfs = @import("squashfs");
|
||||||
const squashfs = @import("zig_squashfs");
|
|
||||||
|
|
||||||
//TODO: Add more options
|
//TODO: Add more options
|
||||||
const help_mgs =
|
const help_mgs =
|
||||||
@@ -30,7 +31,7 @@ const help_mgs =
|
|||||||
const errors = error{InvalidArguments};
|
const errors = error{InvalidArguments};
|
||||||
|
|
||||||
var archive: []const u8 = "";
|
var archive: []const u8 = "";
|
||||||
var extLoc: []const u8 = "squashfs-root";
|
var ext_loc: []const u8 = "squashfs-root";
|
||||||
var offset: u64 = 0;
|
var offset: u64 = 0;
|
||||||
var threads: u32 = 0;
|
var threads: u32 = 0;
|
||||||
var verbose: bool = false;
|
var verbose: bool = false;
|
||||||
@@ -38,35 +39,48 @@ var ignore_xattrs: bool = false;
|
|||||||
var ignore_permissions: bool = false;
|
var ignore_permissions: bool = false;
|
||||||
var force: bool = false;
|
var force: bool = false;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
const alloc = std.heap.smp_allocator;
|
const io = init.io;
|
||||||
var stdout = std.fs.File.stdout();
|
const alloc = init.alloc;
|
||||||
var out = stdout.writer(&[0]u8{});
|
|
||||||
|
var stdout = Io.File.stdout();
|
||||||
|
var out = stdout.writer(io, &[0]u8{});
|
||||||
defer out.interface.flush() catch {};
|
defer out.interface.flush() catch {};
|
||||||
try handleArgs(alloc, &out.interface);
|
|
||||||
|
try handleArgs(init.minimal.args, &out.interface);
|
||||||
if (archive.len == 0) {
|
if (archive.len == 0) {
|
||||||
try out.interface.print("You must provide a squashfs archive\n", .{});
|
try out.interface.print("You must provide a squashfs archive\n", .{});
|
||||||
try out.interface.print(help_mgs, .{});
|
try out.interface.print(help_mgs, .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
var fil = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var arc: squashfs.Archive = try .init(alloc, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||||
defer arc.deinit();
|
defer arc.deinit();
|
||||||
const options: squashfs.ExtractionOptions = .{
|
const options: squashfs.ExtractionOptions = .{
|
||||||
.threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
|
.single_threaded = threads == 1,
|
||||||
.verbose = verbose,
|
.verbose = verbose,
|
||||||
.verbose_writer = if (verbose) &out.interface else null,
|
.verbose_writer = if (verbose) &out.interface else null,
|
||||||
.ignore_xattr = ignore_xattrs,
|
.ignore_xattr = ignore_xattrs,
|
||||||
.ignore_permissions = ignore_permissions,
|
.ignore_permissions = ignore_permissions,
|
||||||
};
|
};
|
||||||
if (force)
|
if (force)
|
||||||
try std.fs.cwd().deleteTree(extLoc);
|
try std.fs.cwd().deleteTree(ext_loc);
|
||||||
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
|
if (threads > 1) {
|
||||||
|
var limited_io = Io.Threaded.init(alloc, .{
|
||||||
|
.argv0 = .init(init.minimal.args),
|
||||||
|
.async_limit = threads,
|
||||||
|
.concurrent_limit = threads,
|
||||||
|
.environ = init.minimal.environ,
|
||||||
|
});
|
||||||
|
try arc.extract(alloc, limited_io.io(), ext_loc, options); //TODO: Handle error gracefully.
|
||||||
|
} else {
|
||||||
|
try arc.extract(alloc, io, ext_loc, options); //TODO: Handle error gracefully.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
fn handleArgs(main_args: std.process.Args, out: *Writer) !void {
|
||||||
var args = try std.process.argsWithAllocator(alloc);
|
var args = main_args.iterate();
|
||||||
defer args.deinit();
|
defer args.deinit();
|
||||||
_ = args.next(); // args[0] is the application launch command.
|
_ = args.next(); // args[0] is the application launch command.
|
||||||
while (args.next()) |arg| {
|
while (args.next()) |arg| {
|
||||||
@@ -87,7 +101,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
try out.print("-d must be followed by a location\n", .{});
|
try out.print("-d must be followed by a location\n", .{});
|
||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
}
|
}
|
||||||
extLoc = nxt.?;
|
ext_loc = nxt.?;
|
||||||
continue;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
} else if (std.mem.eql(u8, arg, "-p")) {
|
||||||
const nxt = args.next();
|
const nxt = args.next();
|
||||||
@@ -114,7 +128,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
continue;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
} else if (std.mem.eql(u8, arg, "--version")) {
|
||||||
try out.print("zig-unsquashfs v", .{});
|
try out.print("zig-unsquashfs v", .{});
|
||||||
try config.version.format(out);
|
try build.version.format(out);
|
||||||
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
|
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
|
||||||
std.process.exit(0);
|
std.process.exit(0);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+21
-5
@@ -3,6 +3,7 @@ const Io = std.Io;
|
|||||||
|
|
||||||
const Decomp = @import("../decomp.zig");
|
const Decomp = @import("../decomp.zig");
|
||||||
const DataBlock = @import("../inode.zig").DataBlock;
|
const DataBlock = @import("../inode.zig").DataBlock;
|
||||||
|
const Cache = @import("../util/cache.zig");
|
||||||
|
|
||||||
const Extractor = @This();
|
const Extractor = @This();
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ size: u64,
|
|||||||
frag_data: ?[]u8 = null,
|
frag_data: ?[]u8 = null,
|
||||||
frag_offset: u32 = 0,
|
frag_offset: u32 = 0,
|
||||||
|
|
||||||
|
cache: ?Cache = null,
|
||||||
|
|
||||||
pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, blocks: []DataBlock, start: u64, size: u64) Extractor {
|
pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, blocks: []DataBlock, start: u64, size: u64) Extractor {
|
||||||
return .{
|
return .{
|
||||||
.data = data,
|
.data = data,
|
||||||
@@ -34,6 +37,9 @@ pub fn addFrag(self: *Extractor, frag_data: []u8, frag_offset: u32) void {
|
|||||||
self.frag_data = frag_data;
|
self.frag_data = frag_data;
|
||||||
self.frag_offset = frag_offset;
|
self.frag_offset = frag_offset;
|
||||||
}
|
}
|
||||||
|
pub fn addCache(self: *Extractor, cache: Cache) void {
|
||||||
|
self.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extractAsync(self: Extractor, alloc: std.mem.Allocator, io: Io, file: Io.File) Error!void {
|
pub fn extractAsync(self: Extractor, alloc: std.mem.Allocator, io: Io, file: Io.File) Error!void {
|
||||||
if (self.size == 0) return;
|
if (self.size == 0) return;
|
||||||
@@ -86,10 +92,20 @@ fn blockThread(self: Extractor, alloc: std.mem.Allocator, map_data: []u8, read_o
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = self.decomp(alloc, data, map_data[offset..][0..size]) catch |inner_err| {
|
if (self.cache != null) {
|
||||||
err.* = inner_err;
|
const decomp_block = self.cache.?.get(read_offset, block.size) catch |inner_err| {
|
||||||
return;
|
switch (inner_err) {
|
||||||
};
|
error.Canceled => return error.Canceled,
|
||||||
|
else => |e| err.* = e,
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
@memcpy(map_data[offset..][0..size], decomp_block[0..size]);
|
||||||
|
} else {
|
||||||
|
_ = self.decomp(alloc, data, map_data[offset..][0..size]) catch |inner_err| {
|
||||||
|
err.* = inner_err;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn fragThread(self: Extractor, map_data: []u8) error{Canceled}!void {
|
fn fragThread(self: Extractor, map_data: []u8) error{Canceled}!void {
|
||||||
const size = self.size % self.block_size;
|
const size = self.size % self.block_size;
|
||||||
@@ -100,4 +116,4 @@ fn fragThread(self: Extractor, map_data: []u8) error{Canceled}!void {
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
|
||||||
const Error = Io.File.WritePositionalError || Io.File.MemoryMap.CreateError || Decomp.Error;
|
pub const Error = Io.File.WritePositionalError || Io.File.MemoryMap.CreateError || Decomp.Error || Cache.Error;
|
||||||
|
|||||||
+14
-3
@@ -3,6 +3,7 @@ const Io = std.Io;
|
|||||||
|
|
||||||
const Decomp = @import("../decomp.zig");
|
const Decomp = @import("../decomp.zig");
|
||||||
const DataBlock = @import("../inode.zig").DataBlock;
|
const DataBlock = @import("../inode.zig").DataBlock;
|
||||||
|
const Cache = @import("../util/cache.zig");
|
||||||
|
|
||||||
const Reader = @This();
|
const Reader = @This();
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ sparse_block: bool = false,
|
|||||||
frag_data: ?[]u8 = null,
|
frag_data: ?[]u8 = null,
|
||||||
frag_offset: u32 = 0,
|
frag_offset: u32 = 0,
|
||||||
|
|
||||||
|
cache: ?Cache = null,
|
||||||
|
|
||||||
block: [1024 * 1024]u8 = undefined,
|
block: [1024 * 1024]u8 = undefined,
|
||||||
|
|
||||||
interface: Io.Reader = .{
|
interface: Io.Reader = .{
|
||||||
@@ -55,6 +58,9 @@ pub fn addFrag(self: *Reader, frag_data: []u8, frag_offset: u32) void {
|
|||||||
self.frag_data = frag_data;
|
self.frag_data = frag_data;
|
||||||
self.frag_offset = frag_offset;
|
self.frag_offset = frag_offset;
|
||||||
}
|
}
|
||||||
|
pub fn addCache(self: *Reader, cache: Cache) void {
|
||||||
|
self.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
fn advance(self: *Reader) Io.Reader.Error!void {
|
fn advance(self: *Reader) Io.Reader.Error!void {
|
||||||
if (self.block_idx > self.blocks.len) return error.EndOfStream;
|
if (self.block_idx > self.blocks.len) return error.EndOfStream;
|
||||||
@@ -98,9 +104,14 @@ fn advance(self: *Reader) Io.Reader.Error!void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = self.decomp(self.alloc, self.data[self.offset..][0..block.size], self.block[0..size]) catch return error.ReadFailed;
|
if (self.cache == null) {
|
||||||
self.interface.buffer = self.block[0..size];
|
_ = self.decomp(self.alloc, self.data[self.offset..][0..block.size], self.block[0..size]) catch return error.ReadFailed;
|
||||||
self.interface.end = size;
|
self.interface.buffer = self.block[0..size];
|
||||||
|
self.interface.end = size;
|
||||||
|
} else {
|
||||||
|
self.interface.buffer = self.cache.?.get(self.io, self.offset, block.size) catch return error.ReadFailed;
|
||||||
|
self.interface.end = self.interface.buffer.len;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
|
||||||
|
pub const Error = error{OutOfMemory} || Reader.Error;
|
||||||
|
|
||||||
pub const Entry = struct {
|
pub const Entry = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
block_start: u32,
|
block_start: u32,
|
||||||
|
|||||||
+198
-6
@@ -1,11 +1,15 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const DataExtractor = @import("data/extractor.zig");
|
||||||
const Decomp = @import("decomp.zig");
|
const Decomp = @import("decomp.zig");
|
||||||
|
const Directory = @import("directory.zig");
|
||||||
const ExtractionOptions = @import("options.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const Lookup = @import("lookup.zig");
|
const Lookup = @import("lookup.zig");
|
||||||
|
const MetadataReader = @import("meta_rdr.zig");
|
||||||
const Superblock = @import("archive.zig").Superblock;
|
const Superblock = @import("archive.zig").Superblock;
|
||||||
|
const Cache = @import("util/cache.zig");
|
||||||
const XattrTable = @import("xattr.zig");
|
const XattrTable = @import("xattr.zig");
|
||||||
|
|
||||||
pub fn extract(
|
pub fn extract(
|
||||||
@@ -17,16 +21,204 @@ pub fn extract(
|
|||||||
inode: Inode,
|
inode: Inode,
|
||||||
ext_loc: []const u8,
|
ext_loc: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
) !void {}
|
) !void {
|
||||||
|
const path = std.mem.trim(ext_loc, "/");
|
||||||
|
|
||||||
pub fn extractReal(
|
var id_table: Lookup.Table(u16) = .init(alloc, data, decomp, super.id_start, super.id_count);
|
||||||
|
defer id_table.deinit();
|
||||||
|
|
||||||
|
var xattr_table: XattrTable = .init(alloc, data, decomp, super.xattr_start);
|
||||||
|
defer xattr_table.deinit();
|
||||||
|
|
||||||
|
var buf: [150]ReturnUnion = undefined;
|
||||||
|
var sel: Io.Select(ReturnUnion) = .init(io, &buf);
|
||||||
|
defer while (sel.cancel()) |res|
|
||||||
|
switch (res) {
|
||||||
|
.path => |p| {
|
||||||
|
const path_return = p catch continue;
|
||||||
|
if (path_return.path.len != path.len)
|
||||||
|
alloc.free(path_return.path);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
var loop = io.async(io, finishLoop, .{ alloc, io, &sel, &id_table, &xattr_table, options });
|
||||||
|
|
||||||
|
var cache: Cache = .init(alloc, data, decomp);
|
||||||
|
defer cache.deinit();
|
||||||
|
|
||||||
|
var frag_table: Lookup.Table(Lookup.FragEntry) = .init(alloc, data, decomp, super.frag_start, super.frag_count);
|
||||||
|
defer frag_table.deinit();
|
||||||
|
|
||||||
|
switch (inode.hdr.type) {
|
||||||
|
.file, .ext_file => try sel.async(
|
||||||
|
.path,
|
||||||
|
extractFile,
|
||||||
|
.{ alloc, io, data, decomp, &cache, &frag_table, inode, path, true },
|
||||||
|
),
|
||||||
|
.dir, .ext_dir => try sel.async(
|
||||||
|
.path,
|
||||||
|
extractDir,
|
||||||
|
.{ alloc, io, super, data, decomp, &sel, &cache, &frag_table, inode, path, true },
|
||||||
|
),
|
||||||
|
.symlink, .ext_symlink => try sel.async(
|
||||||
|
.void,
|
||||||
|
extractSymlink,
|
||||||
|
.{ alloc, io, inode, path, true },
|
||||||
|
),
|
||||||
|
else => try sel.async(
|
||||||
|
.path,
|
||||||
|
extractNod,
|
||||||
|
.{ alloc, io, inode, path, true },
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
try loop.await(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finishLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xattr_table: *XattrTable, options: ExtractionOptions) !void {}
|
||||||
|
|
||||||
|
fn extractFile(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
io: Io,
|
||||||
|
data: []u8,
|
||||||
|
decomp: Decomp.Fn,
|
||||||
|
cache: *Cache,
|
||||||
|
frag_table: *Lookup.Table(Lookup.FragEntry),
|
||||||
|
inode: Inode,
|
||||||
|
path: []const u8,
|
||||||
|
origin: bool,
|
||||||
|
) Error!PathReturn {
|
||||||
|
defer if (!origin) inode.deinit(alloc);
|
||||||
|
errdefer alloc.free(path);
|
||||||
|
|
||||||
|
try io.checkCancel();
|
||||||
|
|
||||||
|
var ret: PathReturn = .{
|
||||||
|
.hdr = inode.hdr,
|
||||||
|
.path = path,
|
||||||
|
};
|
||||||
|
|
||||||
|
var ext: DataExtractor = switch (inode.data) {
|
||||||
|
.file => |f| blk: {
|
||||||
|
var rdr: DataExtractor = .init(data, decomp, super.block_size, f.blocks, f.size, f.block_start);
|
||||||
|
rdr.addCache(cache);
|
||||||
|
|
||||||
|
if (f.frag_idx == 0xFFFFFFFF) break :blk rdr;
|
||||||
|
|
||||||
|
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
|
||||||
|
if (entry.size.uncompressed) {
|
||||||
|
rdr.addFrag(data[entry.block_start..][0..entry.size.size]);
|
||||||
|
} else {
|
||||||
|
rdr.addFrag(try cache.get(io, entry.block_start, entry.size));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.ext_file => |f| blk: {
|
||||||
|
if (f.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = f.xattr_idx;
|
||||||
|
|
||||||
|
var rdr: DataExtractor = .init(data, decomp, super.block_size, f.blocks, f.size, f.block_start);
|
||||||
|
rdr.addCache(cache);
|
||||||
|
|
||||||
|
if (f.frag_idx == 0xFFFFFFFF) break :blk rdr;
|
||||||
|
|
||||||
|
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
|
||||||
|
if (entry.size.uncompressed) {
|
||||||
|
rdr.addFrag(data[entry.block_start..][0..entry.size.size]);
|
||||||
|
} else {
|
||||||
|
rdr.addFrag(try cache.get(io, entry.block_start, entry.size));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{});
|
||||||
|
defer atomic.deinit(io);
|
||||||
|
|
||||||
|
try ext.extractAsync(alloc, io, atomic.file);
|
||||||
|
|
||||||
|
try atomic.link(io);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
fn extractDir(
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
io: Io,
|
io: Io,
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
data: []u8,
|
data: []u8,
|
||||||
decomp: Decomp.Fn,
|
decomp: Decomp.Fn,
|
||||||
frag_table: Lookup.Table(u64),
|
sel: *Io.Select(ReturnUnion),
|
||||||
|
cache: *Cache,
|
||||||
|
frag_table: *Lookup.Table(Lookup.FragEntry),
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
ext_loc: []const u8,
|
path: []const u8,
|
||||||
options: ExtractionOptions,
|
origin: bool,
|
||||||
) !void {}
|
) Error!PathReturn {
|
||||||
|
defer if (!origin) inode.deinit(alloc);
|
||||||
|
errdefer alloc.free(path);
|
||||||
|
|
||||||
|
var ret: PathReturn = .{
|
||||||
|
.hdr = inode.hdr,
|
||||||
|
.path = path,
|
||||||
|
};
|
||||||
|
|
||||||
|
try Io.Dir.cwd().createDirPath(io, path);
|
||||||
|
|
||||||
|
var dir: Directory = switch (inode.data) {
|
||||||
|
.dir => |d| blk: {
|
||||||
|
var meta: MetadataReader = .init(alloc, data, decomp, d.block_start);
|
||||||
|
try meta.interface.discardAll(d.block_offset);
|
||||||
|
|
||||||
|
break :blk Directory.init(alloc, &meta.interface, d.size);
|
||||||
|
},
|
||||||
|
.ext_dir => |d| blk: {
|
||||||
|
if (d.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = d.xattr_idx;
|
||||||
|
|
||||||
|
var meta: MetadataReader = .init(alloc, data, decomp, d.block_start);
|
||||||
|
try meta.interface.discardAll(d.block_offset);
|
||||||
|
|
||||||
|
break :blk Directory.init(alloc, &meta.interface, d.size);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
defer dir.deinit(alloc);
|
||||||
|
|
||||||
|
for (dir.entries) |entry| {
|
||||||
|
var new_inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry);
|
||||||
|
|
||||||
|
const new_path = std.mem.concat(alloc, &.{ path, "/", entry.name }) catch |err| {
|
||||||
|
new_inode.deinit(alloc);
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
sel.async();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn extractSymlink(alloc: std.mem.Allocator, io: Io, inode: Inode, path: []const u8, origin: bool) Error!void {
|
||||||
|
defer 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, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
const ReturnUnion = union(enum) {
|
||||||
|
path: Error!PathReturn,
|
||||||
|
dir: Error!PathReturn,
|
||||||
|
void: Error!void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Error = error{} || Decomp.Error || Directory.Error || DataExtractor.Error;
|
||||||
|
|
||||||
|
const PathReturn = struct {
|
||||||
|
hdr: Inode.Header,
|
||||||
|
path: []const u8,
|
||||||
|
xattr_idx: ?u32 = null,
|
||||||
|
};
|
||||||
|
|||||||
+201
-6
@@ -1,11 +1,15 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const DataReader = @import("data/reader.zig");
|
||||||
const Decomp = @import("decomp.zig");
|
const Decomp = @import("decomp.zig");
|
||||||
|
const Directory = @import("directory.zig");
|
||||||
const ExtractionOptions = @import("options.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const Lookup = @import("lookup.zig");
|
const Lookup = @import("lookup.zig");
|
||||||
|
const MetadaReader = @import("meta_rdr.zig");
|
||||||
const Superblock = @import("archive.zig").Superblock;
|
const Superblock = @import("archive.zig").Superblock;
|
||||||
|
const Cache = @import("util/cache.zig");
|
||||||
const XattrTable = @import("xattr.zig");
|
const XattrTable = @import("xattr.zig");
|
||||||
|
|
||||||
pub fn extract(
|
pub fn extract(
|
||||||
@@ -17,7 +21,37 @@ pub fn extract(
|
|||||||
inode: Inode,
|
inode: Inode,
|
||||||
ext_loc: []const u8,
|
ext_loc: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
) !void {}
|
) !void {
|
||||||
|
const path = std.mem.trim(ext_loc, "/");
|
||||||
|
|
||||||
|
var cache: Cache = .init(alloc, data, decomp);
|
||||||
|
defer cache.deinit();
|
||||||
|
|
||||||
|
var frag_table: Lookup.Table(Lookup.FragEntry) = .init(alloc, data, decomp, super.frag_start, super.frag_count);
|
||||||
|
defer frag_table.deinit();
|
||||||
|
|
||||||
|
var id_table: Lookup.Table(u16) = .init(alloc, data, decomp, super.id_start, super.id_count);
|
||||||
|
defer id_table.deinit();
|
||||||
|
|
||||||
|
var xattr_table: XattrTable = .init(alloc, data, decomp, super.xattr_start);
|
||||||
|
defer xattr_table.deinit();
|
||||||
|
|
||||||
|
return extractReal(
|
||||||
|
alloc,
|
||||||
|
io,
|
||||||
|
super,
|
||||||
|
data,
|
||||||
|
decomp,
|
||||||
|
&cache,
|
||||||
|
&frag_table,
|
||||||
|
&id_table,
|
||||||
|
&xattr_table,
|
||||||
|
inode,
|
||||||
|
path,
|
||||||
|
options,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extractReal(
|
pub fn extractReal(
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
@@ -25,10 +59,171 @@ pub fn extractReal(
|
|||||||
super: Superblock,
|
super: Superblock,
|
||||||
data: []u8,
|
data: []u8,
|
||||||
decomp: Decomp.Fn,
|
decomp: Decomp.Fn,
|
||||||
frag_table: Lookup.Table(u64),
|
cache: *Cache,
|
||||||
id_table: Lookup.Table(u16),
|
frag_table: *Lookup.Table(Lookup.FragEntry),
|
||||||
xattr_table: XattrTable,
|
id_table: *Lookup.Table(u16),
|
||||||
|
xattr_table: *XattrTable,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
ext_loc: []const u8,
|
path: []const u8,
|
||||||
options: ExtractionOptions,
|
options: ExtractionOptions,
|
||||||
) !void {}
|
origin: bool,
|
||||||
|
) !void {
|
||||||
|
defer if (!origin) {
|
||||||
|
alloc.free(path);
|
||||||
|
inode.deinit(alloc);
|
||||||
|
};
|
||||||
|
|
||||||
|
var xattr_idx: ?u32 = null;
|
||||||
|
|
||||||
|
switch (inode.data) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
try Io.Dir.cwd().createDirPath(io, path);
|
||||||
|
|
||||||
|
var dir: Directory = switch (inode.data) {
|
||||||
|
.dir => |d| blk: {
|
||||||
|
var meta: MetadaReader = .init(alloc, data, decomp, d.block_start);
|
||||||
|
try meta.interface.discardAll(d.block_offset);
|
||||||
|
|
||||||
|
break :blk Directory.init(alloc, &meta.interface, d.size);
|
||||||
|
},
|
||||||
|
.ext_dir => |d| blk: {
|
||||||
|
if (d.xattr_idx != 0xFFFFFFFF) xattr_idx = d.xattr_idx;
|
||||||
|
|
||||||
|
var meta: MetadaReader = .init(alloc, data, decomp, d.block_start);
|
||||||
|
try meta.interface.discardAll(d.block_offset);
|
||||||
|
|
||||||
|
break :blk Directory.init(alloc, &meta.interface, d.size);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
defer dir.deinit(alloc);
|
||||||
|
|
||||||
|
for (dir.entries) |entry| {
|
||||||
|
var new_inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry);
|
||||||
|
|
||||||
|
const new_path = std.mem.concat(alloc, &.{ path, "/", entry.name }) catch |err| {
|
||||||
|
new_inode.deinit(alloc);
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
try extractReal(alloc, io, super, data, decomp, cache, frag_table, id_table, xattr_table, new_inode, new_path, options, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.file, .ext_file => {
|
||||||
|
var rdr: DataReader = switch (inode.data) {
|
||||||
|
.file => |f| blk: {
|
||||||
|
var rdr: DataReader = .init(alloc, data, decomp, super.block_size, f.blocks, f.size, f.block_start);
|
||||||
|
rdr.addCache(cache);
|
||||||
|
|
||||||
|
if (f.frag_idx == 0xFFFFFFFF) break :blk rdr;
|
||||||
|
|
||||||
|
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
|
||||||
|
if (entry.size.uncompressed) {
|
||||||
|
rdr.addFrag(data[entry.block_start..][0..entry.size.size]);
|
||||||
|
} else {
|
||||||
|
rdr.addFrag(try cache.get(io, entry.block_start, entry.size));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.ext_file => |f| blk: {
|
||||||
|
if (f.xattr_idx != 0xFFFFFFFF) xattr_idx = f.xattr_idx;
|
||||||
|
|
||||||
|
var rdr: DataReader = .init(alloc, data, decomp, super.block_size, f.blocks, f.size, f.block_start);
|
||||||
|
rdr.addCache(cache);
|
||||||
|
|
||||||
|
if (f.frag_idx == 0xFFFFFFFF) break :blk rdr;
|
||||||
|
|
||||||
|
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
|
||||||
|
if (entry.size.uncompressed) {
|
||||||
|
rdr.addFrag(data[entry.block_start..][0..entry.size.size]);
|
||||||
|
} else {
|
||||||
|
rdr.addFrag(try cache.get(io, entry.block_start, entry.size));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{});
|
||||||
|
defer atomic.deinit(io);
|
||||||
|
|
||||||
|
var writer = atomic.file.writer(io, &[0]u8{});
|
||||||
|
_ = try rdr.interface.streamRemaining(&writer.interface);
|
||||||
|
try writer.flush();
|
||||||
|
|
||||||
|
try atomic.link(io);
|
||||||
|
},
|
||||||
|
.symlink => |s| return Io.Dir.cwd().symLink(io, s.target, path, .{}),
|
||||||
|
.ext_symlink => |s| return Io.Dir.cwd().symLink(io, s.target, path, .{}),
|
||||||
|
else => {
|
||||||
|
var dev: u32 = 0;
|
||||||
|
var mode: u32 = undefined;
|
||||||
|
|
||||||
|
const DT = std.posix.DT;
|
||||||
|
|
||||||
|
switch (inode.data) {
|
||||||
|
.char_dev => |d| {
|
||||||
|
dev = d.device;
|
||||||
|
mode = DT.CHR;
|
||||||
|
},
|
||||||
|
.block_dev => |d| {
|
||||||
|
dev = d.device;
|
||||||
|
mode = DT.BLK;
|
||||||
|
},
|
||||||
|
.ext_char_dev => |d| {
|
||||||
|
if (d.xattr_idx != 0xFFFFFFFF) xattr_idx = d.xattr_idx;
|
||||||
|
|
||||||
|
dev = d.device;
|
||||||
|
mode = DT.CHR;
|
||||||
|
},
|
||||||
|
.ext_block_dev => |d| {
|
||||||
|
if (d.xattr_idx != 0xFFFFFFFF) xattr_idx = d.xattr_idx;
|
||||||
|
|
||||||
|
dev = d.device;
|
||||||
|
mode = DT.BLK;
|
||||||
|
},
|
||||||
|
.fifo => mode = DT.FIFO,
|
||||||
|
.socket => mode = DT.SOCK,
|
||||||
|
.ext_fifo => |i| {
|
||||||
|
if (i.xattr_idx != 0xFFFFFFFF) xattr_idx = i.xattr_idx;
|
||||||
|
|
||||||
|
mode = DT.FIFO;
|
||||||
|
},
|
||||||
|
.ext_socket => |i| {
|
||||||
|
if (i.xattr_idx != 0xFFFFFFFF) xattr_idx = i.xattr_idx;
|
||||||
|
|
||||||
|
mode = DT.SOCK;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
const sentinel_path = try alloc.dupeSentinel(u8, path, 0);
|
||||||
|
defer alloc.free(sentinel_path);
|
||||||
|
|
||||||
|
const res = std.os.linux.mknod(sentinel_path, mode, dev);
|
||||||
|
if (res != 0)
|
||||||
|
return error.MknodError;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ignore_permissions and (options.ignore_xattr or xattr_idx == null)) return;
|
||||||
|
|
||||||
|
var fil: Io.File = try Io.Dir.cwd().openFile(io, path, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.setTimestamps(io, .{
|
||||||
|
.modify_timestamp = .init(Io.Timestamp.fromNanoseconds(@as(i96, @intCast(inode.hdr.mod_time)) * std.time.ns_per_s)),
|
||||||
|
});
|
||||||
|
try fil.setPermissions(io, @enumFromInt(inode.hdr.permissions));
|
||||||
|
try fil.setOwner(io, try id_table.get(io, inode.hdr.uid_idx), try id_table.get(io, inode.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
if (!options.ignore_xattr and xattr_idx != null) {
|
||||||
|
const xattr = try xattr_table.get(alloc, io, xattr_idx.?);
|
||||||
|
defer xattr.deinit(alloc);
|
||||||
|
|
||||||
|
for (xattr.kvs) |kv| {
|
||||||
|
const res = std.os.linux.fsetxattr(fil.handle, kv.key, kv.value, kv.value.len, 0);
|
||||||
|
if (res != 0)
|
||||||
|
return error.SetXattrError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const DataBlock = @import("inode.zig").DataBlock;
|
||||||
const Decomp = @import("decomp.zig");
|
const Decomp = @import("decomp.zig");
|
||||||
const MetadataReader = @import("meta_rdr.zig");
|
const MetadataReader = @import("meta_rdr.zig");
|
||||||
const ProtectedMap = @import("util/protected_map.zig");
|
const ProtectedMap = @import("util/protected_map.zig");
|
||||||
@@ -57,3 +58,11 @@ pub fn Table(comptime T: anytype) type {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
pub const FragEntry = extern struct {
|
||||||
|
block_start: u64,
|
||||||
|
size: DataBlock,
|
||||||
|
_: u32,
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Decomp = @import("../decomp.zig");
|
||||||
|
|
||||||
|
const CacheMap = @import("protected_map.zig").ProtectedMap(u64, CachedBlock, decompressBlock);
|
||||||
|
|
||||||
|
const Cache = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
data: []u8,
|
||||||
|
decomp: Decomp.Fn,
|
||||||
|
|
||||||
|
cache: CacheMap,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn) Cache {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.data = data,
|
||||||
|
.decomp = decomp,
|
||||||
|
|
||||||
|
.cache = .init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Cache) void {
|
||||||
|
self.cache.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Cache, io: Io, offset: u64, compressed_size: u32) Error![]u8 {
|
||||||
|
const res: CachedBlock = try self.cache.getOrPut(io, offset, .{ self.alloc, self.data, self.decomp, offset, compressed_size });
|
||||||
|
return res.block[0..res.size];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompressBlock(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, offset: u64, compressed_size: u32) !CachedBlock {
|
||||||
|
const block = data[offset..][0..compressed_size];
|
||||||
|
var new_cache: CachedBlock = undefined;
|
||||||
|
|
||||||
|
new_cache.size = @truncate(try decomp(alloc, block, &new_cache.block));
|
||||||
|
|
||||||
|
return new_cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
pub const Error = CacheMap.Error;
|
||||||
|
|
||||||
|
const CachedBlock = struct {
|
||||||
|
block: [1024 * 1024]u8,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
@@ -28,7 +28,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
|
|||||||
self.map.deinit();
|
self.map.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOrPut(self: *Map, io: Io, key: K, create_fn_args: std.meta.ArgsTuple(create_fn)) !*T {
|
pub fn getOrPut(self: *Map, io: Io, key: K, create_fn_args: std.meta.ArgsTuple(create_fn)) Error!*T {
|
||||||
{
|
{
|
||||||
try self.mut.lockShared(io);
|
try self.mut.lockShared(io);
|
||||||
defer self.mut.unlockShared(io);
|
defer self.mut.unlockShared(io);
|
||||||
@@ -70,6 +70,9 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
|
|||||||
|
|
||||||
// Map Types
|
// Map Types
|
||||||
|
|
||||||
|
pub const Error = error{ Canceled, OutOfMemory } ||
|
||||||
|
if (@TypeOf(CreateError) == void) error{} else CreateError;
|
||||||
|
|
||||||
const CreateError: type = switch (@typeInfo(@typeInfo(create_fn).@"fn".return_type)) {
|
const CreateError: type = switch (@typeInfo(@typeInfo(create_fn).@"fn".return_type)) {
|
||||||
.error_union => |e| e.error_set,
|
.error_union => |e| e.error_set,
|
||||||
else => void,
|
else => void,
|
||||||
|
|||||||
+129
@@ -1,3 +1,132 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Decomp = @import("decomp.zig");
|
||||||
|
const Lookup = @import("lookup.zig");
|
||||||
|
const MetadataReader = @import("meta_rdr.zig");
|
||||||
|
const Ref = @import("inode.zig").Ref;
|
||||||
|
|
||||||
const XattrTable = @This();
|
const XattrTable = @This();
|
||||||
|
|
||||||
|
table_start: u64,
|
||||||
|
|
||||||
|
table: Lookup.Table(Entry),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64) XattrTable {
|
||||||
|
const start = std.mem.readInt(u64, data[table_start..][0..8], .little);
|
||||||
|
const table_num = std.mem.readInt(u32, data[table_start + 8 ..][0..4], .little);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.table_start = start,
|
||||||
|
.table = .init(alloc, data, decomp, table_start + 16, table_num),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *XattrTable) void {
|
||||||
|
self.table.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !Xattr {
|
||||||
|
const entry: Entry = try self.table.get(io, idx);
|
||||||
|
|
||||||
|
const out: std.ArrayList(KeyValue) = try .initCapacity(alloc, entry.count);
|
||||||
|
errdefer {
|
||||||
|
for (out.items) |kv|
|
||||||
|
kv.deinit(alloc);
|
||||||
|
out.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta: MetadataReader = .init(alloc, self.table.data, self.table.decomp, self.table_start + entry.ref.block_start);
|
||||||
|
try meta.interface.discardAll(entry.ref.block_offset);
|
||||||
|
|
||||||
|
for (0..entry.count) |_| {
|
||||||
|
var key_entry: KeyEntry = undefined;
|
||||||
|
|
||||||
|
try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little);
|
||||||
|
|
||||||
|
const prefix = switch (key_entry.type.type) {
|
||||||
|
.user => "user.",
|
||||||
|
.trusted => "trusted.",
|
||||||
|
.security => "security.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = try alloc.allocSentinel(u8, key_entry.name_size + prefix.len, 0);
|
||||||
|
errdefer alloc.free(key);
|
||||||
|
|
||||||
|
@memcpy(key[0..prefix.len], prefix);
|
||||||
|
|
||||||
|
try meta.interface.readSliceEndian(u8, key[prefix.len..][0..key_entry.name_size], .little);
|
||||||
|
|
||||||
|
const value = if (key_entry.type.out_of_line) blk: {
|
||||||
|
try meta.interface.discardAll(4);
|
||||||
|
|
||||||
|
var ref: Ref = undefined;
|
||||||
|
try meta.interface.readSliceEndian(Ref, @ptrCast(&ref), .little);
|
||||||
|
|
||||||
|
var value_meta: MetadataReader = .init(alloc, self.table.data, self.table.decomp, self.table_start + ref.block_start);
|
||||||
|
try value_meta.interface.discardAll(ref.block_offset);
|
||||||
|
|
||||||
|
break :blk try readValue(alloc, &value_meta.interface);
|
||||||
|
} else try readValue(alloc, &meta.interface);
|
||||||
|
|
||||||
|
const ptr = out.addOneAssumeCapacity();
|
||||||
|
|
||||||
|
ptr.* = .{
|
||||||
|
.key = key,
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readValue(alloc: std.mem.Allocator, rdr: *Io.Reader) ![]u8 {
|
||||||
|
var size: u32 = undefined;
|
||||||
|
try rdr.readSliceEndian(u32, @ptrCast(&size), .little);
|
||||||
|
|
||||||
|
const value = try alloc.alloc(u8, size);
|
||||||
|
errdefer alloc.free(value);
|
||||||
|
|
||||||
|
try rdr.readSliceEndian(u8, value, .little);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
const Xattr = struct {
|
||||||
|
kvs: []KeyValue,
|
||||||
|
|
||||||
|
pub fn deinit(self: Xattr, alloc: std.mem.Allocator) void {
|
||||||
|
for (self.kvs) |kv|
|
||||||
|
kv.deinit(alloc);
|
||||||
|
|
||||||
|
alloc.free(self.kvs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const KeyValue = struct {
|
||||||
|
key: [:0]const u8,
|
||||||
|
value: []const u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: KeyValue, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.key);
|
||||||
|
alloc.free(self.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const KeyEntry = extern struct {
|
||||||
|
type: packed struct(u16) {
|
||||||
|
type: enum(u8) {
|
||||||
|
user,
|
||||||
|
trusted,
|
||||||
|
security,
|
||||||
|
},
|
||||||
|
out_of_line: bool,
|
||||||
|
_: u7,
|
||||||
|
},
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Entry = extern struct {
|
||||||
|
ref: Ref,
|
||||||
|
count: u32,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user