@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const io = std.io;
|
|
||||||
|
|
||||||
pub const InodeRef = packed struct {
|
|
||||||
offset: u16,
|
|
||||||
block_start: u32,
|
|
||||||
_: u16 = 0,
|
|
||||||
|
|
||||||
pub fn init(block_start: u32, offset: u16) InodeRef {
|
|
||||||
return .{
|
|
||||||
.offset = offset,
|
|
||||||
.block_start = block_start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const InodeType = enum(u16) {
|
|
||||||
dir = 1,
|
|
||||||
file,
|
|
||||||
sym,
|
|
||||||
block,
|
|
||||||
char,
|
|
||||||
fifo,
|
|
||||||
sock,
|
|
||||||
ext_dir,
|
|
||||||
ext_file,
|
|
||||||
ext_sym,
|
|
||||||
ext_block,
|
|
||||||
ext_char,
|
|
||||||
ext_fifo,
|
|
||||||
ext_sock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dir = @import("dir.zig");
|
|
||||||
const file = @import("file.zig");
|
|
||||||
const sym = @import("sym.zig");
|
|
||||||
const misc = @import("misc.zig");
|
|
||||||
|
|
||||||
pub const InodeData = union(enum) {
|
|
||||||
dir: dir.DirInode,
|
|
||||||
file: file.FileInode,
|
|
||||||
sym: sym.SymInode,
|
|
||||||
block: misc.DeviceInode,
|
|
||||||
char: misc.DeviceInode,
|
|
||||||
fifo: misc.IPCInode,
|
|
||||||
sock: misc.IPCInode,
|
|
||||||
ext_dir: dir.ExtDirInode,
|
|
||||||
ext_file: file.ExtFileInode,
|
|
||||||
ext_sym: sym.ExtSymInode,
|
|
||||||
ext_block: misc.ExtDeviceInode,
|
|
||||||
ext_char: misc.ExtDeviceInode,
|
|
||||||
ext_fifo: misc.ExtIPCInode,
|
|
||||||
ext_sock: misc.ExtIPCInode,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const InodeHeader = packed struct {
|
|
||||||
inode_type: InodeType,
|
|
||||||
perm: u16,
|
|
||||||
uid_idx: u16,
|
|
||||||
gid_idx: u16,
|
|
||||||
mod_time: u32,
|
|
||||||
num: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Inode = struct {
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
header: InodeHeader,
|
|
||||||
data: InodeData,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: io.AnyReader, block_size: u32) !Inode {
|
|
||||||
const hdr = try rdr.readStruct(InodeHeader);
|
|
||||||
const data: InodeData = switch (hdr.inode_type) {
|
|
||||||
.dir => .{ .dir = try .init(rdr) },
|
|
||||||
.file => .{ .file = try .init(alloc, rdr, block_size) },
|
|
||||||
.sym => .{ .sym = try .init(alloc, rdr) },
|
|
||||||
.block => .{ .block = try .init(rdr) },
|
|
||||||
.char => .{ .char = try .init(rdr) },
|
|
||||||
.fifo => .{ .fifo = try .init(rdr) },
|
|
||||||
.sock => .{ .sock = try .init(rdr) },
|
|
||||||
.ext_dir => .{ .ext_dir = try .init(rdr) },
|
|
||||||
.ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) },
|
|
||||||
.ext_sym => .{ .ext_sym = try .init(alloc, rdr) },
|
|
||||||
.ext_block => .{ .ext_block = try .init(rdr) },
|
|
||||||
.ext_char => .{ .ext_char = try .init(rdr) },
|
|
||||||
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
|
|
||||||
.ext_sock => .{ .ext_sock = try .init(rdr) },
|
|
||||||
};
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.header = hdr,
|
|
||||||
.data = data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Inode) void {
|
|
||||||
switch (self.data) {
|
|
||||||
.file => |d| d.deinit(self.alloc),
|
|
||||||
.sym => |d| d.deinit(self.alloc),
|
|
||||||
.ext_file => |d| d.deinit(self.alloc),
|
|
||||||
.ext_sym => |d| d.deinit(self.alloc),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+67
-18
@@ -1,38 +1,87 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const io = std.io;
|
|
||||||
|
|
||||||
pub const DeviceInode = packed struct {
|
pub const Symlink = struct {
|
||||||
hard_links: u32,
|
hard_link: u32,
|
||||||
device: u32,
|
// size: u32,
|
||||||
|
target: []const u8,
|
||||||
|
|
||||||
pub fn init(rdr: io.AnyReader) !DeviceInode {
|
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !Symlink {
|
||||||
return rdr.readStruct(DeviceInode);
|
var fixed: [8]u8 = undefined;
|
||||||
|
_ = try rdr.read(&fixed);
|
||||||
|
const size = std.mem.readInt(u32, fixed[4..8], .little);
|
||||||
|
const target = 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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const fs = std.fs;
|
|
||||||
const io = std.io;
|
|
||||||
|
|
||||||
const File = @import("../file.zig").File;
|
|
||||||
const Reader = @import("../reader.zig").Reader;
|
|
||||||
const BlockSize = @import("../inode/file.zig").BlockSize;
|
|
||||||
const DecompressionType = @import("../decompress.zig").DecompressType;
|
|
||||||
const FileHolder = @import("../readers/file_holder.zig").FileHolder;
|
|
||||||
const FileOffsetWriter = @import("../readers/file_holder.zig").FileOffsetWriter;
|
|
||||||
const DataReader = @import("data_reader.zig").DataReader;
|
|
||||||
const Config = @import("../file.zig").Config;
|
|
||||||
|
|
||||||
/// A specialized File data reader that's meant to write all of it's data at once.
|
|
||||||
/// Can be re-used freely until deinit() is called.
|
|
||||||
pub const DataExtractor = struct {
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
decomp: DecompressionType,
|
|
||||||
holder: *FileHolder,
|
|
||||||
block_size: u32,
|
|
||||||
file_size: u64,
|
|
||||||
sizes: []BlockSize,
|
|
||||||
block_offset: []u64,
|
|
||||||
frag_data: ?[]u8 = null,
|
|
||||||
|
|
||||||
pub fn init(fil: *File, reader: *Reader) !DataExtractor {
|
|
||||||
var data_start: u64 = 0;
|
|
||||||
var sizes: []BlockSize = undefined;
|
|
||||||
var file_size: u64 = 0;
|
|
||||||
var frag_idx: u32 = 0;
|
|
||||||
var frag_offset: u32 = 0;
|
|
||||||
switch (fil.inode.data) {
|
|
||||||
.file => |f| {
|
|
||||||
data_start = f.data_start;
|
|
||||||
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
|
|
||||||
@memcpy(sizes, f.blocks);
|
|
||||||
file_size = f.size;
|
|
||||||
frag_idx = f.frag_idx;
|
|
||||||
frag_offset = f.frag_offset;
|
|
||||||
},
|
|
||||||
.ext_file => |f| {
|
|
||||||
data_start = f.data_start;
|
|
||||||
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
|
|
||||||
@memcpy(sizes, f.blocks);
|
|
||||||
file_size = f.size;
|
|
||||||
frag_idx = f.frag_idx;
|
|
||||||
frag_offset = f.frag_offset;
|
|
||||||
},
|
|
||||||
else => return File.FileError.NotNormalFile,
|
|
||||||
}
|
|
||||||
var out: DataExtractor = .{
|
|
||||||
.alloc = reader.alloc,
|
|
||||||
.decomp = reader.super.decomp,
|
|
||||||
.holder = &reader.holder,
|
|
||||||
.block_size = reader.super.block_size,
|
|
||||||
.file_size = file_size,
|
|
||||||
.sizes = sizes,
|
|
||||||
.block_offset = try reader.alloc.alloc(u64, sizes.len),
|
|
||||||
};
|
|
||||||
errdefer out.deinit();
|
|
||||||
var offset: u64 = data_start;
|
|
||||||
for (0.., out.block_offset) |i, _| {
|
|
||||||
out.block_offset[i] = offset;
|
|
||||||
offset += out.sizes[i].size;
|
|
||||||
}
|
|
||||||
if (frag_idx != 0xFFFFFFFF) {
|
|
||||||
const frag_ent = try reader.frag_table.getValue(reader, frag_idx);
|
|
||||||
out.frag_data = try frag_ent.getData(reader, frag_offset, @truncate(file_size % reader.super.block_size));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *DataExtractor) void {
|
|
||||||
self.alloc.free(self.sizes);
|
|
||||||
self.alloc.free(self.block_offset);
|
|
||||||
if (self.frag_data != null) self.alloc.free(self.frag_data.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn processBlockToFile(self: *DataExtractor, wg: *std.Thread.WaitGroup, errs: *MutexList, block_ind: usize, fil: *fs.File) void {
|
|
||||||
defer wg.finish();
|
|
||||||
if (self.sizes[block_ind].not_compressed) {
|
|
||||||
@branchHint(.unlikely);
|
|
||||||
if (self.sizes[block_ind].size == 0) {
|
|
||||||
if (block_ind == self.sizes.len - 1) {
|
|
||||||
fil.pwriteAll(&[1]u8{0}, self.file_size - 1) catch |err| {
|
|
||||||
std.debug.print("yo1\n", .{});
|
|
||||||
errs.append(err) catch {};
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
fil.pwriteAll(&[1]u8{0}, ((block_ind + 1) * self.block_size) - 1) catch |err| {
|
|
||||||
std.debug.print("yo2\n", .{});
|
|
||||||
errs.append(err) catch {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dat = self.alloc.alloc(u8, self.sizes[block_ind].size) catch |err| {
|
|
||||||
errs.append(err) catch {};
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(dat);
|
|
||||||
_ = self.holder.file.preadAll(dat, self.block_offset[block_ind]) catch |err| {
|
|
||||||
errs.append(err) catch {};
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
fil.pwriteAll(dat, block_ind * self.block_size) catch |err| {
|
|
||||||
errs.append(err) catch {};
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
@branchHint(.likely);
|
|
||||||
const offset_rdr = self.holder.readerAt(self.block_offset[block_ind]);
|
|
||||||
var fil_wrtr: FileOffsetWriter = .init(fil, block_ind * self.block_size);
|
|
||||||
var limit = std.io.limitedReader(offset_rdr, self.sizes[block_ind].size);
|
|
||||||
self.decomp.decompressTo(
|
|
||||||
self.alloc,
|
|
||||||
limit.reader().any(),
|
|
||||||
fil_wrtr.any(),
|
|
||||||
) catch |err| {
|
|
||||||
errs.append(err) catch {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fragmentToFile(self: *DataExtractor, wg: *std.Thread.WaitGroup, errs: *MutexList, fil: *fs.File) void {
|
|
||||||
defer wg.finish();
|
|
||||||
fil.pwriteAll(self.frag_data.?, self.block_size * self.sizes.len) catch |err| {
|
|
||||||
errs.append(err) catch {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the data completely to the given file.
|
|
||||||
/// Ignores the file's current offset and writes from the beginning of the file.
|
|
||||||
/// Returns the amount of bytes written.
|
|
||||||
///
|
|
||||||
/// Optimized for lower memory usage by using File.pwrite.
|
|
||||||
pub fn writeToFile(self: *DataExtractor, pool: *std.Thread.Pool, fil: *fs.File) !void {
|
|
||||||
var wg: std.Thread.WaitGroup = .{};
|
|
||||||
var errs: MutexList = .init(self.alloc);
|
|
||||||
defer errs.deinit();
|
|
||||||
for (0..self.sizes.len) |i| {
|
|
||||||
wg.start();
|
|
||||||
try pool.spawn(processBlockToFile, .{ self, &wg, &errs, i, fil });
|
|
||||||
}
|
|
||||||
if (self.frag_data != null) {
|
|
||||||
wg.start();
|
|
||||||
try pool.spawn(fragmentToFile, .{ self, &wg, &errs, fil });
|
|
||||||
}
|
|
||||||
wg.wait();
|
|
||||||
if (errs.list.items.len > 0) {
|
|
||||||
//TODO: better handle all the errors
|
|
||||||
return errs.list.items[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn processBlock(self: *DataExtractor, errs: std.ArrayList(anyerror), data_out: std.AutoHashMap([]u8), block_ind: u32) void {
|
|
||||||
// const offset_rdr = self.holder.readerAt(self.block_offset[block_ind]);
|
|
||||||
// const out = self.decomp.decompress(
|
|
||||||
// self.alloc,
|
|
||||||
// std.io.limitedReader(offset_rdr, self.sizes[block_ind].size),
|
|
||||||
// ) catch |err| {
|
|
||||||
// errs.append(err);
|
|
||||||
// return;
|
|
||||||
// };
|
|
||||||
// data_out.put(block_ind, )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Write the data completely to the given writer.
|
|
||||||
// Returns the amount of bytes written.
|
|
||||||
//
|
|
||||||
// To write data in order, some data may end up cached temporarily.
|
|
||||||
// pub fn writeToWriter(self: DataExtractor, pool: *std.Thread.Pool, writer: io.AnyWriter) !void {
|
|
||||||
// const wg: std.Thread.WaitGroup = .{};
|
|
||||||
// const errs: std.ArrayList(anyerror) = .init(self.alloc);
|
|
||||||
// const data: std.AutoHashMap(u32, []u8) = .init(self.alloc);
|
|
||||||
// const cond: std.Thread. = .{};
|
|
||||||
// defer errs.deinit();
|
|
||||||
// for (0..self.sizes.len) |i| {
|
|
||||||
// pool.spawnWg(&wg, processBlock, .{ &self, i, fil });
|
|
||||||
// }
|
|
||||||
// wg.wait();
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
|
|
||||||
const MutexList = struct {
|
|
||||||
list: std.ArrayList(anyerror),
|
|
||||||
mut: std.Thread.Mutex = .{},
|
|
||||||
|
|
||||||
fn init(alloc: std.mem.Allocator) MutexList {
|
|
||||||
return .{
|
|
||||||
.list = .init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn deinit(self: *MutexList) void {
|
|
||||||
self.list.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append(self: *MutexList, err: anyerror) !void {
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
try self.list.append(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const io = std.io;
|
|
||||||
const fs = std.fs;
|
|
||||||
|
|
||||||
const File = @import("../file.zig").File;
|
|
||||||
const Reader = @import("../reader.zig").Reader;
|
|
||||||
const BlockSize = @import("../inode/file.zig").BlockSize;
|
|
||||||
const DecompressionType = @import("../decompress.zig").DecompressType;
|
|
||||||
const FileOffsetReader = @import("../readers/file_holder.zig").FileOffsetReader;
|
|
||||||
const FragEntry = @import("../fragment.zig").FragEntry;
|
|
||||||
|
|
||||||
const DataReaderError = error{
|
|
||||||
EOF,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DataReader = struct {
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
decomp: DecompressionType,
|
|
||||||
rdr: FileOffsetReader,
|
|
||||||
block_size: u32,
|
|
||||||
file_size: u64,
|
|
||||||
sizes: []BlockSize,
|
|
||||||
frag_data: ?[]u8 = null,
|
|
||||||
|
|
||||||
next_block_num: u32 = 0,
|
|
||||||
cur_bloc: []u8 = &[0]u8{},
|
|
||||||
cur_offset: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(fil: *File, reader: *Reader) !DataReader {
|
|
||||||
var data_start: u64 = 0;
|
|
||||||
var sizes: []BlockSize = undefined;
|
|
||||||
var file_size: u64 = 0;
|
|
||||||
var frag_idx: u32 = 0;
|
|
||||||
var frag_offset: u32 = 0;
|
|
||||||
switch (fil.inode.data) {
|
|
||||||
.file => |f| {
|
|
||||||
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
|
|
||||||
@memcpy(sizes, f.blocks);
|
|
||||||
data_start = f.data_start;
|
|
||||||
file_size = f.size;
|
|
||||||
frag_idx = f.frag_idx;
|
|
||||||
frag_offset = f.frag_offset;
|
|
||||||
},
|
|
||||||
.ext_file => |f| {
|
|
||||||
sizes = try reader.alloc.alloc(BlockSize, f.blocks.len);
|
|
||||||
@memcpy(sizes, f.blocks);
|
|
||||||
data_start = f.data_start;
|
|
||||||
file_size = f.size;
|
|
||||||
frag_idx = f.frag_idx;
|
|
||||||
frag_offset = f.frag_offset;
|
|
||||||
},
|
|
||||||
else => return File.FileError.NotNormalFile,
|
|
||||||
}
|
|
||||||
var out: DataReader = .{
|
|
||||||
.alloc = reader.alloc,
|
|
||||||
.decomp = reader.super.decomp,
|
|
||||||
.rdr = reader.holder.readerAt(data_start),
|
|
||||||
.block_size = reader.super.block_size,
|
|
||||||
.file_size = file_size,
|
|
||||||
.sizes = sizes,
|
|
||||||
};
|
|
||||||
errdefer out.deinit();
|
|
||||||
if (frag_idx != 0xFFFFFFFF) {
|
|
||||||
const frag_ent = try reader.frag_table.getValue(reader, frag_idx);
|
|
||||||
out.frag_data = try frag_ent.getData(reader, frag_offset, @truncate(file_size % reader.super.block_size));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
pub fn fromFragEntry(reader: *Reader, ent: FragEntry) !DataReader {
|
|
||||||
const size = try reader.alloc.alloc(BlockSize, 1);
|
|
||||||
size[0] = ent.size;
|
|
||||||
return .{
|
|
||||||
.alloc = reader.alloc,
|
|
||||||
.decomp = reader.super.decomp,
|
|
||||||
.rdr = reader.holder.readerAt(ent.start),
|
|
||||||
.block_size = reader.super.block_size,
|
|
||||||
.sizes = size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *DataReader) void {
|
|
||||||
self.alloc.free(self.sizes);
|
|
||||||
if (self.cur_bloc.len > 0) self.alloc.free(self.cur_bloc);
|
|
||||||
if (self.frag_data != null) self.alloc.free(self.frag_data.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(self: *DataReader, offset: u32) !void {
|
|
||||||
var cur_skip: u32 = 0;
|
|
||||||
var to_skip: u32 = 0;
|
|
||||||
while (cur_skip < offset) {
|
|
||||||
if (self.cur_offset >= self.cur_bloc.len) try self.readNextBlock();
|
|
||||||
to_skip = @min(offset - cur_skip, self.cur_bloc.len - self.cur_offset);
|
|
||||||
cur_skip += to_skip;
|
|
||||||
self.cur_offset += to_skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readNextBlock(self: *DataReader) !void {
|
|
||||||
defer self.next_block_num += 1;
|
|
||||||
if (self.next_block_num == self.sizes.len and self.frag_data != null) {
|
|
||||||
try self.sizeBlock(self.frag_data.?.len);
|
|
||||||
@memcpy(self.cur_bloc, self.frag_data.?);
|
|
||||||
return;
|
|
||||||
} else if (self.next_block_num >= self.sizes.len) {
|
|
||||||
return DataReaderError.EOF;
|
|
||||||
}
|
|
||||||
const siz = self.sizes[self.next_block_num];
|
|
||||||
if (siz.size == 0) {
|
|
||||||
if (self.next_block_num == self.sizes.len) {
|
|
||||||
try self.sizeBlock(@truncate(self.file_size % self.block_size));
|
|
||||||
} else {
|
|
||||||
try self.sizeBlock(self.block_size);
|
|
||||||
}
|
|
||||||
@memset(self.cur_bloc, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (siz.not_compressed) {
|
|
||||||
try self.sizeBlock(siz.size);
|
|
||||||
_ = try self.rdr.any().readAll(self.cur_bloc);
|
|
||||||
} else {
|
|
||||||
self.alloc.free(self.cur_bloc);
|
|
||||||
var limit = std.io.limitedReader(self.rdr, siz.size);
|
|
||||||
var dat = try self.decomp.decompress(self.alloc, limit.reader().any());
|
|
||||||
self.cur_bloc = try dat.toOwnedSlice();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sizeBlock(self: *DataReader, size: usize) !void {
|
|
||||||
if (!self.alloc.resize(self.cur_bloc, size)) {
|
|
||||||
self.alloc.free(self.cur_bloc);
|
|
||||||
self.cur_bloc = try self.alloc.alloc(u8, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *DataReader, bytes: []u8) !usize {
|
|
||||||
var cur_read: usize = 0;
|
|
||||||
var to_read: usize = 0;
|
|
||||||
while (cur_read < bytes.len) {
|
|
||||||
if (self.cur_offset >= self.cur_bloc.len) {
|
|
||||||
self.readNextBlock() catch |err| {
|
|
||||||
if (err == DataReaderError.EOF) return cur_read;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
to_read = @min(bytes.len - cur_read, self.cur_bloc.len - self.cur_offset);
|
|
||||||
@memcpy(bytes[cur_read .. cur_read + to_read], self.cur_bloc[self.cur_offset .. @as(usize, self.cur_offset) + to_read]);
|
|
||||||
self.cur_offset += @truncate(to_read);
|
|
||||||
cur_read += to_read;
|
|
||||||
}
|
|
||||||
return cur_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any(self: *DataReader) io.AnyReader {
|
|
||||||
return .{
|
|
||||||
.context = @ptrCast(self),
|
|
||||||
.readFn = readOpaque,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readOpaque(context: *const anyopaque, bytes: []u8) !usize {
|
|
||||||
var self: *DataReader = @constCast(@ptrCast(@alignCast(context)));
|
|
||||||
return self.read(bytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const fs = std.fs;
|
|
||||||
const io = std.io;
|
|
||||||
|
|
||||||
const File = std.fs.File;
|
|
||||||
|
|
||||||
pub const FileHolder = struct {
|
|
||||||
file: File,
|
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
pub fn init(path: []const u8, offset: u64) !FileHolder {
|
|
||||||
return .{
|
|
||||||
.file = try fs.cwd().openFile(path, .{ .mode = .read_write }),
|
|
||||||
.offset = offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: FileHolder) void {
|
|
||||||
self.file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reader(self: *FileHolder) File.Reader {
|
|
||||||
return self.file.reader();
|
|
||||||
}
|
|
||||||
pub fn readerAt(self: *FileHolder, offset: u64) FileOffsetReader {
|
|
||||||
return .{
|
|
||||||
.file = &self.file,
|
|
||||||
.offset = self.offset + offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn writerAt(self: *FileHolder, offset: u64) FileOffsetWriter {
|
|
||||||
// return .{
|
|
||||||
// .file = &self.file,
|
|
||||||
// .offset = self.offset + offset,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const FileOffsetWriter = struct {
|
|
||||||
file: *File,
|
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
pub fn init(fil: *File, init_offset: u64) FileOffsetWriter {
|
|
||||||
return .{
|
|
||||||
.file = fil,
|
|
||||||
.offset = init_offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Error = fs.File.PWriteError;
|
|
||||||
|
|
||||||
pub fn write(self: *FileOffsetWriter, bytes: []const u8) !usize {
|
|
||||||
try self.file.pwriteAll(bytes, self.offset);
|
|
||||||
self.offset += bytes.len;
|
|
||||||
return bytes.len;
|
|
||||||
}
|
|
||||||
pub fn any(self: *FileOffsetWriter) io.AnyWriter {
|
|
||||||
return .{
|
|
||||||
.context = @ptrCast(self),
|
|
||||||
.writeFn = writeOpaque,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn writeOpaque(context: *const anyopaque, bytes: []const u8) anyerror!usize {
|
|
||||||
var rdr: *FileOffsetWriter = @constCast(@ptrCast(@alignCast(context)));
|
|
||||||
return try rdr.write(bytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const FileOffsetReader = struct {
|
|
||||||
file: *File,
|
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
pub const Error = fs.File.PReadError;
|
|
||||||
|
|
||||||
pub fn read(self: *FileOffsetReader, bytes: []u8) !usize {
|
|
||||||
const red = try self.file.preadAll(bytes, self.offset);
|
|
||||||
self.offset += red;
|
|
||||||
return red;
|
|
||||||
}
|
|
||||||
pub fn any(self: *FileOffsetReader) io.AnyReader {
|
|
||||||
return .{
|
|
||||||
.context = @ptrCast(self),
|
|
||||||
.readFn = readOpaque,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn readOpaque(context: *const anyopaque, bytes: []u8) !usize {
|
|
||||||
var rdr: *FileOffsetReader = @constCast(@ptrCast(@alignCast(context)));
|
|
||||||
return try rdr.read(bytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const io = std.io;
|
|
||||||
|
|
||||||
const DecompressType = @import("../decompress.zig").DecompressType;
|
|
||||||
|
|
||||||
const MetadataHeader = packed struct {
|
|
||||||
size: u15,
|
|
||||||
not_compressed: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const MetadataReader = struct {
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
decomp: DecompressType,
|
|
||||||
reader: io.AnyReader,
|
|
||||||
block: []u8 = &[0]u8{},
|
|
||||||
offset: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, decomp: DecompressType, rdr: io.AnyReader) MetadataReader {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.decomp = decomp,
|
|
||||||
.reader = rdr,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *MetadataReader) void {
|
|
||||||
self.alloc.free(self.block);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(self: *MetadataReader, offset: u16) !void {
|
|
||||||
var cur_skip: u32 = 0;
|
|
||||||
var to_skip: u32 = 0;
|
|
||||||
while (cur_skip < offset) {
|
|
||||||
if (self.offset >= self.block.len) try self.readNextBlock();
|
|
||||||
to_skip = @min(offset - cur_skip, self.block.len - self.offset);
|
|
||||||
cur_skip += to_skip;
|
|
||||||
self.offset += to_skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readNextBlock(self: *MetadataReader) !void {
|
|
||||||
self.offset = 0;
|
|
||||||
if (self.block.len > 0) self.alloc.free(self.block);
|
|
||||||
const hdr = try self.reader.readStruct(MetadataHeader);
|
|
||||||
if (hdr.not_compressed) {
|
|
||||||
self.block = try self.alloc.alloc(u8, hdr.size);
|
|
||||||
_ = try self.reader.readAll(self.block);
|
|
||||||
} else {
|
|
||||||
var limit = std.io.limitedReader(self.reader, hdr.size);
|
|
||||||
var dat = try self.decomp.decompress(self.alloc, limit.reader().any());
|
|
||||||
self.block = try dat.toOwnedSlice();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any(self: *MetadataReader) io.AnyReader {
|
|
||||||
return .{
|
|
||||||
.context = @ptrCast(self),
|
|
||||||
.readFn = readOpaque,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *MetadataReader, bytes: []u8) !usize {
|
|
||||||
var cur_read: usize = 0;
|
|
||||||
var to_read: usize = 0;
|
|
||||||
while (cur_read < bytes.len) {
|
|
||||||
if (self.offset >= self.block.len) try self.readNextBlock();
|
|
||||||
to_read = @min(bytes.len - cur_read, self.block.len - self.offset);
|
|
||||||
@memcpy(bytes[cur_read .. cur_read + to_read], self.block[self.offset .. @as(usize, self.offset) + to_read]);
|
|
||||||
self.offset += @truncate(to_read);
|
|
||||||
cur_read += to_read;
|
|
||||||
}
|
|
||||||
return cur_read;
|
|
||||||
}
|
|
||||||
fn readOpaque(context: *const anyopaque, bytes: []u8) !usize {
|
|
||||||
var rdr: *MetadataReader = @constCast(@ptrCast(@alignCast(context)));
|
|
||||||
return rdr.read(bytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+55
-1
@@ -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
@@ -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
@@ -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];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const config = @import("config");
|
|
||||||
|
|
||||||
const File = @import("file.zig").File;
|
|
||||||
const Reader = @import("reader.zig").Reader;
|
|
||||||
const ExtractConfig = @import("file.zig").File.ExtractConfig;
|
|
||||||
|
|
||||||
const stdout = std.io.getStdOut();
|
|
||||||
|
|
||||||
var extr_files: std.ArrayList([]const u8) = undefined;
|
|
||||||
var offset: u64 = 0;
|
|
||||||
var verbose: bool = false;
|
|
||||||
var unbreak: bool = false;
|
|
||||||
var deref: bool = false;
|
|
||||||
var processors: u16 = 0;
|
|
||||||
var list: ListTypes = .None;
|
|
||||||
|
|
||||||
var filename: []const u8 = "";
|
|
||||||
var extr_location: []const u8 = "";
|
|
||||||
|
|
||||||
const ListTypes = enum {
|
|
||||||
None,
|
|
||||||
List,
|
|
||||||
ListAttr,
|
|
||||||
ListNumeric,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn help() !void {
|
|
||||||
const help_msg =
|
|
||||||
\\Basic Usage: zig-unsquashfs [Options] SQUASHFS_FILE EXTRACT_LOCATION
|
|
||||||
\\
|
|
||||||
\\General options:
|
|
||||||
\\ -e <path> Path to a file or directory inside the archive to extract instead of the whole archive.
|
|
||||||
\\ Can be given multiple times.
|
|
||||||
\\ -o <bytes> Skip <bytes> before reading from the archive.
|
|
||||||
\\ -v Verbose output.
|
|
||||||
\\ --help Prints this help message.
|
|
||||||
\\ -h Same as --help
|
|
||||||
\\
|
|
||||||
\\Extraction options:
|
|
||||||
\\ --unbreak-symlinks Attempt extract symlink targets along with symlinks. Will not place files outside of the extraction location.
|
|
||||||
\\ -us Same as --unbreak-symlinks
|
|
||||||
\\ --deref-symlinks Replace symlink files with their target.
|
|
||||||
\\ -ds Same as --deref-symlinks
|
|
||||||
\\ -p <#> Use at most # of processors. Defaults to logical core count.
|
|
||||||
\\
|
|
||||||
\\Listing Options:
|
|
||||||
\\ -l List files instead of extracting. When used, you do not need to specify an extraction location.
|
|
||||||
\\ -ll Like -l, but with file attributes.
|
|
||||||
\\ -lln Like -ll, but with numeric uids and gids.
|
|
||||||
\\
|
|
||||||
\\Other:
|
|
||||||
\\ --version Print version number.
|
|
||||||
\\
|
|
||||||
;
|
|
||||||
_ = try stdout.writeAll(help_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var alloc: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
|
||||||
extr_files = .init(alloc.allocator());
|
|
||||||
defer extr_files.deinit();
|
|
||||||
var args = std.process.argsWithAllocator(alloc.allocator()) catch {
|
|
||||||
_ = try stdout.writeAll("Unable allocate memory");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer args.deinit();
|
|
||||||
_ = args.next();
|
|
||||||
while (args.next()) |arg| {
|
|
||||||
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
|
||||||
try help();
|
|
||||||
return;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-v")) {
|
|
||||||
verbose = true;
|
|
||||||
} else if (std.mem.eql(u8, arg, "--unbreak-symlinks") or std.mem.eql(u8, arg, "-us")) {
|
|
||||||
unbreak = true;
|
|
||||||
} else if (std.mem.eql(u8, arg, "--deref-symlinks") or std.mem.eql(u8, arg, "-ds")) {
|
|
||||||
deref = true;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-l")) {
|
|
||||||
list = .List;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-ll")) {
|
|
||||||
list = .ListAttr;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-lln")) {
|
|
||||||
list = .ListNumeric;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-e")) {
|
|
||||||
const next = args.next();
|
|
||||||
if (next == null) {
|
|
||||||
_ = try stdout.writeAll("path required after -e\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try extr_files.append(next.?);
|
|
||||||
} else if (std.mem.eql(u8, arg, "-o")) {
|
|
||||||
const next = args.next();
|
|
||||||
if (next == null) {
|
|
||||||
_ = try stdout.writeAll("offset required after -o\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
offset = try std.fmt.parseInt(u64, next.?, 10);
|
|
||||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
|
||||||
const next = args.next();
|
|
||||||
if (next == null) {
|
|
||||||
_ = try stdout.writeAll("number required after -p\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processors = try std.fmt.parseInt(u16, next.?, 10);
|
|
||||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
|
||||||
try config.version.format("", .{}, stdout.writer());
|
|
||||||
_ = try stdout.write("\n");
|
|
||||||
return;
|
|
||||||
} else if (filename.len == 0) {
|
|
||||||
filename = arg;
|
|
||||||
} else if (extr_location.len == 0) {
|
|
||||||
extr_location = arg;
|
|
||||||
} else {
|
|
||||||
_ = try stdout.writeAll("invalid or too many arguments\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (filename.len == 0) {
|
|
||||||
_ = try stdout.writeAll("no archive given\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (list == .None and extr_location.len == 0) {
|
|
||||||
_ = try stdout.writeAll("no extract location given\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rdr = Reader.init(
|
|
||||||
alloc.allocator(),
|
|
||||||
filename,
|
|
||||||
offset,
|
|
||||||
) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error opening {s} as squashfs: {any}\n", .{ filename, err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer rdr.deinit();
|
|
||||||
switch (list) {
|
|
||||||
.None => {
|
|
||||||
var conf = ExtractConfig.init() catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error getting system info: {any}\n", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
conf.deref_sym = deref;
|
|
||||||
conf.unbreak_sym = unbreak;
|
|
||||||
conf.verbose = verbose;
|
|
||||||
if (extr_files.items.len == 0) {
|
|
||||||
rdr.root.extract(&rdr, conf, extr_location) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error extracting archive: {any}\n", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
for (extr_files.items) |path| {
|
|
||||||
var fil = rdr.root.open(&rdr, path) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error extracting {s}: {any}\n", .{ path, err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer fil.deinit(alloc.allocator());
|
|
||||||
fil.extract(&rdr, conf, extr_location) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error extracting {s}: {any}\n", .{ path, err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
if (extr_files.items.len == 0) {
|
|
||||||
try printFile(alloc.allocator(), &rdr, &rdr.root);
|
|
||||||
} else {
|
|
||||||
for (extr_files.items) |path| {
|
|
||||||
var fil = rdr.root.open(&rdr, path) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error finding {s}: {any}\n", .{ path, err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer fil.deinit(alloc.allocator());
|
|
||||||
try printFile(alloc.allocator(), &rdr, &fil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn printFile(alloc: std.mem.Allocator, rdr: *Reader, f: *File) anyerror!void {
|
|
||||||
const pth = try f.file_path(alloc);
|
|
||||||
defer alloc.free(pth);
|
|
||||||
if (list == .List) {
|
|
||||||
try std.fmt.format(stdout.writer(), "{s}\n", .{pth});
|
|
||||||
if (f.isDir()) {
|
|
||||||
try printDir(alloc, rdr, f);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try std.fmt.format(stdout.writer(), "{s} {d}/{d} {d} {s}\n", .{ "tmp-perm", try f.uid(rdr), try f.gid(rdr), f.size(), pth });
|
|
||||||
if (f.isDir()) {
|
|
||||||
try printDir(alloc, rdr, f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn printDir(alloc: std.mem.Allocator, rdr: *Reader, f: *File) anyerror!void {
|
|
||||||
var iter = try f.iterator(rdr);
|
|
||||||
defer iter.deinit();
|
|
||||||
while (iter.next()) |fil| {
|
|
||||||
try printFile(alloc, rdr, fil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user