From dfd88fee137ea3d005661b38756e603cd08d02e3 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Wed, 3 Jun 2026 06:28:25 -0500 Subject: [PATCH] Fixed some bugs; found some bugs --- src/archive.zig | 4 +++ src/bin/unsquashfs.zig | 12 ++++--- src/data/extractor.zig | 3 ++ src/decomp_cache.zig | 3 ++ src/directory.zig | 2 ++ src/extract.zig | 72 +++++++++++++++++++++++++++++------------- src/file.zig | 51 ++++++++++++++++++------------ src/lookup.zig | 4 ++- src/meta_rdr.zig | 9 ++++-- 9 files changed, 109 insertions(+), 51 deletions(-) diff --git a/src/archive.zig b/src/archive.zig index 4c6f9a9..0b3c270 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -161,6 +161,8 @@ test "SingleFileExtraction" { const alloc = std.testing.allocator; const io = std.testing.io; + std.Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {}; + var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); defer archive_file.close(io); var arc: Archive = try .init(alloc, io, archive_file); @@ -178,6 +180,8 @@ test "FullExtraction" { const alloc = std.testing.allocator; const io = std.testing.io; + std.Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {}; + var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); defer archive_file.close(io); var arc: Archive = try .init(alloc, io, archive_file); diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig index d235679..a43c0a9 100644 --- a/src/bin/unsquashfs.zig +++ b/src/bin/unsquashfs.zig @@ -32,7 +32,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; @@ -64,8 +64,12 @@ pub fn main(init: std.process.Init) !void { .ignore_permissions = ignore_permissions, }; if (force) - try Io.Dir.cwd().deleteTree(io, extLoc); - try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully. + try Io.Dir.cwd().deleteTree(io, ext_loc); + if (threads != 1) { + try arc.extract(alloc, io, ext_loc, options); //TODO: Handle error gracefully. + } else { + try arc.extract(alloc, Io.Threaded.global_single_threaded.io(), ext_loc, options); + } } fn handleArgs(args: std.process.Args, out: *Writer) !void { @@ -90,7 +94,7 @@ fn handleArgs(args: std.process.Args, 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 = arg_iter.next(); diff --git a/src/data/extractor.zig b/src/data/extractor.zig index a4288dc..f51deb2 100644 --- a/src/data/extractor.zig +++ b/src/data/extractor.zig @@ -33,6 +33,9 @@ pub fn addFragment(self: *Extractor, data: []u8, offset: u32) void { } pub fn asyncExtract(self: Extractor, io: Io, fil: Io.File) Error!void { + if (self.size == 0) return; + + // Initializ the file with the correct size. try fil.writePositionalAll(io, &.{0}, self.size - 1); var map = try fil.createMemoryMap(io, .{ .len = self.size, .protection = .{ .write = true } }); diff --git a/src/decomp_cache.zig b/src/decomp_cache.zig index 69514d8..4b64c8e 100644 --- a/src/decomp_cache.zig +++ b/src/decomp_cache.zig @@ -90,6 +90,9 @@ pub fn finished(self: *DecompCache, io: Io, offset: u64) void { std.debug.print("Finished using cache, but cache does not exist: {}\n", .{offset}); return; } + + self.mut.lockUncancelable(io); + defer self.mut.unlock(io); const use = cache.?.usage.fetchSub(1, .acquire); if (use == 0) self.cond.broadcast(io); diff --git a/src/directory.zig b/src/directory.zig index 93fa5b4..bb50216 100644 --- a/src/directory.zig +++ b/src/directory.zig @@ -43,6 +43,8 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u32) Error!Directory { .type = raw.type, .name = name, }); + + read += @sizeOf(RawEntry) + raw.name_size + 1; } } diff --git a/src/extract.zig b/src/extract.zig index 0cd1046..09150a3 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -1,38 +1,43 @@ const std = @import("std"); const Io = std.Io; - const Atomic = std.atomic.Value; -const DecompCache = @import("decomp_cache.zig"); -const ExtractionOptions = @import("options.zig"); -const Inode = @import("inode.zig"); -const Superblock = @import("archive.zig").Superblock; -const Directory = @import("directory.zig"); const DataExtractor = @import("data/extractor.zig"); const DataReader = @import("data/reader.zig"); +const DecompCache = @import("decomp_cache.zig"); +const Directory = @import("directory.zig"); +const ExtractionOptions = @import("options.zig"); +const Inode = @import("inode.zig"); const Lookup = @import("lookup.zig"); +const Superblock = @import("archive.zig").Superblock; 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, "/"); - var buf: [50]ReturnUnion = undefined; + var buf: [1000]ReturnUnion = undefined; var sel: Io.Select(ReturnUnion) = .init(io, &buf); defer { + std.debug.print("starting cancel loop...\n", .{}); while (sel.cancel()) |ret| { + std.debug.print("HELLO!\n", .{}); switch (ret) { .dir_ret => |d| { const res = d catch continue; - alloc.free(res.path); + if (!res.origin) + alloc.free(res.path); + alloc.destroy(res.sub_files); }, .file_ret => |f| { const res = f catch continue; - alloc.free(res.path); + if (!res.origin) + alloc.free(res.path); }, else => {}, } } + std.debug.print("goodbye\n", .{}); } var id_table: Lookup.Table(u16) = .init(alloc, cache, super.id_start, super.id_count); @@ -46,7 +51,7 @@ pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCac var frag_table: Lookup.Table(Lookup.FragmentEntry) = .init(alloc, cache, super.frag_start, super.frag_count); defer frag_table.deinit(); - try extractReal(alloc, io, cache, super, &sel, &frag_table, path, inode, null, false); + try extractReal(alloc, io, cache, super, &sel, &frag_table, path, inode, null, true); try ret_loop.await(io); } @@ -65,10 +70,6 @@ fn extractReal( ) Error!void { io.checkCancel() catch |err| { if (parent != null) _ = parent.?.fetchSub(1, .acquire); - if (!origin) { - alloc.free(path); - inode.deinit(alloc); - } return err; }; @@ -108,6 +109,7 @@ fn extractDir( parent: ?*Atomic(usize), origin: bool, ) Error!DirReturn { + std.debug.print("started extract of {s}\n", .{path}); defer { if (parent != null) _ = parent.?.fetchSub(1, .acquire); @@ -115,6 +117,8 @@ fn extractDir( } errdefer if (!origin) alloc.free(path); + try Io.Dir.cwd().createDirPath(io, path); + const dir = inode.directory(alloc, io, cache, super.dir_start) catch |err| switch (err) { error.NotDirectory => unreachable, else => |e| return e, @@ -145,6 +149,7 @@ fn extractDir( errdefer new_inode.deinit(alloc); const new_path = try std.mem.concat(alloc, u8, &.{ path, "/", entry.name }); + errdefer alloc.free(new_path); try extractReal( alloc, @@ -172,6 +177,7 @@ fn extractFile( parent: ?*Atomic(usize), origin: bool, ) Error!FileReturn { + std.debug.print("started extract of {s}\n", .{path}); defer { if (parent != null) _ = parent.?.fetchSub(1, .acquire); @@ -230,6 +236,7 @@ fn extractFile( return ret; } fn extractSymlink(alloc: std.mem.Allocator, io: Io, path: []const u8, inode: Inode, parent: ?*Atomic(usize), origin: bool) Error!void { + std.debug.print("started extract of {s}\n", .{path}); defer { if (parent != null) _ = parent.?.fetchSub(1, .acquire); @@ -248,6 +255,7 @@ fn extractSymlink(alloc: std.mem.Allocator, io: Io, path: []const u8, inode: Ino try Io.Dir.cwd().symLink(io, target, path, .{}); } fn extractNod(alloc: std.mem.Allocator, path: []const u8, inode: Inode, parent: ?*Atomic(usize), origin: bool) Error!FileReturn { + std.debug.print("started extract of {s}\n", .{path}); defer { if (parent != null) _ = parent.?.fetchSub(1, .acquire); @@ -319,7 +327,8 @@ 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: *XattrTable, sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void { - while (true) { + var origin_finished = false; + while (!origin_finished) { const finished = try sel.await(); switch (finished) { @@ -328,11 +337,19 @@ fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xa if (ret.sub_files.load(.unordered) != 0) { sel.queue.putOne(io, .{ .dir_ret = ret }) catch |err| { if (!ret.origin) alloc.free(ret.path); + alloc.destroy(ret.sub_files); return err; }; continue; } - if (!ret.origin) alloc.free(ret.path); + defer { + if (!ret.origin) { + alloc.free(ret.path); + } else { + origin_finished = true; + } + } + defer std.debug.print("finished extract of {s}\n", .{ret.path}); alloc.destroy(ret.sub_files); if (!options.ignore_permissions and (!options.ignore_xattr and ret.xattr_idx != null)) { @@ -347,7 +364,10 @@ fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xa 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.?); + const xattrs = xattr_table.get(alloc, io, ret.xattr_idx.?) catch |err| { + std.debug.print("YO {}\n", .{err}); + return err; + }; defer xattrs.deinit(alloc); for (xattrs.xattrs) |x| { @@ -360,7 +380,14 @@ fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xa }, .file_ret => |f| { const ret = try f; - if (!ret.origin) alloc.free(ret.path); + defer { + if (!ret.origin) { + alloc.free(ret.path); + } else { + origin_finished = true; + } + } + defer std.debug.print("finished extract of {s}\n", .{ret.path}); if (!options.ignore_permissions and (!options.ignore_xattr and ret.xattr_idx != null)) { const file = try Io.Dir.cwd().openFile(io, ret.path, .{}); @@ -374,7 +401,10 @@ fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xa 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.?); + const xattrs = xattr_table.get(alloc, io, ret.xattr_idx.?) catch |err| { + std.debug.print("YO!!! {s} {}\n", .{ ret.path, err }); + return err; + }; defer xattrs.deinit(alloc); for (xattrs.xattrs) |x| { @@ -387,8 +417,6 @@ fn returnLoop(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xa }, .void_ret => |v| try v, } - - if (sel.group.token.load(.unordered) == null) break; } } @@ -401,7 +429,7 @@ const ReturnUnion = union(enum) { }; const Error = error{ Canceled, MknodError } || Directory.Error || Io.Dir.CreateFileAtomicError || Io.File.Atomic.LinkError || - DataExtractor.Error || Io.Dir.SymLinkError; + DataExtractor.Error || Io.Dir.SymLinkError || Io.Dir.CreateDirPathError; const FileReturn = struct { path: []const u8, diff --git a/src/file.zig b/src/file.zig index 464fb80..f3e7e7a 100644 --- a/src/file.zig +++ b/src/file.zig @@ -3,6 +3,7 @@ const Io = std.Io; const Archive = @import("archive.zig"); const Directory = @import("directory.zig"); +const Extract = @import("extract.zig"); const ExtractionOptions = @import("options.zig"); const Inode = @import("inode.zig"); @@ -26,7 +27,7 @@ pub fn init(alloc: std.mem.Allocator, archive: *Archive, inode: Inode, name: []c } pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry: Directory.Entry) !SfsFile { const new_name = try alloc.alloc(u8, entry.name.len); - defer alloc.free(new_name); + errdefer alloc.free(new_name); @memcpy(new_name, entry.name); return .{ @@ -62,37 +63,45 @@ pub fn deinit(self: SfsFile) void { self.alloc.free(self.name); } +pub fn isDir(self: *SfsFile) bool { + return switch (self.inode.hdr.type) { + .dir, .ext_dir => true, + else => false, + }; +} /// 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 { const path = std.mem.trim(u8, filepath, "/"); const first_element: []const u8 = std.mem.sliceTo(path, '/'); + var found_file: SfsFile = blk: { + const dir: Directory = try self.inode.directory(alloc, io, &self.archive.cache, self.archive.super.dir_start); + defer dir.deinit(alloc); - const dir: Directory = try self.inode.directory(alloc, io, &self.archive.cache, self.archive.super.dir_start); - defer dir.deinit(alloc); - - var cur_slice = dir.entries; - var idx: usize = undefined; - while (cur_slice.len > 0) { - idx = cur_slice.len / 2; - switch (std.mem.order(u8, first_element, cur_slice[idx].name)) { - .eq => break, - .lt => cur_slice = cur_slice[0..idx], - .gt => cur_slice = cur_slice[idx..], + var cur_slice = dir.entries; + var idx: usize = undefined; + while (cur_slice.len > 0) { + idx = cur_slice.len / 2; + switch (std.mem.order(u8, first_element, cur_slice[idx].name)) { + .eq => break, + .lt => cur_slice = cur_slice[0..idx], + .gt => cur_slice = cur_slice[idx..], + } + } else { + return error.NotFound; } - } else { - return error.NotFound; - } - if (first_element.len == path.len) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]); - if (cur_slice[idx].type != .dir) return error.NotFound; - 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..]); + break :blk try .initDirEntry(alloc, io, self.archive, cur_slice[idx]); + }; + if (first_element.len == path.len) return found_file; + defer found_file.deinit(); + + if (!found_file.isDir()) return error.NotFound; + + return found_file.open(alloc, io, path[first_element.len..]); } -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 98f6517..9d30541 100644 --- a/src/lookup.zig +++ b/src/lookup.zig @@ -49,7 +49,9 @@ pub fn Table(comptime T: anytype) type { .values = .init(alloc), }; } - pub fn deinit(self: *LookupTable) void { + pub fn deinit(self: *LookupTable, io: Io) void { + self.mut.lockUncancelable(io); + var iter = self.values.valueIterator(); while (iter.next()) |v| self.alloc.free(v.*); diff --git a/src/meta_rdr.zig b/src/meta_rdr.zig index d32ca11..ef659bb 100644 --- a/src/meta_rdr.zig +++ b/src/meta_rdr.zig @@ -66,7 +66,8 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { if (r.seek >= r.end) { const self: *MetadataReader = @fieldParentPtr("interface", r); self.advance() catch |err| { - std.debug.print("error advancing metadata reader: {}\n", .{err}); + if (err != error.Canceled) + std.debug.print("error advancing metadata reader: {}\n", .{err}); return Reader.Error.ReadFailed; }; } @@ -79,7 +80,8 @@ fn discard(r: *Reader, limit: Limit) Reader.Error!usize { if (r.seek >= r.end) { const self: *MetadataReader = @fieldParentPtr("interface", r); self.advance() catch |err| { - std.debug.print("error advancing metadata reader: {}\n", .{err}); + if (err != error.Canceled) + std.debug.print("error advancing metadata reader: {}\n", .{err}); return Reader.Error.ReadFailed; }; } @@ -91,7 +93,8 @@ fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize { if (r.seek >= r.end) { const self: *MetadataReader = @fieldParentPtr("interface", r); self.advance() catch |err| { - std.debug.print("error advancing metadata reader: {}\n", .{err}); + if (err != error.Canceled) + std.debug.print("error advancing metadata reader: {}\n", .{err}); return Reader.Error.ReadFailed; }; }