Updated unsquashfs for zig 0.16.0

Fixed a couple bugs
Added scaffold for extraction
This commit is contained in:
Caleb Gardner
2026-05-29 18:50:45 -05:00
parent 2cb0863cc1
commit 56ad79ba94
10 changed files with 145 additions and 57 deletions
+7 -1
View File
@@ -56,6 +56,9 @@ pub fn build(b: *std.Build) !void {
b.installArtifact(lib); b.installArtifact(lib);
const exe_config = b.addOptions();
exe_config.addOption(std.SemanticVersion,"version", version);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "unsquashfs", .name = "unsquashfs",
.use_llvm = debug, .use_llvm = debug,
@@ -65,9 +68,12 @@ pub fn build(b: *std.Build) !void {
.target = target, .target = target,
.root_source_file = b.path("src/bin/unsquashfs.zig"), .root_source_file = b.path("src/bin/unsquashfs.zig"),
.valgrind = debug, .valgrind = debug,
.imports = &.{
.{ .name = "config", .module = exe_config.createModule() },
.{ .name = "squashfs", .module = lib.root_module }
},
}), }),
}); });
exe.root_module.linkLibrary(lib);
b.installArtifact(exe); b.installArtifact(exe);
+1 -1
View File
@@ -2,7 +2,7 @@
.name = .squashfs, .name = .squashfs,
.version = "0.0.6", .version = "0.0.6",
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications. .fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
.minimum_zig_version = "0.15.2", .minimum_zig_version = "0.16.0",
.dependencies = .{ .dependencies = .{
.zlib_ng = .{ .zlib_ng = .{
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133", .url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133",
+31 -13
View File
@@ -5,6 +5,7 @@ const MemoryMap = File.MemoryMap;
const Decomp = @import("decomp.zig"); const Decomp = @import("decomp.zig");
const DecompCache = @import("decomp_cache.zig"); const DecompCache = @import("decomp_cache.zig");
const Extract = @import("extract.zig");
const ExtractionOptions = @import("options.zig"); const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const SfsFile = @import("file.zig"); const SfsFile = @import("file.zig");
@@ -36,7 +37,7 @@ pub fn initAdvanced(alloc: std.mem.Allocator, io: Io, fil: File, offset: u64, ca
return .{ return .{
.super = super, .super = super,
.cache = .init( .cache = try .init(
alloc, alloc,
map, map,
super.compression, super.compression,
@@ -74,12 +75,15 @@ pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const
} }
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void { pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {
_ = self; const root_inode: Inode = try .initRef(
_ = alloc; alloc,
_ = io; io,
_ = ext_loc; &self.cache,
_ = options; self.super.inode_start,
return error.TODO; self.super.block_size,
self.super.root_ref,
);
return Extract.extract(alloc, io, root_inode, &self.cache, self.super, ext_loc, options);
} }
// Superblock // Superblock
@@ -93,7 +97,19 @@ pub const Superblock = extern struct {
compression: Decomp.Enum, compression: Decomp.Enum,
block_log: u16, block_log: u16,
flags: packed struct(u16) { flags: packed struct(u16) {
TODO: u16, inode_uncompressed: bool,
data_uncompressed: bool,
check: bool,
frag_uncompressed: bool,
frag_never: bool,
frag_always: bool,
de_dupe: bool,
exportable: bool,
xattr_uncompressed: bool,
xattr_never: bool,
compression_options: bool,
id_uncompressed: bool,
_: u4,
}, },
id_count: u16, id_count: u16,
ver_maj: u16, ver_maj: u16,
@@ -114,6 +130,8 @@ pub const Superblock = extern struct {
return error.InvalidVersion; return error.InvalidVersion;
if (self.block_log != std.math.log2(self.block_size)) if (self.block_log != std.math.log2(self.block_size))
return error.BadBlockLog; return error.BadBlockLog;
if (self.flags.check)
return error.BadCheckFlag;
} }
}; };
@@ -127,10 +145,10 @@ test "Basics" {
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io); defer archive_file.close(io);
var arc: Archive = try .init(io, archive_file); var arc: Archive = try .init(alloc, io, archive_file);
defer arc.deinit(io); defer arc.deinit(io);
var root_file = try arc.root(alloc); var root_file = try arc.root(alloc, io);
defer root_file.deinit(); defer root_file.deinit();
} }
@@ -143,10 +161,10 @@ test "SingleFileExtraction" {
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io); defer archive_file.close(io);
var arc: Archive = try .init(io, archive_file); var arc: Archive = try .init(alloc, io, archive_file);
defer arc.deinit(io); defer arc.deinit(io);
var ext_file = try arc.open(alloc, TestFile); var ext_file = try arc.open(alloc, io, TestFile);
defer ext_file.deinit(); defer ext_file.deinit();
try ext_file.extract(alloc, io, TestFileExtractLocation, .default); try ext_file.extract(alloc, io, TestFileExtractLocation, .default);
@@ -160,7 +178,7 @@ test "FullExtraction" {
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{}); var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io); defer archive_file.close(io);
var arc: Archive = try .init(io, archive_file); var arc: Archive = try .init(alloc, io, archive_file);
defer arc.deinit(io); defer arc.deinit(io);
try arc.extract(alloc, io, TestFullExtractLocation, .default); try arc.extract(alloc, io, TestFullExtractLocation, .default);
+25 -22
View File
@@ -1,9 +1,11 @@
const std = @import("std"); const std = @import("std");
const Writer = std.Io.Writer; const Io = std.Io;
const Writer = Io.Writer;
const File = Io.File;
const builtin = @import("builtin"); const builtin = @import("builtin");
const config = @import("config"); const config = @import("config");
const squashfs = @import("zig_squashfs"); const squashfs = @import("squashfs");
//TODO: Add more options //TODO: Add more options
const help_mgs = const help_mgs =
@@ -38,40 +40,41 @@ 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 alloc = init.gpa;
var stdout = std.fs.File.stdout(); const io = init.io;
var out = stdout.writer(&[0]u8{});
var stdout = 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: File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
defer fil.close(); defer fil.close(io);
var arc: squashfs.Archive = try .init(alloc, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully. var arc: squashfs.Archive = try .initAdvanced(alloc, io, fil, offset, 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
defer arc.deinit(); defer arc.deinit(io);
const options: squashfs.ExtractionOptions = .{ const options: squashfs.ExtractionOptions = .{
.threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
.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 Io.Dir.cwd().deleteTree(io, extLoc);
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully. try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
} }
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { fn handleArgs(args: std.process.Args, out: *Writer) !void {
var args = try std.process.argsWithAllocator(alloc); var arg_iter = args.iterate();
defer args.deinit(); defer arg_iter.deinit();
_ = args.next(); // args[0] is the application launch command. _ = arg_iter.skip(); // args[0] is the application launch command.
while (args.next()) |arg| { while (arg_iter.next()) |arg| {
if (std.mem.eql(u8, arg, "-o")) { if (std.mem.eql(u8, arg, "-o")) {
const nxt = args.next(); const nxt = arg_iter.next();
if (nxt == null or nxt.?.len == 0) { if (nxt == null or nxt.?.len == 0) {
try out.print("-o must be followed by a number\n", .{}); try out.print("-o must be followed by a number\n", .{});
return errors.InvalidArguments; return errors.InvalidArguments;
@@ -82,7 +85,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
}; };
continue; continue;
} else if (std.mem.eql(u8, arg, "-d")) { } else if (std.mem.eql(u8, arg, "-d")) {
const nxt = args.next(); const nxt = arg_iter.next();
if (nxt == null or nxt.?.len == 0) { if (nxt == null or nxt.?.len == 0) {
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;
@@ -90,7 +93,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
extLoc = nxt.?; extLoc = 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 = arg_iter.next();
if (nxt == null or nxt.?.len == 0) { if (nxt == null or nxt.?.len == 0) {
try out.print("-p must be followed by a number\n", .{}); try out.print("-p must be followed by a number\n", .{});
return errors.InvalidArguments; return errors.InvalidArguments;
+4 -4
View File
@@ -5,17 +5,17 @@ const c = @import("c");
const Error = @import("decomp.zig").Error; const Error = @import("decomp.zig").Error;
pub fn zlibDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize { pub fn zlibDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var strem: c.zng_stream = .{ var strem: c.z_stream = .{
.next_in = in.ptr, .next_in = in.ptr,
.avail_in = @truncate(in.len), .avail_in = @truncate(in.len),
.next_out = out.ptr, .next_out = out.ptr,
.avail_out = @truncate(out.len), .avail_out = @truncate(out.len),
}; };
var res = c.zng_inflateInit(&strem); var res = c.inflateInit(&strem);
if (res != c.Z_OK) return Error.ReadFailed; if (res != c.Z_OK) return Error.ReadFailed;
defer _ = c.zng_inflateEnd(&strem); defer _ = c.inflateEnd(&strem);
res = c.zng_inflate(&strem, c.Z_FULL_FLUSH); res = c.inflate(&strem, c.Z_FULL_FLUSH);
if (res != c.Z_OK) return Error.ReadFailed; if (res != c.Z_OK) return Error.ReadFailed;
return strem.total_out; return strem.total_out;
+4 -4
View File
@@ -19,7 +19,7 @@ cond: std.Io.Condition = .init,
max_mem: u64, max_mem: u64,
cur_mem: u64 = 0, cur_mem: u64 = 0,
pub fn init(alloc: std.mem.Allocator, map: MemoryMap, compression: Decomp.Enum, max_mem: u64) DecompCache { pub fn init(alloc: std.mem.Allocator, map: MemoryMap, compression: Decomp.Enum, max_mem: u64) !DecompCache {
return .{ return .{
.alloc = alloc, .alloc = alloc,
.map = map, .map = map,
@@ -55,8 +55,8 @@ pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_si
const cache = try self.cache.getOrPut(offset); const cache = try self.cache.getOrPut(offset);
if (cache.found_existing) { if (cache.found_existing) {
_ = cache.?.usage.fetchAdd(1, .acquire); _ = cache.value_ptr.usage.fetchAdd(1, .acquire);
return cache.?.data; return cache.value_ptr.data;
} }
errdefer self.cache.removeByPtr(cache.key_ptr); errdefer self.cache.removeByPtr(cache.key_ptr);
@@ -104,7 +104,7 @@ fn ensureSpace(self: *DecompCache, io: Io, size: u64) !void {
} }
} }
if (self.cur_mem + size <= self.max_mem) return; if (self.cur_mem + size <= self.max_mem) return;
try self.cond.wait(io, self.mut.mutex); try self.cond.wait(io, &self.mut.mutex);
} }
} }
+1 -1
View File
@@ -46,7 +46,7 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u32) !Directory {
} }
} }
return entries.toOwnedSlice(alloc); return .{ .entries = try entries.toOwnedSlice(alloc) };
} }
pub fn deinit(self: Directory, alloc: std.mem.Allocator) void { pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
for (self.entries) |entry| for (self.entries) |entry|
+51
View File
@@ -0,0 +1,51 @@
const std = @import("std");
const Io = std.Io;
const DecompCache = @import("decomp_cache.zig");
const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig");
const Superblock = @import("archive.zig").Superblock;
pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCache, super: Superblock, ext_loc: []const u8, options: ExtractionOptions) !void {
_ = alloc;
_ = io;
_ = inode;
_ = cache;
_ = super;
_ = ext_loc;
_ = options;
return error.TODO;
}
pub fn extractDir(alloc: std.mem.Allocator, io: Io, path: []const u8, d: anytype) Error!PathReturn {}
pub fn extractFile(alloc: std.mem.Allocator, io: Io, path: []const u8, d: anytype) Error!PathReturn {
const atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{});
defer atomic.deinit(io);
// TODO
try atomic.link(io);
// return .{
// .path = path,
// };
return error.TODO;
}
// Utility types
const ReturnUnion = union {
path_ret: Error!PathReturn,
};
const Error = error{};
const PathReturn = struct {
path: []const u8,
uid_idx: u32,
gid_idx: u32,
mod_time: u32,
permission: u16,
xattr_idx: ?u32,
};
+19 -9
View File
@@ -1,3 +1,11 @@
const std = @import("std");
const Io = std.Io;
const Archive = @import("archive.zig");
const Directory = @import("directory.zig");
const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig");
const SfsFile = @This(); const SfsFile = @This();
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
@@ -28,7 +36,7 @@ pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry:
.inode = try .initDirEntry( .inode = try .initDirEntry(
alloc, alloc,
io, io,
archive.cache, &archive.cache,
archive.super.inode_start, archive.super.inode_start,
archive.super.block_size, archive.super.block_size,
entry, entry,
@@ -61,7 +69,7 @@ pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u
const first_element: []const u8 = std.mem.sliceTo(path, '/'); const first_element: []const u8 = std.mem.sliceTo(path, '/');
const dir: Directory = try self.inode.directory(alloc, io, self.archive.cache, self.archive.super.dir_start); const dir: Directory = try self.inode.directory(alloc, io, &self.archive.cache, self.archive.super.dir_start);
defer dir.deinit(alloc); defer dir.deinit(alloc);
var cur_slice = dir.entries; var cur_slice = dir.entries;
@@ -76,7 +84,7 @@ pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u
} else { } else {
return error.NotFound; return error.NotFound;
} }
if (first_element.len == path) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]); if (first_element.len == path.len) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
if (cur_slice[idx].type != .dir) return error.NotFound; if (cur_slice[idx].type != .dir) return error.NotFound;
const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]); const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
defer tmp_file.deinit(); defer tmp_file.deinit();
@@ -84,9 +92,11 @@ pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u
return tmp_file.open(alloc, io, path[first_element.len..]); return tmp_file.open(alloc, io, path[first_element.len..]);
} }
const std = @import("std"); pub fn extract(self: SfsFile, alloc: std.mem.Allocator, io: Io, ext_dir: []const u8, options: ExtractionOptions) !void {
const Io = std.Io; _ = self;
_ = alloc;
const Archive = @import("archive.zig"); _ = io;
const Directory = @import("directory.zig"); _ = ext_dir;
const Inode = @import("inode.zig"); _ = options;
return error.TODO;
}
+2 -2
View File
@@ -280,7 +280,7 @@ const ExtFile = struct {
.blocks = blocks, .blocks = blocks,
}; };
} }
pub fn deinit(self: File, alloc: std.mem.Allocator) void { pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
alloc.free(self.blocks); alloc.free(self.blocks);
} }
}; };
@@ -342,7 +342,7 @@ const ExtSymlink = struct {
}; };
} }
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void { pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
alloc.free(self.target); alloc.free(self.target);
} }
}; };