Compare commits
71 Commits
69ce562b6c
...
zig-0.14
| Author | SHA1 | Date | |
|---|---|---|---|
| 451e53264d | |||
| 99e3b81100 | |||
| 1eceb8d899 | |||
| b50f28026f | |||
| 1269d3e30d | |||
| b3f4a02b72 | |||
| ebaacf57a3 | |||
| 13f92f2e83 | |||
| 3fb95dd3fa | |||
| 9871b0b2c0 | |||
| 61d194e80a | |||
| 8c44c77456 | |||
| a96ad46a6c | |||
| d5c50b19f2 | |||
| 8998d28253 | |||
| 9c1d90f60b | |||
| eb214feefa | |||
| de988f083f | |||
| b4af1233e5 | |||
| 4d52627d5d | |||
| d6b136bc8f | |||
| 87563e43a5 | |||
| b0dced90bc | |||
| 69d90242ba | |||
| 61c86c9fea | |||
| 23687eabb0 | |||
| 5c14b7db48 | |||
| 60e183512b | |||
| fd1f83d855 | |||
| d48ed4259e | |||
| 8cc576a7fd | |||
| 10304139e4 | |||
| 4af3e0373e | |||
| 5be59be220 | |||
| f122d1b4be | |||
| e4a6c32528 | |||
| c057099591 | |||
| 17dbda3326 | |||
| 985e2bd7e5 | |||
| 7a4105bebd | |||
| b0ecbe16bd | |||
| c4e2dab3f7 | |||
| dd452060cb | |||
| 82011a092c | |||
| 3bfd262824 | |||
| 6f02f9f14d | |||
| 66f6cfa069 | |||
| af06021b1b | |||
| 213dfa8b92 | |||
| e91d75458e | |||
| 41a6b0d6f3 | |||
| 128ed9f001 | |||
| 1150b0d427 | |||
| 1b0a0221c4 | |||
| 9f345e5fdb | |||
| f77c2ecf48 | |||
| bbf3539dcf | |||
| 43295fb823 | |||
| ff2ef6feaa | |||
| 986f308c60 | |||
| fc068fdbd9 | |||
| e010763fc6 | |||
| 5daffdafc7 | |||
| 6dd3054006 | |||
| b0c71c59f8 | |||
| 3684a958a0 | |||
| a866804853 | |||
| 246d63d48a | |||
| b4848de95d | |||
| b3a5ff8f94 | |||
| 58e89c0981 |
@@ -1,2 +1,4 @@
|
|||||||
|
testing/
|
||||||
|
|
||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# zig-squashfs
|
||||||
|
|
||||||
|
Messing around with zig via making a squashfs library. May amount to something. Or not.
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
|
||||||
|
Performance is reatively bad (when compared to the official [squashfs-tools](https://github.com/plougher/squashfs-tools), but the basics should fully work.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// version if version isn't provided during build
|
||||||
|
const def_version = "0.0.0+testing";
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const opt = b.addOptions();
|
||||||
|
const ver = b.option([]const u8, "version", "sematic version") orelse def_version;
|
||||||
|
const sem_ver = try std.SemanticVersion.parse(ver);
|
||||||
|
opt.addOption(std.SemanticVersion, "version", sem_ver);
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
const lib_mod = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
const lib = b.addLibrary(.{
|
||||||
|
.linkage = .static,
|
||||||
|
.name = "zig_squashfs",
|
||||||
|
.root_module = lib_mod,
|
||||||
|
.version = sem_ver,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe_mod = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe_mod.addImport("squashfs", lib_mod);
|
||||||
|
exe_mod.addOptions("config", opt);
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.linkage = .static,
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = exe_mod,
|
||||||
|
.version = sem_ver,
|
||||||
|
});
|
||||||
|
|
||||||
|
b.installArtifact(lib);
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const lib_unit_tests = b.addTest(.{
|
||||||
|
.root_module = lib_mod,
|
||||||
|
});
|
||||||
|
const exe_unit_test = b.addTest(.{
|
||||||
|
.root_module = exe_mod,
|
||||||
|
});
|
||||||
|
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||||
|
const run_exe_unit_tests = b.addRunArtifact(exe_unit_test);
|
||||||
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
test_step.dependOn(&run_lib_unit_tests.step);
|
||||||
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
.{
|
||||||
|
.name = .zig_squashfs,
|
||||||
|
.version = "0.0.1",
|
||||||
|
.fingerprint = 0x527960c72c03ffe3, // Changing this has security and trust implications.
|
||||||
|
|
||||||
|
.minimum_zig_version = "0.14.0",
|
||||||
|
|
||||||
|
// This field is optional.
|
||||||
|
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||||
|
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||||
|
// Once all dependencies are fetched, `zig build` no longer requires
|
||||||
|
// internet connectivity.
|
||||||
|
.dependencies = .{
|
||||||
|
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||||
|
//.example = .{
|
||||||
|
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||||
|
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||||
|
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||||
|
// // which will prevent zig from using it.
|
||||||
|
// .url = "https://example.com/foo.tar.gz",
|
||||||
|
//
|
||||||
|
// // This is computed from the file contents of the directory of files that is
|
||||||
|
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||||
|
// // `paths`.
|
||||||
|
// //
|
||||||
|
// // This field is the source of truth; packages do not come from a `url`; they
|
||||||
|
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||||
|
// // obtain a package matching this `hash`.
|
||||||
|
// //
|
||||||
|
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||||
|
// .hash = "...",
|
||||||
|
//
|
||||||
|
// // When this is provided, the package is found in a directory relative to the
|
||||||
|
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||||
|
// // computed. This field and `url` are mutually exclusive.
|
||||||
|
// .path = "foo",
|
||||||
|
//
|
||||||
|
// // When this is set to `true`, a package is declared to be lazily
|
||||||
|
// // fetched. This makes the dependency only get fetched if it is
|
||||||
|
// // actually used.
|
||||||
|
// .lazy = false,
|
||||||
|
//},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Specifies the set of files and directories that are included in this package.
|
||||||
|
// Only files and directories listed here are included in the `hash` that
|
||||||
|
// is computed for this package. Only files listed here will remain on disk
|
||||||
|
// when using the zig package manager. As a rule of thumb, one should list
|
||||||
|
// files required for compilation plus any license(s).
|
||||||
|
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||||
|
// the build root itself.
|
||||||
|
// A directory listed here means that all files within, recursively, are included.
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
|
||||||
|
"LICENSE",
|
||||||
|
"README.md",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const InodeType = @import("inode.zig").Type;
|
||||||
|
const Compression = @import("superblock.zig").Compression;
|
||||||
|
|
||||||
|
const Header = extern struct { //use extern instead of packed, due to bit alignment
|
||||||
|
count: u32,
|
||||||
|
block: u32,
|
||||||
|
num: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const RawEntry = struct {
|
||||||
|
offset: u16,
|
||||||
|
num_offset: i16,
|
||||||
|
type: InodeType,
|
||||||
|
size: u16,
|
||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, rdr: anytype) !RawEntry {
|
||||||
|
var fixed: [8]u8 = undefined;
|
||||||
|
_ = try rdr.read(&fixed);
|
||||||
|
const size = std.mem.readInt(u16, fixed[6..8], .little);
|
||||||
|
const name = try alloc.alloc(u8, size + 1);
|
||||||
|
_ = try rdr.read(name);
|
||||||
|
return .{
|
||||||
|
.offset = std.mem.readInt(u16, fixed[0..2], .little),
|
||||||
|
.num_offset = std.mem.readInt(i16, fixed[2..4], .little),
|
||||||
|
.type = @enumFromInt(std.mem.readInt(u16, fixed[4..6], .little)),
|
||||||
|
.size = size,
|
||||||
|
.name = name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn readDirectory(alloc: std.mem.Allocator, rdr: anytype, size: u32) ![]Entry {
|
||||||
|
var entries: std.ArrayList(Entry) = .init(alloc);
|
||||||
|
errdefer entries.deinit();
|
||||||
|
var cur_red: u32 = 3; // dir size includes "." & "..", so its actual size is off by 3.
|
||||||
|
var hdr: Header = undefined;
|
||||||
|
while (cur_red < size) {
|
||||||
|
_ = try rdr.read(std.mem.asBytes(&hdr));
|
||||||
|
cur_red += 12;
|
||||||
|
try entries.ensureUnusedCapacity(hdr.count + 1);
|
||||||
|
for (0..hdr.count + 1) |_| {
|
||||||
|
const raw_ent: RawEntry = try .init(alloc, rdr);
|
||||||
|
cur_red += 9 + raw_ent.size;
|
||||||
|
errdefer alloc.free(raw_ent.name);
|
||||||
|
entries.appendAssumeCapacity(.{
|
||||||
|
.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 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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
+355
@@ -0,0 +1,355 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const dir = @import("directory.zig");
|
||||||
|
|
||||||
|
const DirEntry = dir.Entry;
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
const SfsReader = @import("reader.zig").SfsReader;
|
||||||
|
const ToReader = @import("reader/to_read.zig").ToRead;
|
||||||
|
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;
|
||||||
|
|
||||||
|
pub fn File(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
pub const FileError = error{
|
||||||
|
NotRegular,
|
||||||
|
NotDirectory,
|
||||||
|
NotFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
rdr: *SfsReader(T),
|
||||||
|
// 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, inode);
|
||||||
|
_ = f;
|
||||||
|
//TODO: fragments
|
||||||
|
// 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, inode);
|
||||||
|
_ = f;
|
||||||
|
//TODO: Fragments
|
||||||
|
// if (f.hasFragment()) {
|
||||||
|
// try out.data_reader.?.addFragment(
|
||||||
|
// try rdr.frag_table.get(f.frag_idx),
|
||||||
|
// f.frag_offset,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
|
||||||
|
return .init(rdr, inode, name);
|
||||||
|
}
|
||||||
|
pub fn initFromEntry(rdr: *SfsReader(T), ent: DirEntry) !Self {
|
||||||
|
var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ent.block + rdr.super.inode_start);
|
||||||
|
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: *Self) void {
|
||||||
|
self.rdr.alloc.free(self.name);
|
||||||
|
self.inode.deinit(self.rdr.alloc);
|
||||||
|
if (self.entries != null) {
|
||||||
|
for (self.entries.?) |e| {
|
||||||
|
e.deinit(self.rdr.alloc);
|
||||||
|
}
|
||||||
|
self.rdr.alloc.free(self.entries.?);
|
||||||
|
}
|
||||||
|
if (self.data_reader != null) {
|
||||||
|
self.data_reader.?.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uid(self: Self) !u32 {
|
||||||
|
return self.rdr.id_table.get(self.inode.hdr.uid_idx);
|
||||||
|
}
|
||||||
|
pub fn gid(self: Self) !u32 {
|
||||||
|
return self.rdr.id_table.get(self.inode.hdr.uid_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Reader = std.io.GenericReader(*DataReader(T), anyerror, DataReader(T).read);
|
||||||
|
|
||||||
|
pub fn read(self: *Self, buf: []u8) !usize {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
pub fn iterate(self: Self) Iterator {
|
||||||
|
return .{
|
||||||
|
.rdr = self.rdr,
|
||||||
|
.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 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();
|
||||||
|
try self.extractInode(op, &wg, &errs, &pol, self.inode, path);
|
||||||
|
wg.wait();
|
||||||
|
if (errs.items.len > 0) return errs.items[0];
|
||||||
|
}
|
||||||
|
fn extractInode(
|
||||||
|
self: *Self,
|
||||||
|
op: ExtractionOptions,
|
||||||
|
wg: *WaitGroup,
|
||||||
|
errs: *std.ArrayList(anyerror),
|
||||||
|
pol: *Pool,
|
||||||
|
inode: Inode,
|
||||||
|
path: []const u8,
|
||||||
|
) !void {
|
||||||
|
wg.start();
|
||||||
|
defer wg.finish(); //TODO: When everthing is threaded, this will need to be handled by the threads, not here.
|
||||||
|
switch (inode.hdr.type) {
|
||||||
|
.file, .ext_file => {
|
||||||
|
var fil = try std.fs.cwd().createFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
var data: DataReader(T) = try .init(self.rdr, inode);
|
||||||
|
defer data.deinit();
|
||||||
|
try data.writeTo(fil); // TODO: Thread
|
||||||
|
const fil_uid = self.rdr.id_table.get(inode.hdr.uid_idx) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error getting uid {} from table: {}\n", .{ inode.hdr.uid_idx, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const fil_gid = self.rdr.id_table.get(inode.hdr.gid_idx) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error getting gid {} from table: {}\n", .{ inode.hdr.gid_idx, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fil.chmod(inode.hdr.perm) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fil.chown(fil_uid, fil_gid) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
//TODO: update mtime.
|
||||||
|
},
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
std.fs.cwd().makeDir(path) catch |err| {
|
||||||
|
if (err != std.fs.Dir.MakeError.PathAlreadyExists) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dir_block: u32 = 0;
|
||||||
|
var dir_offset: u16 = 0;
|
||||||
|
var dir_size: u32 = 0;
|
||||||
|
switch (inode.data) {
|
||||||
|
.dir => |d| {
|
||||||
|
dir_block = d.block;
|
||||||
|
dir_offset = d.offset;
|
||||||
|
dir_size = d.size;
|
||||||
|
},
|
||||||
|
.ext_dir => |d| {
|
||||||
|
dir_block = d.block;
|
||||||
|
dir_offset = d.offset;
|
||||||
|
dir_size = d.size;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
var meta: MetadataReader(T) = .init(self.rdr.alloc, self.rdr.super.comp, self.rdr.rdr, dir_block + self.rdr.super.dir_start);
|
||||||
|
try meta.skip(dir_offset);
|
||||||
|
const entries = try dir.readDirectory(self.rdr.alloc, &meta, dir_size);
|
||||||
|
defer self.rdr.alloc.free(entries);
|
||||||
|
for (entries) |ent| {
|
||||||
|
defer ent.deinit(self.rdr.alloc);
|
||||||
|
var new_path: []u8 = undefined;
|
||||||
|
if (path[path.len - 1] == '/') {
|
||||||
|
new_path = self.rdr.alloc.alloc(u8, path.len + ent.name.len) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error allocating memory: {}\n", .{err}) catch {};
|
||||||
|
}
|
||||||
|
errs.append(err) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
@memcpy(new_path[0..path.len], path);
|
||||||
|
@memcpy(new_path[path.len..], ent.name);
|
||||||
|
} else {
|
||||||
|
new_path = self.rdr.alloc.alloc(u8, path.len + ent.name.len + 1) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error allocating memory: {}\n", .{err}) catch {};
|
||||||
|
}
|
||||||
|
errs.append(err) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
@memcpy(new_path[0..path.len], path);
|
||||||
|
new_path[path.len] = '/';
|
||||||
|
@memcpy(new_path[path.len + 1 ..], ent.name);
|
||||||
|
}
|
||||||
|
defer self.rdr.alloc.free(new_path);
|
||||||
|
|
||||||
|
meta = .init(self.rdr.alloc, self.rdr.super.comp, self.rdr.rdr, ent.block + self.rdr.super.inode_start);
|
||||||
|
meta.skip(ent.offset) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error reading inode: {}\n", .{err}) catch {};
|
||||||
|
}
|
||||||
|
errs.append(err) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
const new_inode = Inode.init(&meta, self.rdr.alloc, self.rdr.super.block_size) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error reading inode: {}\n", .{err}) catch {};
|
||||||
|
}
|
||||||
|
errs.append(err) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer new_inode.deinit(self.rdr.alloc);
|
||||||
|
self.extractInode(op, wg, errs, pol, new_inode, new_path) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error extracting {s}: {}\n", .{ new_path, err }) catch {};
|
||||||
|
}
|
||||||
|
errs.append(err) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var fil = std.fs.cwd().openDir(path, .{ .iterate = true }) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error openning {s} to set permissions: {}\n", .{ path, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const fil_uid = self.rdr.id_table.get(inode.hdr.uid_idx) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error getting uid {} from table: {}\n", .{ inode.hdr.uid_idx, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const fil_gid = self.rdr.id_table.get(inode.hdr.gid_idx) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error getting gid {} from table: {}\n", .{ inode.hdr.gid_idx, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fil.chmod(inode.hdr.perm) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fil.chown(fil_uid, fil_gid) catch |err| {
|
||||||
|
if (op.verbose) {
|
||||||
|
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// .symlink, .ext_symlink => {},
|
||||||
|
else => {
|
||||||
|
std.debug.print("TODO: {}\n", .{inode.hdr.type});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const BlockSize = @import("inode/file.zig").BlockSize;
|
||||||
|
|
||||||
|
pub const FragEntry = packed struct {
|
||||||
|
block: u64,
|
||||||
|
size: BlockSize,
|
||||||
|
_: u32,
|
||||||
|
};
|
||||||
@@ -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 => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Dir = packed struct {
|
||||||
|
block: u32,
|
||||||
|
hard_link: u32,
|
||||||
|
size: u16,
|
||||||
|
offset: u16,
|
||||||
|
parent_num: u32,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype) !Dir {
|
||||||
|
var out: Dir = undefined;
|
||||||
|
_ = try rdr.read(std.mem.asBytes(&out));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtDir = packed struct {
|
||||||
|
hard_link: u32,
|
||||||
|
size: u32,
|
||||||
|
block: u32,
|
||||||
|
parent_num: u32,
|
||||||
|
idx_count: u16,
|
||||||
|
offset: u16,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype) !ExtDir {
|
||||||
|
var out: ExtDir = undefined;
|
||||||
|
_ = try rdr.read(std.mem.asBytes(&out));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const BlockSize = packed struct {
|
||||||
|
size: u24,
|
||||||
|
uncompressed: bool,
|
||||||
|
_: u7,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const File = struct {
|
||||||
|
block: u32,
|
||||||
|
frag_idx: u32,
|
||||||
|
frag_offset: u32,
|
||||||
|
size: u32,
|
||||||
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !File {
|
||||||
|
var fixed: [16]u8 = undefined;
|
||||||
|
_ = try rdr.read(&fixed);
|
||||||
|
const frag_idx = std.mem.readInt(u32, fixed[4..8], .little);
|
||||||
|
const size = std.mem.readInt(u32, fixed[12..16], .little);
|
||||||
|
var blocks: u32 = size / block_size;
|
||||||
|
if (size % block_size > 0 and frag_idx == 0xffffffff) {
|
||||||
|
blocks += 1;
|
||||||
|
}
|
||||||
|
const block_sizes = try alloc.alloc(BlockSize, blocks);
|
||||||
|
errdefer alloc.free(block_sizes);
|
||||||
|
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
|
||||||
|
return .{
|
||||||
|
.block = std.mem.readInt(u32, fixed[0..4], .little),
|
||||||
|
.frag_idx = frag_idx,
|
||||||
|
.frag_offset = std.mem.readInt(u32, fixed[8..12], .little),
|
||||||
|
.size = size,
|
||||||
|
.block_sizes = block_sizes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn hasFragment(self: File) bool {
|
||||||
|
return self.frag_idx != 0xffffffff;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtFile = struct {
|
||||||
|
block: u64,
|
||||||
|
size: u64,
|
||||||
|
sparse: u64,
|
||||||
|
hard_link: u32,
|
||||||
|
frag_idx: u32,
|
||||||
|
frag_offset: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !ExtFile {
|
||||||
|
var fixed: [40]u8 = undefined;
|
||||||
|
_ = try rdr.read(&fixed);
|
||||||
|
const size = std.mem.readInt(u64, fixed[8..16], .little);
|
||||||
|
const frag_idx = std.mem.readInt(u32, fixed[28..32], .little);
|
||||||
|
var blocks: u32 = @truncate(size / block_size);
|
||||||
|
if (size % block_size > 0 and frag_idx == 0xffffffff) {
|
||||||
|
blocks += 1;
|
||||||
|
}
|
||||||
|
const block_sizes = try alloc.alloc(BlockSize, blocks);
|
||||||
|
errdefer alloc.free(block_sizes);
|
||||||
|
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
|
||||||
|
return .{
|
||||||
|
.block = std.mem.readInt(u64, fixed[0..8], .little),
|
||||||
|
.size = size,
|
||||||
|
.sparse = std.mem.readInt(u64, fixed[16..24], .little),
|
||||||
|
.hard_link = std.mem.readInt(u32, fixed[24..28], .little),
|
||||||
|
.frag_idx = frag_idx,
|
||||||
|
.frag_offset = std.mem.readInt(u32, fixed[32..36], .little),
|
||||||
|
.xattr_idx = std.mem.readInt(u32, fixed[36..40], .little),
|
||||||
|
.block_sizes = block_sizes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn hasFragment(self: ExtFile) bool {
|
||||||
|
return self.frag_idx != 0xffffffff;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Symlink = struct {
|
||||||
|
hard_link: u32,
|
||||||
|
// size: u32,
|
||||||
|
target: []const u8,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !Symlink {
|
||||||
|
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 ExtSymlink = struct {
|
||||||
|
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,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype) !ExtDev {
|
||||||
|
var out: ExtDev = undefined;
|
||||||
|
_ = try rdr.read(std.mem.asBytes(&out));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const IPC = packed struct {
|
||||||
|
hard_link: u32,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype) !IPC {
|
||||||
|
var out: IPC = undefined;
|
||||||
|
_ = try rdr.read(std.mem.asBytes(&out));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtIPC = packed struct {
|
||||||
|
hard_link: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn init(rdr: anytype) !ExtIPC {
|
||||||
|
var out: ExtIPC = undefined;
|
||||||
|
_ = try rdr.read(std.mem.asBytes(&out));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
const File = @import("file.zig").File;
|
||||||
|
const Table = @import("table.zig").Table;
|
||||||
|
const PRead = @import("reader/p_read.zig").PRead;
|
||||||
|
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;
|
||||||
|
|
||||||
|
pub const SfsError = error{
|
||||||
|
NotExportable,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn SfsReader(comptime T: type) type {
|
||||||
|
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
rdr: PRead(T),
|
||||||
|
|
||||||
|
super: Superblock = undefined,
|
||||||
|
/// ID table. Can be accessed directly
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Inode = @import("../inode.zig");
|
||||||
|
const PRead = @import("p_read.zig").PRead;
|
||||||
|
const SfsReader = @import("../reader.zig").SfsReader;
|
||||||
|
const FragEntry = @import("../fragment.zig").FragEntry;
|
||||||
|
const BlockSize = @import("../inode/file.zig").BlockSize;
|
||||||
|
const Compression = @import("../superblock.zig").Compression;
|
||||||
|
|
||||||
|
const DataReaderError = error{
|
||||||
|
EOF,
|
||||||
|
InvalidIndex,
|
||||||
|
ExtractionActive,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DecompCompletion = struct {
|
||||||
|
errs: std.ArrayList(anyerror),
|
||||||
|
map: std.AutoArrayHashMap(usize, []u8),
|
||||||
|
mut: std.Thread.Mutex = .{},
|
||||||
|
cond: std.Thread.Condition = .{},
|
||||||
|
|
||||||
|
fn init(alloc: std.mem.Allocator) DecompCompletion {
|
||||||
|
return .{
|
||||||
|
.errs = .init(alloc),
|
||||||
|
.map = .init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn deinit(self: *DecompCompletion) void {
|
||||||
|
self.errs.deinit();
|
||||||
|
self.map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(self: *DecompCompletion) void {
|
||||||
|
self.mut.lock();
|
||||||
|
defer self.mut.unlock();
|
||||||
|
self.errs.clearAndFree();
|
||||||
|
self.map.clearAndFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(self: *DecompCompletion, idx: usize, data: []u8) !void {
|
||||||
|
self.mut.lock();
|
||||||
|
defer self.mut.unlock();
|
||||||
|
defer self.cond.signal();
|
||||||
|
try self.map.put(idx, data);
|
||||||
|
}
|
||||||
|
fn addErr(self: *DecompCompletion, err: anyerror) void {
|
||||||
|
self.mut.lock();
|
||||||
|
defer self.mut.unlock();
|
||||||
|
defer self.cond.signal();
|
||||||
|
self.errs.append(err) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getBlock(self: *DecompCompletion, idx: usize) ?[]u8 {
|
||||||
|
const res = self.map.fetchSwapRemove(idx);
|
||||||
|
if (res == null) return null;
|
||||||
|
return res.?.value;
|
||||||
|
}
|
||||||
|
fn hasErrs(self: DecompCompletion) bool {
|
||||||
|
return self.errs.items.len > 0;
|
||||||
|
}
|
||||||
|
fn condWait(self: *DecompCompletion) void {
|
||||||
|
self.cond.wait(&self.mut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn DataReader(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
rdr: PRead(T),
|
||||||
|
comp: Compression,
|
||||||
|
block_size: u32,
|
||||||
|
|
||||||
|
sizes: []BlockSize,
|
||||||
|
offsets: []u64,
|
||||||
|
file_size: u64,
|
||||||
|
|
||||||
|
frag: ?[]u8 = null,
|
||||||
|
|
||||||
|
completion: DecompCompletion,
|
||||||
|
|
||||||
|
pub fn init(rdr: *SfsReader(T), inode: Inode) !Self {
|
||||||
|
var sizes: []BlockSize = undefined;
|
||||||
|
var file_size: u64 = 0;
|
||||||
|
var offsets: []u64 = undefined;
|
||||||
|
switch (inode.data) {
|
||||||
|
.file => |f| {
|
||||||
|
sizes = f.block_sizes;
|
||||||
|
file_size = f.size;
|
||||||
|
offsets = try rdr.alloc.alloc(u64, sizes.len);
|
||||||
|
if (sizes.len > 0) offsets[0] = f.block;
|
||||||
|
},
|
||||||
|
.ext_file => |f| {
|
||||||
|
sizes = f.block_sizes;
|
||||||
|
file_size = f.size;
|
||||||
|
offsets = try rdr.alloc.alloc(u64, sizes.len);
|
||||||
|
if (sizes.len > 0) offsets[0] = f.block;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
if (offsets.len > 1) {
|
||||||
|
for (1..offsets.len) |i| {
|
||||||
|
offsets[i] = offsets[i - 1] + sizes[i - 1].size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .{
|
||||||
|
.alloc = rdr.alloc,
|
||||||
|
.rdr = rdr.rdr,
|
||||||
|
.comp = rdr.super.comp,
|
||||||
|
.block_size = rdr.super.block_size,
|
||||||
|
.sizes = sizes,
|
||||||
|
.offsets = offsets,
|
||||||
|
.file_size = file_size,
|
||||||
|
.completion = .init(rdr.alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.alloc.free(self.offsets);
|
||||||
|
self.completion.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addFragment(self: *Self, data: []u8) void {
|
||||||
|
self.frag = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeTo(self: *Self, wrt: anytype) !void {
|
||||||
|
comptime std.debug.assert(std.meta.hasFn(@TypeOf(wrt), "write") or std.meta.hasFn(@TypeOf(wrt), "pwrite"));
|
||||||
|
var write_thr = try std.Thread.spawn(
|
||||||
|
.{ .allocator = self.alloc },
|
||||||
|
writeThread,
|
||||||
|
.{ self, wrt, null, null },
|
||||||
|
);
|
||||||
|
defer self.completion.clear();
|
||||||
|
for (0..self.numBlocks()) |i| {
|
||||||
|
var thr = std.Thread.spawn(
|
||||||
|
.{ .allocator = self.alloc },
|
||||||
|
decompThread,
|
||||||
|
.{ self, i },
|
||||||
|
) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
thr.detach();
|
||||||
|
}
|
||||||
|
write_thr.join();
|
||||||
|
if (self.completion.hasErrs()) return self.completion.errs.items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeToNoBlock(self: *Self, wrt: anytype, comptime finish: anytype, finish_args: anytype) !void {
|
||||||
|
comptime std.debug.assert(std.meta.hasFn(@TypeOf(wrt), "write") or std.meta.hasFn(@TypeOf(wrt), "pwrite"));
|
||||||
|
errdefer self.completion.clear();
|
||||||
|
var write_thr = try std.Thread.spawn(
|
||||||
|
.{ .allocator = self.alloc },
|
||||||
|
writeThread,
|
||||||
|
.{ self, wrt, finish, finish_args },
|
||||||
|
);
|
||||||
|
write_thr.detach();
|
||||||
|
for (0..self.numBlocks()) |i| {
|
||||||
|
var thr = std.Thread.spawn(
|
||||||
|
.{ .allocator = self.alloc },
|
||||||
|
decompThread,
|
||||||
|
.{ self, i },
|
||||||
|
) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
thr.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numBlocks(self: Self) usize {
|
||||||
|
var out = self.sizes.len;
|
||||||
|
if (self.frag != null) out += 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
/// Returns the decompressed data block at the given idx.
|
||||||
|
/// If the block is sparse (filled with 0s), a zero length slice is returned.
|
||||||
|
fn blockAt(self: Self, idx: usize) ![]u8 {
|
||||||
|
if (idx >= self.numBlocks()) return DataReaderError.InvalidIndex;
|
||||||
|
const size = self.sizes[idx];
|
||||||
|
if (size.size == 0) return &[0]u8{};
|
||||||
|
const block = try self.alloc.alloc(u8, blk: {
|
||||||
|
if (idx == self.numBlocks() - 1) break :blk self.file_size % self.block_size;
|
||||||
|
break :blk self.block_size;
|
||||||
|
});
|
||||||
|
if (idx == self.sizes.len and self.frag != null) {
|
||||||
|
@memcpy(block, self.frag.?);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
if (size.uncompressed) {
|
||||||
|
_ = try self.rdr.pread(block, self.offsets[idx]);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
_ = try self.comp.decompress(
|
||||||
|
1024 * 1024,
|
||||||
|
self.alloc,
|
||||||
|
self.rdr.readerAt(self.offsets[idx]).reader(),
|
||||||
|
block,
|
||||||
|
);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeThread(
|
||||||
|
self: *Self,
|
||||||
|
wrt: anytype,
|
||||||
|
comptime finish: anytype,
|
||||||
|
finish_args: anytype,
|
||||||
|
) void {
|
||||||
|
var cur_idx: usize = 0;
|
||||||
|
self.completion.mut.lock();
|
||||||
|
defer self.completion.mut.unlock();
|
||||||
|
while (cur_idx < self.numBlocks() and !self.completion.hasErrs()) {
|
||||||
|
self.completion.condWait();
|
||||||
|
if (self.completion.hasErrs()) break;
|
||||||
|
if (comptime std.meta.hasFn(@TypeOf(wrt), "pwrite")) {
|
||||||
|
for (self.completion.map.keys()) |_| {
|
||||||
|
const k = self.completion.map.keys()[0];
|
||||||
|
const blk = self.completion.getBlock(k).?;
|
||||||
|
defer self.alloc.free(blk);
|
||||||
|
if (blk.len > 0) {
|
||||||
|
_ = wrt.pwrite(blk, self.block_size * k) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
_ = wrt.pwrite(&[1]u8{0}, (self.block_size * (k + 1)) - 1) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cur_idx += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
while (self.completion.getBlock(cur_idx)) |blk| {
|
||||||
|
defer self.alloc.free(blk);
|
||||||
|
if (blk.len > 0) {
|
||||||
|
_ = wrt.write(blk) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const blank: [1024 * 1024]u8 = [1]u8{0} ** (1024 * 1024);
|
||||||
|
_ = wrt.write(blank[0..self.block_size]) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cur_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (comptime @TypeOf(finish) != @TypeOf(null) and @TypeOf(finish_args) != @TypeOf(null)) @call(.auto, finish, finish_args);
|
||||||
|
}
|
||||||
|
fn decompThread(
|
||||||
|
self: *Self,
|
||||||
|
idx: usize,
|
||||||
|
) void {
|
||||||
|
if (self.completion.hasErrs()) return;
|
||||||
|
defer self.completion.cond.signal();
|
||||||
|
const block = self.blockAt(idx) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.completion.add(idx, block) catch |err| {
|
||||||
|
self.completion.addErr(err);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
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(
|
||||||
|
8192,
|
||||||
|
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,43 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
const Reader = std.io.GenericReader(*Self, anyerror, read);
|
||||||
|
pub fn reader(self: anytype) Reader {
|
||||||
|
return .{
|
||||||
|
.context = @constCast(self),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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});
|
||||||
|
var root = try rdr.root();
|
||||||
|
defer root.deinit();
|
||||||
|
var iter = root.iterate();
|
||||||
|
while (true) {
|
||||||
|
var f = try iter.next();
|
||||||
|
if (f == null) break;
|
||||||
|
defer f.?.deinit();
|
||||||
|
std.debug.print("{s}\n", .{f.?.name});
|
||||||
|
}
|
||||||
|
std.debug.print("Finished OpenFile test\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
var 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);
|
||||||
|
|
||||||
|
std.debug.print("Finished ExtractSingleFile test\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
std.debug.print("Finished ExtractAll test\n", .{});
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const InodeRef = @import("inode.zig").Ref;
|
||||||
|
|
||||||
|
pub const Superblock = packed struct {
|
||||||
|
magic: u32,
|
||||||
|
inode_count: u32,
|
||||||
|
mod_time: u32,
|
||||||
|
block_size: u32,
|
||||||
|
frag_count: u32,
|
||||||
|
comp: Compression,
|
||||||
|
block_log: 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,
|
||||||
|
ver_maj: u16,
|
||||||
|
ver_min: u16,
|
||||||
|
root_ref: InodeRef,
|
||||||
|
size: u64,
|
||||||
|
id_start: u64,
|
||||||
|
xattr_start: u64,
|
||||||
|
inode_start: u64,
|
||||||
|
dir_start: u64,
|
||||||
|
frag_start: u64,
|
||||||
|
export_start: u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DecompressError = error{
|
||||||
|
LzoUnavailable,
|
||||||
|
Lz4Unavailable,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Compression = enum(u16) {
|
||||||
|
gzip = 1,
|
||||||
|
lzma,
|
||||||
|
lzo,
|
||||||
|
xz,
|
||||||
|
lz4,
|
||||||
|
zstd,
|
||||||
|
|
||||||
|
pub fn decompress(self: Compression, comptime max_size: u32, 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: [max_size]u8 = undefined;
|
||||||
|
var decomp = std.compress.zstd.decompressor(source, .{ .window_buffer = &window });
|
||||||
|
return decomp.read(dest);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const PRead = @import("reader/p_read.zig").PRead;
|
||||||
|
const Compression = @import("superblock.zig").Compression;
|
||||||
|
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
||||||
|
|
||||||
|
pub const TableError = error{
|
||||||
|
InvalidIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Table(comptime T: type, comptime R: type) type {
|
||||||
|
comptime std.debug.assert(std.meta.hasFn(R, "pread"));
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
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 .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.rdr = rdr,
|
||||||
|
.comp = comp,
|
||||||
|
.offset = offset,
|
||||||
|
.table_count = table_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.alloc.free(self.table);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(self: *Self, to_add: usize) !void {
|
||||||
|
if (!self.alloc.resize(self.table, self.table.len + to_add)) {
|
||||||
|
const new_table = try self.alloc.alloc(T, self.table.len + to_add);
|
||||||
|
@memcpy(new_table[0..self.table.len], self.table);
|
||||||
|
self.alloc.free(self.table);
|
||||||
|
self.table = new_table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user