Fixed a couple decompressor issues.

Re-added ExtractionOptions
This commit is contained in:
Caleb J. Gardner
2026-04-03 22:27:40 -05:00
parent 4ee15b036a
commit d0787a5200
14 changed files with 101 additions and 60 deletions
+18 -8
View File
@@ -3,13 +3,22 @@ const std = @import("std");
const DecompTypes = @import("decomp/types.zig"); const DecompTypes = @import("decomp/types.zig");
const Decompressor = @import("decomp.zig"); const Decompressor = @import("decomp.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const ExtractionOptions = @import("options.zig");
pub const Error = error{
BadMagic,
BadBlockLog,
BadVersion,
BadCheck,
};
const Archive = @This(); const Archive = @This();
super: Superblock, super: Superblock,
stateless_decomp: Decompressor.StatelessDecomp, stateless_decomp: Decompressor,
/// Create an Archive from a File.
pub fn init(fil: std.fs.File, offset: u64) !Archive { pub fn init(fil: std.fs.File, offset: u64) !Archive {
var super: Superblock = undefined; var super: Superblock = undefined;
var fil_rdr = fil.reader(&[0]u8{}); var fil_rdr = fil.reader(&[0]u8{});
@@ -20,16 +29,17 @@ pub fn init(fil: std.fs.File, offset: u64) !Archive {
return .{ return .{
.super = super, .super = super,
.stateless_decomp = DecompTypes.getStatelessFn(super.compression), .stateless_decomp = .{ .vtable = &.{ .stateless = try DecompTypes.getStatelessFn(super.compression) } },
}; };
} }
pub const Error = error{ pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void{
BadMagic, _ = self;
BadBlockLog, _ = alloc;
BadVersion, _ = path;
BadCheck, _ = options;
}; return error.TODO;
}
// Superblock // Superblock
+11 -12
View File
@@ -51,18 +51,17 @@ pub fn main() !void {
} }
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully. var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
defer fil.close(); defer fil.close();
// var arc: squashfs.Archive = try .init(fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully. var arc: squashfs.Archive = try .init(fil, offset); //TODO: Handle error gracefully.
// defer arc.deinit(); const options: squashfs.ExtractionOptions = .{
// const options: squashfs.ExtractionOptions = .{ .threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
// .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 std.fs.cwd().deleteTree(extLoc); try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
// try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
} }
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
+2 -5
View File
@@ -11,8 +11,8 @@ pub const Error = error{
const Decompressor = @This(); const Decompressor = @This();
alloc: std.mem.Allocator = std.heap.page_allocator, alloc: std.mem.Allocator = std.heap.smp_allocator,
vtable: *struct { vtable: *const struct {
decompress: *const fn (*const Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress, decompress: *const fn (*const Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress,
stateless: StatelessDecomp, stateless: StatelessDecomp,
}, },
@@ -20,9 +20,6 @@ vtable: *struct {
pub fn decompress(self: *const 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); 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: *const 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); return self.vtable.stateless(self.alloc, in, out);
+2 -2
View File
@@ -3,10 +3,10 @@ const std = @import("std");
const c = @import("../../c_libs.zig").c; const c = @import("../../c_libs.zig").c;
const Decompressor = @import("../../decomp.zig"); const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { 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); const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
if (res > 0) return @abs(res); if (res > 0) return @abs(res);
return Decompressor.Error.ReadFailed; // TOOD: Find out what errors can be returned. return Decompressor.Error.ReadFailed; // TOOD: Find out what errors can be returned.
} }
+12 -12
View File
@@ -49,10 +49,10 @@ fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Err
stream.avail_in = in.len; stream.avail_in = in.len;
stream.next_out = out.ptr; stream.next_out = out.ptr;
stream.avail_out = out.len; stream.avail_out = out.len;
var res = c.lzma_alone_decoder(stream, out.len, 0); var res = c.lzma_alone_decoder(stream, out.len);
decodeResult(res) catch |err| { decodeResult(res) catch |err| {
self.err = err; self.err = err;
lzmaErrorToDecompError(err); return lzmaErrorToDecompError(err);
}; };
while (true) { while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN); res = c.lzma_code(&stream, c.LZMA_RUN);
@@ -60,7 +60,7 @@ fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Err
if (res == c.LZMA_STREAM_END) break; if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| { decodeResult(res) catch |err| {
self.err = err; self.err = err;
lzmaErrorToDecompError(err); return lzmaErrorToDecompError(err);
}; };
} }
return stream.total_out; return stream.total_out;
@@ -71,19 +71,19 @@ pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Err
.avail_in = in.len, .avail_in = in.len,
.next_out = out.ptr, .next_out = out.ptr,
.avail_out = out.len, .avail_out = out.len,
.allocator = .{ .allocator = &.{
.alloc = lzmaAlloc, .alloc = lzmaAlloc,
.free = lzmaFree, .free = lzmaFree,
.@"opaque" = &alloc, .@"opaque" = @ptrCast(@constCast(&alloc)),
}, },
}; };
var res = c.lzma_alone_decoder(&stream, out.len, 0); var res = c.lzma_alone_decoder(&stream, out.len);
decodeResult(res) catch |err| lzmaErrorToDecompError(err); decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
while (true) { while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN); res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue; if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break; if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| lzmaErrorToDecompError(err); decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
} }
return stream.total_out; return stream.total_out;
} }
@@ -107,7 +107,7 @@ inline fn decodeResult(res: c_uint) Error!void {
} }
fn lzmaErrorToDecompError(err: Error) Decompressor.Error { fn lzmaErrorToDecompError(err: Error) Decompressor.Error {
switch (err) { switch (err) {
Error.OutOfMemory => return err, Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed, Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
Error.Format => return Decompressor.Error.ReadFailed, Error.Format => return Decompressor.Error.ReadFailed,
Error.Options => return Decompressor.Error.ReadFailed, Error.Options => return Decompressor.Error.ReadFailed,
@@ -120,12 +120,12 @@ fn lzmaErrorToDecompError(err: Error) Decompressor.Error {
} }
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque { fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(ptr); var alloc: *std.mem.Allocator = @alignCast(@ptrCast(ptr));
return alloc.rawAlloc(size, .@"1", 0) catch return null; return alloc.rawAlloc(size, .@"1", 0);
} }
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void { fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
if (alloc_ptr == null) return; if (alloc_ptr == null) return;
var alloc: *std.mem.Allocator = @ptrCast(ptr); var alloc: *std.mem.Allocator = @alignCast(@ptrCast(ptr));
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0); alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
} }
+1 -1
View File
@@ -3,7 +3,7 @@ const std = @import("std");
const c = @import("../../c_libs.zig").c; const c = @import("../../c_libs.zig").c;
const Decompressor = @import("../../decomp.zig"); const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize { pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var out_len = out.len; var out_len = out.len;
+14 -14
View File
@@ -23,12 +23,12 @@ pub fn init(alloc: std.mem.Allocator) !Xz {
pub fn deinit(self: *Xz) void { pub fn deinit(self: *Xz) void {
var values = self.streams.valueIterator(); var values = self.streams.valueIterator();
while (values.next()) |val| { while (values.next()) |val| {
c.lzma_end(val); c.xz_end(val);
} }
self.streams.deinit(); self.streams.deinit();
} }
fn getOrCreate(self: *Xz) !*c.lzma_stream { fn getOrCreate(self: *Xz) !*c.xz_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId()); const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr; if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{ res.value_ptr.* = .{
@@ -49,10 +49,10 @@ fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Err
stream.avail_in = in.len; stream.avail_in = in.len;
stream.next_out = out.ptr; stream.next_out = out.ptr;
stream.avail_out = out.len; stream.avail_out = out.len;
var res = c.lzma_stream_decoder(stream, out.len, 0); var res = c.lzma_alone_decoder(stream, out.len);
decodeResult(res) catch |err| { decodeResult(res) catch |err| {
self.err = err; self.err = err;
xzErrorToDecompError(err); return xzErrorToDecompError(err);
}; };
while (true) { while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN); res = c.lzma_code(&stream, c.LZMA_RUN);
@@ -60,7 +60,7 @@ fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Err
if (res == c.LZMA_STREAM_END) break; if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| { decodeResult(res) catch |err| {
self.err = err; self.err = err;
xzErrorToDecompError(err); return xzErrorToDecompError(err);
}; };
} }
return stream.total_out; return stream.total_out;
@@ -71,19 +71,19 @@ pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Err
.avail_in = in.len, .avail_in = in.len,
.next_out = out.ptr, .next_out = out.ptr,
.avail_out = out.len, .avail_out = out.len,
.allocator = .{ .allocator = &.{
.alloc = lzmaAlloc, .alloc = lzmaAlloc,
.free = lzmaFree, .free = lzmaFree,
.@"opaque" = &alloc, .@"opaque" = @ptrCast(@constCast(&alloc)),
}, },
}; };
var res = c.lzma_stream_decoder(&stream, out.len, 0); var res = c.lzma_alone_decoder(&stream, out.len);
decodeResult(res) catch |err| xzErrorToDecompError(err); decodeResult(res) catch |err| return xzErrorToDecompError(err);
while (true) { while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN); res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue; if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break; if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| xzErrorToDecompError(err); decodeResult(res) catch |err| return xzErrorToDecompError(err);
} }
return stream.total_out; return stream.total_out;
} }
@@ -107,7 +107,7 @@ inline fn decodeResult(res: c_uint) Error!void {
} }
fn xzErrorToDecompError(err: Error) Decompressor.Error { fn xzErrorToDecompError(err: Error) Decompressor.Error {
switch (err) { switch (err) {
Error.OutOfMemory => return err, Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed, Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
Error.Format => return Decompressor.Error.ReadFailed, Error.Format => return Decompressor.Error.ReadFailed,
Error.Options => return Decompressor.Error.ReadFailed, Error.Options => return Decompressor.Error.ReadFailed,
@@ -120,12 +120,12 @@ fn xzErrorToDecompError(err: Error) Decompressor.Error {
} }
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque { fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(ptr); var alloc: *std.mem.Allocator = @alignCast(@ptrCast(ptr));
return alloc.rawAlloc(size, .@"1", 0) catch return null; return alloc.rawAlloc(size, .@"1", 0);
} }
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void { fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
if (alloc_ptr == null) return; if (alloc_ptr == null) return;
var alloc: *std.mem.Allocator = @ptrCast(ptr); var alloc: *std.mem.Allocator = @alignCast(@ptrCast(ptr));
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0); alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
} }
+2 -2
View File
@@ -57,7 +57,6 @@ pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Err
inline fn decodeError(res: usize) Error!void { inline fn decodeError(res: usize) Error!void {
if (c.ZSTD_isError(res) == 0) return; if (c.ZSTD_isError(res) == 0) return;
return switch (c.ZSTD_getErrorCode(res)) { return switch (c.ZSTD_getErrorCode(res)) {
c.ZSTD_error_GENERIC => Error.Generic,
c.ZSTD_error_prefix_unknown => Error.PrefixUnknown, c.ZSTD_error_prefix_unknown => Error.PrefixUnknown,
c.ZSTD_error_version_unsupported => Error.VersionUnsupported, c.ZSTD_error_version_unsupported => Error.VersionUnsupported,
c.ZSTD_error_frameParameter_unsupported => Error.FrameParameterUnsupported, c.ZSTD_error_frameParameter_unsupported => Error.FrameParameterUnsupported,
@@ -91,11 +90,12 @@ inline fn decodeError(res: usize) Error!void {
c.ZSTD_error_srcBuffer_wrong => Error.SrcBufferWrong, c.ZSTD_error_srcBuffer_wrong => Error.SrcBufferWrong,
c.ZSTD_error_sequenceProducer_failed => Error.SequenceProducerFailed, c.ZSTD_error_sequenceProducer_failed => Error.SequenceProducerFailed,
c.ZSTD_error_externalSequences_invalid => Error.ExternalSequencesInvalid, c.ZSTD_error_externalSequences_invalid => Error.ExternalSequencesInvalid,
else => Error.Generic,
}; };
} }
inline fn ZstdErrorToDecompError(err: Error) Decompressor.Error { inline fn ZstdErrorToDecompError(err: Error) Decompressor.Error {
return switch (err) { return switch (err) {
Error.OutOfMemory => err, Error.OutOfMemory => Decompressor.Error.OutOfMemory,
Error.Generic => Decompressor.Error.ReadFailed, Error.Generic => Decompressor.Error.ReadFailed,
Error.PrefixUnknown => Decompressor.Error.ReadFailed, Error.PrefixUnknown => Decompressor.Error.ReadFailed,
Error.VersionUnsupported => Decompressor.Error.ReadFailed, Error.VersionUnsupported => Decompressor.Error.ReadFailed,
+1 -1
View File
@@ -4,7 +4,7 @@ const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig"); const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub 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 rdr: Reader = .static(in);
+1 -1
View File
@@ -4,7 +4,7 @@ const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig"); const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub 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 rdr: Reader = .static(in);
+1 -1
View File
@@ -4,7 +4,7 @@ const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig"); const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub 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); const buf = try alloc.alloc(u8, out.len);
+1 -1
View File
@@ -4,7 +4,7 @@ const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig"); const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = .{ .stateless = stateless } }, interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub 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); const buf = try alloc.alloc(u8, out.len * 2);
+33
View File
@@ -0,0 +1,33 @@
//! Options for file/directory extraction.
const std = @import("std");
const Writer = std.Io.Writer;
const ExtractionOptions = @This();
/// The number of threads used for extraction. 0 implies single threaded.
threads: usize = 1,
/// Don't set the file's owner & permissions after extraction
ignore_permissions: bool = false,
/// Don't set xattr values. Currently xattrs are never set anyway.
ignore_xattr: bool = false,
/// Replace symlinks with their target.
dereference_symlinks: bool = false,
/// Verbose logging. If true, verbose_writer must be set
verbose: bool = false,
/// Where to print verbose log.
verbose_writer: ?*Writer = null,
pub const SingleThreadedDefault: ExtractionOptions = .{};
pub fn Default() !ExtractionOptions {
return .{
.threads = try std.Thread.getCpuCount(),
};
}
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
return .{
.verbose = true,
.verbose_writer = wrt,
.threads = try std.Thread.getCpuCount(),
};
}
+2
View File
@@ -1 +1,3 @@
pub const Archive = @import("archive.zig"); pub const Archive = @import("archive.zig");
pub const ExtractionOptions = @import("options.zig");