Merge pull request #1 from CalebQ42/reset-4

Reset 4
This commit is contained in:
Caleb Gardner
2025-07-25 06:07:55 -05:00
committed by GitHub
26 changed files with 1731 additions and 1652 deletions
+3 -2
View File
@@ -23,14 +23,15 @@ 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,
.name = "zig-unsquashfs", .name = "unsquashfs",
.root_module = exe_mod, .root_module = exe_mod,
.version = sem_ver, .version = sem_ver,
}); });
+146
View File
@@ -0,0 +1,146 @@
const std = @import("std");
const config = @import("config");
const squashfs = @import("squashfs");
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.
\\
\\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 Similiar to -l, but with file attributes.
\\ -lln Similiar to -ll, but with numeric uids and gids.
\\
\\Other:
\\ --help Prints this help message.
\\ -h Same as --help
\\ --version Print version number.
\\
;
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,
};
pub fn main() !void {
const alloc = std.heap.smp_allocator;
extr_files = .init(alloc);
defer extr_files.deinit();
var args = std.process.argsWithAllocator(alloc) catch {
_ = try stdout.writeAll("Unable to 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 stdout.writeAll(help_msg);
return;
} else if (std.mem.eql(u8, arg, "--version")) {
try config.version.format("", .{}, stdout.writer());
_ = try stdout.write("\n");
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 (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;
}
const fil = try std.fs.cwd().openFile(filename, .{});
defer fil.close();
var th_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = std.heap.smp_allocator };
var rdr = squashfs.SfsFile.init(
th_alloc.allocator(),
fil,
offset,
) catch |err| {
try std.fmt.format(stdout.writer(), "Error opening {s} as squashfs: {any}\n", .{ filename, err });
return;
};
defer rdr.deinit();
//TODO: list and extr_files;
var op: squashfs.ExtractionOptions = squashfs.ExtractionOptions.init() catch |err| {
try std.fmt.format(stdout.writer(), "Error setting extraction options: {any}\n", .{err});
return;
};
op.verbose = verbose;
op.dereference_symlinks = deref;
op.unbreak_symlinks = unbreak;
if (processors != 0) op.thread_count = processors;
rdr.extract(op, extr_location) catch |err| {
try std.fmt.format(stdout.writer(), "Error extracting archive: {any}\n", .{err});
return;
};
}
-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);
}
},
}
}
};
+48 -44
View File
@@ -1,66 +1,70 @@
const std = @import("std"); const std = @import("std");
const io = std.io;
const InodeType = @import("inode/inode.zig").InodeType; const InodeType = @import("inode.zig").Type;
const Compression = @import("superblock.zig").Compression;
const DirHeader = extern struct { const Header = extern struct { //use extern instead of packed, due to bit alignment
count: u32, count: u32,
inode_block_start: u32, block: u32,
inode_num: u32, num: u32,
}; };
const RawDirEntryStart = packed struct { const RawEntry = 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, offset: u16,
inode_num: u32, num_offset: i16,
type: InodeType,
size: u16,
name: []const u8, name: []const u8,
fn init(alloc: std.mem.Allocator, hdr: DirHeader, rdr: io.AnyReader) !DirEntry { pub fn init(alloc: std.mem.Allocator, rdr: anytype) !RawEntry {
const raw = try rdr.readStruct(RawDirEntryStart); var fixed: [8]u8 = undefined;
const name = try alloc.alloc(u8, raw.name_size + 1); _ = try rdr.read(&fixed);
errdefer alloc.free(name); const size = std.mem.readInt(u16, fixed[6..8], .little);
const name = try alloc.alloc(u8, size + 1);
_ = try rdr.read(name); _ = try rdr.read(name);
return .{ return .{
.block_start = hdr.inode_block_start, .offset = std.mem.readInt(u16, fixed[0..2], .little),
.offset = raw.inode_block_offset, .num_offset = std.mem.readInt(i16, fixed[2..4], .little),
.inode_num = if (raw.inode_num_difference > 0) .type = @enumFromInt(std.mem.readInt(u16, fixed[4..6], .little)),
hdr.inode_num + @abs(raw.inode_num_difference) .size = size,
else
hdr.inode_num - @abs(raw.inode_num_difference),
.name = name, .name = name,
}; };
} }
};
pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void { pub const Entry = struct {
block: u32,
offset: u16,
num: u32,
type: InodeType,
name: []const u8,
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
alloc.free(self.name); alloc.free(self.name);
} }
}; };
pub fn readDirectory(alloc: std.mem.Allocator, rdr: io.AnyReader, size: u64) !std.StringHashMap(DirEntry) { pub fn readDirectory(alloc: std.mem.Allocator, rdr: anytype, size: u32) ![]Entry {
var out: std.StringHashMap(DirEntry) = .init(alloc); var entries: std.ArrayList(Entry) = .init(alloc);
errdefer out.deinit(); errdefer entries.deinit();
var red_size: u64 = 3; var cur_red: u32 = 3; // dir size includes "." & "..", so its actual size is off by 3.
var hdr: DirHeader = undefined; var hdr: Header = undefined;
while (red_size < size) { while (cur_red < size) {
hdr = try rdr.readStruct(DirHeader); _ = try rdr.read(std.mem.asBytes(&hdr));
red_size += 12; cur_red += 12;
var i: u32 = 0; try entries.ensureUnusedCapacity(hdr.count + 1);
try out.ensureUnusedCapacity(hdr.count + 1); for (0..hdr.count + 1) |_| {
while (i <= hdr.count) : (i += 1) { const raw_ent: RawEntry = try .init(alloc, rdr);
var tmp: DirEntry = try .init(alloc, hdr, rdr); cur_red += 9 + raw_ent.size;
errdefer tmp.deinit(alloc); errdefer alloc.free(raw_ent.name);
out.putAssumeCapacity(tmp.name, tmp); entries.appendAssumeCapacity(.{
red_size += 8 + tmp.name.len; .block = hdr.block,
.offset = raw_ent.offset,
.num = @truncate(@abs(@as(i64, hdr.num) + raw_ent.num_offset)),
.type = raw_ent.type,
.name = raw_ent.name,
});
} }
} }
return out; return entries.toOwnedSlice();
} }
+23
View File
@@ -0,0 +1,23 @@
const std = @import("std");
const Self = @This();
/// Replace symlinks with their targets
dereference_symlinks: bool = false,
/// Always extract a symlink's target if it's part of the archive.
/// May result in the symlink's target being changed.
unbreak_symlinks: bool = false,
/// Do not set file's permissions & owner when extracted.
ignore_permissions: bool = false,
/// Verbose logging
verbose: bool = false,
/// Verbose logging writer. If not set, stdout is used.
verbose_logger: std.io.AnyWriter = std.io.getStdOut().writer().any(),
/// Number of threads used during extraction. Defualts to std.Thread.getCpuCount().
thread_count: usize,
pub fn init() !Self {
return .{
.thread_count = try std.Thread.getCpuCount(),
};
}
+516 -341
View File
@@ -1,361 +1,536 @@
const std = @import("std"); const std = @import("std");
const io = std.io;
const fs = std.fs;
const builtin = @import("builtin"); const builtin = @import("builtin");
const inode = @import("inode/inode.zig"); const dir = @import("directory.zig");
const directory = @import("directory.zig");
const Reader = @import("reader.zig").Reader; const DirEntry = dir.Entry;
const DirEntry = @import("directory.zig").DirEntry; const Inode = @import("inode.zig");
const DataReader = @import("readers/data_reader.zig").DataReader; const SfsReader = @import("reader.zig").SfsReader;
const DataExtractor = @import("readers/data_extractor.zig").DataExtractor; const ToReader = @import("reader/to_read.zig").ToRead;
const MetadataReader = @import("readers/metadata.zig").MetadataReader; const ExtractionOptions = @import("extract_options.zig");
const DataReader = @import("reader/data.zig").DataReader;
const Compression = @import("superblock.zig").Compression;
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
/// A file or directory inside of a squashfs. pub fn File(comptime T: type) type {
/// Make sure to call deinit(); return struct {
pub const File = struct { pub const FileError = error{
name: []const u8, NotRegular,
inode: inode.Inode, NotDirectory,
parent_path: []const u8, NotFound,
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 { const Self = @This();
if (self.parent_path.len == 0) {
const out = try alloc.alloc(u8, self.name.len); rdr: *SfsReader(T),
@memcpy(out, self.name); // parent: *File(T),
inode: Inode,
name: []const u8,
/// Directory entries. Only populated on directories.
entries: ?[]DirEntry = null,
/// File reader. Only populated on regular files.
data_reader: ?DataReader(T) = null,
pub fn init(rdr: *SfsReader(T), inode: Inode, name: []const u8) !Self {
const name_cpy: []u8 = try rdr.alloc.alloc(u8, name.len);
@memcpy(name_cpy, name);
var out = Self{
.rdr = rdr,
.inode = inode,
.name = name_cpy,
};
switch (inode.data) {
.dir => |d| {
var meta = MetadataReader(T).init(
rdr.alloc,
rdr.super.comp,
rdr.rdr,
d.block + rdr.super.dir_start,
);
try meta.skip(d.offset);
out.entries = try dir.readDirectory(rdr.alloc, &meta, d.size);
},
.ext_dir => |d| {
var meta = MetadataReader(T).init(
rdr.alloc,
rdr.super.comp,
rdr.rdr,
d.block + rdr.super.dir_start,
);
try meta.skip(d.offset);
out.entries = try dir.readDirectory(rdr.alloc, &meta, d.size);
},
.file => |f| {
out.data_reader = try .init(
rdr.alloc,
rdr.rdr,
rdr.super.comp,
f.block,
f.size,
f.block_sizes,
rdr.super.block_size,
);
if (f.hasFragment()) {
try out.data_reader.?.addFragment(
try rdr.frag_table.get(f.frag_idx),
f.frag_offset,
);
}
},
.ext_file => |f| {
out.data_reader = try .init(
rdr.alloc,
rdr.rdr,
rdr.super.comp,
f.block,
f.size,
f.block_sizes,
rdr.super.block_size,
);
if (f.hasFragment()) {
try out.data_reader.?.addFragment(
try rdr.frag_table.get(f.frag_idx),
f.frag_offset,
);
}
},
else => {},
}
return out; return out;
} }
return std.mem.concat(alloc, u8, &[3][]const u8{ self.parent_path, "/", self.name }); pub fn initFromRef(rdr: *SfsReader(T), ref: Inode.Ref, name: []const u8) !Self {
} var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ref.block + rdr.super.inode_start);
try meta.skip(ref.offset);
pub fn uid(self: File, rdr: *Reader) !u32 { const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
return rdr.id_table.getValue(rdr, self.inode.header.uid_idx); return .init(rdr, inode, name);
} }
pub fn initFromEntry(rdr: *SfsReader(T), ent: DirEntry) !Self {
pub fn gid(self: File, rdr: *Reader) !u32 { var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ent.block + rdr.super.inode_start);
return rdr.id_table.getValue(rdr, self.inode.header.gid_idx); try meta.skip(ent.offset);
} const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
return .init(rdr, inode, ent.name);
pub fn deinit(self: *File, alloc: std.mem.Allocator) void { }
self.inode.deinit(); pub fn deinit(self: Self) void {
alloc.free(self.name); self.rdr.alloc.free(self.name);
alloc.free(self.parent_path); self.inode.deinit(self.rdr.alloc);
if (self.data_rdr != null) self.data_rdr.?.deinit(); if (self.entries != null) {
if (self.dirEntries != null) { for (self.entries.?) |e| {
var iter = self.dirEntries.?.iterator(); e.deinit(self.rdr.alloc);
while (iter.next()) |ent| { }
ent.value_ptr.deinit(alloc); self.rdr.alloc.free(self.entries.?);
}
if (self.data_reader != null) {
self.data_reader.?.deinit();
} }
self.dirEntries.?.deinit();
} }
}
pub fn isDir(self: File) bool { pub fn uid(self: Self) !u32 {
return switch (self.inode.header.inode_type) { return self.rdr.id_table.get(self.inode.hdr.uid_idx);
.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); pub fn gid(self: Self) !u32 {
switch (self.inode.header.inode_type) { return self.rdr.id_table.get(self.inode.hdr.uid_idx);
.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 Reader = std.io.GenericReader(*DataReader(T), anyerror, DataReader(T).read);
const name = clean_path[0..split_idx];
const ent = self.dirEntries.?.get(name); pub fn read(self: *Self, buf: []u8) !usize {
if (ent == null) { if (self.data_reader == null) return FileError.NotRegular;
return self.data_reader.?.read(buf);
}
pub fn reader(self: *Self) !Reader {
if (self.data_reader == null) return FileError.NotRegular;
return self.data_reader.?.reader();
}
pub fn open(self: Self, path: []const u8) !Self {
if (self.entries == null) return FileError.NotDirectory;
if (path.len == 0) return self;
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
if (idx == 0) return self.open(path[1..]);
const name = path[0..idx];
for (self.entries.?) |e| {
if (std.mem.eql(u8, e.name, name)) {
var fil: Self = try .initFromEntry(self.rdr, e);
if (idx >= path.len - 1) return fil;
defer fil.deinit();
return fil.open(path[idx + 1 ..]);
}
}
return FileError.NotFound; return FileError.NotFound;
} }
var fil = try fromDirEntry(rdr, ent.?, try self.file_path(rdr.alloc)); pub fn iterate(self: Self) Iterator {
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 .{ return .{
.thread_count = @truncate(try std.Thread.getCpuCount()), .rdr = self.rdr,
.max_mem = @min(sys_mem / 4, 1024 * 1024 * 1024), .entries = self.entries.?,
};
}
const Iterator = struct {
rdr: *SfsReader(T),
entries: []DirEntry,
idx: u32 = 0,
pub fn next(self: *Iterator) !?File(T) {
if (self.idx >= self.entries.len) return null;
const out = try Self.initFromEntry(self.rdr, self.entries[self.idx]);
self.idx += 1;
return out;
}
pub fn reset(self: *Iterator) void {
self.idx = 0;
}
};
const WaitGroup = std.Thread.WaitGroup;
const Pool = std.Thread.Pool;
const Mutex = std.Thread.Mutex;
pub const ExtractError = error{FileExists};
pub fn extract(self: Self, op: ExtractionOptions, path: []const u8) !void {
var exists = true;
var stat: ?std.fs.File.Stat = null;
if (std.fs.cwd().statFile(path)) |s| {
stat = s;
} else |err| {
if (err == std.fs.File.OpenError.FileNotFound) {
exists = false;
} else {
return err;
}
}
switch (self.inode.hdr.type) {
.dir, .ext_dir => {
if (exists and stat.?.kind != .directory) {
return ExtractError.FileExists;
}
},
else => if (exists) return ExtractError.FileExists,
}
var wg: WaitGroup = .{};
var pol: Pool = undefined;
try pol.init(.{
.n_jobs = op.thread_count,
.allocator = self.rdr.alloc,
});
defer pol.deinit();
var errs: std.ArrayList(anyerror) = .init(self.rdr.alloc);
defer errs.deinit();
self.extractReal(op, path, &errs, &wg, &pol, true);
wg.wait();
if (errs.items.len > 0) return errs.items[0];
}
fn extractReal(
self: Self,
op: ExtractionOptions,
path: []const u8,
errs: *std.ArrayList(anyerror),
wg: *WaitGroup,
pol: *Pool,
first: bool,
) void {
if (errs.items.len > 0) return;
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"extracting inode {} \"{s}\" to {s}...\n",
.{ self.inode.hdr.num, self.name, path },
) catch {};
}
return switch (self.inode.hdr.type) {
.dir, .ext_dir => {
var complete = false;
wg.start();
defer if (!complete) wg.finish();
std.fs.cwd().makeDir(path) catch |err| {
if (err != std.fs.Dir.MakeError.PathAlreadyExists) {
errs.append(err) catch {};
return;
}
};
const dir_wg = self.rdr.alloc.create(WaitGroup) catch |err| {
errs.append(err) catch {};
return;
};
dir_wg.* = .{};
for (self.entries.?) |ent| {
const fil = initFromEntry(self.rdr, ent) catch |err| {
std.fmt.format(
op.verbose_logger,
"error extracting inode {} \"{s}\": {}\n",
.{ ent.num, path, err },
) catch {};
continue;
};
const ext_path = blk: {
if (path[path.len - 1] == '/') {
var new = self.rdr.alloc.alloc(u8, path.len + ent.name.len) catch |err| {
break :blk err;
};
@memcpy(new[0..path.len], path);
@memcpy(new[path.len..], ent.name);
break :blk new;
}
var new = self.rdr.alloc.alloc(u8, path.len + ent.name.len + 1) catch |err| {
break :blk err;
};
@memcpy(new[0..path.len], path);
new[path.len] = '/';
@memcpy(new[path.len + 1 ..], ent.name);
break :blk new;
} catch |err| {
std.fmt.format(
op.verbose_logger,
"error extracting inode {} \"{s}\": {}\n",
.{ ent.num, path, err },
) catch {};
continue;
};
var thr = std.Thread.spawn(.{ .allocator = self.rdr.alloc }, extractReal, .{
fil,
op,
ext_path,
errs,
dir_wg,
pol,
false,
}) catch |err| {
self.rdr.alloc.free(ext_path);
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"error extracting inode {} \"{s}\": {}\n",
.{ ent.num, path, err },
) catch {};
}
continue;
};
thr.detach();
}
var thr = std.Thread.spawn(
.{ .allocator = self.rdr.alloc },
extractDirWait,
.{
self,
op,
path,
dir_wg,
wg,
first,
},
) catch |err| {
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"error spawning wait thread for \"{s}\": {}\n",
.{ path, err },
) catch {};
}
self.extractDirWait(op, path, dir_wg, wg, first);
return;
};
thr.detach();
complete = true;
},
.file, .ext_file => {
var complete = false;
wg.start();
defer if (!complete) wg.finish();
var ext_fil = std.fs.cwd().createFile(path, .{}) catch |err| {
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"error creating file \"{s}\": {}\n",
.{ path, err },
) catch {};
}
errs.append(err) catch {};
return;
};
defer if (!complete) ext_fil.close();
var fil_errs = self.rdr.alloc.create(std.ArrayList(anyerror)) catch |err| {
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"error allocating memory: {}\n",
.{err},
) catch {};
}
errs.append(err) catch {};
return;
};
defer if (!complete) self.rdr.alloc.destroy(fil_errs);
fil_errs.* = .init(self.rdr.alloc);
defer if (!complete) fil_errs.deinit();
@constCast(&self.data_reader.?).setPool(pol);
self.data_reader.?.writeToNoBlock(
errs,
ext_fil,
extractRegFinish,
.{
self,
op,
path,
fil_errs,
errs,
wg,
ext_fil,
first,
},
) catch |err| {
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"error extracting file \"{s}\": {}\n",
.{ path, err },
) catch {};
}
errs.append(err) catch {};
return;
};
complete = true;
},
.symlink, .ext_symlink => {},
.block_dev, .ext_block_dev, .char_dev, .ext_char_dev, .fifo, .ext_fifo => {
//TODO: check for all oses that accept unix permissions.
},
else => {
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"inode {} \"{s}\" is a socket file. Ignoring.\n",
.{ self.inode.hdr.num, path },
) catch {};
}
},
};
}
fn extractDirWait(
self: Self,
op: ExtractionOptions,
path: []const u8,
dir_wg: *WaitGroup,
wg: *WaitGroup,
first: bool,
) void {
dir_wg.wait();
self.rdr.alloc.destroy(dir_wg);
defer {
wg.finish();
if (!first) {
self.rdr.alloc.free(path);
self.deinit();
}
}
if (op.ignore_permissions) return;
const dir_uid = self.uid() catch |err| {
std.fmt.format(
op.verbose_logger,
"error getting uid for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
const dir_gid = self.gid() catch |err| {
std.fmt.format(
op.verbose_logger,
"error getting gid for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
var ext_dir = std.fs.cwd().openFile(path, .{}) catch |err| {
std.fmt.format(
op.verbose_logger,
"error setting owner & permissions for \"{s}\": {}\n",
.{ path, err },
) catch {};
return;
};
defer ext_dir.close();
ext_dir.chmod(self.inode.hdr.perm) catch |err| {
std.fmt.format(
op.verbose_logger,
"error setting permissions for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
ext_dir.chown(dir_uid, dir_gid) catch |err| {
std.fmt.format(
op.verbose_logger,
"error setting owner for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
}
fn extractRegFinish(
self: Self,
op: ExtractionOptions,
path: []const u8,
fil_errs: *std.ArrayList(anyerror),
errs: *std.ArrayList(anyerror),
wg: *WaitGroup,
fil: std.fs.File,
first: bool,
) void {
defer {
wg.finish();
fil.close();
self.rdr.alloc.destroy(fil_errs);
if (!first) {
self.deinit();
self.rdr.alloc.free(path);
}
}
if (fil_errs.items.len > 0) {
if (op.verbose) {
std.fmt.format(
op.verbose_logger,
"error extracting inode {} to \"{s}\": {}\n",
.{ self.inode.hdr.num, path, fil_errs.items[0] },
) catch {};
}
errs.append(fil_errs.items[0]) catch {};
return;
}
if (op.ignore_permissions) return;
const fil_uid = self.uid() catch |err| {
std.fmt.format(
op.verbose_logger,
"error getting uid for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
const fil_gid = self.gid() catch |err| {
std.fmt.format(
op.verbose_logger,
"error getting gid for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
fil.chmod(self.inode.hdr.perm) catch |err| {
std.fmt.format(
op.verbose_logger,
"error setting permissions for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
};
fil.chown(fil_uid, fil_gid) catch |err| {
std.fmt.format(
op.verbose_logger,
"error setting owner for inode {} \"{s}\": {}\n",
.{ self.inode.hdr.num, path, err },
) catch {};
return;
}; };
} }
}; };
}
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 -20
View File
@@ -1,26 +1,7 @@
const std = @import("std");
const BlockSize = @import("inode/file.zig").BlockSize; const BlockSize = @import("inode/file.zig").BlockSize;
const Reader = @import("reader.zig").Reader;
pub const FragEntry = packed struct { pub const FragEntry = packed struct {
start: u64, block: u64,
size: BlockSize, size: BlockSize,
_: u32, _: 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;
}
}; };
+93
View File
@@ -0,0 +1,93 @@
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,
};
pub 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,
};
pub const Header = packed struct {
type: Type,
perm: u16,
uid_idx: u16,
gid_idx: u16,
mod_time: u32,
num: u32,
};
pub 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 {
var hdr: Header = undefined;
_ = try rdr.read(std.mem.asBytes(&hdr));
const data: Data = switch (hdr.type) {
.dir => .{ .dir = try .init(rdr) },
.file => .{ .file = try .init(rdr, alloc, block_size) },
.symlink => .{ .symlink = try .init(rdr, alloc) },
.block_dev => .{ .block_dev = try .init(rdr) },
.char_dev => .{ .char_dev = try .init(rdr) },
.fifo => .{ .fifo = try .init(rdr) },
.socket => .{ .socket = try .init(rdr) },
.ext_dir => .{ .ext_dir = try .init(rdr) },
.ext_file => .{ .ext_file = try .init(rdr, alloc, block_size) },
.ext_symlink => .{ .ext_symlink = try .init(rdr, alloc) },
.ext_block_dev => .{ .ext_block_dev = try .init(rdr) },
.ext_char_dev => .{ .ext_char_dev = try .init(rdr) },
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
.ext_socket => .{ .ext_socket = try .init(rdr) },
};
return .{
.hdr = hdr,
.data = data,
};
}
pub fn deinit(self: Self, alloc: std.mem.Allocator) void {
switch (self.data) {
.file => |f| alloc.free(f.block_sizes),
.ext_file => |f| alloc.free(f.block_sizes),
.symlink => |s| alloc.free(s.target),
.ext_symlink => |s| alloc.free(s.target),
else => {},
}
}
+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); var out: Dir = undefined;
_ = try 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); var out: ExtDir = undefined;
_ = try rdr.read(std.mem.asBytes(&out));
return out;
} }
}; };
+43 -42
View File
@@ -1,76 +1,77 @@
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, frag_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 = try 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]), .frag_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 { pub fn hasFragment(self: File) bool {
alloc.free(self.blocks); return self.frag_idx != 0xffffffff;
} }
}; };
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, frag_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 = @truncate(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 = try 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]), .frag_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 = block_sizes,
}; };
} }
pub fn deinit(self: ExtFileInode, alloc: std.mem.Allocator) void { pub fn hasFragment(self: ExtFile) bool {
alloc.free(self.blocks); return self.frag_idx != 0xffffffff;
} }
}; };
-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 = try 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 = try 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 {
var 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); var 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); var 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); var 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);
}
};
+73 -127
View File
@@ -1,139 +1,85 @@
const std = @import("std"); const std = @import("std");
const inode = @import("inode/inode.zig"); const Inode = @import("inode.zig");
const Table = @import("table.zig").Table;
const FileHolder = @import("readers/file_holder.zig").FileHolder;
const Superblock = @import("superblock.zig").Superblock;
const File = @import("file.zig").File; const File = @import("file.zig").File;
const MetadataReader = @import("readers/metadata.zig").MetadataReader; const Table = @import("table.zig").Table;
const DirEntry = @import("directory.zig").DirEntry; const PRead = @import("reader/p_read.zig").PRead;
const FragEntry = @import("fragment.zig").FragEntry; const FragEntry = @import("fragment.zig").FragEntry;
const Superblock = @import("superblock.zig").Superblock;
const ExtractionOptions = @import("extract_options.zig");
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
/// A squashfs archive reader. Make sure to call deinit(). pub const SfsError = error{
/// For most actions, you'll want to use Reader.root. NotExportable,
pub const Reader = struct {
alloc: std.mem.Allocator,
holder: FileHolder,
super: Superblock,
root: File,
frag_table: Table(FragEntry),
export_table: Table(inode.InodeRef),
id_table: Table(u32),
pub fn init(alloc: std.mem.Allocator, filepath: []const u8, offset: u64) !Reader {
var holder: FileHolder = try .init(filepath, offset);
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 {
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"; pub fn SfsReader(comptime T: type) type {
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
test "root iter" { return struct {
var rdr: Reader = try .init(std.testing.allocator, test_sfs_path, 0); const Self = @This();
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" { alloc: std.mem.Allocator,
const sfs_file_path = "PortableApps/Cool_Retro_Term-dac2b4f-x86_64.AppImage"; rdr: PRead(T),
const extract_path = "testing/Cool_Retro_Term-dac2b4f-x86_64.AppImage";
std.fs.cwd().deleteFile(extract_path) catch |err| { super: Superblock = undefined,
if (err != std.fs.Dir.DeleteFileError.FileNotFound) { /// ID table. Can be accessed directly
return err; id_table: Table(u32, T) = undefined,
/// Fragment table. Can be accessed directly
frag_table: Table(FragEntry, T) = undefined,
/// Export table. Each element is an inode referce.
/// If accessing directly, keep in mind, the table starts at inode 1, as such it's recommended to use the InodeAt function instead.
export_table: Table(Inode.Ref, T) = undefined,
pub fn init(alloc: std.mem.Allocator, rdr: T, offset: u64) !Self {
var out: Self = .{
.alloc = alloc,
.rdr = .init(rdr, offset),
};
_ = try rdr.pread(std.mem.asBytes(&out.super), 0);
out.frag_table = .init(alloc, out.rdr, out.super.comp, out.super.frag_start, out.super.frag_count);
out.id_table = .init(alloc, out.rdr, out.super.comp, out.super.id_start, out.super.id_count);
out.export_table = .init(alloc, out.rdr, out.super.comp, out.super.export_start, out.super.inode_count);
return out;
}
pub fn deinit(self: *Self) void {
self.id_table.deinit();
self.frag_table.deinit();
self.export_table.deinit();
}
/// A representation of the archives root folder.
pub fn root(self: *Self) !File(T) {
return .initFromRef(self, self.super.root_ref, "");
}
/// Get the file at path. Equivelent to calling open on the root File.
pub fn open(self: *Self, path: []const u8) !File(T) {
var rt = try self.root();
if (path.len == 0 or (path.len == 1 and path[0] == '/') or path.len == 1 and path[0] == '.') return rt;
defer rt.deinit();
return rt.open(path);
}
/// Extract the entire archive to the given path & with the given options.
/// Equivelent to calling extract on the root File.
pub fn extract(self: *Self, op: ExtractionOptions, path: []const u8) !void {
var rt = try self.root();
defer rt.deinit();
return rt.extract(op, path);
}
/// Returns the Inode with the given Inode Number.
/// Requires the archive to have an export table.
pub fn inodeAt(self: Self, num: u32) !Inode {
if (!self.super.flags.has_export) return SfsError.NotExportable;
const ref = try self.export_table.get(num - 1);
var meta = MetadataReader(T).init(
self.alloc,
self.super.comp,
self.rdr,
self.super.inode_start + ref.block,
);
try meta.skip(ref.offset);
return .init(meta, self.alloc, self.super.block_size);
} }
}; };
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);
} }
+373
View File
@@ -0,0 +1,373 @@
const std = @import("std");
const PRead = @import("p_read.zig").PRead;
const FragEntry = @import("../fragment.zig").FragEntry;
const BlockSize = @import("../inode/file.zig").BlockSize;
const Compression = @import("../superblock.zig").Compression;
const DataReaderError = error{
EOF,
ThreadPoolNotSet,
InvalidIndex,
};
pub fn DataReader(comptime T: type) type {
return struct {
const Self = @This();
alloc: std.mem.Allocator,
pool: ?*std.Thread.Pool = null,
rdr: PRead(T),
comp: Compression,
offsets: []u64,
file_size: u64,
block_size: u32,
sizes: []BlockSize,
frag: []u8 = &[0]u8{},
read_block: []u8 = &[0]u8{},
read_offset: u64 = 0,
read_idx: u32 = 0,
pub fn init(
alloc: std.mem.Allocator,
rdr: PRead(T),
comp: Compression,
init_offset: u64,
file_size: u64,
sizes: []BlockSize,
block_size: u32,
) !Self {
var cur_offset = init_offset;
const offsets = try alloc.alloc(u64, sizes.len);
for (0..sizes.len) |i| {
offsets[i] = cur_offset;
cur_offset += sizes[i].size;
}
return .{
.alloc = alloc,
.rdr = rdr,
.comp = comp,
.offsets = offsets,
.file_size = file_size,
.block_size = block_size,
.sizes = sizes,
};
}
pub fn deinit(self: Self) void {
self.alloc.free(self.offsets);
self.alloc.free(self.frag);
if (self.read_idx < self.sizes.len) self.alloc.free(self.read_block);
}
pub fn addFragment(self: *Self, entry: FragEntry, offset: u32) !void {
self.frag = try self.alloc.alloc(u8, self.file_size % self.block_size);
if (entry.size.size == 0) {
@memset(self.frag, 0);
return;
} else if (entry.size.uncompressed) {
_ = try self.rdr.pread(self.frag, entry.block + offset);
return;
}
const block = try self.alloc.alloc(u8, offset + self.frag.len);
defer self.alloc.free(block);
_ = try self.comp.decompress(
self.alloc,
self.rdr.readerAt(entry.block).reader(),
block,
);
@memcpy(self.frag, block[offset..]);
}
pub fn setPool(self: *Self, pool: *std.Thread.Pool) void {
self.pool = pool;
}
fn blockAt(self: Self, idx: usize) ![]u8 {
if (self.frag.len > 0 and idx == self.sizes.len) return self.frag;
if (idx >= self.sizes.len) return DataReaderError.InvalidIndex;
const size = blk: {
if (idx == self.sizes.len - 1 and self.frag.len == 0) {
break :blk self.file_size % self.block_size;
}
break :blk self.block_size;
};
const block = try self.alloc.alloc(u8, size);
errdefer self.alloc.free(block);
if (self.sizes[idx].size == 0) {
@memset(block, 0);
return block;
} else if (self.sizes[idx].uncompressed) {
_ = try self.rdr.pread(block, self.offsets[idx]);
return block;
}
_ = try self.comp.decompress(
self.alloc,
self.rdr.readerAt(self.offsets[idx]).reader(),
block,
);
return block;
}
fn numBlocks(self: Self) usize {
var out = self.sizes.len;
if (self.frag.len > 0) out += 1;
return out;
}
const Reader = std.io.GenericReader(*Self, anyerror, read);
pub fn read(self: *Self, buf: []u8) !usize {
var cur_red: usize = 0;
var to_read: usize = 0;
while (cur_red < buf.len) {
if (self.read_offset >= self.read_block.len) {
if (self.read_idx == self.sizes.len or (self.frag.len == 0 and self.read_idx == self.sizes.len - 1)) {
self.block_size = self.file_size % self.block_size;
}
self.read_block = self.blockAt(self.read_idx) catch |err| {
if (err == DataReaderError.EOF) return cur_red;
return err;
};
self.read_idx += 1;
}
to_read = @min(buf.len - cur_red, self.block_size - self.read_offset);
@memcpy(buf[cur_red .. cur_red + to_read], self.read_block[self.read_offset .. self.read_offset + to_read]);
cur_red += to_read;
self.read_offset += to_read;
}
return cur_red;
}
pub fn reader(self: *Self) Reader {
return .{ .context = self };
}
/// Write the entire file's contents to the writer using multiple threads.
/// If availble, pwrite will be used.
pub fn writeTo(self: Self, writer: anytype) !usize {
if (self.pool == null) return DataReaderError.ThreadPoolNotSet;
var mut: std.Thread.Mutex = .{};
var cur_idx: usize = 0;
var wg: std.Thread.WaitGroup = .{};
var completed: std.AutoHashMap(usize, []u8) = .init(self.alloc);
defer completed.deinit();
var errs: std.ArrayList(anyerror) = .init(self.alloc);
defer errs.deinit();
for (0..self.numBlocks()) |i| {
wg.start();
self.pool.?.spawn(
comptime blk: {
if (std.meta.hasFn(@TypeOf(writer), "pwrite")) {
break :blk writeToThreadPWrite;
}
break :blk writeToThread;
},
blk: {
if (comptime std.meta.hasFn(@TypeOf(writer), "pwrite")) {
break :blk .{ self, &wg, &errs, i, writer };
}
break :blk .{ self, &wg, &mut, &cur_idx, &errs, &completed, i, writer };
},
);
}
wg.wait();
if (errs.items.len > 0) return errs.items[0];
return self.file_size;
}
/// Similiar to writeTo, but does not block until finished.
/// When all blocks have been written, on_finish and wg.finish() (in that order) will be called.
/// NOTE: wg.start() is not called;
pub fn writeToNoBlock(
self: Self,
errs: *std.ArrayList(anyerror),
writer: anytype,
comptime on_finish: anytype,
on_finish_args: anytype,
) !void {
if (self.pool == null) return DataReaderError.ThreadPoolNotSet;
if (self.numBlocks() == 0) {
@call(.auto, on_finish, on_finish_args);
return;
}
var mut: std.Thread.Mutex = .{};
var cur_idx: usize = 0;
var block_wg = try self.alloc.create(std.Thread.WaitGroup);
block_wg.* = .{};
const finish_mut = try self.alloc.create(std.Thread.Mutex);
finish_mut.* = .{};
var completed: ?std.AutoHashMap(usize, []u8) = null;
if (!comptime std.meta.hasFn(@TypeOf(writer), "pwrite")) {
completed = std.AutoHashMap(usize, []u8).init(self.alloc);
}
block_wg.startMany(self.numBlocks());
for (0..self.numBlocks()) |i| {
var thr = try std.Thread.spawn(
.{ .allocator = self.alloc },
comptime blk: {
if (std.meta.hasFn(@TypeOf(writer), "pwrite")) {
break :blk noBlockThreadPWrite;
}
break :blk noBlockThread;
},
blk: {
if (comptime std.meta.hasFn(@TypeOf(writer), "pwrite")) {
break :blk .{ self, block_wg, errs, i, writer, finish_mut, on_finish, on_finish_args };
} else {
break :blk .{ self, block_wg, &mut, &cur_idx, errs, &completed.?, i, writer, finish_mut, on_finish, on_finish_args };
}
},
);
thr.detach();
}
}
fn writeBlockTo(
self: Self,
mut: *std.Thread.Mutex,
cur_idx: *usize,
errs: *std.ArrayList(anyerror),
completed: *std.AutoHashMap(usize, []u8),
idx: usize,
writer: anytype,
) void {
//TODO: We can marginally reduce memory usage if we don't store sparse blocks in completed.
if (errs.items.len > 0) return; // Indicates an error has occured in another thread.
const block = self.blockAt(idx) catch |err| {
errs.append(err) catch {};
return;
};
defer if (idx < self.sizes.len) {
self.alloc.free(block);
};
mut.lock();
defer mut.unlock();
if (cur_idx.* == idx) {
_ = writer.write(block) catch |err| {
errs.append(err) catch {};
return;
};
} else {
completed.put(idx, block) catch |err| {
errs.append(err) catch {};
return;
};
}
if (completed.count() == 0) return;
for (cur_idx.*..self.numBlocks()) |i| {
const val = completed.get(i);
if (val == null) return;
_ = writer.write(val.?) catch |err| {
errs.append(err) catch {};
return;
};
_ = completed.remove(i);
cur_idx.* += 1;
if (completed.count() == 0) return;
}
}
fn writeBlockToPWrite(
self: Self,
errs: *std.ArrayList(anyerror),
idx: usize,
writer: anytype,
) void {
if (errs.items.len > 0) return;
if (idx < self.sizes.len and self.sizes[idx].size == 0) {
var pos = idx * self.block_size;
if (self.frag.len == 0 and idx == self.sizes.len - 1) {
pos += self.file_size % self.block_size;
} else {
pos += self.block_size;
}
_ = writer.pwrite(&[1]u8{0}, pos - 1) catch |err| {
errs.append(err) catch {};
};
} else {
const block = self.blockAt(idx) catch |err| {
errs.append(err) catch {};
return;
};
defer if (idx < self.sizes.len) {
self.alloc.free(block);
};
_ = writer.pwrite(block, idx * self.block_size) catch |err| {
errs.append(err) catch {};
return;
};
}
}
fn writeToThread(
self: Self,
wg: *std.Thread.WaitGroup,
mut: *std.Thread.Mutex,
cur_idx: *usize,
errs: *std.ArrayList(anyerror),
completed: *std.AutoHashMap(usize, []u8),
idx: usize,
writer: anytype,
) void {
self.writeBlockTo(mut, cur_idx, errs, completed, idx, writer);
wg.finish();
}
fn writeToThreadPWrite(
self: Self,
wg: *std.Thread.WaitGroup,
errs: std.ArrayList(anyerror),
idx: usize,
writer: anytype,
) void {
self.writeBlockToPWrite(errs, idx, writer);
wg.finish();
}
fn noBlockThread(
self: Self,
block_wg: *std.Thread.WaitGroup,
mut: *std.Thread.Mutex,
cur_idx: *usize,
errs: *std.ArrayList(anyerror),
completed: *std.AutoHashMap(usize, []u8),
idx: usize,
writer: anytype,
finish_mut: *std.Thread.Mutex,
comptime on_finish: anytype,
on_finish_args: anytype,
) void {
self.writeBlockTo(mut, cur_idx, errs, completed, idx, writer);
finish_mut.lock();
block_wg.finish();
defer finish_mut.unlock();
if (block_wg.isDone()) {
@call(.auto, on_finish, on_finish_args);
completed.deinit();
}
}
fn noBlockThreadPWrite(
self: Self,
block_wg: *std.Thread.WaitGroup,
errs: *std.ArrayList(anyerror),
idx: usize,
writer: anytype,
finish_mut: *std.Thread.Mutex,
comptime on_finish: anytype,
on_finish_args: anytype,
) void {
self.writeBlockToPWrite(errs, idx, writer);
finish_mut.lock();
block_wg.finish();
const isDone = block_wg.isDone();
defer {
finish_mut.unlock();
if (isDone) self.alloc.destroy(finish_mut);
}
if (isDone) {
self.alloc.destroy(block_wg);
@call(.auto, on_finish, on_finish_args);
}
}
};
}
+81
View File
@@ -0,0 +1,81 @@
const std = @import("std");
const PRead = @import("p_read.zig").PRead;
const Compression = @import("../superblock.zig").Compression;
const MetaHeader = packed struct {
size: u15,
uncompressed: bool,
};
pub fn MetadataReader(comptime T: type) type {
comptime std.debug.assert(std.meta.hasFn(T, "read"));
return struct {
const Self = @This();
alloc: std.mem.Allocator,
comp: Compression,
rdr: PRead(T),
offset: u64,
block: [8192]u8 = undefined,
block_size: usize = 0,
block_offset: u32 = 0,
pub fn init(alloc: std.mem.Allocator, comp: Compression, rdr: PRead(T), offset: u64) Self {
return .{
.alloc = alloc,
.comp = comp,
.rdr = rdr,
.offset = offset,
};
}
fn readNextBlock(self: *Self) !void {
var hdr: MetaHeader = undefined;
_ = try self.rdr.pread(std.mem.asBytes(&hdr), self.offset);
self.offset += 2;
if (hdr.uncompressed) {
self.block_size = try self.rdr.pread(self.block[0..hdr.size], self.offset);
} else {
self.block_size = try self.comp.decompress(
self.alloc,
self.rdr.readerAt(self.offset).reader(),
&self.block,
);
}
self.offset += hdr.size;
self.block_offset = 0;
}
pub fn skip(self: *Self, offset: u32) !void {
var skipped: u32 = 0;
var hdr: MetaHeader = undefined;
while (offset - skipped >= 8192) {
_ = try self.rdr.pread(std.mem.asBytes(&hdr), self.offset);
self.offset += 2 + hdr.size;
skipped += 8192;
}
var to_skip: u32 = 0;
while (skipped < offset) {
if (self.block_offset >= self.block_size) try self.readNextBlock();
to_skip = @min(self.block_size - self.block_offset, offset - skipped);
self.block_offset += to_skip;
skipped += to_skip;
}
}
pub fn read(self: *Self, buf: []u8) !usize {
var cur_red: usize = 0;
var to_read: usize = 0;
while (cur_red < buf.len) {
if (self.block_offset >= self.block_size) try self.readNextBlock();
to_read = @min(buf.len - cur_red, self.block_size - self.block_offset);
@memcpy(buf[cur_red .. cur_red + to_read], self.block[self.block_offset .. self.block_offset + to_read]);
cur_red += to_read;
self.block_offset += @truncate(to_read);
}
return cur_red;
}
};
}
+29
View File
@@ -0,0 +1,29 @@
const std = @import("std");
const ToRead = @import("to_read.zig").ToRead;
/// A simple wrapper around a type with the pread([]u8, u64) function.
/// Provides a couple useful utility functions.
pub fn PRead(comptime T: type) type {
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
return struct {
const Self = @This();
rdr: T,
offset: u64,
pub fn init(rdr: T, offset: u64) Self {
return .{
.rdr = rdr,
.offset = offset,
};
}
pub fn pread(self: Self, buf: []u8, offset: u64) !usize {
return self.rdr.pread(buf, self.offset + offset);
}
pub fn readerAt(self: Self, offset: u64) ToRead(T) {
return .init(self.rdr, self.offset + offset);
}
};
}
+42
View File
@@ -0,0 +1,42 @@
const std = @import("std");
pub fn ToRead(comptime T: type) type {
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
return struct {
const Self = @This();
pub const Error = anyerror;
rdr: T,
offset: u64,
pub fn init(rdr: T, init_offset: u64) Self {
return .{
.rdr = rdr,
.offset = init_offset,
};
}
pub fn read(self: *Self, buf: []u8) !usize {
const red = try self.rdr.pread(buf, self.offset);
self.offset += red;
return red;
}
pub fn readAll(self: *Self, buf: []u8) !usize {
var cur_red = try self.read(buf);
if (cur_red == 0) return cur_red;
var res: usize = 0;
while (cur_red < buf.len) {
res = try self.read(buf[cur_red..]);
if (res == 0) break;
cur_red += res;
}
return cur_red;
}
pub fn reader(self: anytype) std.io.Reader(*Self, anyerror, read) {
return .{
.context = @constCast(self),
};
}
};
}
-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);
}
};
+55 -1
View File
@@ -1 +1,55 @@
pub const Reader = @import("reader.zig").Reader; const std = @import("std");
pub const SfsReader = @import("reader.zig").SfsReader;
pub const ExtractionOptions = @import("extract_options.zig");
pub const SfsFile = SfsReader(std.fs.File);
const test_archive = "testing/LinuxPATest.sfs";
test "OpenFile" {
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
defer sfs_fil.close();
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
defer rdr.deinit();
_ = try rdr.frag_table.get(rdr.super.frag_count - 1);
_ = try rdr.id_table.get(rdr.super.id_count - 1);
_ = try rdr.export_table.get(rdr.super.inode_count - 1);
std.debug.print("{}\n", .{rdr.super});
const root = try rdr.root();
defer root.deinit();
var iter = root.iterate();
while (try iter.next()) |f| {
defer f.deinit();
std.debug.print("{s}\n", .{f.name});
}
}
test "ExtractSingleFile" {
const single_file = "PortableApps/Notepad++Portable/App/Notepad++/doLocalConf.xml";
const single_file_extr_loc = "testing/doLocalConf.xml";
std.fs.cwd().deleteFile(single_file_extr_loc) catch {};
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
defer sfs_fil.close();
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
defer rdr.deinit();
const fil = try rdr.open(single_file);
defer fil.deinit();
var op: ExtractionOptions = try .init();
op.verbose = true;
try fil.extract(op, single_file_extr_loc);
}
test "ExtractAll" {
const extr_dir = "testing/testExtract";
std.fs.cwd().deleteTree(extr_dir) catch {};
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
defer sfs_fil.close();
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
defer rdr.deinit();
const op: ExtractionOptions = try .init();
// op.verbose = true;
try rdr.extract(op, extr_dir);
}
+62 -22
View File
@@ -1,10 +1,6 @@
const math = @import("std").math; const std = @import("std");
const SuperblockError = error{ const InodeRef = @import("inode.zig").Ref;
InvalidMagic,
InvalidBlockLog,
InvalidVersion,
};
pub const Superblock = packed struct { pub const Superblock = packed struct {
magic: u32, magic: u32,
@@ -12,28 +8,72 @@ 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: InodeRef,
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 { pub 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, alloc: std.mem.Allocator, source: anytype, dest: []u8) !usize {
switch (self) {
.gzip => {
var decomp = std.compress.zlib.decompressor(source);
return decomp.read(dest);
},
.lzma => {
var decomp = try std.compress.lzma.decompress(alloc, source);
defer decomp.deinit();
return decomp.read(dest);
},
.lzo => return DecompressError.LzoUnavailable,
.xz => {
var decomp = try std.compress.xz.decompress(alloc, source);
defer decomp.deinit();
return decomp.read(dest);
},
.lz4 => return DecompressError.Lz4Unavailable,
.zstd => {
var window: [std.compress.zstd.DecompressorOptions.default_window_buffer_len]u8 = undefined;
var decomp = std.compress.zstd.decompressor(source, .{ .window_buffer = &window });
return decomp.read(dest);
},
} }
} }
}; };
+59 -40
View File
@@ -1,56 +1,75 @@
const std = @import("std"); const std = @import("std");
const Reader = @import("reader.zig").Reader; const PRead = @import("reader/p_read.zig").PRead;
const DecompressType = @import("decompress.zig").DecompressType; const Compression = @import("superblock.zig").Compression;
const FileHolder = @import("readers/file_holder.zig").FileHolder; const MetadataReader = @import("reader/metadata.zig").MetadataReader;
const FileOffsetReader = @import("readers/file_holder.zig").FileOffsetReader;
const MetadataReader = @import("readers/metadata.zig").MetadataReader;
const TableError = error{InvalidIndex}; pub const TableError = error{
InvalidIndex,
};
/// A lazily read squashfs table. pub fn Table(comptime T: type, comptime R: type) type {
pub fn Table( comptime std.debug.assert(std.meta.hasFn(R, "pread"));
comptime T: type,
) type {
return struct { return struct {
decomp: DecompressType, const Self = @This();
table: []T = &[0]T{},
offset: u64,
item_count: u32,
pub fn init(read: *Reader, offset: u64, item_count: u32) Self { alloc: std.mem.Allocator,
rdr: PRead(R),
comp: Compression,
offset: u64,
table_count: u32,
mut: std.Thread.RwLock = .{},
table: []T = &[0]T{},
pub fn init(alloc: std.mem.Allocator, rdr: PRead(R), comp: Compression, offset: u64, table_count: u32) Self {
return .{ return .{
.decomp = read.super.decomp, .alloc = alloc,
.rdr = rdr,
.comp = comp,
.offset = offset, .offset = offset,
.item_count = item_count, .table_count = table_count,
}; };
} }
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void { pub fn deinit(self: Self) void {
if (self.table.len != 0) alloc.free(self.table); self.alloc.free(self.table);
} }
pub fn getValue(self: *Self, read: *Reader, i: u64) !T { fn resize(self: *Self, to_add: usize) !void {
if (i >= self.item_count) return TableError.InvalidIndex; if (!self.alloc.resize(self.table, self.table.len + to_add)) {
if (self.table.len > i) return self.table[i]; const new_table = try self.alloc.alloc(T, self.table.len + to_add);
var offset_rdr: FileOffsetReader = undefined; @memcpy(new_table[0..self.table.len], self.table);
var meta_rdr: MetadataReader = undefined; self.alloc.free(self.table);
var meta_buf: [8]u8 = [1]u8{0} ** 8; self.table = new_table;
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();
pub fn get(self: *Self, idx: u32) !T {
if (idx >= self.table_count) return TableError.InvalidIndex;
self.mut.lockShared();
defer self.mut.unlockShared();
if (idx >= self.table.len) {
return self.getAndFill(idx);
}
return self.table[idx];
}
fn getAndFill(self: *Self, idx: u32) !T {
self.mut.unlockShared();
defer self.mut.lockShared();
self.mut.lock();
defer self.mut.unlock();
var to_read: usize = 0;
var offset: u64 = 0;
while (idx >= self.table.len) {
to_read = @min(self.table_count - self.table.len, comptime 8192 / @sizeOf(T));
try self.resize(to_read);
_ = try self.rdr.pread(std.mem.asBytes(&offset), self.offset);
self.offset += 8;
var meta: MetadataReader(R) = .init(self.alloc, self.comp, self.rdr, offset);
_ = try meta.read(std.mem.sliceAsBytes(self.table[self.table.len - to_read ..]));
}
return self.table[idx];
}
}; };
} }
-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);
}
}