From 78ae403f61db951f0dd65e7260614a1972beabab Mon Sep 17 00:00:00 2001 From: kayomn Date: Fri, 24 Feb 2023 00:35:36 +0000 Subject: [PATCH] Refine and fix bugs in the Oar library --- source/oar.cpp | 337 ++++++++++++++++++++++++++++--------------------- 1 file changed, 191 insertions(+), 146 deletions(-) diff --git a/source/oar.cpp b/source/oar.cpp index 4f8c5f8..a60a902 100644 --- a/source/oar.cpp +++ b/source/oar.cpp @@ -3,14 +3,25 @@ export module oar; import coral; import coral.files; -constexpr coral::usize signature_length{4}; +/** + * Length of the full magic signature at the beginning of an Oar file. + */ +constexpr coral::usize signature_length {4}; -constexpr coral::usize signature_version_length{1}; +/** + * Length of the magic signature at the beginning of an Oar file without the version indicator + * byte. + */ +constexpr coral::usize signature_identifier_length {signature_length - 1}; -constexpr coral::usize signature_identifier_length{signature_length - signature_version_length}; - -constexpr coral::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; +/** + * Hardcoded signature magic value that this implementation of Oar expects when reading archives. + */ +constexpr coral::u8 signature_magic[signature_length] {'o', 'a', 'r', 0}; +/** + * Oar file header format. + */ struct header { coral::u8 signature_magic[signature_length]; @@ -21,6 +32,9 @@ struct header { static_assert(sizeof(header) == 512); +/** + * Oar file header format. + */ struct entry { coral::path path; @@ -33,144 +47,168 @@ struct entry { static_assert(sizeof(entry) == 512); -export namespace oar { - struct archive_file_reader : public coral::file_reader { - enum class [[nodiscard]] close_result { - ok, - }; - - enum class [[nodiscard]] open_result { - ok, - io_unavailable, - archive_invalid, - archive_unsupported, - not_found, - }; - - archive_file_reader(coral::file_reader * archive_reader) { - this->archive_reader = archive_reader; - this->data_offset = 0; - this->data_length = 0; - this->data_cursor = 0; - } - - close_result close() { - return close_result::ok; - } - - bool is_open() const { - return this->data_offset >= sizeof(header); - } - - open_result open(coral::path const & file_path) { - if (this->is_open()) switch (this->close()) { - case close_result::ok: break; - default: coral::unreachable(); - } - - if (!this->archive_reader->seek(0).is_ok()) return open_result::io_unavailable; - - constexpr coral::usize header_size = sizeof(header); - coral::u8 archive_header_buffer[header_size]{0}; - - { - coral::expected const read_bytes{archive_reader->read(archive_header_buffer)}; - - if ((!read_bytes.is_ok()) || (read_bytes.value() != header_size)) - return open_result::archive_invalid; - } - - header const * const archive_header{reinterpret_cast
(archive_header_buffer)}; - - if (!coral::equals({archive_header->signature_magic, signature_identifier_length}, - {signature_magic, signature_identifier_length})) return open_result::archive_invalid; - - if (archive_header->signature_magic[signature_identifier_length] != - signature_magic[signature_identifier_length]) return open_result::archive_unsupported; - - // Read file table. - coral::u64 head{0}; - coral::u64 tail{archive_header->entry_count - 1}; - constexpr coral::usize entry_size{sizeof(entry)}; - coral::u8 file_entry_buffer[entry_size]{0}; - - while (head <= tail) { - coral::u64 const midpoint{head + ((tail - head) / 2)}; - - if (!archive_reader->seek(header_size + (entry_size * midpoint)).is_ok()) - return open_result::archive_invalid; - - { - coral::expected const read_bytes{archive_reader->read(file_entry_buffer)}; - - if ((!read_bytes.is_ok()) || (read_bytes.value() != header_size)) - return open_result::archive_invalid; - } - - entry const * const archive_entry{reinterpret_cast(file_entry_buffer)}; - coral::size const comparison{file_path.compare(archive_entry->path)}; - - if (comparison == 0) { - this->data_offset = archive_entry->data_offset; - this->data_length = archive_entry->data_length; - this->data_cursor = archive_entry->data_offset; - - return open_result::ok; - } - - if (comparison > 0) { - head = (midpoint + 1); - } else { - tail = (midpoint - 1); - } - } - - return open_result::not_found; - } - - coral::expected read(coral::slice const & buffer) override { - if (!this->is_open()) return coral::io_error::unavailable; - - if (this->archive_reader->seek(this->data_offset + this->data_cursor).is_ok()) - return coral::io_error::unavailable; - - coral::expected const bytes_read{this->archive_reader->read(buffer.sliced(0, - coral::min(buffer.length, static_cast(( - this->data_offset + this->data_length) - this->data_cursor))))}; - - if (!bytes_read.is_ok()) this->data_cursor += bytes_read.value(); - - return bytes_read; - } - - coral::expected seek(coral::u64 offset) override { - if (!this->is_open()) return coral::io_error::unavailable; - - return coral::io_error::unavailable; - } - - coral::expected tell() override { - if (!this->is_open()) return coral::io_error::unavailable; - - return this->data_cursor; - } - - private: - coral::file_reader * archive_reader; - - coral::u64 data_offset; - - coral::u64 data_length; - - coral::u64 data_cursor; +/** + * Archive file access interface. + */ +struct archive_file : public coral::file_reader { + /** + * Results of a find operation performed on an [archive_file]. + * + * [find_result::ok] means that the find operation was successful. + * + * [find_result::io_unavailable] signals a failure to communicate with the underlying + * [coral::file_reader] for whatever reason. + * + * [find_result::archive_invalid] signals that data was read but it does not match the format + * of an Oar archive. This is typically because the underlying [coral::file_reader] is not + * reading from an Oar archive file. + * + * [find_result::archive_unsupported] signals that data was read and was formatted as expected + * for an Oar archive, however, it is from an unsupported version of the archive format. + * + * [find_result::not_found] indicates that no entry in the archive could be found that matches + * the given query. + */ + enum class [[nodiscard]] find_result { + ok, + io_unavailable, + archive_invalid, + archive_unsupported, + not_found, }; + archive_file(coral::file_reader * archive_reader) { + this->archive_reader = archive_reader; + } + + /** + * Performs a lookup for a file entry matching the path `file_path` in the archive, returning + * [find_result] to indicate the result of the operation. + */ + find_result find(coral::path const & file_path) { + this->data_offset = 0; + this->data_length = 0; + this->data_cursor = 0; + + if (!this->archive_reader->seek(0).is_ok()) return find_result::io_unavailable; + + constexpr coral::usize header_size {sizeof(header)}; + coral::u8 archive_header_buffer[header_size] {0}; + + if (!this->archive_reader->read(archive_header_buffer).and_test( + [](coral::usize value) -> bool { return value == header_size; })) + return find_result::archive_invalid; + + header const * const archive_header { + reinterpret_cast
(archive_header_buffer)}; + + if (!coral::equals({archive_header->signature_magic, signature_identifier_length}, + {signature_magic, signature_identifier_length})) return find_result::archive_invalid; + + if (archive_header->signature_magic[signature_identifier_length] != + signature_magic[signature_identifier_length]) return find_result::archive_unsupported; + + // Read file table. + coral::u64 head {0}; + coral::u64 tail {archive_header->entry_count - 1}; + constexpr coral::usize entry_size {sizeof(entry)}; + coral::u8 archive_entry_buffer[entry_size] {0}; + + while (head <= tail) { + coral::u64 const midpoint {head + ((tail - head) / 2)}; + + if (!this->archive_reader->seek(header_size + (entry_size * midpoint)).is_ok()) + return find_result::archive_invalid; + + if (!this->archive_reader->read(archive_entry_buffer).and_test( + [](coral::usize value) -> bool { return value == entry_size; })) + return find_result::archive_invalid; + + entry const * const archive_entry { + reinterpret_cast(archive_entry_buffer)}; + + coral::size const comparison {file_path.compare(archive_entry->path)}; + + if (comparison == 0) { + this->data_offset = archive_entry->data_offset; + this->data_length = archive_entry->data_length; + this->data_cursor = archive_entry->data_offset; + + return find_result::ok; + } + + if (comparison > 0) { + head = (midpoint + 1); + } else { + tail = (midpoint - 1); + } + } + + return find_result::not_found; + } + + /** + * Attempts to read `data.length` bytes from the file and fill `data` with it, returning the + * number of bytes actually read or a [coral::io_error] value to indicate an error occured. + */ + coral::expected read(coral::slice const & data) override { + if (this->data_offset < sizeof(header)) return coral::io_error::unavailable; + + coral::usize const data_tail {this->data_offset + this->data_length}; + + if (!this->archive_reader->seek(coral::clamp(this->data_offset + this->data_cursor, + this->data_offset, data_tail)).is_ok()) return coral::io_error::unavailable; + + coral::expected const data_read {this->archive_reader->read( + data.sliced(0, coral::min(data.length, data_tail - this->data_cursor)))}; + + if (data_read.is_ok()) this->data_cursor += data_read.value(); + + return data_read; + } + + /** + * Attempts to seek to `offset` absolute position in the file, returning the new absolute + * cursor or a [coral::io_error] value to indicate an error occured. + */ + coral::expected seek(coral::u64 offset) override { + if (this->data_offset < sizeof(header)) return coral::io_error::unavailable; + + this->data_cursor = offset; + + return coral::io_error::unavailable; + } + + /** + * Attempts to read to read the absolute file cursor position, returning it or a + * [coral::io_error] value to indicate an error occured. + */ + coral::expected tell() override { + if (this->data_offset < sizeof(header)) return coral::io_error::unavailable; + + return this->data_cursor; + } + + private: + coral::file_reader * archive_reader {nullptr}; + + coral::u64 data_offset {0}; + + coral::u64 data_length {0}; + + coral::u64 data_cursor {0}; +}; + +export namespace oar { struct archive : public coral::fs { archive(coral::fs * backing_fs, coral::path const & archive_path) { this->backing_fs = backing_fs; this->archive_path = archive_path; } + /** + * Queries the archive for the [coral::fs::access_rules] and returns them. + */ access_rules query_access() override { return { .can_read = true, @@ -178,27 +216,34 @@ export namespace oar { }; } + /** + * Attempts to open a readable context for reading from the archive file identified by + * `file_path`, doing nothing if the requested file could not be found. + */ void read_file(coral::path const & file_path, coral::closure const & then) override { - if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return; + if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return; - this->backing_fs->read_file(this->archive_path, [&](coral::file_reader & archive_reader) { - archive_file_reader file_reader{&archive_reader}; + this->backing_fs->read_file(this->archive_path, + [&](coral::file_reader & archive_reader) { + archive_file file{&archive_reader}; - if (file_reader.open(file_path) != archive_file_reader::open_result::ok) return; + if (file.find(file_path) != archive_file::find_result::ok) return; - then(file_reader); - - if (file_reader.close() != archive_file_reader::close_result::ok) return; - }); - } + then(file); + }); + } + /** + * Attempts to open a writable context for reading from the archive file identified by + * `file_path`, however this will always do nothing as archive file-systems are read-only. + */ void write_file(coral::path const & file_path, coral::closure const & then) override { - // Read-only file system. - } + // Read-only file system. + } private: coral::fs * backing_fs;