From c01caba69be788b2224dd86cbf6c83e5c912552b Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Thu, 2 Apr 2026 01:42:43 -0500 Subject: [PATCH] More decompressor work --- src/bin/unsquashfs.zig | 254 ++++++++++++++++++++--------------------- src/decomp/c/xz.zig | 129 +++++++++++++++++++++ src/decomp/c/zlib.zig | 3 +- src/decomp/c/zstd.zig | 3 +- src/decomp/zig/xz.zig | 10 ++ 5 files changed, 270 insertions(+), 129 deletions(-) diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig index a945c01..23c49b5 100644 --- a/src/bin/unsquashfs.zig +++ b/src/bin/unsquashfs.zig @@ -1,133 +1,133 @@ -const std = @import("std"); -const Writer = std.Io.Writer; -const builtin = @import("builtin"); +// const std = @import("std"); +// const Writer = std.Io.Writer; +// const builtin = @import("builtin"); -const config = @import("config"); -const squashfs = @import("zig_squashfs"); +// const config = @import("config"); +// const squashfs = @import("zig_squashfs"); -//TODO: Add more options -const help_mgs = - \\ - \\Usage: unsquashfs [options] - \\ - \\Options: - \\ -d Extract to the given location instead of "squashfs-root" - \\ - \\ -o Start reading the archive at the given offset. - \\ -dx Don't set xattr values - \\ -dp Don't set permissions (includes setting uid & gid owner) - \\ - \\ -p Specify how many threads to use. If no present or zero, the system's logical cores count is used. - \\ -v Verbose - \\ - \\ --force Force extraction. If the destination already exists, it will be deleted. - \\ - \\ --help Display this messages - \\ --version Display the version - \\ -; +// //TODO: Add more options +// const help_mgs = +// \\ +// \\Usage: unsquashfs [options] +// \\ +// \\Options: +// \\ -d Extract to the given location instead of "squashfs-root" +// \\ +// \\ -o Start reading the archive at the given offset. +// \\ -dx Don't set xattr values +// \\ -dp Don't set permissions (includes setting uid & gid owner) +// \\ +// \\ -p Specify how many threads to use. If no present or zero, the system's logical cores count is used. +// \\ -v Verbose +// \\ +// \\ --force Force extraction. If the destination already exists, it will be deleted. +// \\ +// \\ --help Display this messages +// \\ --version Display the version +// \\ +// ; -const errors = error{InvalidArguments}; +// const errors = error{InvalidArguments}; -var archive: []const u8 = ""; -var extLoc: []const u8 = "squashfs-root"; -var offset: u64 = 0; -var threads: u32 = 0; -var verbose: bool = false; -var ignore_xattrs: bool = false; -var ignore_permissions: bool = false; -var force: bool = false; +// var archive: []const u8 = ""; +// var extLoc: []const u8 = "squashfs-root"; +// var offset: u64 = 0; +// var threads: u32 = 0; +// var verbose: bool = false; +// 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{}); - 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", .{}); - try out.interface.print(help_mgs, .{}); - return; - } - var fil: std.fs.File = try std.fs.cwd().openFile(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. - defer arc.deinit(); - const options: squashfs.ExtractionOptions = .{ - .threads = if (threads == 0) try std.Thread.getCpuCount() else threads, - .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. -} +// pub fn main() !void { +// const alloc = std.heap.smp_allocator; +// var stdout = std.fs.File.stdout(); +// 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", .{}); +// try out.interface.print(help_mgs, .{}); +// return; +// } +// var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully. +// defer fil.close(); +// var arc: squashfs.Archive = try .init(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, +// .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. +// } -fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { - var args = try std.process.argsWithAllocator(alloc); - defer args.deinit(); - _ = args.next(); // args[0] is the application launch command. - while (args.next()) |arg| { - if (std.mem.eql(u8, arg, "-o")) { - const nxt = args.next(); - if (nxt == null or nxt.?.len == 0) { - try out.print("-o must be followed by a number\n", .{}); - return errors.InvalidArguments; - } - offset = std.fmt.parseInt(u64, nxt.?, 10) catch { - try out.print("-o must be followed by a number\n", .{}); - return errors.InvalidArguments; - }; - continue; - } else if (std.mem.eql(u8, arg, "-d")) { - const nxt = args.next(); - if (nxt == null or nxt.?.len == 0) { - try out.print("-d must be followed by a location\n", .{}); - return errors.InvalidArguments; - } - extLoc = nxt.?; - continue; - } else if (std.mem.eql(u8, arg, "-p")) { - const nxt = args.next(); - if (nxt == null or nxt.?.len == 0) { - try out.print("-p must be followed by a number\n", .{}); - return errors.InvalidArguments; - } - threads = std.fmt.parseInt(u32, nxt.?, 10) catch { - try out.print("-p must be followed by a number\n", .{}); - return errors.InvalidArguments; - }; - continue; - } else if (std.mem.eql(u8, arg, "-v")) { - verbose = true; - continue; - } else if (std.mem.eql(u8, arg, "-dx")) { - ignore_xattrs = true; - continue; - } else if (std.mem.eql(u8, arg, "-dp")) { - ignore_permissions = true; - continue; - } else if (std.mem.eql(u8, arg, "--force")) { - force = true; - continue; - } else if (std.mem.eql(u8, arg, "--version")) { - try out.print("zig-unsquashfs v", .{}); - try config.version.format(out); - try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode }); - std.process.exit(0); - return; - } else if (std.mem.eql(u8, arg, "--help")) { - try out.print(help_mgs, .{}); - std.process.exit(0); - return; - } - if (archive.len > 0) { - try out.print("you can only provide one file at a time\n", .{}); - try out.print(help_mgs, .{}); - return errors.InvalidArguments; - } - archive = arg; - } -} +// fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { +// var args = try std.process.argsWithAllocator(alloc); +// defer args.deinit(); +// _ = args.next(); // args[0] is the application launch command. +// while (args.next()) |arg| { +// if (std.mem.eql(u8, arg, "-o")) { +// const nxt = args.next(); +// if (nxt == null or nxt.?.len == 0) { +// try out.print("-o must be followed by a number\n", .{}); +// return errors.InvalidArguments; +// } +// offset = std.fmt.parseInt(u64, nxt.?, 10) catch { +// try out.print("-o must be followed by a number\n", .{}); +// return errors.InvalidArguments; +// }; +// continue; +// } else if (std.mem.eql(u8, arg, "-d")) { +// const nxt = args.next(); +// if (nxt == null or nxt.?.len == 0) { +// try out.print("-d must be followed by a location\n", .{}); +// return errors.InvalidArguments; +// } +// extLoc = nxt.?; +// continue; +// } else if (std.mem.eql(u8, arg, "-p")) { +// const nxt = args.next(); +// if (nxt == null or nxt.?.len == 0) { +// try out.print("-p must be followed by a number\n", .{}); +// return errors.InvalidArguments; +// } +// threads = std.fmt.parseInt(u32, nxt.?, 10) catch { +// try out.print("-p must be followed by a number\n", .{}); +// return errors.InvalidArguments; +// }; +// continue; +// } else if (std.mem.eql(u8, arg, "-v")) { +// verbose = true; +// continue; +// } else if (std.mem.eql(u8, arg, "-dx")) { +// ignore_xattrs = true; +// continue; +// } else if (std.mem.eql(u8, arg, "-dp")) { +// ignore_permissions = true; +// continue; +// } else if (std.mem.eql(u8, arg, "--force")) { +// force = true; +// continue; +// } else if (std.mem.eql(u8, arg, "--version")) { +// try out.print("zig-unsquashfs v", .{}); +// try config.version.format(out); +// try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode }); +// std.process.exit(0); +// return; +// } else if (std.mem.eql(u8, arg, "--help")) { +// try out.print(help_mgs, .{}); +// std.process.exit(0); +// return; +// } +// if (archive.len > 0) { +// try out.print("you can only provide one file at a time\n", .{}); +// try out.print(help_mgs, .{}); +// return errors.InvalidArguments; +// } +// archive = arg; +// } +// } diff --git a/src/decomp/c/xz.zig b/src/decomp/c/xz.zig index e69de29..f4fb99c 100644 --- a/src/decomp/c/xz.zig +++ b/src/decomp/c/xz.zig @@ -0,0 +1,129 @@ +const std = @import("std"); + +const c = @import("../../c_libs.zig").c; +const Decompressor = @import("../../decomp.zig"); + +const Xz = @This(); + +streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream), + +interface: Decompressor, + +err: ?Error = null, + +pub fn init(alloc: std.mem.Allocator) !Xz { + return .{ + .streams = try .init(alloc), + .interface = &.{ + .alloc = alloc, + .vtable = .{ .decompress = decompress, .stateless = stateless }, + }, + }; +} + +fn getOrCreate(self: *Xz) !*c.lzma_stream { + const res = try self.streams.getOrPut(std.Thread.getCurrentId()); + if (res.found_existing) return res.value_ptr; + res.value_ptr.* = .{ + .alloc = .{ + .alloc = lzmaAlloc, + .free = lzmaFree, + .@"opaque" = &self.interface.alloc, + }, + }; + return res.value_ptr; +} + +fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { + var self: *Xz = @fieldParentPtr("interface", decomp); + + const stream = try self.getOrCreate(); + stream.next_in = in.ptr; + stream.avail_in = in.len; + stream.next_out = out.ptr; + stream.avail_out = out.len; + var res = c.lzma_stream_decoder(stream, out.len, 0); + decodeResult(res) catch |err| xzErrorToDecompError(err); + while (true) { + res = c.lzma_code(&stream, c.LZMA_RUN); + if (res == c.LZMA_OK) continue; + if (res == c.LZMA_STREAM_END) break; + decodeResult(res) catch |err| xzErrorToDecompError(err); + } + return stream.total_out; +} +fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + var stream: c.lzma_stream = .{ + .next_in = in.ptr, + .avail_in = in.len, + .next_out = out.ptr, + .avail_out = out.len, + .allocator = .{ + .alloc = lzmaAlloc, + .free = lzmaFree, + .@"opaque" = &alloc, + }, + }; + var res = c.lzma_stream_decoder(&stream, out.len, 0); + decodeResult(res) catch |err| xzErrorToDecompError(err); + while (true) { + res = c.lzma_code(&stream, c.LZMA_RUN); + if (res == c.LZMA_OK) continue; + if (res == c.LZMA_STREAM_END) break; + decodeResult(res) catch |err| xzErrorToDecompError(err); + } + return stream.total_out; +} + +inline fn decodeResult(res: c_uint) Error!void { + return switch (res) { + c.LZMA_OK => {}, + c.LZMA_STREAM_END => {}, + c.LZMA_NO_CHECK => {}, + c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck, + c.LZMA_MEM_ERROR => Error.OutOfMemory, + c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory, + c.LZMA_FORMAT_ERROR => Error.Format, + c.LZMA_OPTIONS_ERROR => Error.Options, + c.LZMA_DATA_ERROR => Error.Data, + c.LZMA_BUF_ERROR => Error.BufferExhausted, + c.LZMA_PROG_ERROR => Error.Programming, + c.LZMA_SEEK_NEEDED => Error.SeekNeeded, + else => Error.Unknown, + }; +} +fn xzErrorToDecompError(err: Error) Decompressor.Error { + switch (err) { + Error.OutOfMemory => return err, + Error.UnsupportedCheck => return Decompressor.Error.ReadFailed, + Error.Format => return Decompressor.Error.ReadFailed, + Error.Options => return Decompressor.Error.ReadFailed, + Error.Data => return Decompressor.Error.ReadFailed, + Error.BufferExhausted => return Decompressor.Error.WriteFailed, + Error.Programming => return Decompressor.Error.ReadFailed, + Error.SeekNeeded => return Decompressor.Error.ReadFailed, + Error.Unknown => return Decompressor.Error.ReadFailed, + } +} + +fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque { + var alloc: *std.mem.Allocator = @ptrCast(ptr); + return alloc.rawAlloc(size, .@"1", 0) catch return null; +} +fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void { + if (alloc_ptr == null) return; + var alloc: *std.mem.Allocator = @ptrCast(ptr); + alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0); +} + +pub const Error = error{ + OutOfMemory, + UnsupportedCheck, + Format, + Options, + Data, + BufferExhausted, + Programming, + SeekNeeded, + Unknown, +}; diff --git a/src/decomp/c/zlib.zig b/src/decomp/c/zlib.zig index b7821c1..944277c 100644 --- a/src/decomp/c/zlib.zig +++ b/src/decomp/c/zlib.zig @@ -14,7 +14,7 @@ err: ?Error = null, pub fn init(alloc: std.mem.Allocator) !Zlib { return .{ .streams = try .init(alloc), - .interface = .{ + .interface = &.{ .alloc = alloc, .vtable = .{ .decompress = decompress, .stateless = stateless }, }, @@ -29,6 +29,7 @@ fn getOrCreate(self: *Zlib) !*c.zng_stream { .zalloc = zalloc, .zfree = zfree, }; + return res.value_ptr; } fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { diff --git a/src/decomp/c/zstd.zig b/src/decomp/c/zstd.zig index cc5e432..47f2c22 100644 --- a/src/decomp/c/zstd.zig +++ b/src/decomp/c/zstd.zig @@ -14,7 +14,7 @@ err: ?Error = null, pub fn init(alloc: std.mem.Allocator) !Zstd { return .{ .streams = try .init(alloc), - .interface = .{ + .interface = &.{ .alloc = alloc, .vtable = .{ .decompress = decompress, .stateless = stateless }, }, @@ -26,6 +26,7 @@ fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx { if (res.found_existing) return res.value_ptr; res.value_ptr.* = c.ZSTD_createDCtx(); if (res.value_ptr.* == null) return Error.OutOfMemory; + return res.value_ptr; } fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { diff --git a/src/decomp/zig/xz.zig b/src/decomp/zig/xz.zig index e69de29..e371afa 100644 --- a/src/decomp/zig/xz.zig +++ b/src/decomp/zig/xz.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const xz = std.compress.xz; +const Reader = std.Io.Reader; + +const Decompressor = @import("../../decomp.zig"); + +interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, + +fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +}