More work on re-implementing everything
Finished MetadataReader Finished DirEntry Started work on File Added most important things to Inode Fixed test.zig Fixed some build issues causing SEGV
This commit is contained in:
+2
-1
@@ -9,12 +9,13 @@
|
|||||||
"usePlaceholders": false,
|
"usePlaceholders": false,
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"build_on_save": true,
|
// "build_on_save": true,
|
||||||
"use_placeholders": false,
|
"use_placeholders": false,
|
||||||
"build_on_save_args": [
|
"build_on_save_args": [
|
||||||
"-fincremental",
|
"-fincremental",
|
||||||
"-Dallow_lzo=true",
|
"-Dallow_lzo=true",
|
||||||
"-Ddebug=true",
|
"-Ddebug=true",
|
||||||
|
"test",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.valgrind = debug,
|
.valgrind = debug,
|
||||||
.error_tracing = debug,
|
.error_tracing = debug,
|
||||||
.strip = debug,
|
.strip = !debug,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "config", .module = zig_squashfs_options.createModule() },
|
.{ .name = "config", .module = zig_squashfs_options.createModule() },
|
||||||
.{ .name = "c", .module = c_import.createModule() },
|
.{ .name = "c", .module = c_import.createModule() },
|
||||||
@@ -78,7 +78,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
},
|
},
|
||||||
.valgrind = debug,
|
.valgrind = debug,
|
||||||
.error_tracing = debug,
|
.error_tracing = debug,
|
||||||
.strip = if (debug == true) false else null,
|
.strip = !debug,
|
||||||
}),
|
}),
|
||||||
.use_llvm = debug,
|
.use_llvm = debug,
|
||||||
.version = version,
|
.version = version,
|
||||||
@@ -92,19 +92,15 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.valgrind = debug,
|
.valgrind = true,
|
||||||
.error_tracing = debug,
|
.error_tracing = true,
|
||||||
.strip = debug,
|
.strip = false,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "config", .module = zig_squashfs_options.createModule() },
|
.{ .name = "config", .module = zig_squashfs_options.createModule() },
|
||||||
.{ .name = "c", .module = c_import.createModule() },
|
.{ .name = "c", .module = c_import.createModule() },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
.use_llvm = true, // Helps with lldb degugging
|
.use_llvm = debug, // Helps with lldb degugging
|
||||||
.test_runner = .{
|
|
||||||
.mode = .simple,
|
|
||||||
.path = b.path("src/test.zig"),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (deps) |d|
|
for (deps) |d|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[tools]
|
[tools]
|
||||||
zig = "master" # 0.16.0 compilation errors with SEGV
|
zig = "master"
|
||||||
|
|||||||
+55
-3
@@ -5,18 +5,28 @@ const MemoryMap = Io.File.MemoryMap;
|
|||||||
const c = @import("c");
|
const c = @import("c");
|
||||||
const config = @import("config");
|
const config = @import("config");
|
||||||
|
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const File = @import("file.zig");
|
||||||
const Superblock = @import("super.zig").Superblock;
|
const Superblock = @import("super.zig").Superblock;
|
||||||
const DecompCache = @import("util/decomp_cache.zig");
|
const DecompCache = @import("util/decomp_cache.zig");
|
||||||
const CompressionType = @import("util/decompress.zig").CompressionType;
|
const CompressionType = @import("util/decompress.zig").CompressionType;
|
||||||
|
|
||||||
const Archive = @This();
|
const Archive = @This();
|
||||||
|
|
||||||
|
const CACHE_MIN = 16 * 1024 * 1024;
|
||||||
|
const CACHE_MAX = 1 * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
map: MemoryMap,
|
map: MemoryMap,
|
||||||
cache: DecompCache,
|
cache: DecompCache,
|
||||||
|
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, io: Io, file: Io.File, offset: u64, max_cache_size: u64) !Archive {
|
/// Open a squashfs archive from an Io.File.
|
||||||
|
pub fn init(alloc: std.mem.Allocator, io: Io, fil: Io.File) !Archive {
|
||||||
|
return initAdvanced(alloc, io, fil, 0, 0);
|
||||||
|
}
|
||||||
|
/// If max_cache_size is zero, a size is selected based on system ram, up to 1GB with a minimum of 16MB.
|
||||||
|
pub fn initAdvanced(alloc: std.mem.Allocator, io: Io, file: Io.File, offset: u64, max_cache_size: u64) !Archive {
|
||||||
var rdr = file.reader(io, &[0]u8{});
|
var rdr = file.reader(io, &[0]u8{});
|
||||||
try rdr.seekTo(offset);
|
try rdr.seekTo(offset);
|
||||||
|
|
||||||
@@ -29,15 +39,57 @@ pub fn init(alloc: std.mem.Allocator, io: Io, file: Io.File, offset: u64, max_ca
|
|||||||
|
|
||||||
const map = try file.createMemoryMap(
|
const map = try file.createMemoryMap(
|
||||||
io,
|
io,
|
||||||
.{ .offset = offset, .len = super.size, .protection = .{ .read = true } },
|
.{
|
||||||
|
.offset = offset,
|
||||||
|
.len = super.size,
|
||||||
|
.protection = .{ .read = true },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
const cache_size = blk: {
|
||||||
|
if (max_cache_size > CACHE_MIN) break :blk CACHE_MIN;
|
||||||
|
const sys_mem = std.process.totalSystemMemory() catch break :blk CACHE_MIN;
|
||||||
|
var min = @min(CACHE_MAX, sys_mem / 4);
|
||||||
|
if (min < CACHE_MIN and sys_mem > CACHE_MIN)
|
||||||
|
min = CACHE_MIN;
|
||||||
|
break :blk min;
|
||||||
|
};
|
||||||
return .{
|
return .{
|
||||||
.map = map,
|
.map = map,
|
||||||
.cache = try .init(alloc, map, super.compression, max_cache_size),
|
.cache = try .init(
|
||||||
|
alloc,
|
||||||
|
map,
|
||||||
|
super.compression,
|
||||||
|
cache_size,
|
||||||
|
),
|
||||||
|
|
||||||
.super = super,
|
.super = super,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *Archive, io: Io) void {
|
pub fn deinit(self: *Archive, io: Io) void {
|
||||||
|
self.cache.deinit(io);
|
||||||
self.map.destroy(io);
|
self.map.destroy(io);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn root(self: *Archive, alloc: std.mem.Allocator, io: Io) !File {
|
||||||
|
return .fromRef(alloc, io, self, "", self.super.root_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||||
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
|
|
||||||
|
var root_file = try self.root(alloc, io);
|
||||||
|
|
||||||
|
if (path.len == 0 or std.mem.eql(u8, path, ".")) return root_file;
|
||||||
|
defer root_file.deinit();
|
||||||
|
|
||||||
|
return root_file.open(alloc, io, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_dir: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = io;
|
||||||
|
_ = ext_dir;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
var fil: std.Io.File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
var fil: std.Io.File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
||||||
defer fil.close(io);
|
defer fil.close(io);
|
||||||
var arc: squashfs.Archive = try .init(alloc, io, fil, offset, 1 * 1024 * 1024 * 1024); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
var arc: squashfs.Archive = try .initAdvanced(alloc, io, fil, offset, 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||||
defer arc.deinit(io);
|
defer arc.deinit(io);
|
||||||
const options: squashfs.ExtractionOptions = .{
|
const options: squashfs.ExtractionOptions = .{
|
||||||
.single_threaded = threads == 1,
|
.single_threaded = threads == 1,
|
||||||
@@ -67,7 +67,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
};
|
};
|
||||||
if (force)
|
if (force)
|
||||||
try Io.Dir.cwd().deleteTree(io, extLoc);
|
try Io.Dir.cwd().deleteTree(io, extLoc);
|
||||||
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
|
try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(out: *Writer, args: std.process.Args) !void {
|
fn handleArgs(out: *Writer, args: std.process.Args) !void {
|
||||||
@@ -135,3 +135,7 @@ fn handleArgs(out: *Writer, args: std.process.Args) !void {
|
|||||||
archive = arg;
|
archive = arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
std.testing.refAllDecls(squashfs.Archive);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
|
||||||
|
const Header = extern struct {
|
||||||
|
count: u32,
|
||||||
|
block_start: u32,
|
||||||
|
num: u32,
|
||||||
|
};
|
||||||
|
const Entry = extern struct {
|
||||||
|
block_offset: u16,
|
||||||
|
num_offset: i16,
|
||||||
|
inode_type: Inode.Type,
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DirEntry = @This();
|
||||||
|
|
||||||
|
inode_type: Inode.Type,
|
||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
block_start: u32,
|
||||||
|
block_offset: u32,
|
||||||
|
num: u32,
|
||||||
|
|
||||||
|
pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readEntries(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]DirEntry {
|
||||||
|
var out: std.ArrayList(DirEntry) = try .initCapacity(alloc, 50);
|
||||||
|
errdefer out.deinit(alloc);
|
||||||
|
|
||||||
|
var tot_read: u32 = 3;
|
||||||
|
while (tot_read < size) {
|
||||||
|
var hdr: Header = undefined;
|
||||||
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
|
tot_read += @sizeOf(Header);
|
||||||
|
|
||||||
|
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||||
|
|
||||||
|
for (0..hdr.count + 1) |_| {
|
||||||
|
var ent: Entry = undefined;
|
||||||
|
try rdr.readSliceEndian(Entry, @ptrCast(&ent), .little);
|
||||||
|
tot_read += @sizeOf(Entry) + ent.name_size + 1;
|
||||||
|
|
||||||
|
const name = try alloc.alloc(u8, ent.name_size + 1);
|
||||||
|
errdefer alloc.free(name);
|
||||||
|
|
||||||
|
try rdr.readSliceEndian(u8, name, .little);
|
||||||
|
|
||||||
|
out.appendAssumeCapacity(.{
|
||||||
|
.inode_type = ent.inode_type,
|
||||||
|
.name = name,
|
||||||
|
|
||||||
|
.block_offset = ent.block_offset,
|
||||||
|
.block_start = hdr.block_start,
|
||||||
|
.num = @intCast(@as(i64, @intCast(hdr.num)) + ent.num_offset),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toOwnedSlice(alloc);
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
//! A wrapper around an Inode to make common activities easier.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
NotFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
const File = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
archive: *Archive,
|
||||||
|
|
||||||
|
name: []const u8,
|
||||||
|
inode: Inode,
|
||||||
|
|
||||||
|
pub fn fromEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry: DirEntry) !File {
|
||||||
|
var meta: MetadataReader = .init(io, &archive.cache, archive.super.inode_start + entry.block_start);
|
||||||
|
defer meta.deinit();
|
||||||
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
|
|
||||||
|
const new_name = try alloc.alloc(u8, entry.name.len);
|
||||||
|
errdefer alloc.free(new_name);
|
||||||
|
@memcpy(new_name, entry.name);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.archive = archive,
|
||||||
|
|
||||||
|
.name = new_name,
|
||||||
|
.inode = try .fromReader(alloc, &meta.interface, archive.super.block_size),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// Create a File from an Inode.Ref. name should be created using the alloc given.
|
||||||
|
pub fn fromRef(alloc: std.mem.Allocator, io: Io, archive: *Archive, name: []const u8, ref: Inode.Ref) !File {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.archive = archive,
|
||||||
|
|
||||||
|
.name = name,
|
||||||
|
.inode = try .fromRef(
|
||||||
|
alloc,
|
||||||
|
io,
|
||||||
|
&archive.cache,
|
||||||
|
archive.super.inode_start,
|
||||||
|
archive.super.block_size,
|
||||||
|
ref,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn copy(alloc: std.mem.Allocator, from: File) !File {
|
||||||
|
const new_name = try alloc.alloc(u8, from.name.len);
|
||||||
|
errdefer alloc.free(new_name);
|
||||||
|
@memcpy(new_name, from.name);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.archive = from.archive,
|
||||||
|
|
||||||
|
.inode = try .copy(alloc, from.inode),
|
||||||
|
.name = new_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: File) void {
|
||||||
|
self.alloc.free(self.name);
|
||||||
|
self.inode.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||||
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
|
|
||||||
|
if (path.len == 0 or std.mem.eql(u8, path, ".")) return .copy(alloc, self);
|
||||||
|
|
||||||
|
const first_element = std.mem.sliceTo(path, '/');
|
||||||
|
|
||||||
|
const entries = try self.inode.readDirectory(alloc, io, &self.archive.cache, self.archive.super.dir_start);
|
||||||
|
defer {
|
||||||
|
for (entries) |entry|
|
||||||
|
entry.deinit(alloc);
|
||||||
|
alloc.free(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Potentially I could use linear searching on small dir tables...
|
||||||
|
var search_slice = entries;
|
||||||
|
var idx = search_slice.len / 2;
|
||||||
|
while (search_slice.len > 0) {
|
||||||
|
const order = std.mem.order(u8, first_element, search_slice[idx].name);
|
||||||
|
switch (order) {
|
||||||
|
.eq => break,
|
||||||
|
.gt => search_slice = search_slice[idx..],
|
||||||
|
.lt => search_slice = search_slice[0..idx],
|
||||||
|
}
|
||||||
|
idx = search_slice.len / 2;
|
||||||
|
}
|
||||||
|
if (search_slice.len == 0) return Error.NotFound;
|
||||||
|
|
||||||
|
var fil: File = try .fromEntry(alloc, io, self.archive, search_slice[idx]);
|
||||||
|
if (path.len == first_element.len) return fil;
|
||||||
|
defer fil.deinit();
|
||||||
|
|
||||||
|
return fil.open(alloc, io, filepath[first_element.len..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = io;
|
||||||
|
_ = ext_loc;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
+111
-20
@@ -1,10 +1,15 @@
|
|||||||
//! A file-system object. Represents a File or directory.
|
//! A file-system object. Represents a File or directory.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
const Reader = Io.Reader;
|
||||||
|
|
||||||
const dir = @import("inode_data/dir.zig");
|
const DirEntry = @import("dir_entry.zig");
|
||||||
const file = @import("inode_data/file.zig");
|
const DirTypes = @import("inode_data/dir.zig");
|
||||||
const misc = @import("inode_data/misc.zig");
|
const FileTypes = @import("inode_data/file.zig");
|
||||||
|
const MiscTypes = @import("inode_data/misc.zig");
|
||||||
|
const DecompCache = @import("util/decomp_cache.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
pub const Ref = packed struct(u64) {
|
pub const Ref = packed struct(u64) {
|
||||||
block_offset: u16,
|
block_offset: u16,
|
||||||
@@ -12,7 +17,7 @@ pub const Ref = packed struct(u64) {
|
|||||||
_: u16,
|
_: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const InodeType = enum(u16) {
|
pub const Type = enum(u16) {
|
||||||
dir = 1,
|
dir = 1,
|
||||||
file,
|
file,
|
||||||
symlink,
|
symlink,
|
||||||
@@ -29,25 +34,25 @@ pub const InodeType = enum(u16) {
|
|||||||
ext_socket,
|
ext_socket,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const InodeData = union(InodeType) {
|
pub const Data = union(Type) {
|
||||||
dir: dir.Dir,
|
dir: DirTypes.Dir,
|
||||||
file: file.File,
|
file: FileTypes.File,
|
||||||
symlink: misc.Symlink,
|
symlink: MiscTypes.Symlink,
|
||||||
block_dev: misc.Dev,
|
block_dev: MiscTypes.Dev,
|
||||||
char_dev: misc.Dev,
|
char_dev: MiscTypes.Dev,
|
||||||
fifo: misc.IPC,
|
fifo: MiscTypes.IPC,
|
||||||
socket: misc.IPC,
|
socket: MiscTypes.IPC,
|
||||||
ext_dir: dir.ExtDir,
|
ext_dir: DirTypes.ExtDir,
|
||||||
ext_file: file.ExtFile,
|
ext_file: FileTypes.ExtFile,
|
||||||
ext_symlink: misc.ExtSymlink,
|
ext_symlink: MiscTypes.ExtSymlink,
|
||||||
ext_block_dev: misc.ExtDev,
|
ext_block_dev: MiscTypes.ExtDev,
|
||||||
ext_char_dev: misc.ExtDev,
|
ext_char_dev: MiscTypes.ExtDev,
|
||||||
ext_fifo: misc.ExtIPC,
|
ext_fifo: MiscTypes.ExtIPC,
|
||||||
ext_socket: misc.ExtIPC,
|
ext_socket: MiscTypes.ExtIPC,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Header = packed struct {
|
pub const Header = packed struct {
|
||||||
inode_type: InodeType,
|
inode_type: Type,
|
||||||
permissions: u16,
|
permissions: u16,
|
||||||
uid_idx: u16,
|
uid_idx: u16,
|
||||||
gid_idx: u16,
|
gid_idx: u16,
|
||||||
@@ -55,4 +60,90 @@ pub const Header = packed struct {
|
|||||||
num: u32,
|
num: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
NotDirectory,
|
||||||
|
};
|
||||||
|
|
||||||
const Inode = @This();
|
const Inode = @This();
|
||||||
|
|
||||||
|
hdr: Header,
|
||||||
|
data: Data,
|
||||||
|
|
||||||
|
pub fn fromRef(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, ref: Ref) !Inode {
|
||||||
|
var meta: MetadataReader = .init(io, cache, ref.block_start + inode_start);
|
||||||
|
defer meta.deinit();
|
||||||
|
try meta.interface.discardAll(ref.block_offset);
|
||||||
|
return fromReader(alloc, &meta.interface, block_size);
|
||||||
|
}
|
||||||
|
pub fn fromReader(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 = try .read(rdr) },
|
||||||
|
.file => .{ .file = try .read(alloc, rdr, block_size) },
|
||||||
|
.symlink => .{ .symlink = try .read(alloc, rdr) },
|
||||||
|
.block_dev => .{ .block_dev = try .read(rdr) },
|
||||||
|
.char_dev => .{ .char_dev = try .read(rdr) },
|
||||||
|
.fifo => .{ .fifo = try .read(rdr) },
|
||||||
|
.socket => .{ .socket = try .read(rdr) },
|
||||||
|
.ext_dir => .{ .ext_dir = try .read(rdr) },
|
||||||
|
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
|
||||||
|
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
|
||||||
|
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
|
||||||
|
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
|
||||||
|
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
|
||||||
|
.ext_socket => .{ .ext_socket = try .read(rdr) },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn copy(alloc: std.mem.Allocator, from: Inode) !Inode {
|
||||||
|
var new = from;
|
||||||
|
switch (from.data) {
|
||||||
|
.file => |f| {
|
||||||
|
new.data.file.block_sizes = try alloc.alloc(FileTypes.BlockSize, f.block_sizes.len);
|
||||||
|
@memcpy(new.data.file.block_sizes, f.block_sizes);
|
||||||
|
},
|
||||||
|
.ext_file => |f| {
|
||||||
|
new.data.ext_file.block_sizes = try alloc.alloc(FileTypes.BlockSize, f.block_sizes.len);
|
||||||
|
@memcpy(new.data.ext_file.block_sizes, f.block_sizes);
|
||||||
|
},
|
||||||
|
.symlink => |s| {
|
||||||
|
const new_target = try alloc.alloc(u8, s.target.len);
|
||||||
|
@memcpy(new_target, s.target);
|
||||||
|
new.data.symlink.target = new_target;
|
||||||
|
},
|
||||||
|
.ext_symlink => |s| {
|
||||||
|
const new_target = try alloc.alloc(u8, s.target.len);
|
||||||
|
@memcpy(new_target, s.target);
|
||||||
|
new.data.ext_symlink.target = new_target;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
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 => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readDirectory(self: Inode, alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64) ![]DirEntry {
|
||||||
|
return switch (self.data) {
|
||||||
|
.dir => |d| readDirectoryFromData(alloc, io, cache, dir_start, d),
|
||||||
|
.ext_dir => |d| readDirectoryFromData(alloc, io, cache, dir_start, d),
|
||||||
|
else => Error.NotDirectory,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn readDirectoryFromData(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64, d: anytype) ![]DirEntry {
|
||||||
|
var meta: MetadataReader = .init(io, cache, dir_start + d.block_start);
|
||||||
|
defer meta.deinit();
|
||||||
|
try meta.interface.discardAll(d.block_offset);
|
||||||
|
|
||||||
|
return DirEntry.readEntries(alloc, &meta.interface, d.size);
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub const ExtDir = extern struct {
|
|||||||
|
|
||||||
pub fn read(rdr: *Reader) !ExtDir {
|
pub fn read(rdr: *Reader) !ExtDir {
|
||||||
var d: ExtDir = undefined;
|
var d: ExtDir = undefined;
|
||||||
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
try rdr.readSliceEndian(ExtDir, @ptrCast(&d), .little);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ pub const File = struct {
|
|||||||
block_sizes: []BlockSize,
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||||
var values = struct {
|
const raw_values = struct {
|
||||||
block_start: u32, // bytes 0-3
|
block_start: u32, // bytes 0-3
|
||||||
frag_idx: u32, // bytes 4-7
|
frag_idx: u32, // bytes 4-7
|
||||||
frag_offset: u32, // bytes 8-11
|
frag_offset: u32, // bytes 8-11
|
||||||
size: u32, // bytes 12-15
|
size: u32, // bytes 12-15
|
||||||
};
|
};
|
||||||
|
var values: raw_values = undefined;
|
||||||
try rdr.readSliceEndian(@TypeOf(values), @ptrCast(&values), .little);
|
try rdr.readSliceEndian(@TypeOf(values), @ptrCast(&values), .little);
|
||||||
|
|
||||||
var num_blocks: u32 = values.size / block_size;
|
var num_blocks: u32 = values.size / block_size;
|
||||||
@@ -54,7 +55,7 @@ pub const ExtFile = struct {
|
|||||||
block_sizes: []BlockSize,
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
||||||
var values = struct {
|
const raw_values = struct {
|
||||||
block_start: u64, // bytes 0-7
|
block_start: u64, // bytes 0-7
|
||||||
size: u64, // bytes 8-15
|
size: u64, // bytes 8-15
|
||||||
sparse: u64, // bytes 16-23
|
sparse: u64, // bytes 16-23
|
||||||
@@ -63,9 +64,10 @@ pub const ExtFile = struct {
|
|||||||
frag_offset: u32, // bytes 32-35
|
frag_offset: u32, // bytes 32-35
|
||||||
xattr_idx: u32, // bytes 36-39
|
xattr_idx: u32, // bytes 36-39
|
||||||
};
|
};
|
||||||
|
var values: raw_values = undefined;
|
||||||
try rdr.readSliceEndian(@TypeOf(values), @ptrCast(&values), .little);
|
try rdr.readSliceEndian(@TypeOf(values), @ptrCast(&values), .little);
|
||||||
|
|
||||||
var num_blocks: u32 = values.size / block_size;
|
var num_blocks: u32 = @truncate(values.size / block_size);
|
||||||
if (values.size % block_size != 0 and values.frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
if (values.size % block_size != 0 and values.frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||||
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||||
errdefer alloc.free(sizes);
|
errdefer alloc.free(sizes);
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
pub const Archive = @import("archive.zig");
|
pub const Archive = @import("archive.zig");
|
||||||
pub const ExtractionOptions = @import("options.zig");
|
pub const ExtractionOptions = @import("options.zig");
|
||||||
|
const Test = @import("test.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(Test);
|
||||||
|
}
|
||||||
|
|||||||
+28
-21
@@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
const stuff = @import("builtin");
|
const stuff = @import("builtin");
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
const Archive = @import("archive.zig");
|
||||||
@@ -7,40 +8,46 @@ 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, .{});
|
const io = std.testing.io;
|
||||||
defer fil.close();
|
const alloc = std.testing.allocator;
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
defer fil.close(io);
|
||||||
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
var sfs: Archive = try .init(alloc, io, fil);
|
||||||
return error.BadSuperblock;
|
defer sfs.deinit(io);
|
||||||
}
|
try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TestFile = "Start.exe";
|
const TestFile = "Start.exe";
|
||||||
const TestFileExtractLocation = "testing/Start.exe";
|
const TestFileExtractLocation = "testing/Start.exe";
|
||||||
|
|
||||||
test "ExtractSingleFile" {
|
test "ExtractSingleFile" {
|
||||||
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
const io = std.testing.io;
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
const alloc = std.testing.allocator;
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {};
|
||||||
defer sfs.deinit();
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
var test_fil = try sfs.open(TestFile);
|
defer fil.close(io);
|
||||||
|
var sfs: Archive = try .init(alloc, io, fil);
|
||||||
|
defer sfs.deinit(io);
|
||||||
|
var test_fil = try sfs.open(alloc, io, TestFile);
|
||||||
defer test_fil.deinit();
|
defer test_fil.deinit();
|
||||||
try test_fil.extract(TestFileExtractLocation, .Default);
|
try test_fil.extract(alloc, io, TestFileExtractLocation, .default);
|
||||||
//TODO: validate extracted file.
|
//TODO: validate extracted file.
|
||||||
}
|
}
|
||||||
|
|
||||||
const TestFullExtractLocation = "testing/TestExtract";
|
const TestFullExtractLocation = "testing/TestExtract";
|
||||||
|
|
||||||
test "ExtractCompleteArchive" {
|
test "ExtractCompleteArchive" {
|
||||||
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
const io = std.testing.io;
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
const alloc = std.testing.allocator;
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||||
defer sfs.deinit();
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
try sfs.extract(TestFullExtractLocation, .Default);
|
defer fil.close(io);
|
||||||
|
var sfs: Archive = try .init(alloc, io, fil);
|
||||||
|
defer sfs.deinit(io);
|
||||||
|
try sfs.extract(alloc, io, TestFullExtractLocation, .default);
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||||
|
|||||||
+13
-10
@@ -11,7 +11,7 @@ const DecompCache = @This();
|
|||||||
|
|
||||||
const Cache = struct {
|
const Cache = struct {
|
||||||
cache: []u8,
|
cache: []u8,
|
||||||
usage: Atomic(u32) = .init(0),
|
usage: Atomic(u32),
|
||||||
};
|
};
|
||||||
|
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
@@ -40,11 +40,11 @@ pub fn init(alloc: std.mem.Allocator, map: Io.File.MemoryMap, decomp_type: Decom
|
|||||||
}
|
}
|
||||||
pub fn deinit(self: *DecompCache, io: Io) void {
|
pub fn deinit(self: *DecompCache, io: Io) void {
|
||||||
self.mut.lockUncancelable(io);
|
self.mut.lockUncancelable(io);
|
||||||
self.cache.deinit();
|
self.cache.deinit(self.arena.child_allocator);
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn makeRoom(self: *DecompCache, size: u32) !void {
|
fn makeRoom(self: *DecompCache, io: Io, size: u32) !void {
|
||||||
if (size + self.cur_size < self.max_size) return;
|
if (size + self.cur_size < self.max_size) return;
|
||||||
var iter = self.cache.iterator();
|
var iter = self.cache.iterator();
|
||||||
while (iter.next()) |ent| {
|
while (iter.next()) |ent| {
|
||||||
@@ -55,8 +55,8 @@ fn makeRoom(self: *DecompCache, size: u32) !void {
|
|||||||
}
|
}
|
||||||
if (size + self.cur_size < self.max_size) return;
|
if (size + self.cur_size < self.max_size) return;
|
||||||
}
|
}
|
||||||
self.cond.wait(Io, &self.mut.mutex);
|
try self.cond.wait(io, &self.mut.mutex);
|
||||||
try self.makeRoom(size);
|
return self.makeRoom(io, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkinBlock(self: *DecompCache, io: Io, offset: u64) void {
|
pub fn checkinBlock(self: *DecompCache, io: Io, offset: u64) void {
|
||||||
@@ -65,7 +65,7 @@ pub fn checkinBlock(self: *DecompCache, io: Io, offset: u64) void {
|
|||||||
|
|
||||||
const get = self.cache.getPtr(offset);
|
const get = self.cache.getPtr(offset);
|
||||||
if (get == null) return;
|
if (get == null) return;
|
||||||
const res = get.?.usage.fetchSub(1, .unordered);
|
const res = get.?.usage.fetchSub(1, .acq_rel);
|
||||||
if (res == 0) self.cond.broadcast(io);
|
if (res == 0) self.cond.broadcast(io);
|
||||||
}
|
}
|
||||||
pub fn checkoutBlock(self: *DecompCache, io: Io, offset: u64, block_size: u32, max_result_size: u32) ![]u8 {
|
pub fn checkoutBlock(self: *DecompCache, io: Io, offset: u64, block_size: u32, max_result_size: u32) ![]u8 {
|
||||||
@@ -75,14 +75,14 @@ pub fn checkoutBlock(self: *DecompCache, io: Io, offset: u64, block_size: u32, m
|
|||||||
|
|
||||||
const get = self.cache.getPtr(offset);
|
const get = self.cache.getPtr(offset);
|
||||||
if (get != null) {
|
if (get != null) {
|
||||||
_ = get.?.usage.fetchAdd(1, .unordered);
|
_ = get.?.usage.fetchAdd(1, .acq_rel);
|
||||||
return get.?.cache;
|
return get.?.cache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try self.mut.lock(io);
|
try self.mut.lock(io);
|
||||||
defer self.mut.unlock(io);
|
defer self.mut.unlock(io);
|
||||||
|
|
||||||
try self.makeRoom(max_result_size);
|
try self.makeRoom(io, max_result_size);
|
||||||
|
|
||||||
var alloc = self.arena.allocator();
|
var alloc = self.arena.allocator();
|
||||||
const buf_alloc = self.arena.child_allocator;
|
const buf_alloc = self.arena.child_allocator;
|
||||||
@@ -90,7 +90,7 @@ pub fn checkoutBlock(self: *DecompCache, io: Io, offset: u64, block_size: u32, m
|
|||||||
var out = try alloc.alloc(u8, max_result_size);
|
var out = try alloc.alloc(u8, max_result_size);
|
||||||
errdefer alloc.free(out);
|
errdefer alloc.free(out);
|
||||||
|
|
||||||
const out_size = try self.decomp(buf_alloc, self.map[offset..][0..block_size], out);
|
const out_size = try self.decomp(buf_alloc, self.map.memory[offset..][0..block_size], out);
|
||||||
if (out_size != max_result_size) {
|
if (out_size != max_result_size) {
|
||||||
if (alloc.resize(out, out_size)) {
|
if (alloc.resize(out, out_size)) {
|
||||||
out.len = out_size;
|
out.len = out_size;
|
||||||
@@ -102,7 +102,10 @@ pub fn checkoutBlock(self: *DecompCache, io: Io, offset: u64, block_size: u32, m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.cache.put(alloc, offset, out);
|
try self.cache.put(buf_alloc, offset, .{
|
||||||
|
.cache = out,
|
||||||
|
.usage = .init(1),
|
||||||
|
});
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-8
@@ -1,3 +1,5 @@
|
|||||||
|
//! A cache for decompressed blocks. Used for Metadata & fragments.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
const Reader = Io.Reader;
|
const Reader = Io.Reader;
|
||||||
@@ -31,7 +33,7 @@ interface: Reader = .{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
pub fn init(io: Io, cache: *DecompCache, offset: u64) void {
|
pub fn init(io: Io, cache: *DecompCache, offset: u64) MetadataReader {
|
||||||
return .{
|
return .{
|
||||||
.io = io,
|
.io = io,
|
||||||
|
|
||||||
@@ -41,18 +43,18 @@ pub fn init(io: Io, cache: *DecompCache, offset: u64) void {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *MetadataReader) void {
|
pub fn deinit(self: *MetadataReader) void {
|
||||||
if (self.cur_block_offset != 0)
|
if (self.cur_offset != 0)
|
||||||
self.cache.checkinBlock(self.io, self.cur_block_offset);
|
self.cache.checkinBlock(self.io, self.cur_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance(self: *MetadataReader) !void {
|
fn advance(self: *MetadataReader) !void {
|
||||||
if (self.interface.buffer.len > 0)
|
if (self.interface.buffer.len > 0)
|
||||||
self.cache.checkinBlock(self.io, self.cur_offset);
|
self.cache.checkinBlock(self.io, self.cur_offset);
|
||||||
const hdr = std.mem.readInt(BlockHeader, self.cache.map[self.next_offset..][0..2], .little);
|
const hdr: BlockHeader = @bitCast(std.mem.readInt(u16, self.cache.map.memory[self.next_offset..][0..2], .little));
|
||||||
self.cur_offset = self.next_offset + 2;
|
self.cur_offset = self.next_offset + 2;
|
||||||
self.next_offset += hdr.size;
|
self.next_offset += hdr.size;
|
||||||
if (hdr.uncompressed) {
|
if (hdr.uncompressed) {
|
||||||
self.interface.buffer = self.cache.map[self.cur_offset..][0..hdr.size];
|
self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..hdr.size];
|
||||||
self.interface.end = hdr.size;
|
self.interface.end = hdr.size;
|
||||||
self.interface.seek = 0;
|
self.interface.seek = 0;
|
||||||
return;
|
return;
|
||||||
@@ -62,6 +64,40 @@ fn advance(self: *MetadataReader) !void {
|
|||||||
self.interface.seek = 0;
|
self.interface.seek = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {}
|
fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
|
||||||
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {}
|
if (r.seek == r.end) {
|
||||||
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {}
|
var self: *MetadataReader = @fieldParentPtr("interface", r);
|
||||||
|
self.advance() catch return Reader.Error.ReadFailed;
|
||||||
|
}
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
const to_write = @min(r.end - r.seek, @intFromEnum(limit));
|
||||||
|
const wrote = try w.write(r.buffer[r.seek..][0..to_write]);
|
||||||
|
r.seek += wrote;
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
|
||||||
|
if (r.seek == r.end) {
|
||||||
|
var self: *MetadataReader = @fieldParentPtr("interface", r);
|
||||||
|
self.advance() catch return Reader.Error.ReadFailed;
|
||||||
|
}
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
const to_skip = @min(r.end - r.seek, @intFromEnum(limit));
|
||||||
|
r.seek += to_skip;
|
||||||
|
return to_skip;
|
||||||
|
}
|
||||||
|
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||||
|
if (r.seek == r.end) {
|
||||||
|
var self: *MetadataReader = @fieldParentPtr("interface", r);
|
||||||
|
self.advance() catch return Reader.Error.ReadFailed;
|
||||||
|
}
|
||||||
|
if (vec.len == 0) return 0;
|
||||||
|
var total_copied: usize = 0;
|
||||||
|
for (vec) |v| {
|
||||||
|
const to_cpy = @min(r.end - r.seek, v.len);
|
||||||
|
@memcpy(v[0..to_cpy], r.buffer[r.seek..][0..to_cpy]);
|
||||||
|
r.seek += to_cpy;
|
||||||
|
total_copied += to_cpy;
|
||||||
|
if (r.seek == r.end) break;
|
||||||
|
}
|
||||||
|
return total_copied;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user