From 8a9ff7bf5f298b44fbefba1f59e21a2fc61d9840 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 13 Jun 2026 06:06:27 -0500 Subject: [PATCH] Finished work (theoretically) on single threaded extraction Started work on Multi-threaded extraction Added decompression cache. Added XattrTable --- build.zig | 2 + src/bin/unsquashfs.zig | 50 +++++---- src/data/extractor.zig | 26 ++++- src/data/reader.zig | 17 ++- src/directory.zig | 2 + src/extract-multi.zig | 204 ++++++++++++++++++++++++++++++++++-- src/extract-single.zig | 207 +++++++++++++++++++++++++++++++++++-- src/lookup.zig | 9 ++ src/util/cache.zig | 52 ++++++++++ src/util/protected_map.zig | 5 +- src/xattr.zig | 129 +++++++++++++++++++++++ 11 files changed, 664 insertions(+), 39 deletions(-) create mode 100644 src/util/cache.zig diff --git a/build.zig b/build.zig index 8af5339..bb21525 100644 --- a/build.zig +++ b/build.zig @@ -15,6 +15,7 @@ pub fn build(b: *std.Build) !void { const build_options = b.addOptions(); build_options.addOption(bool, "use_zig_decomp", use_zig_decomp); build_options.addOption(bool, "allow_lzo", allow_lzo); + build_options.addOption(std.SemanticVersion, "version", version); const c = b.addTranslateC(.{ .optimize = optimize, @@ -79,6 +80,7 @@ pub fn build(b: *std.Build) !void { .valgrind = debug, .imports = &.{ .{ .name = "squashfs", .module = lib.root_module }, + .{ .name = "build_options", .module = build_options.createModule() }, }, }), }); diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig index a945c01..4b9ee4c 100644 --- a/src/bin/unsquashfs.zig +++ b/src/bin/unsquashfs.zig @@ -1,9 +1,10 @@ const std = @import("std"); -const Writer = std.Io.Writer; +const Io = std.Io; +const Writer = Io.Writer; const builtin = @import("builtin"); +const build = @import("build_options"); -const config = @import("config"); -const squashfs = @import("zig_squashfs"); +const squashfs = @import("squashfs"); //TODO: Add more options const help_mgs = @@ -30,7 +31,7 @@ const help_mgs = const errors = error{InvalidArguments}; var archive: []const u8 = ""; -var extLoc: []const u8 = "squashfs-root"; +var ext_loc: []const u8 = "squashfs-root"; var offset: u64 = 0; var threads: u32 = 0; var verbose: bool = false; @@ -38,35 +39,48 @@ var ignore_xattrs: bool = false; var ignore_permissions: bool = false; var force: bool = false; -pub fn main() !void { - const alloc = std.heap.smp_allocator; - var stdout = std.fs.File.stdout(); - var out = stdout.writer(&[0]u8{}); +pub fn main(init: std.process.Init) !void { + const io = init.io; + const alloc = init.alloc; + + var stdout = Io.File.stdout(); + var out = stdout.writer(io, &[0]u8{}); defer out.interface.flush() catch {}; - try handleArgs(alloc, &out.interface); + + try handleArgs(init.minimal.args, &out.interface); if (archive.len == 0) { try out.interface.print("You must provide a squashfs archive\n", .{}); try out.interface.print(help_mgs, .{}); 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(); - 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(); const options: squashfs.ExtractionOptions = .{ - .threads = if (threads == 0) try std.Thread.getCpuCount() else threads, + .single_threaded = threads == 1, .verbose = verbose, .verbose_writer = if (verbose) &out.interface else null, .ignore_xattr = ignore_xattrs, .ignore_permissions = ignore_permissions, }; if (force) - try std.fs.cwd().deleteTree(extLoc); - try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully. + try std.fs.cwd().deleteTree(ext_loc); + 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 { - var args = try std.process.argsWithAllocator(alloc); +fn handleArgs(main_args: std.process.Args, out: *Writer) !void { + var args = main_args.iterate(); defer args.deinit(); _ = args.next(); // args[0] is the application launch command. 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", .{}); return errors.InvalidArguments; } - extLoc = nxt.?; + ext_loc = nxt.?; continue; } else if (std.mem.eql(u8, arg, "-p")) { const nxt = args.next(); @@ -114,7 +128,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { continue; } else if (std.mem.eql(u8, arg, "--version")) { 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 }); std.process.exit(0); return; diff --git a/src/data/extractor.zig b/src/data/extractor.zig index daf3e27..a635b67 100644 --- a/src/data/extractor.zig +++ b/src/data/extractor.zig @@ -3,6 +3,7 @@ const Io = std.Io; const Decomp = @import("../decomp.zig"); const DataBlock = @import("../inode.zig").DataBlock; +const Cache = @import("../util/cache.zig"); const Extractor = @This(); @@ -18,6 +19,8 @@ size: u64, frag_data: ?[]u8 = null, 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 { return .{ .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_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 { if (self.size == 0) return; @@ -86,10 +92,20 @@ fn blockThread(self: Extractor, alloc: std.mem.Allocator, map_data: []u8, read_o return; } - _ = self.decomp(alloc, data, map_data[offset..][0..size]) catch |inner_err| { - err.* = inner_err; - return; - }; + if (self.cache != null) { + const decomp_block = self.cache.?.get(read_offset, block.size) catch |inner_err| { + 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 { const size = self.size % self.block_size; @@ -100,4 +116,4 @@ fn fragThread(self: Extractor, map_data: []u8) error{Canceled}!void { // 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; diff --git a/src/data/reader.zig b/src/data/reader.zig index e48473e..f2592f8 100644 --- a/src/data/reader.zig +++ b/src/data/reader.zig @@ -3,6 +3,7 @@ const Io = std.Io; const Decomp = @import("../decomp.zig"); const DataBlock = @import("../inode.zig").DataBlock; +const Cache = @import("../util/cache.zig"); const Reader = @This(); @@ -23,6 +24,8 @@ sparse_block: bool = false, frag_data: ?[]u8 = null, frag_offset: u32 = 0, +cache: ?Cache = null, + block: [1024 * 1024]u8 = undefined, 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_offset = frag_offset; } +pub fn addCache(self: *Reader, cache: Cache) void { + self.cache = cache; +} fn advance(self: *Reader) Io.Reader.Error!void { if (self.block_idx > self.blocks.len) return error.EndOfStream; @@ -98,9 +104,14 @@ fn advance(self: *Reader) Io.Reader.Error!void { return; } - _ = self.decomp(self.alloc, self.data[self.offset..][0..block.size], self.block[0..size]) catch return error.ReadFailed; - self.interface.buffer = self.block[0..size]; - self.interface.end = size; + if (self.cache == null) { + _ = self.decomp(self.alloc, self.data[self.offset..][0..block.size], self.block[0..size]) catch return error.ReadFailed; + 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 { diff --git a/src/directory.zig b/src/directory.zig index ea69d26..b527452 100644 --- a/src/directory.zig +++ b/src/directory.zig @@ -58,6 +58,8 @@ pub fn deinit(self: Directory, alloc: std.mem.Allocator) void { // Types +pub const Error = error{OutOfMemory} || Reader.Error; + pub const Entry = struct { name: []const u8, block_start: u32, diff --git a/src/extract-multi.zig b/src/extract-multi.zig index ad7b9bb..094648d 100644 --- a/src/extract-multi.zig +++ b/src/extract-multi.zig @@ -1,11 +1,15 @@ const std = @import("std"); const Io = std.Io; +const DataExtractor = @import("data/extractor.zig"); const Decomp = @import("decomp.zig"); +const Directory = @import("directory.zig"); const ExtractionOptions = @import("options.zig"); const Inode = @import("inode.zig"); const Lookup = @import("lookup.zig"); +const MetadataReader = @import("meta_rdr.zig"); const Superblock = @import("archive.zig").Superblock; +const Cache = @import("util/cache.zig"); const XattrTable = @import("xattr.zig"); pub fn extract( @@ -17,16 +21,204 @@ pub fn extract( inode: Inode, ext_loc: []const u8, 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, io: Io, super: Superblock, data: []u8, decomp: Decomp.Fn, - frag_table: Lookup.Table(u64), + sel: *Io.Select(ReturnUnion), + cache: *Cache, + frag_table: *Lookup.Table(Lookup.FragEntry), inode: Inode, - ext_loc: []const u8, - options: ExtractionOptions, -) !void {} + path: []const u8, + origin: bool, +) 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, +}; diff --git a/src/extract-single.zig b/src/extract-single.zig index e88758f..c5071d9 100644 --- a/src/extract-single.zig +++ b/src/extract-single.zig @@ -1,11 +1,15 @@ const std = @import("std"); const Io = std.Io; +const DataReader = @import("data/reader.zig"); const Decomp = @import("decomp.zig"); +const Directory = @import("directory.zig"); const ExtractionOptions = @import("options.zig"); const Inode = @import("inode.zig"); const Lookup = @import("lookup.zig"); +const MetadaReader = @import("meta_rdr.zig"); const Superblock = @import("archive.zig").Superblock; +const Cache = @import("util/cache.zig"); const XattrTable = @import("xattr.zig"); pub fn extract( @@ -17,7 +21,37 @@ pub fn extract( inode: Inode, ext_loc: []const u8, 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( alloc: std.mem.Allocator, @@ -25,10 +59,171 @@ pub fn extractReal( super: Superblock, data: []u8, decomp: Decomp.Fn, - frag_table: Lookup.Table(u64), - id_table: Lookup.Table(u16), - xattr_table: XattrTable, + cache: *Cache, + frag_table: *Lookup.Table(Lookup.FragEntry), + id_table: *Lookup.Table(u16), + xattr_table: *XattrTable, inode: Inode, - ext_loc: []const u8, + path: []const u8, 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; + } + } +} diff --git a/src/lookup.zig b/src/lookup.zig index 4de234b..a0bfbf8 100644 --- a/src/lookup.zig +++ b/src/lookup.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Io = std.Io; +const DataBlock = @import("inode.zig").DataBlock; const Decomp = @import("decomp.zig"); const MetadataReader = @import("meta_rdr.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, +}; diff --git a/src/util/cache.zig b/src/util/cache.zig new file mode 100644 index 0000000..bbfb0ac --- /dev/null +++ b/src/util/cache.zig @@ -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, +}; diff --git a/src/util/protected_map.zig b/src/util/protected_map.zig index 1cadf73..bf700ef 100644 --- a/src/util/protected_map.zig +++ b/src/util/protected_map.zig @@ -28,7 +28,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn 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); defer self.mut.unlockShared(io); @@ -70,6 +70,9 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn // 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)) { .error_union => |e| e.error_set, else => void, diff --git a/src/xattr.zig b/src/xattr.zig index 2034ac0..ffafa19 100644 --- a/src/xattr.zig +++ b/src/xattr.zig @@ -1,3 +1,132 @@ 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(); + +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, +};