523 lines
20 KiB
Zig
523 lines
20 KiB
Zig
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 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();
|
|
self.data_reader.?.writeToNoBlock(
|
|
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;
|
|
};
|
|
}
|
|
};
|
|
}
|