diff --git a/src/archive.zig b/src/archive.zig index aa991df..a7b8544 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -119,8 +119,8 @@ pub fn root(self: *Archive) !SfsFile { var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{}); var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp); try meta.interface.discardAll(self.super.root_ref.block_offset); - const inode: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size); - return .init(self, inode, ""); + const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size); + return .init(self, in, ""); } pub fn open(self: *Archive, path: []const u8) !SfsFile { diff --git a/src/file.zig b/src/file.zig index 875a129..e90ec4c 100644 --- a/src/file.zig +++ b/src/file.zig @@ -7,6 +7,8 @@ const Archive = @import("archive.zig"); const DirEntry = @import("dir_entry.zig"); const ExtractionOptions = @import("options.zig"); const Inode = @import("inode.zig"); +const BlockSize = @import("inode_data/file.zig").BlockSize; +const DataReader = @import("util/data.zig"); const MetadataReader = @import("util/metadata.zig"); const FileError = error{ @@ -15,7 +17,7 @@ const FileError = error{ NotSymlink, NotDevice, NotFound, - InvalidExtractionPath, + ExtractionPathExists, }; const SfsFile = @This(); @@ -88,6 +90,44 @@ pub fn permissions(self: SfsFile) u16 { return self.inode.hdr.permissions; } +pub fn isRegular(self: SfsFile) bool { + return switch (self.inode.hdr.inode_type) { + .file, .ext_file => true, + else => false, + }; +} +/// The returned DataReader will no longer work if the File's deinit function is called +/// or, more specifically, it's inode's deinit function is called. +pub fn dataReader(self: SfsFile) !DataReader { + if (!self.isRegular()) return FileError.NotRegularFile; + var frag_idx: u32 = undefined; + var frag_offset: u32 = undefined; + var size: u64 = undefined; + var blocks: []BlockSize = undefined; + var start: u64 = undefined; + switch (self.inode.data) { + .file => |f| { + frag_idx = f.frag_idx; + frag_offset = f.frag_block_offset; + size = f.size; + blocks = f.block_sizes; + start = f.block_start; + }, + .ext_file => |f| { + frag_idx = f.frag_idx; + frag_offset = f.frag_block_offset; + size = f.size; + blocks = f.block_sizes; + start = f.block_start; + }, + else => unreachable, + } + var out: DataReader = .init(self.archive, blocks, start, size); + if (frag_idx != 0xFFFFFFFF) + out.addFragment(try self.archive.frag(frag_idx), frag_offset); + return out; +} + pub fn isDir(self: SfsFile) bool { return switch (self.inode.hdr.inode_type) { .dir, .ext_dir => true, @@ -101,7 +141,7 @@ pub fn iterate(self: SfsFile) !Iterator { .archive = self.archive, }; } -/// Open a file/folder within a directory at the given path. +/// Open a sub-file/folder within a directory at the given path. /// If path is ".", "/", or "./", this File is returned. pub fn open(self: SfsFile, path: []const u8) !SfsFile { if (!self.isDir()) return FileError.NotDirectory; @@ -142,7 +182,7 @@ pub fn isSymlink(self: SfsFile) bool { }; } pub fn symlinkPath(self: SfsFile) ![]const u8 { - if (!self.isSymlink()) FileError.NotSymlink; + if (!self.isSymlink()) return FileError.NotSymlink; return switch (self.inode.data) { .symlink => |s| s.target, .ext_symlink => |s| s.target, @@ -170,9 +210,6 @@ pub fn dev(self: SfsFile) !u32 { /// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist. /// If the gievn path is a folder, the File's contents will be extracted within. pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !void { - std.Options = .{ - .log_level = options.log_level, - }; var alloc = self.archive.allocator(); var ext_path: []u8 = undefined; if (std.fs.cwd().statFile(path)) |stat| { @@ -183,24 +220,24 @@ pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !vo path.len + self.name.len else path.len + self.name.len + 1; - ext_path = alloc.alloc(u8, alloc_size); + ext_path = try alloc.alloc(u8, alloc_size); @memcpy(ext_path[0..path.len], path); @memcpy(ext_path[ext_path.len - self.name.len ..], self.name); if (!has_end_sep) ext_path[path.len] = '/'; } else { - ext_path = path; + ext_path = @constCast(path); } - } else return FileError.InvalidExtractionPath; + } else return FileError.ExtractionPathExists; } else |err| { - if (err == .FileNotFound) { - ext_path = path; + if (err == error.FileNotFound) { + ext_path = @constCast(path); } else { std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err }); return err; } } defer if (ext_path.len > path.len) alloc.free(ext_path); - var pool: std.Thread.Pool = .{}; + var pool: std.Thread.Pool = undefined; try pool.init(.{ .allocator = alloc }); var wg: WaitGroup = .{}; defer pool.deinit(); @@ -220,16 +257,15 @@ const ParentInfo = struct { options: ExtractionOptions, err: *?anyerror, - fn finish(self: *ParentInfo) void { - { - self.mut.lock(); - defer self.mut.unlock(); - self.dir_wg.finish(); - if (!self.dir_wg.isDone()) { - return; - } + fn finish(self: *const ParentInfo) void { + self.mut.lock(); + if (!self.dir_wg.isDone()) { + self.mut.unlock(); + return; } + self.mut.unlock(); self.sfs_fil.archive.allocator().destroy(self.mut); + self.sfs_fil.archive.allocator().destroy(self.dir_wg); defer self.parent_wg.finish(); var fil = std.fs.cwd().openFile(self.path, .{}) catch |err| { std.log.err("Error opening folder {s} to set permissions: {}\n", .{ self.path, err }); @@ -267,33 +303,110 @@ fn extractReal(self: SfsFile, path: []const u8, options: ExtractionOptions, pol: return; }; defer fil.close(); - //TODO: + var dat_rdr = self.dataReader() catch |err| { + std.log.err("Error getting data reader for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err }); + out_err.* = err; + return; + }; + defer dat_rdr.deinit(); + var wrt = fil.writer(&[0]u8{}); + _ = dat_rdr.interface.streamRemaining(&wrt.interface) catch |err| { + std.log.err("Error writing data for {s} (inode {}) to {s}: {}\n", .{ self.name, self.inode.hdr.num, path, err }); + out_err.* = wrt.err orelse err; + return; + }; + wrt.interface.flush() catch |err| { + std.log.err("Error flushing data for {s} (inode {}) to {s}: {}\n", .{ self.name, self.inode.hdr.num, path, err }); + out_err.* = wrt.err orelse err; + return; + }; self.setPerm(fil, options) catch |err| { - std.log.err("Error setting permissions for {s}: {}\n", .{ path, err }); + std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err }); out_err.* = err; return; }; }, - .symlink, .ext_symlink => {}, + .symlink, .ext_symlink => { + //TODO: deal with dereference symlink options + const target_path = self.symlinkPath() catch |err| { + std.log.err("Error getting symlink target path for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err }); + out_err.* = err; + return; + }; + std.fs.cwd().symLink(target_path, path, .{}) catch |err| { + std.log.err("Error creating {s}: {}\n", .{ path, err }); + out_err.* = err; + return; + }; + // self.setPerm(fil, options) catch |err| { + // std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err }); + // out_err.* = err; + // return; + // }; + }, .block_dev, .char_dev, .fifo, .ext_block_dev, .ext_char_dev, .ext_fifo, - => {}, + => { + var mode: u32 = undefined; + var fil_dev: u32 = 0; + switch (self.inode.hdr.inode_type) { + .block_dev, .ext_block_dev => { + mode = std.posix.DT.BLK; + fil_dev = self.dev() catch |err| { + std.log.err("Error getting device number for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err }); + out_err.* = err; + return; + }; + }, + .char_dev, .ext_char_dev => { + mode = std.posix.DT.CHR; + fil_dev = self.dev() catch |err| { + std.log.err("Error getting device number for {s} (inode {}): {}\n", .{ self.name, self.inode.hdr.num, err }); + out_err.* = err; + return; + }; + }, + else => mode = std.posix.DT.FIFO, + } + const res = std.os.linux.mknod(@ptrCast(path), mode, fil_dev); + if (res != 0) { + std.log.err("Error creating device file at {s} with code {}\n", .{ path, res }); + out_err.* = error.MknodError; + return; + } + const fil = std.fs.cwd().openFile(path, .{}) catch |err| { + std.log.err("Error openning {s} to set permissions: {}\n", .{ path, err }); + out_err.* = err; + return; + }; + defer fil.close(); + self.setPerm(fil, options) catch |err| { + std.log.err("Error setting permissions/owner for {s}: {}\n", .{ path, err }); + out_err.* = err; + return; + }; + }, .dir, .ext_dir => { _ = std.fs.cwd().statFile(path) catch |err| { - if (err == .NotFound) {} + if (err == error.FileNotFound) {} }; var dir_wg: *WaitGroup = self.archive.allocator().create(WaitGroup) catch |err| { - std.log.err("Error allocating mutex for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err }); + std.log.err("Error allocating waitgroup for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err }); out_err.* = err; return; }; const parent_info: ParentInfo = .{ - .fil = self, + .sfs_fil = self, .path = path, + .mut = self.archive.allocator().create(Mutex) catch |err| { + std.log.err("Error allocating mutex for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err }); + out_err.* = err; + return; + }, .dir_wg = dir_wg, .parent_wg = wg, .options = options, @@ -313,9 +426,9 @@ fn extractReal(self: SfsFile, path: []const u8, options: ExtractionOptions, pol: break; }; if (iter_fil == null) break; - var fil = iter_fil.?; + const fil = iter_fil.?; dir_wg.start(); - const path_len = path.len + fil.name.len; + var path_len = path.len + fil.name.len; if (!path_has_end_sep) path_len += 1; var new_path = self.archive.allocator().alloc(u8, path_len) catch |err| { std.log.err("Error allocating subpath for {s} (inode {}): {}\n", .{ path, self.inode.hdr.num, err }); @@ -324,7 +437,7 @@ fn extractReal(self: SfsFile, path: []const u8, options: ExtractionOptions, pol: break; }; @memcpy(new_path[0..path.len], path); - @memcpy(new_path[new_path.len - fil.name.len ..], fil.name.len); + @memcpy(new_path[new_path.len - fil.name.len ..], fil.name); if (!path_has_end_sep) new_path[path.len] = '/'; pol.spawn(extractReal, .{ fil, @@ -340,7 +453,6 @@ fn extractReal(self: SfsFile, path: []const u8, options: ExtractionOptions, pol: dir_wg.finish(); break; }; - fil.extractReal; } }, .socket, .ext_socket => { diff --git a/src/inode_data/file.zig b/src/inode_data/file.zig index 826fc65..17afe70 100644 --- a/src/inode_data/file.zig +++ b/src/inode_data/file.zig @@ -16,7 +16,7 @@ pub const File = struct { pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File { var start: [16]u8 = undefined; - try rdr.readSliceAll(u8, &start, .little); + try rdr.readSliceAll(&start); const frag_idx: u32 = std.mem.readInt(u32, start[4..8], .little); const size: u32 = std.mem.readInt(u32, start[12..16], .little); var num_blocks: u32 = size / block_size; @@ -50,7 +50,7 @@ pub const ExtFile = struct { pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile { var start: [40]u8 = undefined; - try rdr.readSliceAll(u8, &start, .little); + try rdr.readSliceAll(&start); const frag_idx: u32 = std.mem.readInt(u32, start[28..32], .little); const size: u64 = std.mem.readInt(u64, start[8..16], .little); var num_blocks: u32 = @truncate(size / block_size); diff --git a/src/inode_data/misc.zig b/src/inode_data/misc.zig index e1c8a01..2a5417f 100644 --- a/src/inode_data/misc.zig +++ b/src/inode_data/misc.zig @@ -7,7 +7,7 @@ pub const Symlink = struct { pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink { var start: [8]u8 = undefined; - try rdr.readSliceAll(u8, &start, .little); + try rdr.readSliceAll(&start); const target_size = std.mem.readInt(u32, start[4..8], .little); const target = try alloc.alloc(u8, target_size + 1); errdefer alloc.free(target); @@ -30,7 +30,7 @@ pub const ExtSymlink = struct { pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink { var start: [8]u8 = undefined; - try rdr.readSliceAll(u8, &start, .little); + try rdr.readSliceAll(&start); const target_size = std.mem.readInt(u32, start[4..8], .little); const target = try alloc.alloc(u8, target_size + 1); errdefer alloc.free(target); diff --git a/src/options.zig b/src/options.zig index 00a88f8..f89c29f 100644 --- a/src/options.zig +++ b/src/options.zig @@ -15,4 +15,4 @@ log_level: std.log.Level = .err, // verboseWriter: ?*Writer = null, pub const Default: ExtractionOptions = .{}; -pub const VerboseDefault: ExtractionOptions = .{ .verbose = true }; +pub const VerboseDefault: ExtractionOptions = .{ .log_level = .debug }; diff --git a/src/table.zig b/src/table.zig index 6114904..693cbf8 100644 --- a/src/table.zig +++ b/src/table.zig @@ -62,12 +62,12 @@ pub fn Table(T: anytype) type { } const is_last = (self.values - 1) / VALS_PER_BLOCK == block_num; const slice_size = if (is_last) self.values - (block_num * VALS_PER_BLOCK) else VALS_PER_BLOCK; - const slice = try self.alloc.alloc(slice_size); + const slice = try self.alloc.alloc(T, slice_size); var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{}); - const offset: u64 = 0; - try rdr.interface.readSliceEndian(u64, @ptrCast(&idx_offset), .little); + var offset: u64 = 0; + try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little); rdr = try self.fil.readerAt(offset, &[0]u8{}); - var meta: MetadataReader = .init(&rdr.interface, self.decomp); + var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp); try meta.interface.readSliceEndian(T, @ptrCast(slice), .little); try self.tab.put(block_num, slice); return slice[idx_offset]; diff --git a/src/util/data.zig b/src/util/data.zig index 24d244e..3b4bf7e 100644 --- a/src/util/data.zig +++ b/src/util/data.zig @@ -18,7 +18,7 @@ block_size: u32, blocks: []BlockSize, -frag: ?FragEntry, // TODO: do something better? +frag: ?FragEntry = null, // TODO: do something better? frag_offset: u32 = 0, size: u64, @@ -49,7 +49,9 @@ pub fn init(archive: *Archive, blocks: []BlockSize, start: u64, size: u64) DataR }; } pub fn deinit(self: *DataReader) void { - self.alloc.free(self.inteface.buffer); + self.alloc.free(self.interface.buffer); + self.interface.end = 0; + self.interface.seek = 0; } pub fn addFragment(self: *DataReader, entry: FragEntry, frag_offset: u32) void { @@ -57,21 +59,29 @@ pub fn addFragment(self: *DataReader, entry: FragEntry, frag_offset: u32) void { self.frag_offset = frag_offset; } -fn blockNum(self: DataReader) u32 { +fn numBlocks(self: DataReader) usize { var res = self.blocks.len; if (self.frag != null) res += 1; return res; } fn advance(self: *DataReader) !void { - if (self.block_idx > self.blocks.len or (self.block_idx == self.blocks.len and self.frag == null)) return Reader.Error.EndOfStream; + if (self.block_idx > self.blocks.len or (self.block_idx == self.blocks.len and self.frag == null)) { + if (self.interface.buffer.len > 0) { + self.alloc.free(self.interface.buffer); + self.interface.buffer = &[0]u8{}; + self.interface.end = 0; + self.interface.seek = 0; + } + return Reader.Error.EndOfStream; + } defer self.block_idx += 1; + const cur_block_size = if (self.block_idx == self.numBlocks() - 1) self.size % self.block_size else self.block_size; + try self.resizeBuffer(cur_block_size); self.interface.seek = 0; - self.alloc.free(self.interface.buffer); + self.interface.end = cur_block_size; if (self.block_idx == self.blocks.len) { // fragment - var rdr = try self.fil.readerAt(self.frag.?.start + self.frag_offset, &[0]u8); - self.interface.buffer = try rdr.interface.readAlloc(self.alloc, self.size % self.block_size); - self.interface.end = self.interface.buffer.len; + var rdr = try self.fil.readerAt(self.frag.?.start, &[0]u8{}); if (self.frag.?.size.uncompressed) { try rdr.interface.discardAll(self.frag_offset); try rdr.interface.readSliceAll(self.interface.buffer); @@ -79,30 +89,45 @@ fn advance(self: *DataReader) !void { } const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size); defer self.alloc.free(tmp_buf); - var limit_rdr = Reader.limited(&rdr.interface, self.frag.?.size.size, tmp_buf); - const needed_block = try self.alloc.alloc(u8, self.frag_offset + self.interface.buffer.len); + var limit_rdr = Reader.limited(&rdr.interface, @enumFromInt(self.frag.?.size.size), tmp_buf); + const needed_block = try self.alloc.alloc(u8, self.frag_offset + cur_block_size); defer self.alloc.free(needed_block); _ = try self.decomp.decompReader(&limit_rdr.interface, needed_block); @memcpy(self.interface.buffer, needed_block[self.frag_offset..]); return; } - const cur_block_size = if (self.block_idx == self.blockNum() - 1) self.size % self.block_size else self.block_size; const block = self.blocks[self.block_idx]; - var rdr = try self.fil.readerAt(self.cur_offset, &[0]u8); - self.interface.end = cur_block_size; - if (block.uncompressed) { - self.interface.buffer = try rdr.interface.readAlloc(self.alloc, cur_block_size); + if (block.size == 0) { + @memset(self.interface.buffer, 0); return; } - var buf: [8192]u8 = undefined; - var limit_rdr = Reader.limited(&rdr.interface, block.size, &buf); + var rdr = try self.fil.readerAt(self.cur_offset, &[0]u8{}); + if (block.uncompressed) { + try rdr.interface.readSliceAll(self.interface.buffer); + return; + } + var buf: [8192]u8 = undefined; //TODO: possibly change for better performance/memory usage. Might need to be a full block in size. + var limit_rdr = Reader.limited(&rdr.interface, @enumFromInt(block.size), &buf); _ = try self.decomp.decompReader(&limit_rdr.interface, self.interface.buffer); } +/// Does not guarentee that data currently in the buffer is retained. +fn resizeBuffer(self: *DataReader, size: usize) !void { + if (self.interface.buffer.len == size) return; + if (!self.alloc.resize(self.interface.buffer, size)) { + self.alloc.free(self.interface.buffer); + self.interface.buffer = self.alloc.alloc(u8, size) catch |err| { + self.interface.buffer = &[0]u8{}; + return err; + }; + } else { + self.interface.buffer.len = size; + } +} fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize { - var self: *DataReader = @fieldParentPtr("interface", rdr); + var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr)); if (rdr.seek >= rdr.end) self.advance() catch |err| { - if (err == .EndOfStream) return err; + if (err == error.EndOfStream) return error.EndOfStream; std.log.err("Error advancing data reader: {}\n", .{err}); return Reader.Error.ReadFailed; }; @@ -114,9 +139,9 @@ fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize { } fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize { - var self: *DataReader = @fieldParentPtr("interface", rdr); + var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr)); if (rdr.seek >= rdr.end) self.advance() catch |err| { - if (err == .EndOfStream) return err; + if (err == error.EndOfStream) return error.EndOfStream; std.log.err("Error advancing data reader: {}\n", .{err}); return Reader.Error.ReadFailed; }; @@ -127,16 +152,16 @@ fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize { } fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize { - var self: *DataReader = @fieldParentPtr("interface", rdr); + var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr)); if (rdr.seek >= rdr.end) self.advance() catch |err| { - if (err == .EndOfStream) return err; + if (err == error.EndOfStream) return error.EndOfStream; std.log.err("Error advancing data reader: {}\n", .{err}); return Reader.Error.ReadFailed; }; var cur_red: usize = 0; for (vec) |s| { const to_copy: usize = @min(rdr.end - rdr.seek, s.len); - @memcpy(s[0..to_copy], self.buf[rdr.seek .. rdr.seek + to_copy]); + @memcpy(s[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]); rdr.seek += to_copy; cur_red += to_copy; if (rdr.end == rdr.seek) break;