Fleshing out more of the library.
Mainly adding functions to File
This commit is contained in:
+23
-12
@@ -42,25 +42,20 @@ pub fn init(fil: std.fs.File, offset: u64) !Archive {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
_ = path;
|
||||
_ = options;
|
||||
return error.TODO;
|
||||
}
|
||||
|
||||
pub fn root(self: Archive, alloc: std.mem.Allocator) !File {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.file = self.file,
|
||||
.super = self.super.toMinimal(),
|
||||
.decomp = self.stateless_decomp.statelessCopy(alloc),
|
||||
|
||||
.inode = try Utils.refToInode(
|
||||
.inode = try Utils.readInode(
|
||||
alloc,
|
||||
&self.stateless_decomp,
|
||||
self.file,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
self.super.root_ref,
|
||||
self.super.root_ref.block_start,
|
||||
self.super.root_ref.block_offset,
|
||||
),
|
||||
.name = "",
|
||||
};
|
||||
@@ -80,7 +75,23 @@ pub fn id(self: Archive, idx: u32) !u16 {
|
||||
}
|
||||
pub fn inode(self: Archive, alloc: std.mem.Allocator, inode_num: u32) !Inode {
|
||||
const ref = try LookupTable.stateless(Inode.Ref, self.file, &self.stateless_decomp, self.super.export_start, inode_num - 1);
|
||||
return Utils.refToInode(alloc, &self.stateless_decomp, self.file, self.super.inode_start, self.super.block_size, ref);
|
||||
return Utils.readInode(
|
||||
alloc,
|
||||
&self.stateless_decomp,
|
||||
self.file,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
ref.block_start,
|
||||
ref.block_offset,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
_ = path;
|
||||
_ = options;
|
||||
return error.TODO;
|
||||
}
|
||||
|
||||
// Superblock
|
||||
|
||||
@@ -17,6 +17,11 @@ vtable: *const struct {
|
||||
stateless: StatelessDecomp,
|
||||
},
|
||||
|
||||
/// Create a copy of the decompressor using it's stateless function and the new allocator.
|
||||
pub fn statelessCopy(self: Decompressor, alloc: std.mem.Allocator) Decompressor {
|
||||
return &.{ .alloc = alloc, .vtable = &.{ .stateless = self.vtable.stateless } };
|
||||
}
|
||||
|
||||
pub fn decompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
|
||||
return self.vtable.decompress(self, in, out);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ pub const Entry = struct {
|
||||
num: u32,
|
||||
inode_type: InodeType,
|
||||
name: []u8,
|
||||
|
||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||
alloc.free(self.name);
|
||||
}
|
||||
};
|
||||
|
||||
// extern instead of packed due to alignment issues (packed will read it as 16 bytes instead of 12).
|
||||
|
||||
+82
-27
@@ -3,8 +3,12 @@ const std = @import("std");
|
||||
const Archive = @import("archive.zig");
|
||||
const Decompressor = @import("decomp.zig");
|
||||
const Directory = @import("directory.zig");
|
||||
const ExtractionOptions = @import("options.zig");
|
||||
const Inode = @import("inode.zig");
|
||||
const DataReader = @import("util/data_reader.zig");
|
||||
const FileIter = @import("util/iter.zig");
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const OffsetFile = @import("util/offset_file.zig");
|
||||
const Utils = @import("util/utils.zig");
|
||||
|
||||
pub const Error = error{
|
||||
@@ -14,9 +18,8 @@ pub const Error = error{
|
||||
|
||||
const File = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
superblock: Archive.MinimalSuperblock,
|
||||
file: OffsetFile,
|
||||
super: Archive.MinimalSuperblock,
|
||||
decomp: Decompressor,
|
||||
|
||||
name: []const u8,
|
||||
@@ -26,44 +29,96 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, entry: Directory.Entry)
|
||||
const new_name = try alloc.alloc(u8, entry.name.len);
|
||||
errdefer alloc.free(new_name);
|
||||
@memcpy(new_name, entry.name);
|
||||
var rdr = archive.file.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(&rdr.interface, &archive.stateless_decomp);
|
||||
try meta.interface.discardAll(entry.block_offset);
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.superblock = archive.super,
|
||||
.file = archive.file,
|
||||
.super = archive.super,
|
||||
.decomp = .{
|
||||
.alloc = alloc,
|
||||
.vtable = &.{ .stateless = archive.stateless_decomp.vtable.stateless },
|
||||
},
|
||||
.name = new_name,
|
||||
.inode = try .read(alloc, &meta.interface, archive.super.block_size),
|
||||
.inode = try Utils.readInode(
|
||||
alloc,
|
||||
&archive.decomp,
|
||||
archive.file,
|
||||
archive.super.inode_start,
|
||||
archive.super.block_size,
|
||||
entry.block_start,
|
||||
entry.block_offset,
|
||||
),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: File) void {
|
||||
self.alloc.free(self.name);
|
||||
self.decomp.alloc.free(self.name);
|
||||
self.inode.deinit(self.decomp.alloc);
|
||||
}
|
||||
|
||||
/// Opens a sub-directory. If the given path is "", ".", "/", or "./", a copy of the File is returned.
|
||||
pub fn open(self: File, alloc: std.mem.Allocator, path: []const u8) !File {
|
||||
// Directory functions
|
||||
|
||||
pub fn isDir(self: File) bool {
|
||||
return switch (self.inode.hdr.inode_type) {
|
||||
.dir, .ext_dir => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
/// Opens a sub-file. If the given path is "" or "." (after trimming /) a copy of the File is returned.
|
||||
pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
|
||||
switch (self.inode.hdr.inode_type) {
|
||||
.dir, .ext_dir => {},
|
||||
.dir, .ext_dir => {
|
||||
var res = try self.inode.findInode(
|
||||
alloc,
|
||||
&self.decomp,
|
||||
self.file,
|
||||
self.super.dir_start,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
filepath,
|
||||
);
|
||||
if (res.name.len == 0) {
|
||||
res.name = try alloc.alloc(u8, self.name.len);
|
||||
@memcpy(res.name, self.name);
|
||||
}
|
||||
return .{
|
||||
.file = self.file,
|
||||
.super = self.super,
|
||||
.decomp = .{
|
||||
.alloc = alloc,
|
||||
.vtable = &.{ .stateless = self.decomp.vtable.stateless },
|
||||
},
|
||||
.name = res.name,
|
||||
.inode = res.inode,
|
||||
};
|
||||
},
|
||||
else => Error.NotDirectory,
|
||||
}
|
||||
if (Utils.pathIsSelf(path)) {
|
||||
const new_name = try alloc.alloc(u8, self.name.len);
|
||||
@memcpy(new_name, self.name);
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.superblock = self.superblock,
|
||||
.decomp = .{
|
||||
.alloc = alloc,
|
||||
.vtable = &.{ .stateless = self.decomp.vtable.stateless },
|
||||
},
|
||||
.name = new_name,
|
||||
.inode = self.inode,
|
||||
};
|
||||
}
|
||||
}
|
||||
pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.entries = try self.inode.readDirectory(alloc, &self.decomp, self.file, self.super.dir_start),
|
||||
};
|
||||
}
|
||||
|
||||
// Regular file functions
|
||||
|
||||
pub fn isRegularFile(self: File) bool {
|
||||
return switch (self.inode.hdr.inode_type) {
|
||||
.file, .ext_file => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
pub fn dataReader(self: File, alloc: std.mem.Allocator) !DataReader {
|
||||
if (!self.isRegularFile()) return Error.NotRegularFile;
|
||||
_ = alloc;
|
||||
return error.TODO;
|
||||
}
|
||||
|
||||
// Universal functions
|
||||
|
||||
pub fn extract(self: File, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
_ = path;
|
||||
_ = options;
|
||||
return error.TODO;
|
||||
}
|
||||
|
||||
+198
@@ -1,10 +1,19 @@
|
||||
//! This is the raw squashfs representation of a file/directory.
|
||||
//! Most of the time using File is a better experience and using Inodes directory
|
||||
//! is only required for more technical use cases.
|
||||
|
||||
const std = @import("std");
|
||||
const Reader = std.Io.Reader;
|
||||
|
||||
const Decompressor = @import("decomp.zig");
|
||||
const Directory = @import("directory.zig");
|
||||
const Dir = @import("inode/dir.zig");
|
||||
const File = @import("inode/file.zig");
|
||||
const Misc = @import("inode/misc.zig");
|
||||
const Sym = @import("inode/sym.zig");
|
||||
const MinimalSuperblock = @import("archive.zig").MinimalSuperblock;
|
||||
const MetadataReader = @import("util/metadata.zig");
|
||||
const OffsetFile = @import("util/offset_file.zig");
|
||||
|
||||
const Inode = @This();
|
||||
|
||||
@@ -43,6 +52,75 @@ pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
|
||||
switch (self.data) {
|
||||
.dir,
|
||||
.ext_dir,
|
||||
.block,
|
||||
.ext_block,
|
||||
.char,
|
||||
.ext_char,
|
||||
.fifo,
|
||||
.ext_fifo,
|
||||
.sock,
|
||||
.ext_sock,
|
||||
=> return self,
|
||||
.file => |f| {
|
||||
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
|
||||
@memcpy(new_sizes, f.block_sizes);
|
||||
return .{
|
||||
.hdr = self.hdr,
|
||||
.data = .{ .file = .{
|
||||
.block_start = f.block_start,
|
||||
.frag_idx = f.frag_idx,
|
||||
.block_offset = f.block_offset,
|
||||
.size = f.size,
|
||||
.block_sizes = new_sizes,
|
||||
} },
|
||||
};
|
||||
},
|
||||
.ext_file => |f| {
|
||||
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
|
||||
@memcpy(new_sizes, f.block_sizes);
|
||||
return .{
|
||||
.hdr = self.hdr,
|
||||
.data = .{ .ext_file = .{
|
||||
.block_start = self.block_start,
|
||||
.size = self.size,
|
||||
.sparse = self.sparse,
|
||||
.hard_links = self.hard_links,
|
||||
.frag_idx = self.frag_idx,
|
||||
.block_offset = self.block_offset,
|
||||
.xattr_idx = self.xattr_idx,
|
||||
.block_sizes = new_sizes,
|
||||
} },
|
||||
};
|
||||
},
|
||||
.symlink => |s| {
|
||||
const new_target = try alloc.alloc(u8, s.target.len);
|
||||
@memcpy(new_target, s.target);
|
||||
return .{
|
||||
.hdr = self.hdr,
|
||||
.data = .{ .symlink = .{
|
||||
.hard_links = s.hard_links,
|
||||
.target = new_target,
|
||||
} },
|
||||
};
|
||||
},
|
||||
.ext_symlink => |s| {
|
||||
const new_target = try alloc.alloc(u8, s.target.len);
|
||||
@memcpy(new_target, s.target);
|
||||
return .{
|
||||
.hdr = self.hdr,
|
||||
.data = .{ .ext_symlink = .{
|
||||
.hard_links = s.hard_links,
|
||||
.xattr_idx = s.xattr_idx,
|
||||
.target = new_target,
|
||||
} },
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Types
|
||||
|
||||
@@ -94,3 +172,123 @@ pub const Data = union(Type) {
|
||||
ext_fifo: Misc.ExtIpc,
|
||||
ext_sock: Misc.ExtIpc,
|
||||
};
|
||||
|
||||
// Errors
|
||||
|
||||
pub const Error = error{
|
||||
NotDirectory,
|
||||
NotFound,
|
||||
NotRegularFile,
|
||||
};
|
||||
|
||||
// Utils functions
|
||||
|
||||
/// For directory inodes, tries to find the inode at the given path. Returns both the inode, and it's file name. If the path is empty or "." then a copy of this inode is returned with no name ("").
|
||||
pub fn findInode(
|
||||
inode: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
decomp: *const Decompressor,
|
||||
fil: OffsetFile,
|
||||
dir_start: u64,
|
||||
inode_start: u64,
|
||||
block_size: u32,
|
||||
filepath: []const u8,
|
||||
) !struct { inode: Inode, name: []const u8 } {
|
||||
switch (inode.data) {
|
||||
.dir => |d| {
|
||||
const path: []const u8 = std.mem.trim(u8, filepath, "/");
|
||||
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
|
||||
return .{ .inode = inode.copy(alloc), .name = "" };
|
||||
return findInodeRaw(
|
||||
alloc,
|
||||
decomp,
|
||||
fil,
|
||||
dir_start,
|
||||
inode_start,
|
||||
block_size,
|
||||
path,
|
||||
d,
|
||||
);
|
||||
},
|
||||
.ext_dir => |d| {
|
||||
const path: []const u8 = std.mem.trim(u8, filepath, "/");
|
||||
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
|
||||
return .{ .inode = inode.copy(alloc), .name = "" };
|
||||
return findInodeRaw(
|
||||
alloc,
|
||||
decomp,
|
||||
fil,
|
||||
dir_start,
|
||||
inode_start,
|
||||
block_size,
|
||||
path,
|
||||
d,
|
||||
);
|
||||
},
|
||||
else => return Error.NotDirectory,
|
||||
}
|
||||
}
|
||||
inline fn findInodeRaw(
|
||||
inode: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
decomp: *const Decompressor,
|
||||
fil: OffsetFile,
|
||||
dir_start: u64,
|
||||
inode_start: u64,
|
||||
block_size: u32,
|
||||
path: []const u8,
|
||||
dat: anytype,
|
||||
) !struct { inode: Inode, name: []const u8 } {
|
||||
const first_element: []const u8 = std.mem.sliceTo(path, '/');
|
||||
|
||||
const dirs = try readDirRaw(alloc, decomp, fil, dir_start, dat);
|
||||
defer {
|
||||
for (dirs) |dir|
|
||||
dir.deinit(alloc);
|
||||
alloc.free(dirs);
|
||||
}
|
||||
|
||||
// Directories are stored ASCIIbetically, so we can use binary search.
|
||||
var cur_slice = dirs;
|
||||
var idx: usize = 0;
|
||||
while (cur_slice.len > 0) {
|
||||
idx = cur_slice.len / 2;
|
||||
const mid_name = cur_slice[idx].name;
|
||||
switch (std.mem.order(u8, first_element, mid_name)) {
|
||||
.gt => {
|
||||
cur_slice = cur_slice[idx + 1 ..];
|
||||
continue;
|
||||
},
|
||||
.lt => {
|
||||
cur_slice = cur_slice[0..idx];
|
||||
continue;
|
||||
},
|
||||
.eq => break,
|
||||
}
|
||||
} else return Error.NotFound;
|
||||
const entry = cur_slice[idx];
|
||||
var rdr = try fil.readerAt(inode_start + entry.block_start, &[0]u8{});
|
||||
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
|
||||
try meta_rdr.interface.discardAll(entry.block_offset);
|
||||
const ret_inode: Inode = try .read(alloc, &meta_rdr.interface, block_size);
|
||||
if (first_element.len == path.len) {
|
||||
const name_copy = try alloc.alloc(u8, entry.name.len);
|
||||
@memcpy(name_copy, entry.name.len);
|
||||
return .{ .inode = ret_inode, .name = name_copy };
|
||||
}
|
||||
return inode.findInode(alloc, decomp, fil, dir_start, inode_start, block_size, path[first_element.len..]);
|
||||
}
|
||||
|
||||
pub fn readDirectory(inode: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64) ![]Directory.Entry {
|
||||
return switch (inode.data) {
|
||||
.dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
|
||||
.ext_dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
|
||||
else => Error.NotDirectory,
|
||||
};
|
||||
}
|
||||
inline fn readDirRaw(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64, dat: anytype) ![]Directory.Entry {
|
||||
var rdr = try fil.readerAt(dir_start + dat.block_start, &[0]u8{});
|
||||
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
|
||||
try meta_rdr.interface.discardAll(dat.block_offset);
|
||||
return Directory.readDirectory(alloc, meta_rdr, dat.size);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
const std = @import("std");
|
||||
|
||||
const MinimalSuperblock = @import("../archive.zig").MinimalSuperblock;
|
||||
const Decompressor = @import("../decomp.zig");
|
||||
const DirEntry = @import("../directory.zig").Entry;
|
||||
const File = @import("../file.zig");
|
||||
const Inode = @import("../inode.zig");
|
||||
const MetadataReader = @import("metadata.zig");
|
||||
const OffsetFile = @import("offset_file.zig");
|
||||
const Utils = @import("utils.zig");
|
||||
|
||||
const Iter = @This();
|
||||
|
||||
file: OffsetFile,
|
||||
super: MinimalSuperblock,
|
||||
decomp: Decompressor,
|
||||
|
||||
entries: []DirEntry,
|
||||
idx: usize = 0,
|
||||
|
||||
pub fn deinit(self: Iter) void {
|
||||
for (self.entries) |ent|
|
||||
ent.deinit(self.decomp.alloc);
|
||||
self.decomp.alloc.free(self.entries);
|
||||
}
|
||||
|
||||
pub fn next(self: *Iter) !?File {
|
||||
if (self.idx >= self.entries.len) return null;
|
||||
defer self.idx += 1;
|
||||
|
||||
const entry = self.entries[self.idx];
|
||||
|
||||
const new_name = try self.decomp.alloc.alloc(u8, entry.name.len);
|
||||
@memcpy(new_name, entry.name);
|
||||
return .{
|
||||
.file = self.file,
|
||||
.super = self.super,
|
||||
.decomp = self.decomp,
|
||||
|
||||
.name = new_name,
|
||||
.inode = Utils.readInode(
|
||||
self.decomp.alloc,
|
||||
&self.decomp,
|
||||
self.file,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
entry.block_start,
|
||||
entry.block_offset,
|
||||
),
|
||||
};
|
||||
}
|
||||
pub fn reset(self: *Iter) void {
|
||||
self.idx = 0;
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Decompressor = @import("../decomp.zig");
|
||||
const DirEntry = @import("../directory.zig").Entry;
|
||||
const Inode = @import("../inode.zig");
|
||||
const MetadataReader = @import("metadata.zig");
|
||||
const OffsetFile = @import("offset_file.zig");
|
||||
@@ -16,9 +17,9 @@ pub fn pathIsSelf(path: []const u8) bool {
|
||||
return std.mem.eql(u8, path, "./");
|
||||
}
|
||||
|
||||
pub fn refToInode(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, inode_start: u64, block_size: u32, ref: Inode.Ref) !Inode {
|
||||
var rdr = try fil.readerAt(inode_start + ref.block_start, &[0]u8{});
|
||||
pub fn readInode(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, inode_start: u64, block_size: u32, block_start: u32, block_offset: u16) !Inode {
|
||||
var rdr = try fil.readerAt(inode_start + block_start, &[0]u8{});
|
||||
var meta: MetadataReader = .init(&rdr.interface, decomp);
|
||||
try meta.interface.discardAll(ref.block_offset);
|
||||
try meta.interface.discardAll(block_offset);
|
||||
return .read(alloc, &meta.interface, block_size);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user