/* vim:set ts=2 sw=2 sts=2 et: */ /** * \author Marcus Holland-Moritz (github@mhxnet.de) * \copyright Copyright (c) Marcus Holland-Moritz * * This file is part of dwarfs. * * dwarfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * dwarfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with dwarfs. If not, see . */ #include #include #include #include #include #include #include #include namespace dwarfs::internal { namespace { template void read_section_header_common(T& header, size_t& start, mmif const& mm, size_t offset) { if (offset + sizeof(T) > mm.size()) { DWARFS_THROW(runtime_error, "truncated section header"); } ::memcpy(&header, mm.as(offset), sizeof(T)); offset += sizeof(T); auto end = offset + header.length; if (end < offset) { DWARFS_THROW(runtime_error, "offset/length overflow"); } if (end > mm.size()) { DWARFS_THROW(runtime_error, "truncated section data"); } start = offset; } template void check_section(T const& sec) { if (!is_known_section_type(sec.type())) { DWARFS_THROW(runtime_error, fmt::format("unknown section type ({0})", static_cast(sec.type()))); } if (!is_known_compression_type(sec.compression())) { DWARFS_THROW(runtime_error, fmt::format("unknown compression type ({0})", static_cast(sec.compression()))); } } } // namespace class fs_section_v1 final : public fs_section::impl { public: fs_section_v1(mmif const& mm, size_t offset); size_t start() const override { return start_; } size_t length() const override { return hdr_.length; } bool is_known_compression() const override { return is_known_compression_type(this->compression()); } bool is_known_type() const override { return is_known_section_type(this->type()); } compression_type compression() const override { return static_cast(hdr_.compression); } section_type type() const override { return hdr_.type; } std::string name() const override { return get_section_name(hdr_.type); } std::string description() const override { return fmt::format("{}, offset={}", hdr_.to_string(), start()); } bool check_fast(mmif const&) const override { return true; } bool check(mmif const&) const override { return true; } bool verify(mmif const&) const override { return true; } std::span data(mmif const& mm) const override { return mm.span(start_, hdr_.length); } std::optional section_number() const override { return std::nullopt; } std::optional xxh3_64_value() const override { return std::nullopt; } std::optional> sha2_512_256_value() const override { return std::nullopt; } private: size_t start_; section_header hdr_; }; class fs_section_v2 final : public fs_section::impl { public: fs_section_v2(mmif const& mm, size_t offset); size_t start() const override { return start_; } size_t length() const override { return hdr_.length; } bool is_known_compression() const override { return is_known_compression_type(this->compression()); } bool is_known_type() const override { return is_known_section_type(this->type()); } compression_type compression() const override { return static_cast(hdr_.compression); } section_type type() const override { return static_cast(hdr_.type); } std::string name() const override { return get_section_name(static_cast(hdr_.type)); } std::string description() const override { std::string_view checksum_status; switch (check_state_.load()) { case check_state::passed: checksum_status = "OK"; break; case check_state::failed: checksum_status = "CHECKSUM ERROR"; break; default: checksum_status = "unknown"; break; } return fmt::format("{} [{}], offset={}", hdr_.to_string(), checksum_status, start()); } bool check_fast(mmif const& mm) const override { if (auto state = check_state_.load(); state != check_state::unknown) { return state == check_state::passed; } return check(mm); } bool check(mmif const& mm) const override { if (check_state_.load() == check_state::failed) { return false; } static auto constexpr kHdrCsLen = sizeof(section_header_v2) - offsetof(section_header_v2, number); auto ok = checksum::verify( checksum::algorithm::XXH3_64, mm.as(start_ - kHdrCsLen), hdr_.length + kHdrCsLen, &hdr_.xxh3_64, sizeof(hdr_.xxh3_64)); auto state = check_state_.load(); if (state != check_state::failed) { auto desired = ok ? check_state::passed : check_state::failed; check_state_.compare_exchange_strong(state, desired); } return ok; } bool verify(mmif const& mm) const override { auto hdr_sha_len = sizeof(section_header_v2) - offsetof(section_header_v2, xxh3_64); return checksum::verify(checksum::algorithm::SHA2_512_256, mm.as(start_ - hdr_sha_len), hdr_.length + hdr_sha_len, &hdr_.sha2_512_256, sizeof(hdr_.sha2_512_256)); } std::span data(mmif const& mm) const override { return mm.span(start_, hdr_.length); } std::optional section_number() const override { return hdr_.number; } std::optional xxh3_64_value() const override { return hdr_.xxh3_64; } std::optional> sha2_512_256_value() const override { return std::vector(hdr_.sha2_512_256, hdr_.sha2_512_256 + sizeof(hdr_.sha2_512_256)); } private: enum class check_state { unknown, passed, failed }; size_t start_; section_header_v2 hdr_; std::atomic mutable check_state_{check_state::unknown}; }; class fs_section_v2_lazy final : public fs_section::impl { public: fs_section_v2_lazy(std::shared_ptr mm, section_type type, size_t offset, size_t size); size_t start() const override { return offset_ + sizeof(section_header_v2); } size_t length() const override { return size_ - sizeof(section_header_v2); } bool is_known_compression() const override { return is_known_compression_type(this->compression()); } bool is_known_type() const override { return is_known_section_type(this->type()); } compression_type compression() const override { return section().compression(); } section_type type() const override { return type_; } std::string name() const override { return get_section_name(type_); } std::string description() const override { return section().description(); } bool check_fast(mmif const& mm) const override { return section().check_fast(mm); } bool check(mmif const& mm) const override { return section().check(mm); } bool verify(mmif const& mm) const override { return section().verify(mm); } std::span data(mmif const& mm) const override { return section().data(mm); } std::optional section_number() const override { return section().section_number(); } std::optional xxh3_64_value() const override { return section().xxh3_64_value(); } std::optional> sha2_512_256_value() const override { return section().sha2_512_256_value(); } private: fs_section::impl const& section() const; std::mutex mutable mx_; std::unique_ptr mutable sec_; std::shared_ptr mutable mm_; section_type const type_; size_t const offset_; size_t const size_; }; fs_section::fs_section(mmif const& mm, size_t offset, int version) { switch (version) { case 1: impl_ = std::make_shared(mm, offset); break; case 2: impl_ = std::make_shared(mm, offset); break; default: DWARFS_THROW(runtime_error, fmt::format("unsupported section version {}", version)); break; } } fs_section::fs_section(std::shared_ptr mm, section_type type, size_t offset, size_t size, int version) { switch (version) { case 2: impl_ = std::make_shared(std::move(mm), type, offset, size); break; default: DWARFS_THROW(runtime_error, fmt::format("unsupported section version {} [lazy]", version)); break; } } fs_section_v1::fs_section_v1(mmif const& mm, size_t offset) { read_section_header_common(hdr_, start_, mm, offset); check_section(*this); } fs_section_v2::fs_section_v2(mmif const& mm, size_t offset) { read_section_header_common(hdr_, start_, mm, offset); // TODO: Don't enforce these checks as we might want to add section types // and compression types in the future without necessarily incrementing // the file system version. // Only enforce them for v1 above, which doesn't have checksums and // where we know the exact set of section and compression types. // check_section(*this); } fs_section_v2_lazy::fs_section_v2_lazy(std::shared_ptr mm, section_type type, size_t offset, size_t size) : mm_{std::move(mm)} , type_{type} , offset_{offset} , size_{size} {} fs_section::impl const& fs_section_v2_lazy::section() const { std::lock_guard lock(mx_); if (!sec_) { sec_ = std::make_unique(*mm_, offset_); mm_.reset(); } return *sec_; } } // namespace dwarfs::internal