Try 4, lol.

This commit is contained in:
Caleb Gardner
2025-07-09 06:42:02 -05:00
parent 60e183512b
commit 5c14b7db48
22 changed files with 331 additions and 1721 deletions
+2 -1
View File
@@ -23,10 +23,11 @@ pub fn build(b: *std.Build) !void {
}); });
const exe_mod = b.createModule(.{ const exe_mod = b.createModule(.{
.root_source_file = b.path("src/zig_unsquashfs.zig"), .root_source_file = b.path("src/bin/unsquashfs.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
exe_mod.addImport("squashfs", lib_mod);
exe_mod.addOptions("config", opt); exe_mod.addOptions("config", opt);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.linkage = .static, .linkage = .static,
+3
View File
@@ -0,0 +1,3 @@
const import = @import("std");
pub fn main() void {}
-85
View File
@@ -1,85 +0,0 @@
const std = @import("std");
const io = std.io;
const compress = std.compress;
const DecompressError = error{
LzoUnsupported,
Lz4Unsupported,
};
pub const DecompressType = enum(u16) {
zlib = 1,
lzma,
lzo,
xz,
lz4,
zstd,
pub fn decompress(self: DecompressType, alloc: std.mem.Allocator, rdr: io.AnyReader) !std.ArrayList(u8) {
var out = std.ArrayList(u8).init(alloc);
errdefer out.deinit();
switch (self) {
.zlib => try compress.zlib.decompress(rdr, out.writer()),
.lzma => {
var decomp = try compress.lzma.decompress(alloc, rdr);
defer decomp.deinit();
try decomp.reader().readAllArrayList(&out, 1024 * 1024);
},
.lzo => return DecompressError.LzoUnsupported,
.xz => {
var decomp = try compress.xz.decompress(alloc, rdr);
defer decomp.deinit();
try decomp.reader().readAllArrayList(&out, 1024 * 1024);
},
.lz4 => return DecompressError.Lz4Unsupported,
.zstd => {
const buf = try alloc.alloc(u8, compress.zstd.DecompressorOptions.default_window_buffer_len);
defer alloc.free(buf);
var decomp = compress.zstd.decompressor(rdr, .{
.window_buffer = buf,
});
try decomp.reader().readAllArrayList(&out, 1024 * 1024);
},
}
return out;
}
pub fn decompressTo(self: DecompressType, alloc: std.mem.Allocator, rdr: io.AnyReader, writer: io.AnyWriter) anyerror!void {
const buf_size: usize = 8192;
switch (self) {
.zlib => try compress.zlib.decompress(rdr, writer),
.lzma => {
var decomp = try compress.lzma.decompress(alloc, rdr);
defer decomp.deinit();
var buf: [buf_size]u8 = undefined;
var red = try decomp.read(&buf);
while (red > 0) : (red = try decomp.read(&buf)) {
_ = try writer.writeAll(&buf);
}
},
.lzo => return DecompressError.LzoUnsupported,
.xz => {
var decomp = try compress.xz.decompress(alloc, rdr);
defer decomp.deinit();
var buf: [buf_size]u8 = undefined;
var red = try decomp.read(&buf);
while (red > 0) : (red = try decomp.read(&buf)) {
_ = try writer.writeAll(&buf);
}
},
.lz4 => return DecompressError.Lz4Unsupported,
.zstd => {
const window_buf = try alloc.alloc(u8, compress.zstd.DecompressorOptions.default_window_buffer_len);
defer alloc.free(window_buf);
var decomp = compress.zstd.decompressor(rdr, .{
.window_buffer = window_buf,
});
var buf: [buf_size]u8 = undefined;
var red = try decomp.read(&buf);
while (red > 0) : (red = try decomp.read(&buf)) {
_ = try writer.writeAll(&buf);
}
},
}
}
};
-66
View File
@@ -1,66 +0,0 @@
const std = @import("std");
const io = std.io;
const InodeType = @import("inode/inode.zig").InodeType;
const DirHeader = extern struct {
count: u32,
inode_block_start: u32,
inode_num: u32,
};
const RawDirEntryStart = packed struct {
inode_block_offset: u16,
/// Difference from the current DirHeader inode_num
inode_num_difference: i16,
/// Extended inodes will be their basic type.
inode_type: InodeType,
name_size: u16,
};
pub const DirEntry = struct {
block_start: u32,
offset: u16,
inode_num: u32,
name: []const u8,
fn init(alloc: std.mem.Allocator, hdr: DirHeader, rdr: io.AnyReader) !DirEntry {
const raw = try rdr.readStruct(RawDirEntryStart);
const name = try alloc.alloc(u8, raw.name_size + 1);
errdefer alloc.free(name);
_ = try rdr.read(name);
return .{
.block_start = hdr.inode_block_start,
.offset = raw.inode_block_offset,
.inode_num = if (raw.inode_num_difference > 0)
hdr.inode_num + @abs(raw.inode_num_difference)
else
hdr.inode_num - @abs(raw.inode_num_difference),
.name = name,
};
}
pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
};
pub fn readDirectory(alloc: std.mem.Allocator, rdr: io.AnyReader, size: u64) !std.StringHashMap(DirEntry) {
var out: std.StringHashMap(DirEntry) = .init(alloc);
errdefer out.deinit();
var red_size: u64 = 3;
var hdr: DirHeader = undefined;
while (red_size < size) {
hdr = try rdr.readStruct(DirHeader);
red_size += 12;
var i: u32 = 0;
try out.ensureUnusedCapacity(hdr.count + 1);
while (i <= hdr.count) : (i += 1) {
var tmp: DirEntry = try .init(alloc, hdr, rdr);
errdefer tmp.deinit(alloc);
out.putAssumeCapacity(tmp.name, tmp);
red_size += 8 + tmp.name.len;
}
}
return out;
}
-361
View File
@@ -1,361 +0,0 @@
const std = @import("std");
const io = std.io;
const fs = std.fs;
const builtin = @import("builtin");
const inode = @import("inode/inode.zig");
const directory = @import("directory.zig");
const Reader = @import("reader.zig").Reader;
const DirEntry = @import("directory.zig").DirEntry;
const DataReader = @import("readers/data_reader.zig").DataReader;
const DataExtractor = @import("readers/data_extractor.zig").DataExtractor;
const MetadataReader = @import("readers/metadata.zig").MetadataReader;
/// A file or directory inside of a squashfs.
/// Make sure to call deinit();
pub const File = struct {
name: []const u8,
inode: inode.Inode,
parent_path: []const u8,
dirEntries: ?std.StringHashMap(DirEntry) = null,
data_rdr: ?DataReader = null,
pub const FileError = error{
NotDirectory,
NotNormalFile,
NotSymlink,
NotFound,
};
fn fromDirEntry(rdr: *Reader, ent: DirEntry, parent_path: []const u8) !File {
var offset_rdr = rdr.holder.readerAt(ent.block_start + rdr.super.inode_table_start);
var meta_rdr: MetadataReader = .init(
rdr.alloc,
rdr.super.decomp,
offset_rdr.any(),
);
defer meta_rdr.deinit();
try meta_rdr.skip(ent.offset);
const name = try rdr.alloc.alloc(u8, ent.name.len);
errdefer rdr.alloc.free(name);
@memcpy(name, ent.name);
var out: File = .{
.name = name,
.inode = try .init(
rdr.alloc,
meta_rdr.any(),
rdr.super.block_size,
),
.parent_path = parent_path,
};
switch (out.inode.header.inode_type) {
.file, .ext_file => {
out.data_rdr = try .init(&out, rdr);
},
else => {},
}
return out;
}
pub fn file_path(self: File, alloc: std.mem.Allocator) ![]u8 {
if (self.parent_path.len == 0) {
const out = try alloc.alloc(u8, self.name.len);
@memcpy(out, self.name);
return out;
}
return std.mem.concat(alloc, u8, &[3][]const u8{ self.parent_path, "/", self.name });
}
pub fn uid(self: File, rdr: *Reader) !u32 {
return rdr.id_table.getValue(rdr, self.inode.header.uid_idx);
}
pub fn gid(self: File, rdr: *Reader) !u32 {
return rdr.id_table.getValue(rdr, self.inode.header.gid_idx);
}
pub fn deinit(self: *File, alloc: std.mem.Allocator) void {
self.inode.deinit();
alloc.free(self.name);
alloc.free(self.parent_path);
if (self.data_rdr != null) self.data_rdr.?.deinit();
if (self.dirEntries != null) {
var iter = self.dirEntries.?.iterator();
while (iter.next()) |ent| {
ent.value_ptr.deinit(alloc);
}
self.dirEntries.?.deinit();
}
}
pub fn isDir(self: File) bool {
return switch (self.inode.header.inode_type) {
.dir, .ext_dir => true,
else => false,
};
}
/// If the File is a directory, tries to return the file at path.
/// An empty path returns itself.
pub fn open(self: *File, rdr: *Reader, path: []const u8) !File {
return self.realOpen(rdr, path, true);
}
fn realOpen(self: *File, rdr: *Reader, path: []const u8, first: bool) (FileError || anyerror)!File {
const clean_path: []const u8 = std.mem.trim(u8, path, "/");
if (clean_path.len == 0) {
return self.*;
}
defer if (!first) self.deinit(rdr.alloc);
switch (self.inode.header.inode_type) {
.dir, .ext_dir => {},
else => return FileError.NotDirectory,
}
try self.readDirEntries(rdr);
const split_idx = std.mem.indexOf(u8, clean_path, "/") orelse clean_path.len;
const name = clean_path[0..split_idx];
const ent = self.dirEntries.?.get(name);
if (ent == null) {
return FileError.NotFound;
}
var fil = try fromDirEntry(rdr, ent.?, try self.file_path(rdr.alloc));
return fil.realOpen(rdr, clean_path[split_idx..], false);
}
/// If the File is a symlink, returns the symlink's target path.
pub fn symPath(self: File) (FileError || anyerror)![]const u8 {
return switch (self.inode.data) {
.sym => |s| s.target,
.ext_sym => |s| s.target,
else => FileError.NotSymlink,
};
}
/// If the File is a directory, returns an iterator that iterates over it's children.
pub fn iterator(self: *File, rdr: *Reader) (FileError || anyerror)!FileIterator {
switch (self.inode.header.inode_type) {
.dir, .ext_dir => {},
else => return FileError.NotDirectory,
}
try self.readDirEntries(rdr);
var files = try rdr.alloc.alloc(File, self.dirEntries.?.count());
errdefer rdr.alloc.free(files);
var dirEntryIter = self.dirEntries.?.valueIterator();
var i: u32 = 0;
while (dirEntryIter.next()) |ent| : (i += 1) {
files[i] = try .fromDirEntry(rdr, ent.*, try self.file_path(rdr.alloc));
}
return .{
.alloc = rdr.alloc,
.files = files,
};
}
fn readDirEntries(self: *File, rdr: *Reader) (FileError || anyerror)!void {
if (self.dirEntries != null) return;
var block_start: u32 = 0;
var offset: u16 = 0;
var siz: u32 = 0;
switch (self.inode.data) {
.dir => |d| {
block_start = d.block_start;
offset = d.offset;
siz = d.size;
},
.ext_dir => |d| {
block_start = d.block_start;
offset = d.offset;
siz = d.size;
},
else => return FileError.NotDirectory,
}
var offset_rdr = rdr.holder.readerAt(rdr.super.dir_table_start + block_start);
var meta_rdr: MetadataReader = .init(
rdr.alloc,
rdr.super.decomp,
offset_rdr.any(),
);
defer meta_rdr.deinit();
try meta_rdr.skip(offset);
self.dirEntries = try directory.readDirectory(rdr.alloc, meta_rdr.any(), siz);
}
pub fn size(self: File) u64 {
return switch (self.inode.data) {
.file => |f| f.size,
.ext_file => |f| f.size,
else => 0,
};
}
/// If the file is a normal file, reads it's data.
pub fn read(self: *File, bytes: []u8) (FileError || anyerror)!usize {
if (self.data_rdr == null) {
return FileError.NotNormalFile;
}
return self.data_rdr.?.read(bytes);
}
const FileReader = io.GenericReader(*File, (FileError || anyerror), read);
pub fn reader(self: *File) FileReader {
return .{
.context = self,
};
}
fn extractor(self: *File, rdr: *Reader) !DataExtractor {
return .init(self, rdr);
}
pub const ExtractConfig = struct {
/// The amount of worker threads to spawn. Defaults to your cpu core count.
thread_count: u16,
/// The maximum amount of additional memory this extraction will use.
/// Default is 1GB or a quarter of your system memory, whichever is smaller.
/// Actually memory usage will be higher, as this does not account of vaious metadata (such as file names).
max_mem: u64,
deref_sym: bool = false,
unbreak_sym: bool = false,
verbose: bool = false,
pub fn init() !ExtractConfig {
const sys_mem = try std.process.totalSystemMemory();
return .{
.thread_count = @truncate(try std.Thread.getCpuCount()),
.max_mem = @min(sys_mem / 4, 1024 * 1024 * 1024),
};
}
};
pub const ExtractError = error{
FileExists,
};
/// Extract's the File to the path.
pub fn extract(self: *File, rdr: *Reader, config: ExtractConfig, path: []const u8) (ExtractError || anyerror)!void {
var pol: std.Thread.Pool = undefined;
try pol.init(.{
.allocator = rdr.alloc,
.n_jobs = config.thread_count,
});
defer pol.deinit();
return self.extractReal(rdr, config, &pol, path, true);
}
fn extractReal(self: *File, rdr: *Reader, config: ExtractConfig, pool: *std.Thread.Pool, path: []const u8, first: bool) (ExtractError || anyerror)!void {
const real_path = std.mem.trimRight(u8, path, "/");
var exists = true;
var stat: ?fs.File.Stat = null;
if (fs.cwd().statFile(real_path)) |s| {
stat = s;
} else |err| {
if (err == fs.File.OpenError.FileNotFound) {
exists = false;
} else return err;
}
switch (self.inode.header.inode_type) {
.dir, .ext_dir => {
if (!exists) {
fs.cwd().makeDir(real_path) catch |err| {
if (config.verbose)
std.log.err("error creating directory {s}: {any}", .{ real_path, err });
return err;
};
}
var iter = try self.iterator(rdr);
defer iter.deinit();
while (iter.next()) |f| {
const extr_path = try std.mem.concat(rdr.alloc, u8, &[3][]const u8{ real_path, "/", f.name });
defer rdr.alloc.free(extr_path);
try f.extractReal(rdr, config, pool, extr_path, false);
}
},
.file, .ext_file => {
if ((!first and exists) or
(first and exists and stat.?.kind != .directory)) return ExtractError.FileExists;
var extr_path: []u8 = undefined;
if (first and exists and stat.?.kind == .directory) {
extr_path = try std.mem.concat(rdr.alloc, u8, &[3][]const u8{ real_path, "/", self.name });
} else {
extr_path = try rdr.alloc.alloc(u8, real_path.len);
@memcpy(extr_path, real_path);
}
defer rdr.alloc.free(extr_path);
var fil = fs.cwd().createFile(extr_path, .{}) catch |err| {
if (config.verbose)
std.log.err("error creating file {s}: {any}", .{ extr_path, err });
return err;
};
defer fil.close();
if (config.thread_count > 1 and self.size() > rdr.super.block_size) {
var ext = try self.extractor(rdr);
defer ext.deinit();
ext.writeToFile(pool, &fil) catch |err| {
if (config.verbose)
std.log.err("error writing file {s}: {any}", .{ self.name, err });
return err;
};
} else {
var buf = [1]u8{0} ** 8192;
var total_red: u64 = 0;
while (total_red < self.size()) {
const red = try self.read(&buf);
total_red += red;
}
}
},
.sym, .ext_sym => {
//TODO: unbreak symlinks & dereference symlinks
if (exists) return ExtractError.FileExists;
fs.cwd().symLink(try self.symPath(), real_path, .{}) catch |err| {
if (config.verbose)
std.log.err("error creating symlink {s}: {any}", .{ self.name, err });
return err;
};
},
.block, .ext_block, .char, .ext_char, .fifo, .ext_fifo => {
if (exists) return ExtractError.FileExists;
comptime if (builtin.os.tag != .linux) return;
const mode: u32 = switch (self.inode.header.inode_type) {
.block, .ext_block => std.posix.S.IFBLK,
.char, .ext_char => std.posix.S.IFCHR,
.fifo, .ext_fifo => std.posix.S.IFIFO,
else => unreachable,
};
const dev = switch (self.inode.data) {
.block, .char => |b| b.device,
.ext_block, .ext_char => |b| b.device,
.fifo, .ext_fifo => 0,
else => unreachable,
};
_ = std.os.linux.mknod(@ptrCast(real_path), mode, dev);
},
.sock, .ext_sock => {}, //TODO
}
//TODO: permissions
}
};
const FileIterator = struct {
alloc: std.mem.Allocator,
files: []File,
curIndex: u32 = 0,
pub fn next(self: *FileIterator) ?*File {
if (self.curIndex >= self.files.len) return null;
defer self.curIndex += 1;
return &self.files[self.curIndex];
}
pub fn reset(self: *FileIterator) void {
self.curIndex = 0;
}
pub fn deinit(self: *FileIterator) void {
for (self.files) |*f| {
f.deinit(self.alloc);
}
self.alloc.free(self.files);
}
};
-26
View File
@@ -1,26 +0,0 @@
const std = @import("std");
const BlockSize = @import("inode/file.zig").BlockSize;
const Reader = @import("reader.zig").Reader;
pub const FragEntry = packed struct {
start: u64,
size: BlockSize,
_: u32,
pub fn getData(self: FragEntry, rdr: *Reader, offset: u32, frag_size: u32) ![]u8 {
var offset_rdr = rdr.holder.readerAt(self.start);
if (self.size.not_compressed) {
const buf = try rdr.alloc.alloc(u8, frag_size);
_ = try offset_rdr.read(buf);
return buf;
}
var limit_rdr = std.io.limitedReader(offset_rdr, self.size.size);
var decomp = try rdr.super.decomp.decompress(rdr.alloc, limit_rdr.reader().any());
var frag_all = try decomp.toOwnedSlice();
defer rdr.alloc.free(frag_all);
const out = try rdr.alloc.alloc(u8, frag_size);
@memcpy(out, frag_all[offset .. offset + frag_size]);
return out;
}
};
+85
View File
@@ -0,0 +1,85 @@
const std = @import("std");
const dir = @import("inode/dir.zig");
const file = @import("inode/file.zig");
const misc = @import("inode/misc.zig");
pub const Ref = packed struct {
offset: u16,
block: u32,
_: u16,
};
const Type = enum(u16) {
dir = 1,
file,
symlink,
block_dev,
char_dev,
fifo,
socket,
ext_dir,
ext_file,
ext_symlink,
ext_block_dev,
ext_char_dev,
ext_fifo,
ext_socket,
};
const Header = packed struct {
type: Type,
perm: u16,
uid_idx: u16,
gid_idx: u16,
mod_time: u32,
num: u32,
};
const Data = union(enum) {
dir: dir.Dir,
file: file.File,
symlink: misc.Symlink,
block_dev: misc.Dev,
char_dev: misc.Dev,
fifo: misc.IPC,
socket: misc.IPC,
ext_dir: dir.ExtDir,
ext_file: file.ExtFile,
ext_symlink: misc.ExtSymlink,
ext_block_dev: misc.ExtDev,
ext_char_dev: misc.ExtDev,
ext_fifo: misc.ExtIPC,
ext_socket: misc.ExtIPC,
};
const Self = @This();
hdr: Header,
data: Data,
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !Self {
std.debug.assert(std.meta.hasFn(@TypeOf(rdr), "read"));
var hdr: Header = undefined;
_ = try rdr.read(std.mem.asBytes(&hdr));
const data = switch (hdr.type) {
.dir => .{ .dir = .init(rdr) },
.file => .{ .file = .init(rdr, alloc, block_size) },
.symlink => .{ .symlink = .init(rdr, alloc) },
.block_dev => .{ .block_dev = .init(rdr) },
.char_dev => .{ .char_dev = .init(rdr) },
.fifo => .{ .fifo = .init(rdr) },
.socket => .{ .socket = .init(rdr) },
.ext_dir => .{ .ext_dir = .init(rdr) },
.ext_file => .{ .ext_file = .init(rdr, alloc, block_size) },
.ext_symlink => .{ .ext_symlink = .init(rdr, alloc) },
.ext_block_dev => .{ .ext_block_dev = .init(rdr) },
.ext_char_dev => .{ .ext_char_dev = .init(rdr) },
.ext_fifo => .{ .ext_fifo = .init(rdr) },
.ext_socket => .{ .ext_socket = .init(rdr) },
};
return .{
.hdr = hdr,
.data = data,
};
}
+17 -23
View File
@@ -1,37 +1,31 @@
const io = @import("std").io; const std = @import("std");
pub const DirInode = packed struct { pub const Dir = packed struct {
block_start: u32, block: u32,
hard_links: u32, hard_link: u32,
/// Note: size is 3 larger then the actual size, due to "." and ".."
size: u16, size: u16,
offset: u16, offset: u16,
parent_num: u32, parent_num: u32,
pub fn init(rdr: io.AnyReader) !DirInode { pub fn init(rdr: anytype) !Dir {
return rdr.readStruct(DirInode); const out: Dir = undefined;
_ = rdr.read(std.mem.asBytes(&out));
return out;
} }
}; };
const DirIndex = struct { pub const ExtDir = packed struct {
offset: u32, hard_link: u32,
block_start: u32,
name_size: u32,
name: []const u8,
};
pub const ExtDirInode = packed struct {
hard_links: u32,
/// Note: size is 3 larger then the actual size, due to "." and ".."
size: u32, size: u32,
block_start: u32, block: u32,
parent_num: u32, parent_num: u32,
index_count: u16, idx_count: u16,
offset: u16, offset: u16,
xattr_inx: u32, xattr_idx: u32,
// TODO: possibly also read dir indexes. Maybe relagate to function...
pub fn init(rdr: io.AnyReader) !ExtDirInode { pub fn init(rdr: anytype) !ExtDir {
return rdr.readStruct(ExtDirInode); const out: ExtDir = undefined;
_ = rdr.read(std.mem.asBytes(&out));
return out;
} }
}; };
+41 -46
View File
@@ -1,76 +1,71 @@
const std = @import("std"); const std = @import("std");
const io = std.io;
pub const BlockSize = packed struct { pub const BlockSize = packed struct {
size: u24, size: u24,
not_compressed: bool, uncompressed: bool,
_: u7, _: u7,
}; };
pub const FileInode = struct { pub const File = struct {
data_start: u32, block: u32,
frag_idx: u32, frag_idx: u32,
frag_offset: u32, offset: u32,
size: u32, size: u32,
blocks: []const BlockSize, block_sizes: []BlockSize,
pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !FileInode { pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !File {
var fixed_buf: [16]u8 = undefined; var fixed: [16]u8 = undefined;
_ = try rdr.readAll(&fixed_buf); _ = try rdr.read(&fixed);
const frag_idx = std.mem.bytesToValue(u32, fixed_buf[4..8]); const frag_idx = std.mem.readInt(u32, fixed[4..8], .little);
const size = std.mem.bytesToValue(u32, fixed_buf[12..16]); const size = std.mem.readInt(u32, fixed[12..16], .little);
var block_num = size / block_size; var blocks: u32 = size / block_size;
if (frag_idx == 0xFFFFFFFF and size % block_size > 0) { if (size % block_size > 0 and frag_idx != 0xffffffff) {
block_num += 1; blocks += 1;
} }
const blocks = try alloc.alloc(BlockSize, block_num); const block_sizes = alloc.alloc(BlockSize, blocks);
_ = try rdr.readAll(@ptrCast(blocks)); errdefer alloc.free(block_sizes);
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
return .{ return .{
.data_start = std.mem.bytesToValue(u32, fixed_buf[0..4]), .block = std.mem.readInt(u32, fixed[0..4], .little),
.frag_idx = frag_idx, .frag_idx = frag_idx,
.frag_offset = std.mem.bytesToValue(u32, fixed_buf[8..12]), .offset = std.mem.readInt(u32, fixed[8..12], .little),
.size = size, .size = size,
.blocks = blocks, .block_sizes = block_sizes,
}; };
} }
pub fn deinit(self: FileInode, alloc: std.mem.Allocator) void {
alloc.free(self.blocks);
}
}; };
pub const ExtFileInode = struct { pub const ExtFile = struct {
data_start: u64, block: u64,
size: u64, size: u64,
sparse: u64, sparse: u64,
hard_links: u32, hard_link: u32,
frag_idx: u32, frag_idx: u32,
frag_offset: u32, offset: u32,
xattr_idx: u32, xattr_idx: u32,
blocks: []const BlockSize, block_sizes: []BlockSize,
pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !ExtFileInode { pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !ExtFile {
var fixed_buf = [1]u8{0} ** 40; var fixed: [40]u8 = undefined;
_ = try rdr.readAll(&fixed_buf); _ = try rdr.read(&fixed);
const size = std.mem.bytesToValue(u64, fixed_buf[8..16]); const size = std.mem.readInt(u64, fixed[8..16], .little);
const frag_idx = std.mem.bytesToValue(u32, fixed_buf[28..32]); const frag_idx = std.mem.readInt(u32, fixed[28..32], .little);
var block_num = size / block_size; var blocks: u32 = size / block_size;
if (frag_idx == 0xFFFFFFFF and size % block_size > 0) { if (size % block_size > 0 and frag_idx != 0xffffffff) {
block_num += 1; blocks += 1;
} }
const blocks = try alloc.alloc(BlockSize, block_num); const block_sizes = alloc.alloc(BlockSize, blocks);
_ = try rdr.readAll(@ptrCast(blocks)); errdefer alloc.free(block_sizes);
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
return .{ return .{
.data_start = std.mem.bytesToValue(u64, fixed_buf[0..8]), .block = std.mem.readInt(u64, fixed[0..8], .little),
.size = size, .size = size,
.sparse = std.mem.bytesToValue(u64, fixed_buf[16..24]), .sparse = std.mem.readInt(u64, fixed[16..24], .little),
.hard_links = std.mem.bytesToValue(u32, fixed_buf[24..28]), .hard_link = std.mem.readInt(u32, fixed[24..28], .little),
.frag_idx = frag_idx, .frag_idx = frag_idx,
.frag_offset = std.mem.bytesToValue(u32, fixed_buf[32..36]), .offset = std.mem.readInt(u32, fixed[32..36], .little),
.xattr_idx = std.mem.bytesToValue(u32, fixed_buf[36..40]), .xattr_idx = std.mem.readInt(u32, fixed[36..40], .little),
.blocks = blocks, .block_sizes = blocks,
}; };
} }
pub fn deinit(self: ExtFileInode, alloc: std.mem.Allocator) void {
alloc.free(self.blocks);
}
}; };
-103
View File
@@ -1,103 +0,0 @@
const std = @import("std");
const io = std.io;
pub const InodeRef = packed struct {
offset: u16,
block_start: u32,
_: u16 = 0,
pub fn init(block_start: u32, offset: u16) InodeRef {
return .{
.offset = offset,
.block_start = block_start,
};
}
};
pub const InodeType = enum(u16) {
dir = 1,
file,
sym,
block,
char,
fifo,
sock,
ext_dir,
ext_file,
ext_sym,
ext_block,
ext_char,
ext_fifo,
ext_sock,
};
const dir = @import("dir.zig");
const file = @import("file.zig");
const sym = @import("sym.zig");
const misc = @import("misc.zig");
pub const InodeData = union(enum) {
dir: dir.DirInode,
file: file.FileInode,
sym: sym.SymInode,
block: misc.DeviceInode,
char: misc.DeviceInode,
fifo: misc.IPCInode,
sock: misc.IPCInode,
ext_dir: dir.ExtDirInode,
ext_file: file.ExtFileInode,
ext_sym: sym.ExtSymInode,
ext_block: misc.ExtDeviceInode,
ext_char: misc.ExtDeviceInode,
ext_fifo: misc.ExtIPCInode,
ext_sock: misc.ExtIPCInode,
};
pub const InodeHeader = packed struct {
inode_type: InodeType,
perm: u16,
uid_idx: u16,
gid_idx: u16,
mod_time: u32,
num: u32,
};
pub const Inode = struct {
alloc: std.mem.Allocator,
header: InodeHeader,
data: InodeData,
pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !Inode {
const hdr = try rdr.readStruct(InodeHeader);
const data: InodeData = switch (hdr.inode_type) {
.dir => .{ .dir = try .init(rdr) },
.file => .{ .file = try .init(alloc, rdr, block_size) },
.sym => .{ .sym = try .init(alloc, rdr) },
.block => .{ .block = try .init(rdr) },
.char => .{ .char = try .init(rdr) },
.fifo => .{ .fifo = try .init(rdr) },
.sock => .{ .sock = try .init(rdr) },
.ext_dir => .{ .ext_dir = try .init(rdr) },
.ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) },
.ext_sym => .{ .ext_sym = try .init(alloc, rdr) },
.ext_block => .{ .ext_block = try .init(rdr) },
.ext_char => .{ .ext_char = try .init(rdr) },
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
.ext_sock => .{ .ext_sock = try .init(rdr) },
};
return .{
.alloc = alloc,
.header = hdr,
.data = data,
};
}
pub fn deinit(self: Inode) void {
switch (self.data) {
.file => |d| d.deinit(self.alloc),
.sym => |d| d.deinit(self.alloc),
.ext_file => |d| d.deinit(self.alloc),
.ext_sym => |d| d.deinit(self.alloc),
else => {},
}
}
};
+67 -18
View File
@@ -1,38 +1,87 @@
const std = @import("std"); const std = @import("std");
const io = std.io;
pub const DeviceInode = packed struct { pub const Symlink = struct {
hard_links: u32, hard_link: u32,
device: u32, // size: u32,
target: []const u8,
pub fn init(rdr: io.AnyReader) !DeviceInode { pub fn init(rdr: anytype, alloc: std.mem.Allocator) !Symlink {
return rdr.readStruct(DeviceInode); var fixed: [8]u8 = undefined;
_ = try rdr.read(&fixed);
const size = std.mem.readInt(u32, fixed[4..8], .little);
const target = alloc.alloc(u8, size);
errdefer alloc.free(target);
_ = try rdr.read(target);
return .{
.hard_link = std.mem.readInt(u32, fixed[0..4], .little),
.target = target,
};
} }
}; };
pub const ExtDeviceInode = packed struct { pub const ExtSymlink = struct {
hard_links: u32, hard_link: u32,
// size: u32,
target: []const u8,
xattr_idx: u32,
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !ExtSymlink {
var fixed: [8]u8 = undefined;
_ = try rdr.read(&fixed);
const size = std.mem.readInt(u32, fixed[4..8], .little);
const target = alloc.alloc(u8, size);
errdefer alloc.free(target);
_ = try rdr.read(target);
var xattr_idx: u32 = 0;
_ = try rdr.read(std.mem.asBytes(&xattr_idx));
return .{
.hard_link = std.mem.readInt(u32, fixed[0..4], .little),
.target = target,
.xattr_idx = xattr_idx,
};
}
};
pub const Dev = packed struct {
hard_link: u32,
device: u32,
pub fn init(rdr: anytype) !Dev {
const out: Dev = undefined;
_ = try rdr.read(std.mem.asBytes(&out));
return out;
}
};
pub const ExtDev = packed struct {
hard_link: u32,
device: u32, device: u32,
xattr_idx: u32, xattr_idx: u32,
pub fn init(rdr: io.AnyReader) !ExtDeviceInode { pub fn init(rdr: anytype) !ExtDev {
return rdr.readStruct(ExtDeviceInode); const out: ExtDev = undefined;
_ = try rdr.read(std.mem.asBytes(&out));
return out;
} }
}; };
pub const IPCInode = packed struct { pub const IPC = packed struct {
hard_links: u32, hard_link: u32,
pub fn init(rdr: io.AnyReader) !IPCInode { pub fn init(rdr: anytype) !IPC {
return rdr.readStruct(IPCInode); const out: IPC = undefined;
_ = try rdr.read(std.mem.asBytes(&out));
return out;
} }
}; };
pub const ExtIPCInode = packed struct { pub const ExtIPC = packed struct {
hard_links: u32, hard_link: u32,
xattr_idx: u32, xattr_idx: u32,
pub fn init(rdr: io.AnyReader) !ExtIPCInode { pub fn init(rdr: anytype) !ExtIPC {
return rdr.readStruct(ExtIPCInode); const out: ExtIPC = undefined;
_ = try rdr.read(std.mem.asBytes(&out));
return out;
} }
}; };
-48
View File
@@ -1,48 +0,0 @@
const std = @import("std");
const io = std.io;
pub const SymInode = struct {
hard_links: u32,
size: u32,
target: []const u8,
pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader) !SymInode {
var fixed_buf = [_]u8{0} ** 8;
_ = try rdr.readAll(@ptrCast(&fixed_buf));
const size = std.mem.bytesToValue(u32, fixed_buf[4..]);
const target = try alloc.alloc(u8, size);
_ = try rdr.readAll(target);
return .{
.hard_links = std.mem.bytesToValue(u32, fixed_buf[0..4]),
.size = size,
.target = target,
};
}
pub fn deinit(self: SymInode, alloc: std.mem.Allocator) void {
alloc.free(self.target);
}
};
pub const ExtSymInode = struct {
hard_links: u32,
size: u32,
target: []const u8,
xattr_idx: u32,
pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader) !ExtSymInode {
var fixed_buf = [_]u8{0} ** 8;
_ = try rdr.readAll(&fixed_buf);
const size = std.mem.bytesToValue(u32, fixed_buf[4..]);
const target = try alloc.alloc(u8, size);
_ = try rdr.readAll(target);
return .{
.hard_links = std.mem.bytesToValue(u32, fixed_buf[0..4]),
.size = size,
.target = target,
.xattr_idx = try rdr.readInt(u32, std.builtin.Endian.little),
};
}
pub fn deinit(self: ExtSymInode, alloc: std.mem.Allocator) void {
alloc.free(self.target);
}
};
+14 -128
View File
@@ -1,139 +1,25 @@
const std = @import("std"); const std = @import("std");
const inode = @import("inode/inode.zig");
const Table = @import("table.zig").Table;
const FileHolder = @import("readers/file_holder.zig").FileHolder;
const Superblock = @import("superblock.zig").Superblock; const Superblock = @import("superblock.zig").Superblock;
const File = @import("file.zig").File;
const MetadataReader = @import("readers/metadata.zig").MetadataReader;
const DirEntry = @import("directory.zig").DirEntry;
const FragEntry = @import("fragment.zig").FragEntry;
/// A squashfs archive reader. Make sure to call deinit(). pub fn Reader(comptime T: type) type {
/// For most actions, you'll want to use Reader.root. std.debug.assert(std.meta.hasFn(T, "pread"));
pub const Reader = struct {
alloc: std.mem.Allocator,
holder: FileHolder,
super: Superblock,
root: File,
frag_table: Table(FragEntry), return struct {
export_table: Table(inode.InodeRef), const Self = @This();
id_table: Table(u32),
pub fn init(alloc: std.mem.Allocator, filepath: []const u8, offset: u64) !Reader { alloc: std.mem.Allocator,
var holder: FileHolder = try .init(filepath, offset); rdr: T,
const super: Superblock = try holder.reader().readStruct(Superblock);
try super.validate();
var out: Reader = .{
.alloc = alloc,
.holder = holder,
.super = super,
.root = undefined,
.frag_table = undefined,
.export_table = undefined,
.id_table = undefined,
};
out.frag_table = .init(
&out,
super.frag_table_start,
super.frag_count,
);
out.export_table = .init(
&out,
super.export_table_start,
super.inode_count,
);
out.id_table = .init(
&out,
super.id_table_start,
super.id_count,
);
out.root = try out.rootFile();
return out;
}
pub fn deinit(self: *Reader) void {
self.frag_table.deinit(self.alloc);
self.export_table.deinit(self.alloc);
self.id_table.deinit(self.alloc);
self.root.deinit(self.alloc);
self.holder.deinit();
}
pub fn open(self: *Reader, path: []const u8) !File { super: Superblock = undefined,
return self.root.open(self, path);
}
fn rootFile(self: *Reader) !File { pub fn init(alloc: std.mem.Allocator, rdr: T) Self {
var offset_rdr = self.holder.readerAt(self.super.root_ref.block_start + self.super.inode_table_start); const out = Self{
var meta_rdr: MetadataReader = .init( .alloc = alloc,
self.alloc, .rdr = rdr,
self.super.decomp, };
offset_rdr.any(), _ = try rdr.pread(std.mem.asBytes(&out.super), 0);
); return out;
defer meta_rdr.deinit();
try meta_rdr.skip(self.super.root_ref.offset);
return .{
.name = "",
.inode = try .init(
self.alloc,
meta_rdr.any(),
self.super.block_size,
),
.parent_path = "",
};
}
};
const test_sfs_path = "testing/LinuxPATest.sfs";
test "root iter" {
var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0);
defer rdr.deinit();
var rootIter = try rdr.root.iterator(&rdr);
defer rootIter.deinit();
while (rootIter.next()) |f| {
std.debug.print("{s}\n", .{f.name});
}
}
test "extract single file" {
const sfs_file_path = "PortableApps/Cool_Retro_Term-dac2b4f-x86_64.AppImage";
const extract_path = "testing/Cool_Retro_Term-dac2b4f-x86_64.AppImage";
std.fs.cwd().deleteFile(extract_path) catch |err| {
if (err != std.fs.Dir.DeleteFileError.FileNotFound) {
return err;
} }
}; };
var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0);
defer rdr.deinit();
var fil = try rdr.open(sfs_file_path);
defer fil.deinit(std.testing.allocator);
try fil.extract(&rdr, try .init(), extract_path);
}
test "extract single directory" {
const sfs_file_path = "Documents";
const extract_path = "testing/Documents";
try std.fs.cwd().deleteTree(extract_path);
var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0);
defer rdr.deinit();
var fil = try rdr.open(sfs_file_path);
defer fil.deinit(std.testing.allocator);
var config: File.ExtractConfig = try .init();
config.verbose = true;
try fil.extract(&rdr, config, extract_path);
}
test "full extract" {
const extract_path = "testing/testExtract";
std.fs.cwd().deleteTree(extract_path) catch |err| {
if (err != std.fs.Dir.DeleteFileError.FileNotFound) {
return err;
}
};
var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0);
defer rdr.deinit();
try rdr.root.extract(&rdr, try .init(), extract_path);
} }
+43
View File
@@ -0,0 +1,43 @@
const std = @import("std");
const Compression = @import("../superblock.zig").Compression;
const MetaHeader = packed struct {
size: u15,
uncompressed: bool,
};
pub fn MetadataReader(comptime T: type) type {
return struct {
const Self = @This();
alloc: std.mem.Allocator,
comp: Compression,
rdr: T,
block: [8192]u8 = undefined,
block_size: usize = 0,
block_offset: u32 = 0,
pub fn init(alloc: std.mem.Allocator, comp: Compression, rdr: T) !Self {
var out: Self = .{
.alloc = alloc,
.comp = comp,
.rdr = rdr,
};
try out.readNextBlock();
return out;
}
fn readNextBlock(self: *Self) !void {
const hdr: MetaHeader = undefined;
_ = try self.rdr.read(std.mem.asBytes(hdr));
self.block_size = try self.comp.decompress(
8192,
self.alloc,
std.io.limitedReader(self.rdr, hdr.size),
self.block,
);
}
};
}
-202
View File
@@ -1,202 +0,0 @@
const std = @import("std");
const fs = std.fs;
const io = std.io;
const File = @import("../file.zig").File;
const Reader = @import("../reader.zig").Reader;
const BlockSize = @import("../inode/file.zig").BlockSize;
const DecompressionType = @import("../decompress.zig").DecompressType;
const FileHolder = @import("../readers/file_holder.zig").FileHolder;
const FileOffsetWriter = @import("../readers/file_holder.zig").FileOffsetWriter;
const DataReader = @import("data_reader.zig").DataReader;
const Config = @import("../file.zig").Config;
/// A specialized File data reader that's meant to write all of it's data at once.
/// Can be re-used freely until deinit() is called.
pub const DataExtractor = struct {
alloc: std.mem.Allocator,
decomp: DecompressionType,
holder: *FileHolder,
block_size: u32,
file_size: u64,
sizes: []BlockSize,
block_offset: []u64,
frag_data: ?[]u8 = null,
pub fn init(fil: *File, reader: *Reader) !DataExtractor {
var data_start: u64 = 0;
var sizes: []BlockSize = undefined;
var file_size: u64 = 0;
var frag_idx: u32 = 0;
var frag_offset: u32 = 0;
switch (fil.inode.data) {
.file => |f| {
data_start = f.data_start;
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
@memcpy(sizes, f.blocks);
file_size = f.size;
frag_idx = f.frag_idx;
frag_offset = f.frag_offset;
},
.ext_file => |f| {
data_start = f.data_start;
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
@memcpy(sizes, f.blocks);
file_size = f.size;
frag_idx = f.frag_idx;
frag_offset = f.frag_offset;
},
else => return File.FileError.NotNormalFile,
}
var out: DataExtractor = .{
.alloc = reader.alloc,
.decomp = reader.super.decomp,
.holder = &reader.holder,
.block_size = reader.super.block_size,
.file_size = file_size,
.sizes = sizes,
.block_offset = try reader.alloc.alloc(u64, sizes.len),
};
errdefer out.deinit();
var offset: u64 = data_start;
for (0.., out.block_offset) |i, _| {
out.block_offset[i] = offset;
offset += out.sizes[i].size;
}
if (frag_idx != 0xFFFFFFFF) {
const frag_ent = try reader.frag_table.getValue(reader, frag_idx);
out.frag_data = try frag_ent.getData(reader, frag_offset, @truncate(file_size % reader.super.block_size));
}
return out;
}
pub fn deinit(self: *DataExtractor) void {
self.alloc.free(self.sizes);
self.alloc.free(self.block_offset);
if (self.frag_data != null) self.alloc.free(self.frag_data.?);
}
fn processBlockToFile(self: *DataExtractor, wg: *std.Thread.WaitGroup, errs: *MutexList, block_ind: usize, fil: *fs.File) void {
defer wg.finish();
if (self.sizes[block_ind].not_compressed) {
@branchHint(.unlikely);
if (self.sizes[block_ind].size == 0) {
if (block_ind == self.sizes.len - 1) {
fil.pwriteAll(&[1]u8{0}, self.file_size - 1) catch |err| {
std.debug.print("yo1\n", .{});
errs.append(err) catch {};
};
} else {
fil.pwriteAll(&[1]u8{0}, ((block_ind + 1) * self.block_size) - 1) catch |err| {
std.debug.print("yo2\n", .{});
errs.append(err) catch {};
};
}
return;
}
const dat = self.alloc.alloc(u8, self.sizes[block_ind].size) catch |err| {
errs.append(err) catch {};
return;
};
defer self.alloc.free(dat);
_ = self.holder.file.preadAll(dat, self.block_offset[block_ind]) catch |err| {
errs.append(err) catch {};
return;
};
fil.pwriteAll(dat, block_ind * self.block_size) catch |err| {
errs.append(err) catch {};
};
} else {
@branchHint(.likely);
const offset_rdr = self.holder.readerAt(self.block_offset[block_ind]);
var fil_wrtr: FileOffsetWriter = .init(fil, block_ind * self.block_size);
var limit = std.io.limitedReader(offset_rdr, self.sizes[block_ind].size);
self.decomp.decompressTo(
self.alloc,
limit.reader().any(),
fil_wrtr.any(),
) catch |err| {
errs.append(err) catch {};
};
}
}
fn fragmentToFile(self: *DataExtractor, wg: *std.Thread.WaitGroup, errs: *MutexList, fil: *fs.File) void {
defer wg.finish();
fil.pwriteAll(self.frag_data.?, self.block_size * self.sizes.len) catch |err| {
errs.append(err) catch {};
};
}
/// Write the data completely to the given file.
/// Ignores the file's current offset and writes from the beginning of the file.
/// Returns the amount of bytes written.
///
/// Optimized for lower memory usage by using File.pwrite.
pub fn writeToFile(self: *DataExtractor, pool: *std.Thread.Pool, fil: *fs.File) !void {
var wg: std.Thread.WaitGroup = .{};
var errs: MutexList = .init(self.alloc);
defer errs.deinit();
for (0..self.sizes.len) |i| {
wg.start();
try pool.spawn(processBlockToFile, .{ self, &wg, &errs, i, fil });
}
if (self.frag_data != null) {
wg.start();
try pool.spawn(fragmentToFile, .{ self, &wg, &errs, fil });
}
wg.wait();
if (errs.list.items.len > 0) {
//TODO: better handle all the errors
return errs.list.items[0];
}
}
// fn processBlock(self: *DataExtractor, errs: std.ArrayList(anyerror), data_out: std.AutoHashMap([]u8), block_ind: u32) void {
// const offset_rdr = self.holder.readerAt(self.block_offset[block_ind]);
// const out = self.decomp.decompress(
// self.alloc,
// std.io.limitedReader(offset_rdr, self.sizes[block_ind].size),
// ) catch |err| {
// errs.append(err);
// return;
// };
// data_out.put(block_ind, )
// }
// Write the data completely to the given writer.
// Returns the amount of bytes written.
//
// To write data in order, some data may end up cached temporarily.
// pub fn writeToWriter(self: DataExtractor, pool: *std.Thread.Pool, writer: io.AnyWriter) !void {
// const wg: std.Thread.WaitGroup = .{};
// const errs: std.ArrayList(anyerror) = .init(self.alloc);
// const data: std.AutoHashMap(u32, []u8) = .init(self.alloc);
// const cond: std.Thread. = .{};
// defer errs.deinit();
// for (0..self.sizes.len) |i| {
// pool.spawnWg(&wg, processBlock, .{ &self, i, fil });
// }
// wg.wait();
// }
};
const MutexList = struct {
list: std.ArrayList(anyerror),
mut: std.Thread.Mutex = .{},
fn init(alloc: std.mem.Allocator) MutexList {
return .{
.list = .init(alloc),
};
}
fn deinit(self: *MutexList) void {
self.list.deinit();
}
fn append(self: *MutexList, err: anyerror) !void {
self.mut.lock();
defer self.mut.unlock();
try self.list.append(err);
}
};
-164
View File
@@ -1,164 +0,0 @@
const std = @import("std");
const io = std.io;
const fs = std.fs;
const File = @import("../file.zig").File;
const Reader = @import("../reader.zig").Reader;
const BlockSize = @import("../inode/file.zig").BlockSize;
const DecompressionType = @import("../decompress.zig").DecompressType;
const FileOffsetReader = @import("../readers/file_holder.zig").FileOffsetReader;
const FragEntry = @import("../fragment.zig").FragEntry;
const DataReaderError = error{
EOF,
};
pub const DataReader = struct {
alloc: std.mem.Allocator,
decomp: DecompressionType,
rdr: FileOffsetReader,
block_size: u32,
file_size: u64,
sizes: []BlockSize,
frag_data: ?[]u8 = null,
next_block_num: u32 = 0,
cur_bloc: []u8 = &[0]u8{},
cur_offset: u32 = 0,
pub fn init(fil: *File, reader: *Reader) !DataReader {
var data_start: u64 = 0;
var sizes: []BlockSize = undefined;
var file_size: u64 = 0;
var frag_idx: u32 = 0;
var frag_offset: u32 = 0;
switch (fil.inode.data) {
.file => |f| {
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
@memcpy(sizes, f.blocks);
data_start = f.data_start;
file_size = f.size;
frag_idx = f.frag_idx;
frag_offset = f.frag_offset;
},
.ext_file => |f| {
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
@memcpy(sizes, f.blocks);
data_start = f.data_start;
file_size = f.size;
frag_idx = f.frag_idx;
frag_offset = f.frag_offset;
},
else => return File.FileError.NotNormalFile,
}
var out: DataReader = .{
.alloc = reader.alloc,
.decomp = reader.super.decomp,
.rdr = reader.holder.readerAt(data_start),
.block_size = reader.super.block_size,
.file_size = file_size,
.sizes = sizes,
};
errdefer out.deinit();
if (frag_idx != 0xFFFFFFFF) {
const frag_ent = try reader.frag_table.getValue(reader, frag_idx);
out.frag_data = try frag_ent.getData(reader, frag_offset, @truncate(file_size % reader.super.block_size));
}
return out;
}
pub fn fromFragEntry(reader: *Reader, ent: FragEntry) !DataReader {
const size = try reader.alloc.alloc(BlockSize, 1);
size[0] = ent.size;
return .{
.alloc = reader.alloc,
.decomp = reader.super.decomp,
.rdr = reader.holder.readerAt(ent.start),
.block_size = reader.super.block_size,
.sizes = size,
};
}
pub fn deinit(self: *DataReader) void {
self.alloc.free(self.sizes);
if (self.cur_bloc.len > 0) self.alloc.free(self.cur_bloc);
if (self.frag_data != null) self.alloc.free(self.frag_data.?);
}
pub fn skip(self: *DataReader, offset: u32) !void {
var cur_skip: u32 = 0;
var to_skip: u32 = 0;
while (cur_skip < offset) {
if (self.cur_offset >= self.cur_bloc.len) try self.readNextBlock();
to_skip = @min(offset - cur_skip, self.cur_bloc.len - self.cur_offset);
cur_skip += to_skip;
self.cur_offset += to_skip;
}
}
fn readNextBlock(self: *DataReader) !void {
defer self.next_block_num += 1;
if (self.next_block_num == self.sizes.len and self.frag_data != null) {
try self.sizeBlock(self.frag_data.?.len);
@memcpy(self.cur_bloc, self.frag_data.?);
return;
} else if (self.next_block_num >= self.sizes.len) {
return DataReaderError.EOF;
}
const siz = self.sizes[self.next_block_num];
if (siz.size == 0) {
if (self.next_block_num == self.sizes.len) {
try self.sizeBlock(@truncate(self.file_size % self.block_size));
} else {
try self.sizeBlock(self.block_size);
}
@memset(self.cur_bloc, 0);
return;
}
if (siz.not_compressed) {
try self.sizeBlock(siz.size);
_ = try self.rdr.any().readAll(self.cur_bloc);
} else {
self.alloc.free(self.cur_bloc);
var limit = std.io.limitedReader(self.rdr, siz.size);
var dat = try self.decomp.decompress(self.alloc, limit.reader().any());
self.cur_bloc = try dat.toOwnedSlice();
}
}
fn sizeBlock(self: *DataReader, size: usize) !void {
if (!self.alloc.resize(self.cur_bloc, size)) {
self.alloc.free(self.cur_bloc);
self.cur_bloc = try self.alloc.alloc(u8, size);
}
}
pub fn read(self: *DataReader, bytes: []u8) !usize {
var cur_read: usize = 0;
var to_read: usize = 0;
while (cur_read < bytes.len) {
if (self.cur_offset >= self.cur_bloc.len) {
self.readNextBlock() catch |err| {
if (err == DataReaderError.EOF) return cur_read;
return err;
};
}
to_read = @min(bytes.len - cur_read, self.cur_bloc.len - self.cur_offset);
@memcpy(bytes[cur_read .. cur_read + to_read], self.cur_bloc[self.cur_offset .. @as(usize, self.cur_offset) + to_read]);
self.cur_offset += @truncate(to_read);
cur_read += to_read;
}
return cur_read;
}
pub fn any(self: *DataReader) io.AnyReader {
return .{
.context = @ptrCast(self),
.readFn = readOpaque,
};
}
fn readOpaque(context: *const anyopaque, bytes: []u8) !usize {
var self: *DataReader = @constCast(@ptrCast(@alignCast(context)));
return self.read(bytes);
}
};
-90
View File
@@ -1,90 +0,0 @@
const std = @import("std");
const fs = std.fs;
const io = std.io;
const File = std.fs.File;
pub const FileHolder = struct {
file: File,
offset: u64,
pub fn init(path: []const u8, offset: u64) !FileHolder {
return .{
.file = try fs.cwd().openFile(path, .{ .mode = .read_write }),
.offset = offset,
};
}
pub fn deinit(self: FileHolder) void {
self.file.close();
}
pub fn reader(self: *FileHolder) File.Reader {
return self.file.reader();
}
pub fn readerAt(self: *FileHolder, offset: u64) FileOffsetReader {
return .{
.file = &self.file,
.offset = self.offset + offset,
};
}
// pub fn writerAt(self: *FileHolder, offset: u64) FileOffsetWriter {
// return .{
// .file = &self.file,
// .offset = self.offset + offset,
// };
// }
};
pub const FileOffsetWriter = struct {
file: *File,
offset: u64,
pub fn init(fil: *File, init_offset: u64) FileOffsetWriter {
return .{
.file = fil,
.offset = init_offset,
};
}
pub const Error = fs.File.PWriteError;
pub fn write(self: *FileOffsetWriter, bytes: []const u8) !usize {
try self.file.pwriteAll(bytes, self.offset);
self.offset += bytes.len;
return bytes.len;
}
pub fn any(self: *FileOffsetWriter) io.AnyWriter {
return .{
.context = @ptrCast(self),
.writeFn = writeOpaque,
};
}
fn writeOpaque(context: *const anyopaque, bytes: []const u8) anyerror!usize {
var rdr: *FileOffsetWriter = @constCast(@ptrCast(@alignCast(context)));
return try rdr.write(bytes);
}
};
pub const FileOffsetReader = struct {
file: *File,
offset: u64,
pub const Error = fs.File.PReadError;
pub fn read(self: *FileOffsetReader, bytes: []u8) !usize {
const red = try self.file.preadAll(bytes, self.offset);
self.offset += red;
return red;
}
pub fn any(self: *FileOffsetReader) io.AnyReader {
return .{
.context = @ptrCast(self),
.readFn = readOpaque,
};
}
fn readOpaque(context: *const anyopaque, bytes: []u8) !usize {
var rdr: *FileOffsetReader = @constCast(@ptrCast(@alignCast(context)));
return try rdr.read(bytes);
}
};
-77
View File
@@ -1,77 +0,0 @@
const std = @import("std");
const io = std.io;
const DecompressType = @import("../decompress.zig").DecompressType;
const MetadataHeader = packed struct {
size: u15,
not_compressed: bool,
};
pub const MetadataReader = struct {
alloc: std.mem.Allocator,
decomp: DecompressType,
reader: io.AnyReader,
block: []u8 = &[0]u8{},
offset: u32 = 0,
pub fn init(alloc: std.mem.Allocator, decomp: DecompressType, rdr: io.AnyReader) MetadataReader {
return .{
.alloc = alloc,
.decomp = decomp,
.reader = rdr,
};
}
pub fn deinit(self: *MetadataReader) void {
self.alloc.free(self.block);
}
pub fn skip(self: *MetadataReader, offset: u16) !void {
var cur_skip: u32 = 0;
var to_skip: u32 = 0;
while (cur_skip < offset) {
if (self.offset >= self.block.len) try self.readNextBlock();
to_skip = @min(offset - cur_skip, self.block.len - self.offset);
cur_skip += to_skip;
self.offset += to_skip;
}
}
fn readNextBlock(self: *MetadataReader) !void {
self.offset = 0;
if (self.block.len > 0) self.alloc.free(self.block);
const hdr = try self.reader.readStruct(MetadataHeader);
if (hdr.not_compressed) {
self.block = try self.alloc.alloc(u8, hdr.size);
_ = try self.reader.readAll(self.block);
} else {
var limit = std.io.limitedReader(self.reader, hdr.size);
var dat = try self.decomp.decompress(self.alloc, limit.reader().any());
self.block = try dat.toOwnedSlice();
}
}
pub fn any(self: *MetadataReader) io.AnyReader {
return .{
.context = @ptrCast(self),
.readFn = readOpaque,
};
}
pub fn read(self: *MetadataReader, bytes: []u8) !usize {
var cur_read: usize = 0;
var to_read: usize = 0;
while (cur_read < bytes.len) {
if (self.offset >= self.block.len) try self.readNextBlock();
to_read = @min(bytes.len - cur_read, self.block.len - self.offset);
@memcpy(bytes[cur_read .. cur_read + to_read], self.block[self.offset .. @as(usize, self.offset) + to_read]);
self.offset += @truncate(to_read);
cur_read += to_read;
}
return cur_read;
}
fn readOpaque(context: *const anyopaque, bytes: []u8) !usize {
var rdr: *MetadataReader = @constCast(@ptrCast(@alignCast(context)));
return rdr.read(bytes);
}
};
-1
View File
@@ -1 +0,0 @@
pub const Reader = @import("reader.zig").Reader;
+59 -23
View File
@@ -1,10 +1,4 @@
const math = @import("std").math; const std = @import("std");
const SuperblockError = error{
InvalidMagic,
InvalidBlockLog,
InvalidVersion,
};
pub const Superblock = packed struct { pub const Superblock = packed struct {
magic: u32, magic: u32,
@@ -12,28 +6,70 @@ pub const Superblock = packed struct {
mod_time: u32, mod_time: u32,
block_size: u32, block_size: u32,
frag_count: u32, frag_count: u32,
decomp: @import("decompress.zig").DecompressType, comp: Compression,
block_log: u16, block_log: u16,
flags: u16, flags: packed struct {
_: u4,
id_uncomp: bool,
comp_options: bool,
no_xattr: bool,
xattr_uncomp: bool,
has_export: bool,
de_dupe: bool,
frag_always: bool,
no_frag: bool,
frag_uncomp: bool,
check: bool,
data_uncomp: bool,
inode_uncomp: bool,
},
id_count: u16, id_count: u16,
ver_maj: u16, ver_maj: u16,
ver_min: u16, ver_min: u16,
root_ref: @import("inode/inode.zig").InodeRef, root_ref: u64,
size: u64, size: u64,
id_table_start: u64, id_start: u64,
xattr_table_start: u64, xattr_start: u64,
inode_table_start: u64, inode_start: u64,
dir_table_start: u64, dir_start: u64,
frag_table_start: u64, frag_start: u64,
export_table_start: u64, export_start: u64,
};
pub fn validate(self: Superblock) SuperblockError!void { const DecompressError = error{
if (self.magic != 0x73717368) { LzoUnavailable,
return SuperblockError.InvalidMagic; Lz4Unavailable,
} else if (self.block_log != math.log2(self.block_size)) { };
return SuperblockError.InvalidBlockLog;
} else if (self.ver_maj != 4 or self.ver_min != 0) { pub const Compression = enum(u16) {
return SuperblockError.InvalidVersion; gzip = 1,
lzma,
lzo,
xz,
lz4,
zstd,
pub fn decompress(self: Compression, comptime max_size: u16, alloc: std.mem.Allocator, source: anytype, dest: *[max_size]u8) !usize {
switch (self) {
.gzip => {
const decomp = std.compress.zlib.decompressor(source);
return decomp.read(dest);
},
.lzma => {
const decomp = try std.compress.lzma.decompress(alloc, source);
return decomp.read(dest);
},
.lzo => return DecompressError.LzoUnavailable,
.xz => {
const decomp = try std.compress.xz.decompress(alloc, source);
return decomp.read(dest);
},
.lz4 => return DecompressError.Lz4Unavailable,
.zstd => {
const window: [@min(std.compress.zstd.DecompressorOptions.default_window_buffer_len, max_size)]u8 = undefined;
const decomp = std.compress.zstd.decompressor(source, .{ .window_buffer = window });
return decomp.read(dest);
},
} }
} }
}; };
-56
View File
@@ -1,56 +0,0 @@
const std = @import("std");
const Reader = @import("reader.zig").Reader;
const DecompressType = @import("decompress.zig").DecompressType;
const FileHolder = @import("readers/file_holder.zig").FileHolder;
const FileOffsetReader = @import("readers/file_holder.zig").FileOffsetReader;
const MetadataReader = @import("readers/metadata.zig").MetadataReader;
const TableError = error{InvalidIndex};
/// A lazily read squashfs table.
pub fn Table(
comptime T: type,
) type {
return struct {
decomp: DecompressType,
table: []T = &[0]T{},
offset: u64,
item_count: u32,
pub fn init(read: *Reader, offset: u64, item_count: u32) Self {
return .{
.decomp = read.super.decomp,
.offset = offset,
.item_count = item_count,
};
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
if (self.table.len != 0) alloc.free(self.table);
}
pub fn getValue(self: *Self, read: *Reader, i: u64) !T {
if (i >= self.item_count) return TableError.InvalidIndex;
if (self.table.len > i) return self.table[i];
var offset_rdr: FileOffsetReader = undefined;
var meta_rdr: MetadataReader = undefined;
var meta_buf: [8]u8 = [1]u8{0} ** 8;
const meta_offset = std.mem.bytesAsValue(u64, &meta_buf);
var to_read: u32 = 0;
while (self.table.len <= i) {
_ = try read.holder.file.preadAll(&meta_buf, self.offset);
self.offset += 8;
offset_rdr = read.holder.readerAt(meta_offset.*);
meta_rdr = .init(read.alloc, self.decomp, offset_rdr.any());
defer meta_rdr.deinit();
to_read = @min(self.item_count - self.table.len, comptime 8192 / @sizeOf(T));
const alloc_size = self.table.len + to_read;
if (self.table.len != 0) read.alloc.free(self.table);
self.table = try read.alloc.alloc(T, alloc_size);
_ = try meta_rdr.any().readAll(@ptrCast(self.table[self.table.len - to_read ..]));
}
return self.table[i];
}
const Self: type = @This();
};
}
-203
View File
@@ -1,203 +0,0 @@
const std = @import("std");
const config = @import("config");
const File = @import("file.zig").File;
const Reader = @import("reader.zig").Reader;
const ExtractConfig = @import("file.zig").File.ExtractConfig;
const stdout = std.io.getStdOut();
var extr_files: std.ArrayList([]const u8) = undefined;
var offset: u64 = 0;
var verbose: bool = false;
var unbreak: bool = false;
var deref: bool = false;
var processors: u16 = 0;
var list: ListTypes = .None;
var filename: []const u8 = "";
var extr_location: []const u8 = "";
const ListTypes = enum {
None,
List,
ListAttr,
ListNumeric,
};
fn help() !void {
const help_msg =
\\Basic Usage: zig-unsquashfs [Options] SQUASHFS_FILE EXTRACT_LOCATION
\\
\\General options:
\\ -e <path> Path to a file or directory inside the archive to extract instead of the whole archive.
\\ Can be given multiple times.
\\ -o <bytes> Skip <bytes> before reading from the archive.
\\ -v Verbose output.
\\ --help Prints this help message.
\\ -h Same as --help
\\
\\Extraction options:
\\ --unbreak-symlinks Attempt extract symlink targets along with symlinks. Will not place files outside of the extraction location.
\\ -us Same as --unbreak-symlinks
\\ --deref-symlinks Replace symlink files with their target.
\\ -ds Same as --deref-symlinks
\\ -p <#> Use at most # of processors. Defaults to logical core count.
\\
\\Listing Options:
\\ -l List files instead of extracting. When used, you do not need to specify an extraction location.
\\ -ll Like -l, but with file attributes.
\\ -lln Like -ll, but with numeric uids and gids.
\\
\\Other:
\\ --version Print version number.
\\
;
_ = try stdout.writeAll(help_msg);
}
pub fn main() !void {
var alloc: std.heap.GeneralPurposeAllocator(.{}) = .init;
extr_files = .init(alloc.allocator());
defer extr_files.deinit();
var args = std.process.argsWithAllocator(alloc.allocator()) catch {
_ = try stdout.writeAll("Unable allocate memory");
return;
};
defer args.deinit();
_ = args.next();
while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
try help();
return;
} else if (std.mem.eql(u8, arg, "-v")) {
verbose = true;
} else if (std.mem.eql(u8, arg, "--unbreak-symlinks") or std.mem.eql(u8, arg, "-us")) {
unbreak = true;
} else if (std.mem.eql(u8, arg, "--deref-symlinks") or std.mem.eql(u8, arg, "-ds")) {
deref = true;
} else if (std.mem.eql(u8, arg, "-l")) {
list = .List;
} else if (std.mem.eql(u8, arg, "-ll")) {
list = .ListAttr;
} else if (std.mem.eql(u8, arg, "-lln")) {
list = .ListNumeric;
} else if (std.mem.eql(u8, arg, "-e")) {
const next = args.next();
if (next == null) {
_ = try stdout.writeAll("path required after -e\n");
return;
}
try extr_files.append(next.?);
} else if (std.mem.eql(u8, arg, "-o")) {
const next = args.next();
if (next == null) {
_ = try stdout.writeAll("offset required after -o\n");
return;
}
offset = try std.fmt.parseInt(u64, next.?, 10);
} else if (std.mem.eql(u8, arg, "-p")) {
const next = args.next();
if (next == null) {
_ = try stdout.writeAll("number required after -p\n");
return;
}
processors = try std.fmt.parseInt(u16, next.?, 10);
} else if (std.mem.eql(u8, arg, "--version")) {
try config.version.format("", .{}, stdout.writer());
_ = try stdout.write("\n");
return;
} else if (filename.len == 0) {
filename = arg;
} else if (extr_location.len == 0) {
extr_location = arg;
} else {
_ = try stdout.writeAll("invalid or too many arguments\n");
return;
}
}
if (filename.len == 0) {
_ = try stdout.writeAll("no archive given\n");
return;
}
if (list == .None and extr_location.len == 0) {
_ = try stdout.writeAll("no extract location given\n");
return;
}
var rdr = Reader.init(
alloc.allocator(),
filename,
offset,
) catch |err| {
try std.fmt.format(stdout.writer(), "Error opening {s} as squashfs: {any}\n", .{ filename, err });
return;
};
defer rdr.deinit();
switch (list) {
.None => {
var conf = ExtractConfig.init() catch |err| {
try std.fmt.format(stdout.writer(), "Error getting system info: {any}\n", .{err});
return;
};
conf.deref_sym = deref;
conf.unbreak_sym = unbreak;
conf.verbose = verbose;
if (extr_files.items.len == 0) {
rdr.root.extract(&rdr, conf, extr_location) catch |err| {
try std.fmt.format(stdout.writer(), "Error extracting archive: {any}\n", .{err});
return;
};
} else {
for (extr_files.items) |path| {
var fil = rdr.root.open(&rdr, path) catch |err| {
try std.fmt.format(stdout.writer(), "Error extracting {s}: {any}\n", .{ path, err });
return;
};
defer fil.deinit(alloc.allocator());
fil.extract(&rdr, conf, extr_location) catch |err| {
try std.fmt.format(stdout.writer(), "Error extracting {s}: {any}\n", .{ path, err });
return;
};
}
}
},
else => {
if (extr_files.items.len == 0) {
try printFile(alloc.allocator(), &rdr, &rdr.root);
} else {
for (extr_files.items) |path| {
var fil = rdr.root.open(&rdr, path) catch |err| {
try std.fmt.format(stdout.writer(), "Error finding {s}: {any}\n", .{ path, err });
return;
};
defer fil.deinit(alloc.allocator());
try printFile(alloc.allocator(), &rdr, &fil);
}
}
},
}
}
fn printFile(alloc: std.mem.Allocator, rdr: *Reader, f: *File) anyerror!void {
const pth = try f.file_path(alloc);
defer alloc.free(pth);
if (list == .List) {
try std.fmt.format(stdout.writer(), "{s}\n", .{pth});
if (f.isDir()) {
try printDir(alloc, rdr, f);
}
return;
}
try std.fmt.format(stdout.writer(), "{s} {d}/{d} {d} {s}\n", .{ "tmp-perm", try f.uid(rdr), try f.gid(rdr), f.size(), pth });
if (f.isDir()) {
try printDir(alloc, rdr, f);
}
}
fn printDir(alloc: std.mem.Allocator, rdr: *Reader, f: *File) anyerror!void {
var iter = try f.iterator(rdr);
defer iter.deinit();
while (iter.next()) |fil| {
try printFile(alloc, rdr, fil);
}
}