diff --git a/src/core/io.zig b/src/core/io.zig index c6dd1cb..48dee34 100644 --- a/src/core/io.zig +++ b/src/core/io.zig @@ -204,6 +204,38 @@ test "Check memory begins with" { try testing.expect(!begins(u8, &.{69, 89, 42, 0}, bytes_sequence)); } +/// +/// Returns a sliced reference of the raw bytes in `pointer`. +/// +/// **Note** that passing a slice will convert it to a byte slice. +/// +pub fn bytes(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) { + .Pointer => |info| if (info.is_const) []const u8 else []u8, + else => @compileError("`pointer` must be a pointer type"), +} { + const Pointer = @TypeOf(pointer); + const pointer_info = @typeInfo(Pointer).Pointer; + + switch (pointer_info.size) { + .Many => @compileError("`pointer` cannot be an unbound pointer type"), + .C => @compileError("`pointer` cannot be a C-style pointer"), + + .One => return @ptrCast(if (pointer_info.is_const) [*]const u8 + else [*]u8, pointer)[0 .. @sizeOf(Pointer)], + + .Slice => return @ptrCast(if (pointer_info.is_const) [*]const u8 else + [*]u8, pointer.ptr)[0 .. (@sizeOf(Pointer) * pointer.len)], + } +} + +test "Bytes of types" { + const testing = std.testing; + + var foo: u32 = 10; + + testing.expectEqual(bytes(&foo), 0x0a); +} + /// /// Returns `true` if `this` is the same length and contains the same data as `that`, otherwise /// `false`. diff --git a/src/oar/main.zig b/src/oar/main.zig index 15f5a77..bc1d5ea 100644 --- a/src/oar/main.zig +++ b/src/oar/main.zig @@ -5,7 +5,13 @@ const std = @import("std"); /// /// pub const Archive = struct { + pub fn deinit(archive: *Archive) { + } + + pub fn init(file_system: *const sys.FileSystem, file_path: sys.Path) { + + } }; /// diff --git a/src/ona/oar.zig b/src/ona/oar.zig new file mode 100644 index 0000000..d24da0d --- /dev/null +++ b/src/ona/oar.zig @@ -0,0 +1,138 @@ +const core = @import("./core.zig"); +const sys = @import("./sys.zig"); + +/// +/// +/// +pub const Archive = struct { + file_system: *const sys.FileSystem, + archive_path: sys.Path, + + /// + /// + /// + const Header = extern struct { + signature: [signature_magic.len]u8, + revision: u8, + entry_offset: u64, + padding: [500]u8 = std.mem.zeroes([500]u8), + + /// + /// Magic identifier used to validate [Entry] data. + /// + const signature_magic = [3]u8{'o', 'a', 'r'}; + + comptime { + const size = @sizeOf(@This()); + + if (size != 512) + @compileError("Header is " ++ + std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); + } + }; + + /// + /// An entry block of an Oar archive file. + /// + /// Typically, following the block in memory is the file data it holds the meta-information for. + /// + const Entry = extern struct { + signature: [signature_magic.len]u8 = signature_magic, + revision: u8, + path: Path, + file_size: u64, + absolute_offset: u64, + padding: [232]u8 = std.mem.zeroes([232]u8), + + comptime { + const entry_size = @sizeOf(Entry); + + if (entry_size != 512) + @compileError("Entry is " ++ + std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); + } + }; + + /// + /// + /// + pub const OpenError = error { + FileNotFound, + EntryNotFound, + UnsupportedArchive, + }; + + /// + /// + /// + pub fn open(archive: Archive, entry_path: Path) OpenError!EntryAccess { + const file_access = try archive.file_system.open(entry_path, .readonly); + + errdefer file_access.close(); + + var header = std.mem.zeroes(Header); + const header_size = @sizeOf(Header); + const io = core.io; + + // Validate header. + if ((try file_access.read(io.bytes(&header)) != header_size) or header + (!core.io.equals(u8, &header.signature, &Header.signature_magic)) or + (header.revision != revision) or + (header.entry_offset <= header_size)) return error.UnsupportedArchive; + + // Go to file table. + try file_access.seek(header.entry_offset); + + // Read file table. + var entry_buffer = std.mem.zeroes([8]Entry); + var bytes_read = try file_access.read(io.bytes(&entry_buffer)); + + while (@mod(bytes_read, @sizeOf(Entry)) == 0) { + for (entry_buffer[0 .. (bytes_read / @sizeOf(Entry))]) |entry| { + if (entry.path.equals(entry_path)) { + file_access.seek(entry.file_offset); + + return Entry{ + .file_access = file_access, + }; + } + } + + bytes_read = try file_access.read(io.bytes(&entry_buffer)); + } + + var head = std.mem.zeroes(usize); + var tail = (header.file_count - 1); + + while (head <= tail) { + const midpoint = (head + (tail - head) / 2); + + const comparison = entry_path.compare(arr[m])); + + if (comparison == 0) return midpoint; + + if (comparison > 0) { + // If x greater, ignore left half + head = (midpoint + 1); + } else { + // If x is smaller, ignore right half + tail = (midpoint - 1); + } + } + + return error.EntryNotFound; + } +}; + +pub const EntryAccess = struct { + file_access: FileAccess, + + pub fn close(entry: Entry) void { + entry.file_access.close(); + } +}; + +/// +/// +/// +const revision = 0; diff --git a/src/ona/sys.zig b/src/ona/sys.zig index 9b04750..63f3ed3 100644 --- a/src/ona/sys.zig +++ b/src/ona/sys.zig @@ -100,37 +100,6 @@ pub const FileSystem = union(enum) { native: []const u8, archive: Archive, - /// - /// Archive file system information. - /// - const Archive = struct { - file_access: core.io.FileAccess, - index_cache: IndexCache, - entry_table: [max_open_entries]Entry = std.mem.zeroes([max_open_entries]Entry), - - /// - /// Hard limit on the maximum number of entries open at once. - /// - const max_open_entries = 16; - - /// - /// Stateful extension of an [oar.Entry]. - /// - const Entry = struct { - owner: ?*core.io.FileAccess, - cursor: u64, - header: oar.Entry, - }; - - /// - /// Table cache for associating [oar.Path] values with offsets to entries in a given file. - /// - const IndexCache = core.table.Hashed(oar.Path, u64, .{ - .equals = oar.Path.equals, - .hash = oar.Path.hash, - }); - }; - /// /// With files typically being backed by a block device, they can produce a variety of errors - /// from physical to virtual errors - these are all encapsulated by the API as general @@ -172,7 +141,9 @@ pub const FileSystem = union(enum) { /// Returns a [FileAccess] reference that provides access to the file referenced by `path`or a /// [OpenError] if it failed. /// - pub fn open(file_system: *FileSystem, path: Path, mode: OpenMode) OpenError!core.io.FileAccess { + pub fn open(file_system: *const FileSystem, path: Path, + mode: OpenMode) OpenError!core.io.FileAccess { + switch (file_system.*) { .archive => |*archive| { if (mode != .readonly) return error.ModeUnsupported;