diff --git a/build.zig b/build.zig index 78509ba..ab1a30b 100644 --- a/build.zig +++ b/build.zig @@ -57,7 +57,7 @@ pub fn build(b: *std.Build) !void { b.installArtifact(lib); const exe_config = b.addOptions(); - exe_config.addOption(std.SemanticVersion,"version", version); + exe_config.addOption(std.SemanticVersion, "version", version); const exe = b.addExecutable(.{ .name = "unsquashfs", @@ -68,10 +68,7 @@ pub fn build(b: *std.Build) !void { .target = target, .root_source_file = b.path("src/bin/unsquashfs.zig"), .valgrind = debug, - .imports = &.{ - .{ .name = "config", .module = exe_config.createModule() }, - .{ .name = "squashfs", .module = lib.root_module } - }, + .imports = &.{ .{ .name = "config", .module = exe_config.createModule() }, .{ .name = "squashfs", .module = lib.root_module } }, }), }); @@ -104,7 +101,7 @@ fn getDependencies(b: *Build, optimize: std.builtin.OptimizeMode, target: Build. var list: std.ArrayList(*Build.Step.Compile) = .empty; - const zlib_ng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target }); + const zlib_ng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target, .zlib_compat = true }); try list.append(alloc, zlib_ng.artifact("zng")); const xz = b.dependency("xz", .{ .optimize = optimize, .target = target }); diff --git a/build.zig.zon b/build.zig.zon index d861362..9f43b75 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .minimum_zig_version = "0.16.0", .dependencies = .{ .zlib_ng = .{ - .url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133", - .hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa", + .url = "git+https://github.com/CalebQ42/zig-zlib-ng#915db2dbd4a540d8a933336d67520b786f168f01", + .hash = "zlib_ng-2.3.3-pre1-2HYS4MlFAADc-eSZv4_xjkxZRCdGUVOYZKdtqhybstgk", }, .zstd = .{ .url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3", diff --git a/src/data/extractor.zig b/src/data/extractor.zig index 1f7d1d2..a4288dc 100644 --- a/src/data/extractor.zig +++ b/src/data/extractor.zig @@ -33,7 +33,7 @@ pub fn addFragment(self: *Extractor, data: []u8, offset: u32) void { } pub fn asyncExtract(self: Extractor, io: Io, fil: Io.File) Error!void { - try fil.writePositionalAll(io, &.{&.{0}}, self.size - 1); + try fil.writePositionalAll(io, &.{0}, self.size - 1); var map = try fil.createMemoryMap(io, .{ .len = self.size, .protection = .{ .write = true } }); defer map.destroy(io); @@ -59,7 +59,7 @@ pub fn asyncExtract(self: Extractor, io: Io, fil: Io.File) Error!void { fn blockThread(self: Extractor, io: Io, map: Io.File.MemoryMap, read_offset: u64, idx: usize, ret_err: *?Error) error{Canceled}!void { const write_pos = idx * self.block_size; - const size = if (self.frag_data == null and idx == self.block_size.len - 1) + const size = if (self.frag_data == null and idx == self.blocks.len - 1) self.size % self.block_size else self.block_size; @@ -70,10 +70,10 @@ fn blockThread(self: Extractor, io: Io, map: Io.File.MemoryMap, read_offset: u64 return; } if (block.uncompressed) { - @memcpy(map[write_pos..][0..size], self.cache.map.memory[read_offset..][0..size]); + @memcpy(map.memory[write_pos..][0..size], self.cache.map.memory[read_offset..][0..size]); return; } - const data = self.cache.get(io, read_offset, block.size, size) catch |err| switch (err) { + const data = self.cache.get(io, read_offset, block.size, @truncate(size)) catch |err| switch (err) { error.Canceled => { io.recancel(); return error.Canceled; @@ -86,9 +86,10 @@ fn blockThread(self: Extractor, io: Io, map: Io.File.MemoryMap, read_offset: u64 defer self.cache.finished(io, read_offset); if (data.len != size) { std.debug.print("Size of decompression at {} is {} and should be {}\n", .{ read_offset, data.len, size }); - return Error.BadDecompressionSize; + ret_err.* = Error.BadDecompressionSize; + return error.Canceled; } - @memcpy(map[write_pos..][0..size], data); + @memcpy(map.memory[write_pos..][0..size], data); } fn fragThread(self: Extractor, map: Io.File.MemoryMap) error{Canceled}!void { const write_pos = self.blocks.len * self.block_size; @@ -99,4 +100,4 @@ fn fragThread(self: Extractor, map: Io.File.MemoryMap) error{Canceled}!void { // Types -pub const Error = error{BadDecompressionSize} || Io.File.WritePositionalError || Io.File.MemoryMap.CreateError; +pub const Error = error{BadDecompressionSize} || Io.File.WritePositionalError || Io.File.MemoryMap.CreateError || DecompCache.Error; diff --git a/src/decomp_cache.zig b/src/decomp_cache.zig index 7e9c19e..52d913b 100644 --- a/src/decomp_cache.zig +++ b/src/decomp_cache.zig @@ -39,7 +39,7 @@ pub fn deinit(self: *DecompCache, io: Io) void { self.cache.deinit(); } -pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_size: u32) ![]u8 { +pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_size: u32) Error![]u8 { { try self.mut.lockShared(io); defer self.mut.unlockShared(io); @@ -110,6 +110,8 @@ fn ensureSpace(self: *DecompCache, io: Io, size: u64) !void { // Types +pub const Error = error{ Canceled, OutOfMemory, ReadFailed }; + const Cache = struct { data: []u8, usage: Atomic(u32), diff --git a/src/extract.zig b/src/extract.zig index 2745ab0..0cd1046 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -11,6 +11,7 @@ const Directory = @import("directory.zig"); const DataExtractor = @import("data/extractor.zig"); const DataReader = @import("data/reader.zig"); const Lookup = @import("lookup.zig"); +const XattrTable = @import("xattr.zig"); pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCache, super: Superblock, ext_loc: []const u8, options: ExtractionOptions) !void { const path = std.mem.trim(u8, ext_loc, "/"); @@ -34,11 +35,17 @@ pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCac } } + var id_table: Lookup.Table(u16) = .init(alloc, cache, super.id_start, super.id_count); + defer id_table.deinit(); + + var xattr_table: XattrTable = try .init(alloc, cache, super.xattr_start); + defer xattr_table.deinit(); + + var ret_loop = io.async(returnLoop, .{ alloc, io, &id_table, &xattr_table, &sel, options }); + var frag_table: Lookup.Table(Lookup.FragmentEntry) = .init(alloc, cache, super.frag_start, super.frag_count); defer frag_table.deinit(); - var ret_loop = io.async(returnLoop, .{ alloc, io, &sel, options }); - try extractReal(alloc, io, cache, super, &sel, &frag_table, path, inode, null, false); try ret_loop.await(io); @@ -311,7 +318,7 @@ fn extractNod(alloc: std.mem.Allocator, path: []const u8, inode: Inode, parent: // Loop -fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: Lookup.Table(u16), xattr_table: Lookup.Table(Lookup.XattrEntry), sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void { +fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xattr_table: *XattrTable, sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void { while (true) { const finished = try sel.await(); @@ -339,15 +346,43 @@ fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: Lookup.Table(u16), xat try file.setPermissions(io, @enumFromInt(ret.permissions)); try file.setOwner(io, try id_table.get(io, ret.uid_idx), try id_table.get(io, ret.gid_idx)); } - if (!options.ignore_xattr and ret.xattr_idx != null) {} + if (!options.ignore_xattr and ret.xattr_idx != null) { + const xattrs = try xattr_table.get(alloc, io, ret.xattr_idx.?); + defer xattrs.deinit(alloc); + + for (xattrs.xattrs) |x| { + const res = std.os.linux.fsetxattr(file.handle, x.key, x.value.ptr, x.value.len, 0); + if (res != 0) + return error.SetXattrFailed; + } + } } }, .file_ret => |f| { const ret = try f; if (!ret.origin) alloc.free(ret.path); - if (!options.ignore_permissions and !options.ignore_xattr) { - // TODO: set permissions & xattr. + if (!options.ignore_permissions and (!options.ignore_xattr and ret.xattr_idx != null)) { + const file = try Io.Dir.cwd().openFile(io, ret.path, .{}); + defer file.close(io); + + if (!options.ignore_permissions) { + try file.setTimestamps(io, .{ + .modify_timestamp = .init(.{ .nanoseconds = @as(i96, @intCast(ret.mod_time)) * std.time.ns_per_s }), + }); + try file.setPermissions(io, @enumFromInt(ret.permissions)); + try file.setOwner(io, try id_table.get(io, ret.uid_idx), try id_table.get(io, ret.gid_idx)); + } + if (!options.ignore_xattr and ret.xattr_idx != null) { + const xattrs = try xattr_table.get(alloc, io, ret.xattr_idx.?); + defer xattrs.deinit(alloc); + + for (xattrs.xattrs) |x| { + const res = std.os.linux.fsetxattr(file.handle, x.key, x.value.ptr, x.value.len, 0); + if (res != 0) + return error.SetXattrFailed; + } + } } }, .void_ret => |v| try v, diff --git a/src/file.zig b/src/file.zig index f71386b..464fb80 100644 --- a/src/file.zig +++ b/src/file.zig @@ -64,7 +64,7 @@ pub fn deinit(self: SfsFile) void { /// Attempts to open the filepath if the SfsFile is a directory. /// If the given path refers to itself (such as "" or "."), a copied SfsFile is returned. -pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile { +pub fn open(self: *SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile { const path = std.mem.trim(u8, filepath, "/"); const first_element: []const u8 = std.mem.sliceTo(path, '/'); @@ -86,17 +86,13 @@ pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u } if (first_element.len == path.len) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]); if (cur_slice[idx].type != .dir) return error.NotFound; - const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]); + var tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]); defer tmp_file.deinit(); return tmp_file.open(alloc, io, path[first_element.len..]); } -pub fn extract(self: SfsFile, alloc: std.mem.Allocator, io: Io, ext_dir: []const u8, options: ExtractionOptions) !void { - _ = self; - _ = alloc; - _ = io; - _ = ext_dir; - _ = options; - return error.TODO; +const Extract = @import("extract.zig"); +pub fn extract(self: *SfsFile, alloc: std.mem.Allocator, io: Io, ext_dir: []const u8, options: ExtractionOptions) !void { + return Extract.extract(alloc, io, self.inode, &self.archive.cache, self.archive.super, ext_dir, options); } diff --git a/src/lookup.zig b/src/lookup.zig index 8b9c410..98f6517 100644 --- a/src/lookup.zig +++ b/src/lookup.zig @@ -52,7 +52,7 @@ pub fn Table(comptime T: anytype) type { pub fn deinit(self: *LookupTable) void { var iter = self.values.valueIterator(); while (iter.next()) |v| - self.alloc.free(v); + self.alloc.free(v.*); self.values.deinit(); } @@ -64,7 +64,7 @@ pub fn Table(comptime T: anytype) type { defer self.mut.unlockShared(io); const val = self.values.get(block); - if (val != null) return val.*[block_idx]; + if (val != null) return val.?[block_idx]; } try self.mut.lock(io); defer self.mut.unlock(io); @@ -75,7 +75,7 @@ pub fn Table(comptime T: anytype) type { errdefer self.values.removeByPtr(val.key_ptr); const offset_offset = self.table_start + (block * 8); - const offset: u64 = std.mem.readInt(u64, self.cache.map.memory[offset_offset..][0..2], .little); + const offset: u64 = std.mem.readInt(u64, self.cache.map.memory[offset_offset..][0..8], .little); var meta: MetadataReader = .init(io, self.cache, offset); defer meta.deinit(io); @@ -98,7 +98,7 @@ pub fn Table(comptime T: anytype) type { // Types -pub const Error = error{} || std.mem.Allocator.Error; +pub const Error = error{Canceled} || std.mem.Allocator.Error || Io.Reader.Error; pub const FragmentEntry = extern struct { start: u64, diff --git a/src/xattr.zig b/src/xattr.zig index d95c248..3ff467f 100644 --- a/src/xattr.zig +++ b/src/xattr.zig @@ -21,8 +21,11 @@ pub fn init(alloc: std.mem.Allocator, cache: *DecompCache, xattr_start: u64) !Xa .table = .init(alloc, cache, xattr_start + 16, num), }; } +pub fn deinit(self: *XattrTable) void { + self.table.deinit(); +} -pub fn get(self: XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !XattrKVs { +pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !XattrKVs { const entry = try self.table.get(io, idx); var meta: MetadataReader = .init(io, self.table.cache, self.table_start + entry.ref.block_start);