From 4ee15b036a1c7d7f4b0e589872c5a9f6f90dcacd Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Fri, 3 Apr 2026 21:25:13 -0500 Subject: [PATCH] Finished(?) decompression stuff --- src/archive.zig | 14 +-- src/bin/unsquashfs.zig | 254 ++++++++++++++++++++-------------------- src/decomp.zig | 13 +- src/decomp/c/lz4.zig | 12 ++ src/decomp/c/lzma.zig | 142 ++++++++++++++++++++++ src/decomp/c/lzo.zig | 28 +++++ src/decomp/c/xz.zig | 21 +++- src/decomp/c/zlib.zig | 11 +- src/decomp/c/zstd.zig | 11 +- src/decomp/types.zig | 68 ++++++++++- src/decomp/zig/lzma.zig | 18 +++ src/decomp/zig/xz.zig | 15 ++- src/decomp/zig/zlib.zig | 2 +- src/decomp/zig/zstd.zig | 2 +- 14 files changed, 455 insertions(+), 156 deletions(-) diff --git a/src/archive.zig b/src/archive.zig index 8448df2..dbaafe7 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -1,11 +1,15 @@ const std = @import("std"); +const DecompTypes = @import("decomp/types.zig"); +const Decompressor = @import("decomp.zig"); const Inode = @import("inode.zig"); const Archive = @This(); super: Superblock, +stateless_decomp: Decompressor.StatelessDecomp, + pub fn init(fil: std.fs.File, offset: u64) !Archive { var super: Superblock = undefined; var fil_rdr = fil.reader(&[0]u8{}); @@ -16,6 +20,7 @@ pub fn init(fil: std.fs.File, offset: u64) !Archive { return .{ .super = super, + .stateless_decomp = DecompTypes.getStatelessFn(super.compression), }; } @@ -36,14 +41,7 @@ pub const Superblock = packed struct { mod_time: u32, block_size: u32, frag_count: u32, - compression: enum(u16) { - gzip = 1, // Though officially named gzip, it actually uses zlib. - lzma, - lzo, - xz, - lz4, - zstd, - }, + compression: DecompTypes.Enum, block_log: u16, flags: packed struct { inode_uncompressed: bool, diff --git a/src/bin/unsquashfs.zig b/src/bin/unsquashfs.zig index 23c49b5..7811f75 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(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.zig b/src/decomp.zig index 4bc30c3..7e11063 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -1,5 +1,7 @@ const std = @import("std"); +pub const StatelessDecomp = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize; + pub const Error = error{ OutOfMemory, EndOfStream, @@ -11,14 +13,17 @@ const Decompressor = @This(); alloc: std.mem.Allocator = std.heap.page_allocator, vtable: *struct { - decompress: *const fn (*Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress, - stateless: *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize, + decompress: *const fn (*const Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress, + stateless: StatelessDecomp, }, -pub fn decompress(self: *Decompressor, in: []u8, out: []u8) Error!usize { +pub fn decompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize { return self.vtable.decompress(self, in, out); } +pub fn stateless(self: Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize { + return self.vtable.stateless(alloc, in, out); +} -fn defaultDecompress(self: *Decompressor, in: []u8, out: []u8) Error!usize { +fn defaultDecompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize { return self.vtable.stateless(self.alloc, in, out); } diff --git a/src/decomp/c/lz4.zig b/src/decomp/c/lz4.zig index e69de29..3a5a2cb 100644 --- a/src/decomp/c/lz4.zig +++ b/src/decomp/c/lz4.zig @@ -0,0 +1,12 @@ +const std = @import("std"); + +const c = @import("../../c_libs.zig").c; +const Decompressor = @import("../../decomp.zig"); + +interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, + +pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + const res = c.LZ4_decompress_safe(in.ptr, out.ptr, in.len, out.len); + if (res > 0) return @abs(res); + return Decompressor.Error.ReadFailed; // TOOD: Find out what errors can be returned. +} diff --git a/src/decomp/c/lzma.zig b/src/decomp/c/lzma.zig index e69de29..a95606f 100644 --- a/src/decomp/c/lzma.zig +++ b/src/decomp/c/lzma.zig @@ -0,0 +1,142 @@ +const std = @import("std"); + +const c = @import("../../c_libs.zig").c; +const Decompressor = @import("../../decomp.zig"); + +const Lzma = @This(); + +streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream), + +interface: Decompressor, + +err: ?Error = null, + +pub fn init(alloc: std.mem.Allocator) !Lzma { + return .{ + .streams = try .init(alloc), + .interface = &.{ + .alloc = alloc, + .vtable = .{ .decompress = decompress, .stateless = stateless }, + }, + }; +} +pub fn deinit(self: *Lzma) void { + var values = self.streams.valueIterator(); + while (values.next()) |val| { + c.lzma_end(val); + } + self.streams.deinit(); +} + +fn getOrCreate(self: *Lzma) !*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: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { + var self: *Lzma = @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_alone_decoder(stream, out.len, 0); + decodeResult(res) catch |err| { + self.err = err; + lzmaErrorToDecompError(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| { + self.err = err; + lzmaErrorToDecompError(err); + }; + } + return stream.total_out; +} +pub 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_alone_decoder(&stream, out.len, 0); + decodeResult(res) catch |err| lzmaErrorToDecompError(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| lzmaErrorToDecompError(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 lzmaErrorToDecompError(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/lzo.zig b/src/decomp/c/lzo.zig index e69de29..7ed04ad 100644 --- a/src/decomp/c/lzo.zig +++ b/src/decomp/c/lzo.zig @@ -0,0 +1,28 @@ +const std = @import("std"); + +const c = @import("../../c_libs.zig").c; +const Decompressor = @import("../../decomp.zig"); + +interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, + +pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + var out_len = out.len; + const res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null); + return switch (res) { + c.LZO_E_OK => out_len, + c.LZO_E_ERROR => Decompressor.Error.ReadFailed, + c.LZO_E_OUT_OF_MEMORY => Decompressor.Error.OutOfMemory, + c.LZO_E_NOT_COMPRESSIBLE => Decompressor.Error.ReadFailed, + c.LZO_E_INPUT_OVERRUN => Decompressor.Error.ReadFailed, + c.LZO_E_OUTPUT_OVERRUN => Decompressor.Error.WriteFailed, + c.LZO_E_LOOKBEHIND_OVERRUN => Decompressor.Error.ReadFailed, + c.LZO_E_EOF_NOT_FOUND => Decompressor.Error.ReadFailed, + c.LZO_E_INPUT_NOT_CONSUMED => Decompressor.Error.ReadFailed, + c.LZO_E_NOT_YET_IMPLEMENTED => Decompressor.Error.ReadFailed, + c.LZO_E_INVALID_ARGUMENT => Decompressor.Error.ReadFailed, + c.LZO_E_INVALID_ALIGNMENT => Decompressor.Error.ReadFailed, + c.LZO_E_OUTPUT_NOT_CONSUMED => Decompressor.Error.WriteFailed, + c.LZO_E_INTERNAL_ERROR => Decompressor.Error.ReadFailed, + else => Decompressor.Error.ReadFailed, + }; +} diff --git a/src/decomp/c/xz.zig b/src/decomp/c/xz.zig index f4fb99c..b324440 100644 --- a/src/decomp/c/xz.zig +++ b/src/decomp/c/xz.zig @@ -20,6 +20,13 @@ pub fn init(alloc: std.mem.Allocator) !Xz { }, }; } +pub fn deinit(self: *Xz) void { + var values = self.streams.valueIterator(); + while (values.next()) |val| { + c.lzma_end(val); + } + self.streams.deinit(); +} fn getOrCreate(self: *Xz) !*c.lzma_stream { const res = try self.streams.getOrPut(std.Thread.getCurrentId()); @@ -34,7 +41,7 @@ fn getOrCreate(self: *Xz) !*c.lzma_stream { return res.value_ptr; } -fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { +fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { var self: *Xz = @fieldParentPtr("interface", decomp); const stream = try self.getOrCreate(); @@ -43,16 +50,22 @@ fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usi 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); + decodeResult(res) catch |err| { + self.err = 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); + decodeResult(res) catch |err| { + self.err = err; + xzErrorToDecompError(err); + }; } return stream.total_out; } -fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +pub 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, diff --git a/src/decomp/c/zlib.zig b/src/decomp/c/zlib.zig index 944277c..459b69f 100644 --- a/src/decomp/c/zlib.zig +++ b/src/decomp/c/zlib.zig @@ -20,6 +20,13 @@ pub fn init(alloc: std.mem.Allocator) !Zlib { }, }; } +pub fn deinit(self: *Zlib) void { + var values = self.streams.valueIterator(); + while (values.next()) |val| { + _ = c.zng_deflateEnd(val); + } + self.streams.deinit(); +} fn getOrCreate(self: *Zlib) !*c.zng_stream { const res = try self.streams.getOrPut(std.Thread.getCurrentId()); @@ -32,7 +39,7 @@ fn getOrCreate(self: *Zlib) !*c.zng_stream { return res.value_ptr; } -fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { +fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { var self: *Zlib = @fieldParentPtr("interface", decomp); var stream = try self.getOrCreate(); @@ -55,7 +62,7 @@ fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usi }; } -fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { _ = alloc; var out_len = out.len; const res = c.zng_uncompress(out.ptr, &out_len, in.ptr, in.len); diff --git a/src/decomp/c/zstd.zig b/src/decomp/c/zstd.zig index 47f2c22..847a9c6 100644 --- a/src/decomp/c/zstd.zig +++ b/src/decomp/c/zstd.zig @@ -20,6 +20,13 @@ pub fn init(alloc: std.mem.Allocator) !Zstd { }, }; } +pub fn deinit(self: *Zstd) void { + var values = self.context.valueIterator(); + while (values.next()) |val| { + _ = c.ZSTD_freeDCtx(val.*); + } + self.context.deinit(); +} fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx { const res = try self.context.getOrPut(std.Thread.getCurrentId()); @@ -29,7 +36,7 @@ fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx { return res.value_ptr; } -fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { +fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { var self: *Zstd = @fieldParentPtr("interface", decomp); const ctx = self.getOrCreate(); @@ -40,7 +47,7 @@ fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usi }; return res; } -fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { _ = alloc; const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len); decodeError(res) catch |err| return ZstdErrorToDecompError(err); diff --git a/src/decomp/types.zig b/src/decomp/types.zig index f9ba627..86722fb 100644 --- a/src/decomp/types.zig +++ b/src/decomp/types.zig @@ -1,8 +1,64 @@ const config = @import("config"); -const Zlib = if (config.use_zig_decomp) @import("zig/zlib.zig") else @import("c/zlib.zig"); -const Lzma = if (config.use_zig_decomp) @import("zig/lzma.zig") else @import("c/lzma.zig"); -const Lzo = if (config.use_zig_decomp) void else @import("c/lzo.zig"); -const Xz = if (config.use_zig_decomp) @import("zig/xz.zig") else @import("c/xz.zig"); -const Lz4 = if (config.use_zig_decomp) void else @import("c/lz4.zig"); -const Zstd = if (config.use_zig_decomp) @import("zig/zstd.zig") else @import("c/zstd.zig"); +const Decompressor = @import("../decomp.zig"); + +pub fn getStatelessFn(decomp: Enum) !Decompressor.StatelessDecomp { + if (config.use_zig_decomp) { + return switch (decomp) { + .gzip => @import("zig/zlib.zig").stateless, + .lzma => @import("zig/lzma.zig").stateless, + .xz => @import("zig/xz.zig").stateless, + .zstd => @import("zig/zstd.zig").stateless, + .lz4 => error.ZigLz4Unsupported, + .lzo => error.ZigLzoUnsupported, + }; + } + return switch (decomp) { + .gzip => @import("c/zlib.zig").stateless, + .lzma => @import("c/lzma.zig").stateless, + .lzo => @import("c/lzo.zig").stateless, + .xz => @import("c/xz.zig").stateless, + .lz4 => @import("c/lz4.zig").stateless, + .zstd => @import("c/zstd.zig").stateless, + }; +} + +pub const Enum = enum(u16) { + gzip = 1, // Though officially named gzip, it actually uses zlib. + lzma, + lzo, + xz, + lz4, + zstd, +}; + +pub const Decomp = if (config.use_zig_decomp) + union(enum) { + gzip: @import("zig/zlib.zig"), + lzma: @import("zig/lzma.zig"), + xz: @import("zig/xz.zig"), + zstd: @import("zig/zstd.zig"), + + pub fn deinit(_: *Decomp) void { + return; + } + } +else + union(enum) { + gzip: @import("c/zlib.zig"), + lzma: @import("c/lzma.zig"), + lzo: @import("c/lzo.zig"), + xz: @import("c/xz.zig"), + lz4: @import("c/lz4.zig"), + zstd: @import("c/zstd.zig"), + + pub fn deinit(self: *Decomp) void { + switch (self) { + .gzip => self.gzip.deinit(), + .lzma => self.lzma.deinit(), + .xz => self.xz.deinit(), + .zstd => self.zstd.deinit(), + else => {}, + } + } + }; diff --git a/src/decomp/zig/lzma.zig b/src/decomp/zig/lzma.zig index e69de29..1f5c089 100644 --- a/src/decomp/zig/lzma.zig +++ b/src/decomp/zig/lzma.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const lzma = std.compress.lzma; +const Reader = std.Io.Reader; + +const Decompressor = @import("../../decomp.zig"); + +interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, + +pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + var rdr: Reader = .static(in); + var decomp = try lzma.decompress(alloc, rdr.adaptToOldInterface()); + defer decomp.deinit(); + const len = decomp.read(out) catch |err| return switch (err) { + error.CorruptInput, error.EndOfStream, error.Overflow => Decompressor.Error.ReadFailed, + else => err, + }; + return len; +} diff --git a/src/decomp/zig/xz.zig b/src/decomp/zig/xz.zig index e371afa..d53b368 100644 --- a/src/decomp/zig/xz.zig +++ b/src/decomp/zig/xz.zig @@ -6,5 +6,18 @@ const Decompressor = @import("../../decomp.zig"); interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, -fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { + var rdr: Reader = .static(in); + var decomp = try xz.decompress(alloc, rdr.adaptToOldInterface()); + defer decomp.deinit(); + const len = decomp.read(out) catch |err| return switch (err) { + error.CorruptInput => Decompressor.Error.ReadFailed, + error.EndOfStream => Decompressor.Error.ReadFailed, + error.EndOfStreamWithNoError => Decompressor.Error.ReadFailed, + error.WrongChecksum => Decompressor.Error.ReadFailed, + error.Unsupported => Decompressor.Error.ReadFailed, + error.Overflow => Decompressor.Error.WriteFailed, + else => err, + }; + return len; } diff --git a/src/decomp/zig/zlib.zig b/src/decomp/zig/zlib.zig index ddec263..997efd2 100644 --- a/src/decomp/zig/zlib.zig +++ b/src/decomp/zig/zlib.zig @@ -6,7 +6,7 @@ const Decompressor = @import("../../decomp.zig"); interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, -fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { const buf = try alloc.alloc(u8, out.len); defer alloc.free(buf); var rdr: Reader = .static(in); diff --git a/src/decomp/zig/zstd.zig b/src/decomp/zig/zstd.zig index 6714a7b..479a391 100644 --- a/src/decomp/zig/zstd.zig +++ b/src/decomp/zig/zstd.zig @@ -6,7 +6,7 @@ const Decompressor = @import("../../decomp.zig"); interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, -fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { +pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { const buf = try alloc.alloc(u8, out.len * 2); defer alloc.free(buf); var rdr: Reader = .static(in);