Inodes! ExtractionOptions! Files! Directories!
This commit is contained in:
+60
-4
@@ -4,21 +4,47 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const File = std.fs.File;
|
||||||
|
|
||||||
|
const DecompMgr = @import("decomp.zig");
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
const InodeRef = Inode.Ref;
|
||||||
|
const SfsFile = @import("file.zig");
|
||||||
const Superblock = @import("super.zig").Superblock;
|
const Superblock = @import("super.zig").Superblock;
|
||||||
|
const Table = @import("table.zig").Table;
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
|
const FragEntry = packed struct {
|
||||||
|
start: u64,
|
||||||
|
size: packed struct {
|
||||||
|
size: u24,
|
||||||
|
uncompressed: bool,
|
||||||
|
_: u7,
|
||||||
|
},
|
||||||
|
_: u32,
|
||||||
|
};
|
||||||
|
|
||||||
const Archive = @This();
|
const Archive = @This();
|
||||||
|
|
||||||
// 4 Gigs
|
// 4 Gigs
|
||||||
const MIN_MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
const MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
parent_alloc: std.mem.Allocator,
|
parent_alloc: std.mem.Allocator,
|
||||||
alloc: std.heap.FixedBufferAllocator,
|
alloc: std.heap.FixedBufferAllocator,
|
||||||
fixed_buf: []u8,
|
fixed_buf: []u8,
|
||||||
|
|
||||||
fil: OffsetFile,
|
fil: OffsetFile,
|
||||||
|
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
|
setup: bool = false,
|
||||||
|
|
||||||
|
decomp: DecompMgr = undefined,
|
||||||
|
|
||||||
|
frag_table: Table(FragEntry) = undefined,
|
||||||
|
id_table: Table(u16) = undefined,
|
||||||
|
export_table: Table(InodeRef) = undefined,
|
||||||
|
|
||||||
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
||||||
return initAdvanced(
|
return initAdvanced(
|
||||||
@@ -26,7 +52,7 @@ pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
|||||||
fil,
|
fil,
|
||||||
0,
|
0,
|
||||||
try std.Thread.getCpuCount(),
|
try std.Thread.getCpuCount(),
|
||||||
@min(MIN_MEM_SIZE, std.process.totalSystemMemory() / 2),
|
@min(MEM_SIZE, try std.process.totalSystemMemory() / 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/// Create the Archive dictating the amount of threads & memory used.
|
/// Create the Archive dictating the amount of threads & memory used.
|
||||||
@@ -37,7 +63,8 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u
|
|||||||
var super: Superblock = undefined;
|
var super: Superblock = undefined;
|
||||||
const red = try fil.pread(@ptrCast(&super), offset);
|
const red = try fil.pread(@ptrCast(&super), offset);
|
||||||
std.debug.assert(red == @sizeOf(Superblock));
|
std.debug.assert(red == @sizeOf(Superblock));
|
||||||
const fixed_buf = alloc.alloc(u8, mem);
|
try super.validate();
|
||||||
|
const fixed_buf = try alloc.alloc(u8, mem);
|
||||||
return .{
|
return .{
|
||||||
.parent_alloc = alloc,
|
.parent_alloc = alloc,
|
||||||
.alloc = .init(fixed_buf),
|
.alloc = .init(fixed_buf),
|
||||||
@@ -47,7 +74,36 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u
|
|||||||
.super = super,
|
.super = super,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Archive) void {
|
pub fn deinit(self: *Archive) void {
|
||||||
self.parent_alloc.free(self.fixed_buf);
|
self.parent_alloc.free(self.fixed_buf);
|
||||||
|
if (self.setup) {
|
||||||
|
self.decomp.deinit();
|
||||||
|
self.frag_table.deinit();
|
||||||
|
self.export_table.deinit();
|
||||||
|
self.id_table.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocator(self: *Archive) std.mem.Allocator {
|
||||||
|
return self.alloc.threadSafeAllocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root(self: *Archive) !SfsFile {
|
||||||
|
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(self.allocator(), &rdr, &self.decomp);
|
||||||
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
|
const inode: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
||||||
|
return .init(self, inode, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
||||||
|
var root_fil = try self.root();
|
||||||
|
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
||||||
|
return root_fil.open(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var root_fil = try self.root();
|
||||||
|
defer root_fil.deinit();
|
||||||
|
return root_fil.extract(path, options);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -93,7 +93,7 @@ pub const DecompThread = struct {
|
|||||||
self.res_size = blk: switch (comp_type) {
|
self.res_size = blk: switch (comp_type) {
|
||||||
.gzip => {
|
.gzip => {
|
||||||
var decomp_rdr = compress.flate.Decompress.init(rdr, .zlib, self.buf);
|
var decomp_rdr = compress.flate.Decompress.init(rdr, .zlib, self.buf);
|
||||||
break :blk decomp_rdr.reader.readSliceAll(self.res);
|
break :blk decomp_rdr.reader.readSliceEndian(u8, self.res, .little);
|
||||||
},
|
},
|
||||||
.lzma => {
|
.lzma => {
|
||||||
var decomp_rdr = compress.lzma.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| {
|
var decomp_rdr = compress.lzma.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| {
|
||||||
@@ -109,7 +109,7 @@ pub const DecompThread = struct {
|
|||||||
},
|
},
|
||||||
.zstd => {
|
.zstd => {
|
||||||
var decomp_rdr = compress.zstd.Decompress.init(rdr, self.buf, .{});
|
var decomp_rdr = compress.zstd.Decompress.init(rdr, self.buf, .{});
|
||||||
break :blk decomp_rdr.reader.readSliceAll(self.res);
|
break :blk decomp_rdr.reader.readSliceEndian(u8, self.res, .little);
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const InodeType = @import("inode.zig").InodeType;
|
||||||
|
|
||||||
|
const Entry = @This();
|
||||||
|
|
||||||
|
const Header = extern struct { // use extern due to bad alignment with packed.
|
||||||
|
count: u32,
|
||||||
|
block_start: u32,
|
||||||
|
num: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const RawEntry = packed struct {
|
||||||
|
offset: u16,
|
||||||
|
inode_offset: i16,
|
||||||
|
inode_type: InodeType,
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
block_start: u32,
|
||||||
|
block_offset: u16,
|
||||||
|
num: u32,
|
||||||
|
inode_type: InodeType,
|
||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry {
|
||||||
|
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
|
||||||
|
var hdr: Header = undefined;
|
||||||
|
var raw: RawEntry = undefined;
|
||||||
|
var out: std.ArrayList(Entry) = .initCapacity(alloc, 0);
|
||||||
|
errdefer {
|
||||||
|
for (out.items) |i|
|
||||||
|
i.deinit(alloc);
|
||||||
|
out.deinit(alloc);
|
||||||
|
}
|
||||||
|
while (cur_red < size) {
|
||||||
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
|
cur_red += @sizeOf(Header);
|
||||||
|
try out.ensureUnusedCapacity(alloc, hdr.num + 1);
|
||||||
|
for (0..hdr.num + 1) |_| {
|
||||||
|
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
||||||
|
const name = try alloc.alloc(u8, raw.name_size + 1);
|
||||||
|
try rdr.readSliceEndian(u8, name, .little);
|
||||||
|
_ = out.addOneAssumeCapacity(.{
|
||||||
|
.block_start = hdr.block_start,
|
||||||
|
.block_offset = raw.offset,
|
||||||
|
.num = @abs(hdr.num + raw.offset),
|
||||||
|
.inode_type = raw.inode_type,
|
||||||
|
.name = name,
|
||||||
|
});
|
||||||
|
cur_red += @sizeOf(RawEntry) + raw.name_size + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toOwnedSlice(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.name);
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
|
const Archive = @import("archive.zig");
|
||||||
|
const DirEntry = @import("dir_entry.zig");
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
|
const FileError = error{
|
||||||
|
NotDirectory,
|
||||||
|
NotRegularFile,
|
||||||
|
NotFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
const File = @This();
|
||||||
|
|
||||||
|
archive: *Archive,
|
||||||
|
|
||||||
|
inode: Inode,
|
||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
/// Initialize a new File.
|
||||||
|
/// name is copied to the File so can be safely freed afterwards.
|
||||||
|
pub fn init(archive: *Archive, inode: Inode, name: []const u8) !File {
|
||||||
|
const new_name = try archive.allocator().alloc(name.len);
|
||||||
|
@memcpy(new_name, name);
|
||||||
|
return .{
|
||||||
|
.archive = archive,
|
||||||
|
.inode = inode,
|
||||||
|
.name = new_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn fromEntry(archive: *Archive, entry: DirEntry) !File {
|
||||||
|
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(archive.allocator(), &rdr, &archive.decomp);
|
||||||
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
|
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
||||||
|
errdefer inode.deinit();
|
||||||
|
const new_name = try archive.allocator().alloc(entry.name.len);
|
||||||
|
@memcpy(new_name, entry.name);
|
||||||
|
return .init(archive, inode, new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: File) void {
|
||||||
|
var alloc = self.archive.allocator();
|
||||||
|
alloc.free(self.name);
|
||||||
|
self.inode.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getEntries(self: *File) ![]DirEntry {
|
||||||
|
if (!self.isDir()) return FileError.NotDirectory;
|
||||||
|
var block_start: u32 = undefined;
|
||||||
|
var block_offset: u16 = undefined;
|
||||||
|
var size: u32 = undefined;
|
||||||
|
switch (self.inode.data) {
|
||||||
|
.dir => |d| {
|
||||||
|
block_start = d.block_start;
|
||||||
|
block_offset = d.block_offset;
|
||||||
|
size = d.size;
|
||||||
|
},
|
||||||
|
.ext_dir => |d| {
|
||||||
|
block_start = d.block_start;
|
||||||
|
block_offset = d.block_offset;
|
||||||
|
size = d.size;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
var rdr = self.archive.fil.readerAt(self.archive.super.dir_start + block_start, &[0]u8{});
|
||||||
|
const alloc = self.archive.allocator();
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, &self.archive.decomp);
|
||||||
|
try meta.interface.discardAll(block_offset);
|
||||||
|
return DirEntry.readDir(alloc, &rdr.interface, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a file/folder within a directory at the given path.
|
||||||
|
/// If path is ".", "/", or "./", this File is returned.
|
||||||
|
pub fn open(self: *File, path: []const u8) !File {
|
||||||
|
if (!self.isDir()) return FileError.NotDirectory;
|
||||||
|
if (pathIsSelf(path)) return self;
|
||||||
|
// Recursively stip ending & leading path separators.
|
||||||
|
// TODO: potentially do this more efficiently or have stricter requirements.
|
||||||
|
if (path[0] == '/') return self.open(path[1..]);
|
||||||
|
if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]);
|
||||||
|
const idx = std.mem.indexOf(u8, path, '/') orelse path.len;
|
||||||
|
const first_element = path[0..idx];
|
||||||
|
if (std.mem.eql(first_element, ".")) return self;
|
||||||
|
const entries = try self.getEntries();
|
||||||
|
var cur_slice = entries;
|
||||||
|
var split = cur_slice.len / 2;
|
||||||
|
while (cur_slice.len == 0) {
|
||||||
|
split = cur_slice.len / 2;
|
||||||
|
const comp = std.mem.order(u8, entries[split].name, first_element);
|
||||||
|
switch (comp) {
|
||||||
|
.eq => {
|
||||||
|
var fil: File = try .fromEntry(self.archive, cur_slice[split]);
|
||||||
|
if (idx == path.len) {
|
||||||
|
return fil;
|
||||||
|
}
|
||||||
|
defer fil.deinit();
|
||||||
|
return fil.open(path[idx + 1 ..]);
|
||||||
|
},
|
||||||
|
.lt => cur_slice = cur_slice[0..split],
|
||||||
|
.gt => cur_slice = cur_slice[split..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FileError.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self: *File, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = path;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParentInfo = struct {
|
||||||
|
fil: *File,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn extractReal(self: *File, path: []const u8, options: ExtractionOptions) void {
|
||||||
|
_ = self;
|
||||||
|
_ = path;
|
||||||
|
_ = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pathIsSelf(path: []const u8) bool {
|
||||||
|
if (path.len == 0) return true;
|
||||||
|
if (path.len == 1 and (path[0] == '/' or path[0] == '.')) return true;
|
||||||
|
if (path.len == 2 and (path[0] == '.' and path[1] == '/')) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
+91
-2
@@ -1,5 +1,94 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const dir = @import("inode_data/dir.zig");
|
||||||
|
const file = @import("inode_data/file.zig");
|
||||||
|
const misc = @import("inode_data/misc.zig");
|
||||||
|
|
||||||
pub const Ref = packed struct {
|
pub const Ref = packed struct {
|
||||||
_: u16,
|
|
||||||
table_offset: u32,
|
|
||||||
block_offset: u16,
|
block_offset: u16,
|
||||||
|
block_start: u32,
|
||||||
|
_: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const InodeType = 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 InodeData = union(InodeType) {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Header = packed struct {
|
||||||
|
inode_type: InodeType,
|
||||||
|
permissions: u16,
|
||||||
|
uid_idx: u16,
|
||||||
|
gid_idx: u16,
|
||||||
|
mod_time: u32,
|
||||||
|
num: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Inode = @This();
|
||||||
|
|
||||||
|
hdr: Header,
|
||||||
|
data: InodeData,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
||||||
|
var hdr: Header = undefined;
|
||||||
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
|
return .{
|
||||||
|
.hdr = hdr,
|
||||||
|
.data = switch (hdr.inode_type) {
|
||||||
|
.dir => .{ .dir = .read(rdr) },
|
||||||
|
.file => .{ .file = .read(alloc, rdr, block_size) },
|
||||||
|
.symlink => .{ .symlink = .read(alloc, rdr) },
|
||||||
|
.block_dev => .{ .block_dev = .read(rdr) },
|
||||||
|
.char_dev => .{ .char_dev = .read(rdr) },
|
||||||
|
.fifo => .{ .fifo = .read(rdr) },
|
||||||
|
.socket => .{ .socket = .read(rdr) },
|
||||||
|
.ext_dir => .{ .ext_dir = .read(rdr) },
|
||||||
|
.ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
|
||||||
|
.ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
|
||||||
|
.ext_block_dev => .{ .ext_block_dev = .read(rdr) },
|
||||||
|
.ext_char_dev => .{ .ext_char_dev = .read(rdr) },
|
||||||
|
.ext_fifo => .{ .ext_fifo = .read(rdr) },
|
||||||
|
.ext_socket => .{ .ext_socket = .read(rdr) },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Inode, 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 => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const Reader = @import("std").Io.Reader;
|
||||||
|
|
||||||
|
pub const Dir = packed struct {
|
||||||
|
block_start: u32,
|
||||||
|
hard_links: u32,
|
||||||
|
size: u16,
|
||||||
|
block_offset: u32,
|
||||||
|
parent_num: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !Dir {
|
||||||
|
var d: Dir = undefined;
|
||||||
|
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtDir = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
size: u32,
|
||||||
|
block_start: u32,
|
||||||
|
parent_num: u32,
|
||||||
|
idx_count: u16,
|
||||||
|
block_offset: u16,
|
||||||
|
xattr_id: u32,
|
||||||
|
// index: []DirIndex
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !ExtDir {
|
||||||
|
var d: ExtDir = undefined;
|
||||||
|
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
pub const BlockSize = packed struct {
|
||||||
|
size: u31,
|
||||||
|
uncompressed: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const File = struct {
|
||||||
|
block_start: u32, // bytes 0-3
|
||||||
|
frag_idx: u32, // bytes 4-7
|
||||||
|
frag_block_offset: u32, // bytes 8-11
|
||||||
|
size: u32, // bytes 12-15
|
||||||
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||||
|
var start: [16]u8 = undefined;
|
||||||
|
try rdr.readSliceEndian(u8, &start, .little);
|
||||||
|
const frag_idx: u32 = std.mem.readInt(u32, start[4..8], .little);
|
||||||
|
const size: u32 = std.mem.readInt(u32, start[12..16], .little);
|
||||||
|
var num_blocks: u32 = size / block_size;
|
||||||
|
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||||
|
const sizes = try alloc.alloc(u32, num_blocks);
|
||||||
|
errdefer alloc.free(sizes);
|
||||||
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
|
return .{
|
||||||
|
.block_start = std.mem.readInt(u32, start[0..4], .little),
|
||||||
|
.frag_idx = frag_idx,
|
||||||
|
.frag_block_offset = std.mem.readInt(u32, start[8..12], .little),
|
||||||
|
.size = size,
|
||||||
|
.block_sizes = sizes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.block_sizes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtFile = struct {
|
||||||
|
block_start: u64, // bytes 0-7
|
||||||
|
size: u64, // bytes 8-15
|
||||||
|
sparse: u64, // bytes 16-23
|
||||||
|
hard_links: u32, // bytes 24-27
|
||||||
|
frag_idx: u32, // bytes 28-31
|
||||||
|
frag_block_offset: u32, // bytes 32-35
|
||||||
|
xattr_idx: u32, // bytes 36-39
|
||||||
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
||||||
|
var start: [40]u8 = undefined;
|
||||||
|
try rdr.readSliceEndian(u8, &start, .little);
|
||||||
|
const frag_idx: u32 = std.mem.readInt(u32, start[28..32], .little);
|
||||||
|
const size: u64 = std.mem.readInt(u64, start[8..16], .little);
|
||||||
|
var num_blocks: u32 = size / block_size;
|
||||||
|
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||||
|
const sizes = try alloc.alloc(u32, num_blocks);
|
||||||
|
errdefer alloc.free(sizes);
|
||||||
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
|
return .{
|
||||||
|
.block_start = std.mem.readInt(u64, start[0..8], .little),
|
||||||
|
.size = size,
|
||||||
|
.sparse = std.mem.readInt(u64, start[16..24], .little),
|
||||||
|
.hard_links = std.mem.readInt(u32, start[24..28], .little),
|
||||||
|
.frag_idx = frag_idx,
|
||||||
|
.frag_block_offset = std.mem.readInt(u32, start[32..36], .little),
|
||||||
|
.xattr_idx = std.mem.readInt(u32, start[36..40], .little),
|
||||||
|
.block_sizes = sizes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.block_sizes);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
pub const Symlink = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
target: []const u8,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
|
||||||
|
var start: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceEndian(u8, &start, .little);
|
||||||
|
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
||||||
|
const target = try alloc.alloc(u8, target_size + 1);
|
||||||
|
errdefer alloc.free(target);
|
||||||
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
|
return .{
|
||||||
|
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
||||||
|
.target = target,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtSymlink = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
target: []const u8,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
|
||||||
|
var start: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceEndian(u8, &start, .little);
|
||||||
|
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
||||||
|
const target = try alloc.alloc(u8, target_size + 1);
|
||||||
|
errdefer alloc.free(target);
|
||||||
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
|
var xattr_idx: u32 = undefined;
|
||||||
|
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
||||||
|
return .{
|
||||||
|
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
||||||
|
.target = target,
|
||||||
|
.xattr_idx = xattr_idx,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Dev = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
dev: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !Dev {
|
||||||
|
var d: Dev = undefined;
|
||||||
|
try rdr.readSliceEndian(Dev, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtDev = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
dev: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !ExtDev {
|
||||||
|
var d: ExtDev = undefined;
|
||||||
|
try rdr.readSliceEndian(ExtDev, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const IPC = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !IPC {
|
||||||
|
var d: IPC = undefined;
|
||||||
|
try rdr.readSliceEndian(IPC, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtIPC = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !ExtIPC {
|
||||||
|
var d: ExtIPC = undefined;
|
||||||
|
try rdr.readSliceEndian(ExtIPC, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
const Writer = @import("std").Io.Writer;
|
||||||
|
|
||||||
|
const ExtractionOptions = @This();
|
||||||
|
|
||||||
|
/// Don't set the file's permissions after extraction
|
||||||
|
ignorePermissions: bool = false,
|
||||||
|
/// Don't set the file's owner after extraction.
|
||||||
|
ignoreOwner: bool = false,
|
||||||
|
/// Replace symlinks with their target.
|
||||||
|
dereferenceSymlinks: bool = false,
|
||||||
|
|
||||||
|
verbose: bool = false,
|
||||||
|
/// If options verbose and verboseWriter not set, logs are printed to stdout.
|
||||||
|
verboseWriter: ?Writer = null,
|
||||||
|
|
||||||
|
pub const Default: ExtractionOptions = .{};
|
||||||
|
pub const VerboseDefault: ExtractionOptions = .{ .verbose = true };
|
||||||
+4
-3
@@ -1,9 +1,10 @@
|
|||||||
const math = @import("std").math;
|
const std = @import("std");
|
||||||
|
const math = std.math;
|
||||||
|
|
||||||
const CompressionType = @import("decomp.zig").CompressionType;
|
const CompressionType = @import("decomp.zig").CompressionType;
|
||||||
const InodeRef = @import("inode.zig").Ref;
|
const InodeRef = @import("inode.zig").Ref;
|
||||||
|
|
||||||
const SQUASHFS_MAGIC: u32 = "hsqs";
|
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
||||||
|
|
||||||
const SuperblockError = error{
|
const SuperblockError = error{
|
||||||
InvalidMagic,
|
InvalidMagic,
|
||||||
@@ -52,7 +53,7 @@ pub const Superblock = packed struct {
|
|||||||
pub fn validate(self: Superblock) !void {
|
pub fn validate(self: Superblock) !void {
|
||||||
if (self.magic != SQUASHFS_MAGIC)
|
if (self.magic != SQUASHFS_MAGIC)
|
||||||
return SuperblockError.InvalidMagic;
|
return SuperblockError.InvalidMagic;
|
||||||
if (self.magic.flags.check)
|
if (self.flags.check)
|
||||||
return SuperblockError.InvalidCheck;
|
return SuperblockError.InvalidCheck;
|
||||||
if (self.ver_maj != 4 or self.ver_min != 0)
|
if (self.ver_maj != 4 or self.ver_min != 0)
|
||||||
return SuperblockError.InvalidVersion;
|
return SuperblockError.InvalidVersion;
|
||||||
|
|||||||
+11
-10
@@ -11,7 +11,7 @@ const TableError = error{
|
|||||||
|
|
||||||
pub fn Table(T: anytype) type {
|
pub fn Table(T: anytype) type {
|
||||||
return struct {
|
return struct {
|
||||||
const This = @This();
|
const Self = @This();
|
||||||
|
|
||||||
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@ pub fn Table(T: anytype) type {
|
|||||||
decomp: *DecompMgr,
|
decomp: *DecompMgr,
|
||||||
tab_start: u64,
|
tab_start: u64,
|
||||||
|
|
||||||
mut: Mutex = .{},
|
|
||||||
|
|
||||||
tab: std.AutoHashMap(u32, []T),
|
tab: std.AutoHashMap(u32, []T),
|
||||||
values: u32,
|
values: u32,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *DecompMgr, tab_start: u64, values: u32) !This {
|
mut: Mutex = .{},
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *DecompMgr, tab_start: u64, values: u32) !Self {
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.fil = fil,
|
.fil = fil,
|
||||||
@@ -37,15 +37,15 @@ pub fn Table(T: anytype) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *This) void {
|
pub fn deinit(self: *Self) void {
|
||||||
var iter = self.tab.valueIterator();
|
var iter = self.tab.valueIterator();
|
||||||
for (iter.next()) |s| {
|
while (iter.next()) |s| {
|
||||||
self.alloc.free(s);
|
self.alloc.free(s.*);
|
||||||
}
|
}
|
||||||
self.tab.deinit();
|
self.tab.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: *This, idx: u32) !T {
|
pub fn get(self: *Self, idx: u32) !T {
|
||||||
if (idx >= self.values) return TableError.InvalidIndex;
|
if (idx >= self.values) return TableError.InvalidIndex;
|
||||||
const block_num = idx / VALS_PER_BLOCK;
|
const block_num = idx / VALS_PER_BLOCK;
|
||||||
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
||||||
@@ -65,11 +65,12 @@ pub fn Table(T: anytype) type {
|
|||||||
const slice = try self.alloc.alloc(slice_size);
|
const slice = try self.alloc.alloc(slice_size);
|
||||||
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
|
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
|
||||||
const offset: u64 = 0;
|
const offset: u64 = 0;
|
||||||
try rdr.interface.readSliceAll(@ptrCast(&idx_offset));
|
try rdr.interface.readSliceEndian(u64, @ptrCast(&idx_offset), .little);
|
||||||
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(&rdr.interface, self.decomp);
|
var meta: MetadataReader = .init(&rdr.interface, self.decomp);
|
||||||
try meta.interface.readSliceAll(@ptrCast(slice));
|
try meta.interface.readSliceEndian(T, @ptrCast(slice), .little);
|
||||||
try self.tab.put(block_num, slice);
|
try self.tab.put(block_num, slice);
|
||||||
|
return slice[idx_offset];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-5
@@ -2,26 +2,74 @@ const std = @import("std");
|
|||||||
const stuff = @import("builtin");
|
const stuff = @import("builtin");
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
const Archive = @import("archive.zig");
|
||||||
const Superblock = @import("super.zig");
|
const Superblock = @import("super.zig").Superblock;
|
||||||
|
|
||||||
const TestArchive = "testing/LinuxPATest.sfs";
|
const TestArchive = "testing/LinuxPATest.sfs";
|
||||||
|
|
||||||
test "Basics" {
|
test "Basics" {
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive);
|
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||||
defer sfs.deinit();
|
defer sfs.deinit();
|
||||||
|
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
||||||
|
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
||||||
|
return error.BadSuperblock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TestFile = "Start.exe";
|
const TestFile = "Start.exe";
|
||||||
const TestFileExtractLocation = "testing/Start.exe";
|
const TestFileExtractLocation = "testing/Start.exe";
|
||||||
|
|
||||||
test "ExtractSingleFile" {}
|
test "ExtractSingleFile" {
|
||||||
|
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||||
|
defer fil.close();
|
||||||
|
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||||
|
defer sfs.deinit();
|
||||||
|
var test_fil = try sfs.open(TestFile);
|
||||||
|
try test_fil.extract(TestFileExtractLocation, .VerboseDefault);
|
||||||
|
//TODO: validate extracted file.
|
||||||
|
}
|
||||||
|
|
||||||
const TestFullExtractLocation = "testing/TestExtract";
|
const TestFullExtractLocation = "testing/TestExtract";
|
||||||
|
|
||||||
test "ExtractCompleteArchive" {}
|
test "ExtractCompleteArchive" {}
|
||||||
|
|
||||||
const CorrectSuperblock = Superblock{
|
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||||
.magic = "hsqs",
|
.magic = std.mem.readInt(u32, "hsqs", .little),
|
||||||
|
.inode_count = 2974,
|
||||||
|
.mod_time = 1632696724,
|
||||||
|
.block_size = 131072,
|
||||||
|
.frag_count = 264,
|
||||||
|
.compression = .zstd,
|
||||||
|
.block_log = 17,
|
||||||
|
.flags = .{
|
||||||
|
.inode_uncompressed = false,
|
||||||
|
.data_uncompressed = false,
|
||||||
|
.check = false,
|
||||||
|
.frag_uncompressed = false,
|
||||||
|
.fragment_never = false,
|
||||||
|
.fragment_always = false,
|
||||||
|
.duplicates = true,
|
||||||
|
.exportable = true,
|
||||||
|
.xattr_uncompressed = false,
|
||||||
|
.xattr_never = false,
|
||||||
|
.compression_options = false,
|
||||||
|
.ids_uncompressed = false,
|
||||||
|
._ = 0,
|
||||||
|
},
|
||||||
|
.id_count = 1,
|
||||||
|
.ver_maj = 4,
|
||||||
|
.ver_min = 0,
|
||||||
|
.root_ref = .{
|
||||||
|
.block_offset = 1363,
|
||||||
|
.table_offset = 29237,
|
||||||
|
._ = 0,
|
||||||
|
},
|
||||||
|
.size = 106841744,
|
||||||
|
.id_start = 106841632,
|
||||||
|
.xattr_start = 106841720,
|
||||||
|
.inode_start = 106778274,
|
||||||
|
.dir_start = 106807998,
|
||||||
|
.frag_start = 106837747,
|
||||||
|
.export_start = 106841602,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const BlockHeader = packed struct {
|
|||||||
const This = @This();
|
const This = @This();
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
rdr: Reader,
|
rdr: *Reader,
|
||||||
decomp: *DecompMgr,
|
decomp: *DecompMgr,
|
||||||
|
|
||||||
buf: [8192]u8 = undefined,
|
buf: [8192]u8 = undefined,
|
||||||
@@ -22,7 +22,7 @@ buf: [8192]u8 = undefined,
|
|||||||
interface: Reader,
|
interface: Reader,
|
||||||
err: anyerror = 0,
|
err: anyerror = 0,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: Reader, decomp: *DecompMgr) This {
|
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *DecompMgr) This {
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.rdr = rdr,
|
.rdr = rdr,
|
||||||
@@ -43,9 +43,9 @@ pub fn init(alloc: std.mem.Allocator, rdr: Reader, decomp: *DecompMgr) This {
|
|||||||
fn advance(self: *This) !void {
|
fn advance(self: *This) !void {
|
||||||
self.interface.seek = 0;
|
self.interface.seek = 0;
|
||||||
var hdr: BlockHeader = undefined;
|
var hdr: BlockHeader = undefined;
|
||||||
try self.rdr.readSliceAll(@ptrCast(&hdr));
|
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little);
|
||||||
if (hdr.uncompressed) {
|
if (hdr.uncompressed) {
|
||||||
try self.rdr.readSliceAll(&self.buf[0..hdr.size]);
|
try self.rdr.readSliceEndian(u8, &self.buf[0..hdr.size], .little);
|
||||||
self.interface.end = hdr.size;
|
self.interface.end = hdr.size;
|
||||||
self.interface.buffer = self.buf[0..hdr.size];
|
self.interface.buffer = self.buf[0..hdr.size];
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user