From 067eaa87c276cac2081a21bf45a6a0638d774131 Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Sat, 7 Feb 2026 10:58:32 -0600 Subject: [PATCH] You can now set when building to use c or zig libraries. --- build.zig | 43 +++++++++----- src/bin/unsquashfs.zig | 16 +++++- src/decomp.zig | 126 ++++++++++++++++++++++++++++++++++++++--- src/util/data.zig | 4 +- 4 files changed, 164 insertions(+), 25 deletions(-) diff --git a/build.zig b/build.zig index 8212fbd..e75861a 100644 --- a/build.zig +++ b/build.zig @@ -1,29 +1,44 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const static = b.option(bool, "static_build", "Build static"); +pub fn build(b: *std.Build) !void { + const static_option = b.option(bool, "static_build", "Build static"); + const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones"); + const version_string_option = b.option([]const u8, "version", "Version of the library/binary"); + + const zig_squashfs_options = b.addOptions(); + zig_squashfs_options.addOption(bool, "use_c_libs", use_c_libs_option orelse false); + const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); - const linkage: std.builtin.LinkMode = .static; // TODO: Add argument to set link mode. - const use_c_libs: bool = false; - _ = use_c_libs; const mod = b.addModule("zig_squashfs", .{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, + .link_libc = if (use_c_libs_option == true) true else false, }); + mod.addOptions("config", zig_squashfs_options); + if (use_c_libs_option == true) + mod.linkSystemLibrary("zstd", .{}); + + const unsquashfs_options = b.addOptions(); + unsquashfs_options.addOption(std.SemanticVersion, "version_string", try std.SemanticVersion.parse(version_string_option orelse "0.0.0-testing")); + + var exe_mod = b.createModule(.{ + .root_source_file = b.path("src/bin/unsquashfs.zig"), + .target = target, + .optimize = optimize, + .link_libc = if (use_c_libs_option == true) true else false, + .imports = &.{ + .{ .name = "zig_squashfs", .module = mod }, + }, + }); + exe_mod.addOptions("config", unsquashfs_options); const exe = b.addExecutable(.{ .name = "unsquashfs", - .linkage = linkage, - .root_module = b.createModule(.{ - .root_source_file = b.path("src/bin/unsquashfs.zig"), - .target = target, - .optimize = optimize, - .imports = &.{ - .{ .name = "zig_squashfs", .module = mod }, - }, - }), + .linkage = if (static_option == true) .static else .dynamic, + .root_module = exe_mod, }); + b.installArtifact(exe); const run_step = b.step("run", "Run the app"); const run_cmd = b.addRunArtifact(exe); diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig index 0eb90d3..41b844f 100644 --- a/src/bin/unsquashfs.zig +++ b/src/bin/unsquashfs.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Writer = std.Io.Writer; +const config = @import("config"); const squashfs = @import("zig_squashfs"); //TODO: Add more options @@ -8,8 +9,11 @@ const help_mgs = \\Usage: unsquashfs [options] \\ \\Options: - \\ -o Start reading the archive at the given offset. \\ -d Extract to the given location instead of "squashfs-root" + \\ + \\ -o Start reading the archive at the given offset. + \\ + \\ --version Display the version ; const errors = error{InvalidArguments}; @@ -24,6 +28,10 @@ pub fn main() !void { var out = stdout.writer(&[0]u8{}); defer out.interface.flush() catch {}; try handleArgs(alloc, &out.interface); + if (archive.len == 0) { + try out.interface.print("You must provide a squashfs archive\n", .{}); + return; + } var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully. defer fil.close(); var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, try std.Thread.getCpuCount(), 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully. @@ -55,6 +63,12 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { } extLoc = nxt.?; continue; + } else if (std.mem.eql(u8, arg, "--version")) { + _ = try out.write("v"); + try config.version_string.format(out); + _ = try out.write("\n"); + std.process.cleanExit(); + return; } if (archive.len > 0) { try out.print("you can only provide one file at a time\n", .{}); diff --git a/src/decomp.zig b/src/decomp.zig index 5e432da..975d0f8 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -1,6 +1,15 @@ +//! Implementations for decompression. +//! TODO: change to vtable interface to allow for shared decompressors for better performance/resource usage. + const std = @import("std"); const Reader = std.Io.Reader; +const config = @import("config"); + +const c = @cImport({ + @cInclude("zstd.h"); +}); + pub const CompressionType = enum(u16) { gzip = 1, lzma, @@ -12,32 +21,53 @@ pub const CompressionType = enum(u16) { pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types. -// pub const DecompressError = error{ -// ReadFailed, -// anyerror, -// }; +pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip; -pub fn gzipDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { +fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { var rdr: Reader = .fixed(in); const buf = try alloc.alloc(u8, out.len); defer alloc.free(buf); var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, buf); return decomp.reader.readSliceShort(out); } +fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { + _ = alloc; + _ = in; + _ = out; + return error.TODO; +} -pub fn lzmaDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { +pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma; + +fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { var rdr: Reader = .fixed(in); var decomp = try std.compress.lzma.decompress(alloc, rdr.adaptToOldInterface()); return decomp.read(out); } +fn cLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { + _ = alloc; + _ = in; + _ = out; + return error.TODO; +} -pub fn xzDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { +pub const xzDecompress = if (config.use_c_libs) cXz else zigXz; + +fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { var rdr: Reader = .fixed(in); var decomp = try std.compress.xz.decompress(alloc, rdr.adaptToOldInterface()); return decomp.read(out); } +fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { + _ = alloc; + _ = in; + _ = out; + return error.TODO; +} -pub fn zstdDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { +pub const zstdDecompress = if (config.use_c_libs) cZstd else zigZstd; + +pub fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { var rdr: Reader = .fixed(in); const buf = try alloc.alloc(u8, std.compress.zstd.default_window_len + std.compress.zstd.block_size_max); defer alloc.free(buf); @@ -46,3 +76,83 @@ pub fn zstdDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!us return decomp.err orelse err; }; } +fn cZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize { + _ = alloc; + const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len); + if (c.ZSTD_isError(res) == 0) return res; + return switch (c.ZSTD_getErrorCode(res)) { + c.ZSTD_error_prefix_unknown => cZstdError.PrefixUnknown, + c.ZSTD_error_version_unsupported => cZstdError.VersionUnsupported, + c.ZSTD_error_frameParameter_unsupported => cZstdError.FrameParameterUnsupported, + c.ZSTD_error_frameParameter_windowTooLarge => cZstdError.FrameParameterWindowTooLarge, + c.ZSTD_error_corruption_detected => cZstdError.CorruptionDetected, + c.ZSTD_error_checksum_wrong => cZstdError.ChecksumWrong, + c.ZSTD_error_literals_headerWrong => cZstdError.LiteralsHeaderWrong, + c.ZSTD_error_dictionary_corrupted => cZstdError.DictionaryCorrupted, + c.ZSTD_error_dictionary_wrong => cZstdError.DictionaryWrong, + c.ZSTD_error_dictionaryCreation_failed => cZstdError.DictionaryCreationFailed, + c.ZSTD_error_parameter_unsupported => cZstdError.ParameterUnsupported, + c.ZSTD_error_parameter_combination_unsupported => cZstdError.ParameterCombinationUnsupported, + c.ZSTD_error_parameter_outOfBound => cZstdError.ParameterOutOfBound, + c.ZSTD_error_tableLog_tooLarge => cZstdError.TableLogTooLarge, + c.ZSTD_error_maxSymbolValue_tooLarge => cZstdError.MaxSymbolValueTooLarge, + c.ZSTD_error_maxSymbolValue_tooSmall => cZstdError.MaxSymbolValueTooSmall, + c.ZSTD_error_cannotProduce_uncompressedBlock => cZstdError.CannotProduceUncompressedBlock, + c.ZSTD_error_stabilityCondition_notRespected => cZstdError.StabilityConditionNotRespected, + c.ZSTD_error_stage_wrong => cZstdError.StageWrong, + c.ZSTD_error_init_missing => cZstdError.InitMissing, + c.ZSTD_error_memory_allocation => cZstdError.MemoryAllocation, + c.ZSTD_error_workSpace_tooSmall => cZstdError.WorkSpaceTooSmall, + c.ZSTD_error_dstSize_tooSmall => cZstdError.DstSizeTooSmall, + c.ZSTD_error_srcSize_wrong => cZstdError.SrcSizeWrong, + c.ZSTD_error_dstBuffer_null => cZstdError.DstBufferNull, + c.ZSTD_error_noForwardProgress_destFull => cZstdError.NoForwardProgressDestFull, + c.ZSTD_error_noForwardProgress_inputEmpty => cZstdError.NoForwardProgressInputEmpty, + c.ZSTD_error_frameIndex_tooLarge => cZstdError.FrameIndexTooLarge, + c.ZSTD_error_seekableIO => cZstdError.SeekableIo, + c.ZSTD_error_dstBuffer_wrong => cZstdError.DstBufferWrong, + c.ZSTD_error_srcBuffer_wrong => cZstdError.SrcBufferWrong, + c.ZSTD_error_sequenceProducer_failed => cZstdError.SequenceProducerFailed, + c.ZSTD_error_externalSequences_invalid => cZstdError.ExternalSequencesInvalid, + c.ZSTD_error_maxCode => cZstdError.MaxCode, + else => cZstdError.Generic, + }; +} + +pub const cZstdError = error{ + Generic, + PrefixUnknown, + VersionUnsupported, + FrameParameterUnsupported, + FrameParameterWindowTooLarge, + CorruptionDetected, + ChecksumWrong, + LiteralsHeaderWrong, + DictionaryCorrupted, + DictionaryWrong, + DictionaryCreationFailed, + ParameterUnsupported, + ParameterCombinationUnsupported, + ParameterOutOfBound, + TableLogTooLarge, + MaxSymbolValueTooLarge, + MaxSymbolValueTooSmall, + CannotProduceUncompressedBlock, + StabilityConditionNotRespected, + StageWrong, + InitMissing, + MemoryAllocation, + WorkSpaceTooSmall, + DstSizeTooSmall, + SrcSizeWrong, + DstBufferNull, + NoForwardProgressDestFull, + NoForwardProgressInputEmpty, + FrameIndexTooLarge, + SeekableIo, + DstBufferWrong, + SrcBufferWrong, + SequenceProducerFailed, + ExternalSequencesInvalid, + MaxCode, +}; diff --git a/src/util/data.zig b/src/util/data.zig index aea7310..2cde849 100644 --- a/src/util/data.zig +++ b/src/util/data.zig @@ -92,10 +92,10 @@ fn advance(self: *DataReader) !void { const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size); defer self.alloc.free(tmp_buf); try rdr.interface.readSliceAll(tmp_buf); - const needed_block = try self.alloc.alloc(u8, self.frag_offset + cur_block_size); + const needed_block = try self.alloc.alloc(u8, self.block_size); defer self.alloc.free(needed_block); _ = try self.decomp(self.alloc, tmp_buf, needed_block); - @memcpy(self.interface.buffer, needed_block[self.frag_offset..]); + @memcpy(self.interface.buffer, needed_block[self.frag_offset .. self.frag_offset + cur_block_size]); return; } const block = self.blocks[self.block_idx];