More decompressor work

This commit is contained in:
Caleb J. Gardner
2026-04-02 01:42:43 -05:00
parent 3093994ac1
commit c01caba69b
5 changed files with 270 additions and 129 deletions
+127 -127
View File
@@ -1,133 +1,133 @@
const std = @import("std"); // const std = @import("std");
const Writer = std.Io.Writer; // const Writer = std.Io.Writer;
const builtin = @import("builtin"); // const builtin = @import("builtin");
const config = @import("config"); // const config = @import("config");
const squashfs = @import("zig_squashfs"); // const squashfs = @import("zig_squashfs");
//TODO: Add more options // //TODO: Add more options
const help_mgs = // const help_mgs =
\\ // \\
\\Usage: unsquashfs [options] <archive> // \\Usage: unsquashfs [options] <archive>
\\ // \\
\\Options: // \\Options:
\\ -d <location> Extract to the given location instead of "squashfs-root" // \\ -d <location> Extract to the given location instead of "squashfs-root"
\\ // \\
\\ -o <offset> Start reading the archive at the given offset. // \\ -o <offset> Start reading the archive at the given offset.
\\ -dx Don't set xattr values // \\ -dx Don't set xattr values
\\ -dp Don't set permissions (includes setting uid & gid owner) // \\ -dp Don't set permissions (includes setting uid & gid owner)
\\ // \\
\\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used. // \\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
\\ -v Verbose // \\ -v Verbose
\\ // \\
\\ --force Force extraction. If the destination already exists, it will be deleted. // \\ --force Force extraction. If the destination already exists, it will be deleted.
\\ // \\
\\ --help Display this messages // \\ --help Display this messages
\\ --version Display the version // \\ --version Display the version
\\ // \\
; // ;
const errors = error{InvalidArguments}; // const errors = error{InvalidArguments};
var archive: []const u8 = ""; // var archive: []const u8 = "";
var extLoc: []const u8 = "squashfs-root"; // var extLoc: []const u8 = "squashfs-root";
var offset: u64 = 0; // var offset: u64 = 0;
var threads: u32 = 0; // var threads: u32 = 0;
var verbose: bool = false; // var verbose: bool = false;
var ignore_xattrs: bool = false; // 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() !void {
const alloc = std.heap.smp_allocator; // const alloc = std.heap.smp_allocator;
var stdout = std.fs.File.stdout(); // var stdout = std.fs.File.stdout();
var out = stdout.writer(&[0]u8{}); // var out = stdout.writer(&[0]u8{});
defer out.interface.flush() catch {}; // defer out.interface.flush() catch {};
try handleArgs(alloc, &out.interface); // try handleArgs(alloc, &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: 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(alloc, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully. // var arc: squashfs.Archive = try .init(fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
defer arc.deinit(); // 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 {
var args = try std.process.argsWithAllocator(alloc); // var args = try std.process.argsWithAllocator(alloc);
defer args.deinit(); // defer args.deinit();
_ = args.next(); // args[0] is the application launch command. // _ = args.next(); // args[0] is the application launch command.
while (args.next()) |arg| { // while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "-o")) { // if (std.mem.eql(u8, arg, "-o")) {
const nxt = args.next(); // const nxt = args.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;
} // }
offset = std.fmt.parseInt(u64, nxt.?, 10) catch { // offset = std.fmt.parseInt(u64, nxt.?, 10) catch {
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;
}; // };
continue; // continue;
} else if (std.mem.eql(u8, arg, "-d")) { // } else if (std.mem.eql(u8, arg, "-d")) {
const nxt = args.next(); // const nxt = args.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;
} // }
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 = args.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;
} // }
threads = std.fmt.parseInt(u32, nxt.?, 10) catch { // threads = std.fmt.parseInt(u32, nxt.?, 10) catch {
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;
}; // };
continue; // continue;
} else if (std.mem.eql(u8, arg, "-v")) { // } else if (std.mem.eql(u8, arg, "-v")) {
verbose = true; // verbose = true;
continue; // continue;
} else if (std.mem.eql(u8, arg, "-dx")) { // } else if (std.mem.eql(u8, arg, "-dx")) {
ignore_xattrs = true; // ignore_xattrs = true;
continue; // continue;
} else if (std.mem.eql(u8, arg, "-dp")) { // } else if (std.mem.eql(u8, arg, "-dp")) {
ignore_permissions = true; // ignore_permissions = true;
continue; // continue;
} else if (std.mem.eql(u8, arg, "--force")) { // } else if (std.mem.eql(u8, arg, "--force")) {
force = true; // force = true;
continue; // continue;
} else if (std.mem.eql(u8, arg, "--version")) { // } else if (std.mem.eql(u8, arg, "--version")) {
try out.print("zig-unsquashfs v", .{}); // try out.print("zig-unsquashfs v", .{});
try config.version.format(out); // try config.version.format(out);
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode }); // try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
std.process.exit(0); // std.process.exit(0);
return; // return;
} else if (std.mem.eql(u8, arg, "--help")) { // } else if (std.mem.eql(u8, arg, "--help")) {
try out.print(help_mgs, .{}); // try out.print(help_mgs, .{});
std.process.exit(0); // std.process.exit(0);
return; // return;
} // }
if (archive.len > 0) { // if (archive.len > 0) {
try out.print("you can only provide one file at a time\n", .{}); // try out.print("you can only provide one file at a time\n", .{});
try out.print(help_mgs, .{}); // try out.print(help_mgs, .{});
return errors.InvalidArguments; // return errors.InvalidArguments;
} // }
archive = arg; // archive = arg;
} // }
} // }
+129
View File
@@ -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,
};
+2 -1
View File
@@ -14,7 +14,7 @@ err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Zlib { pub fn init(alloc: std.mem.Allocator) !Zlib {
return .{ return .{
.streams = try .init(alloc), .streams = try .init(alloc),
.interface = .{ .interface = &.{
.alloc = alloc, .alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless }, .vtable = .{ .decompress = decompress, .stateless = stateless },
}, },
@@ -29,6 +29,7 @@ fn getOrCreate(self: *Zlib) !*c.zng_stream {
.zalloc = zalloc, .zalloc = zalloc,
.zfree = zfree, .zfree = zfree,
}; };
return res.value_ptr;
} }
fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
+2 -1
View File
@@ -14,7 +14,7 @@ err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Zstd { pub fn init(alloc: std.mem.Allocator) !Zstd {
return .{ return .{
.streams = try .init(alloc), .streams = try .init(alloc),
.interface = .{ .interface = &.{
.alloc = alloc, .alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless }, .vtable = .{ .decompress = decompress, .stateless = stateless },
}, },
@@ -26,6 +26,7 @@ fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx {
if (res.found_existing) return res.value_ptr; if (res.found_existing) return res.value_ptr;
res.value_ptr.* = c.ZSTD_createDCtx(); res.value_ptr.* = c.ZSTD_createDCtx();
if (res.value_ptr.* == null) return Error.OutOfMemory; if (res.value_ptr.* == null) return Error.OutOfMemory;
return res.value_ptr;
} }
fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize { fn decompress(decomp: *Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
+10
View File
@@ -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 {
}