Try 4, lol.
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const import = @import("std");
|
||||||
|
|
||||||
|
pub fn main() void {}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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
@@ -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
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+12
-126
@@ -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 {
|
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
holder: FileHolder,
|
rdr: T,
|
||||||
super: Superblock,
|
|
||||||
root: File,
|
|
||||||
|
|
||||||
frag_table: Table(FragEntry),
|
super: Superblock = undefined,
|
||||||
export_table: Table(inode.InodeRef),
|
|
||||||
id_table: Table(u32),
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, filepath: []const u8, offset: u64) !Reader {
|
pub fn init(alloc: std.mem.Allocator, rdr: T) Self {
|
||||||
var holder: FileHolder = try .init(filepath, offset);
|
const out = Self{
|
||||||
const super: Superblock = try holder.reader().readStruct(Superblock);
|
|
||||||
try super.validate();
|
|
||||||
var out: Reader = .{
|
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.holder = holder,
|
.rdr = rdr,
|
||||||
.super = super,
|
|
||||||
.root = undefined,
|
|
||||||
.frag_table = undefined,
|
|
||||||
.export_table = undefined,
|
|
||||||
.id_table = undefined,
|
|
||||||
};
|
};
|
||||||
out.frag_table = .init(
|
_ = try rdr.pread(std.mem.asBytes(&out.super), 0);
|
||||||
&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;
|
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 {
|
|
||||||
return self.root.open(self, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rootFile(self: *Reader) !File {
|
|
||||||
var offset_rdr = self.holder.readerAt(self.super.root_ref.block_start + self.super.inode_table_start);
|
|
||||||
var meta_rdr: MetadataReader = .init(
|
|
||||||
self.alloc,
|
|
||||||
self.super.decomp,
|
|
||||||
offset_rdr.any(),
|
|
||||||
);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
pub const Reader = @import("reader.zig").Reader;
|
|
||||||
|
|||||||
+59
-23
@@ -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);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user