diff --git a/include/bitcoin/database/impl/primitives/nomaps.ipp b/include/bitcoin/database/impl/primitives/nomaps.ipp index 121216c20..a17693b5f 100644 --- a/include/bitcoin/database/impl/primitives/nomaps.ipp +++ b/include/bitcoin/database/impl/primitives/nomaps.ipp @@ -1,217 +1,223 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP - -#include -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -CLASS::nomaps(storage& header, storage& body) NOEXCEPT - : head_(header, 0), - body_(body) -{ -} - -// not thread safe -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::create() NOEXCEPT -{ - Link count{}; - return head_.create() && - head_.get_body_count(count) && body_.truncate(count); -} - -TEMPLATE -bool CLASS::close() NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::backup(bool) NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::restore() NOEXCEPT -{ - Link count{}; - return head_.verify() && - head_.get_body_count(count) && body_.truncate(count); -} - -TEMPLATE -bool CLASS::verify() const NOEXCEPT -{ - Link count{}; - return head_.verify() && - head_.get_body_count(count) && count == body_.count(); -} - -// sizing -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::body_size() const NOEXCEPT -{ - return body_.size(); -} - -TEMPLATE -Link CLASS::count() const NOEXCEPT -{ - return body_.count(); -} - -TEMPLATE -Link CLASS::allocate(const Link& count) NOEXCEPT -{ - return body_.allocate(count); -} - -TEMPLATE -bool CLASS::truncate(const Link& count) NOEXCEPT -{ - return body_.truncate(count); -} - -TEMPLATE -bool CLASS::drop() NOEXCEPT -{ - return body_.truncate(0) && backup(); -} - -// Faults. -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - const auto ec = head_.get_fault(); - return ec ? ec : body_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return system::ceilinged_add(head_.get_space(), body_.get_space()); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return body_.reload(); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -memory CLASS::guard() const NOEXCEPT -{ - return get_memory(); -} - -TEMPLATE -template -memory CLASS::get_memory() const NOEXCEPT -{ - return body_.template get(); -} - -// static -TEMPLATE -template -bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT -{ - static_assert(Element::size == width); - if (!ptr || link.is_terminal()) - return false; - - using namespace system; - const auto start = body::template link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - iostream stream{ offset, size - position }; - reader source{ stream }; - - BC_DEBUG_ONLY(source.set_limit(width * element.count());) - return element.from_data(source); -} - -// TODO: gets are not optimized for shared remap guard. -TEMPLATE -template -bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - return get(get_memory(), link, element); -} - -// TODO: puts are optimized for shared remap guard (required). -TEMPLATE -template -bool CLASS::put(const Link& link, const Element& element) NOEXCEPT -{ - const auto ptr = body_.template get_raw(link); - if (!put(ptr, element)) - return false; - - body_.complete(link, element.count()); - return true; -} - -// protected (unguarded memory access) -TEMPLATE -template -bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT -{ - static_assert(Element::size == width); - if (is_null(it)) - return false; - - using namespace system; - const auto bytes = width * element.count(); - iostream stream{ it, possible_narrow_sign_cast(bytes) }; - flipper sink{ stream }; - - BC_DEBUG_ONLY(sink.set_limit(width * element.count());) - return element.to_data(sink); -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP + +#include +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::nomaps(storage& header, storage& body) NOEXCEPT + : head_(header, 0), + body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::backup(bool) NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && count == body_.count(); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +size_t CLASS::head_size() const NOEXCEPT +{ + return head_.size(); +} + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +TEMPLATE +Link CLASS::allocate(const Link& count) NOEXCEPT +{ + return body_.allocate(count); +} + +TEMPLATE +bool CLASS::truncate(const Link& count) NOEXCEPT +{ + return body_.truncate(count); +} + +TEMPLATE +bool CLASS::drop() NOEXCEPT +{ + return body_.truncate(0) && backup(); +} + +// Faults. +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + const auto ec = head_.get_fault(); + return ec ? ec : body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return system::ceilinged_add(head_.get_space(), body_.get_space()); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +memory CLASS::guard() const NOEXCEPT +{ + return get_memory(); +} + +TEMPLATE +template +memory CLASS::get_memory() const NOEXCEPT +{ + return body_.template get(); +} + +// static +TEMPLATE +template +bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + static_assert(Element::size == width); + if (!ptr || link.is_terminal()) + return false; + + using namespace system; + const auto start = body::template link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + iostream stream{ offset, size - position }; + reader source{ stream }; + + BC_DEBUG_ONLY(source.set_limit(width * element.count());) + return element.from_data(source); +} + +// TODO: gets are not optimized for shared remap guard. +TEMPLATE +template +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + return get(get_memory(), link, element); +} + +// TODO: puts are optimized for shared remap guard (required). +TEMPLATE +template +bool CLASS::put(const Link& link, const Element& element) NOEXCEPT +{ + const auto ptr = body_.template get_raw(link); + if (!put(ptr, element)) + return false; + + body_.complete(link, element.count()); + return true; +} + +// protected (unguarded memory access) +TEMPLATE +template +bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT +{ + static_assert(Element::size == width); + if (is_null(it)) + return false; + + using namespace system; + const auto bytes = width * element.count(); + iostream stream{ it, possible_narrow_sign_cast(bytes) }; + flipper sink{ stream }; + + BC_DEBUG_ONLY(sink.set_limit(width * element.count());) + return element.to_data(sink); +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/store/store_backup.ipp b/include/bitcoin/database/impl/store/store_backup.ipp index 061087734..fb9bca671 100644 --- a/include/bitcoin/database/impl/store/store_backup.ipp +++ b/include/bitcoin/database/impl/store/store_backup.ipp @@ -102,11 +102,13 @@ code CLASS::backup(const event_handler& handler, bool prune) NOEXCEPT // Rename /temporary to /primary (atomic). if ((ec = file::rename_ex(temporary, primary))) return ec; - // Delete the rotated /secondary, superseded by the new /primary. + // Delete the rotated /secondary, superseded by the new /primary. Best + // effort, as the snapshot is committed (a remnant is deleted by the next + // backup or restore, and is unreachable while /primary exists). if (file::is_directory(secondary)) { - if ((ec = file::clear_directory_ex(secondary))) return ec; - ec = file::remove_ex(secondary); + /* bool */ file::clear_directory(secondary); + /* bool */ file::remove(secondary); } return ec; diff --git a/include/bitcoin/database/impl/store/store_restore.ipp b/include/bitcoin/database/impl/store/store_restore.ipp index bb26f9513..a09daf495 100644 --- a/include/bitcoin/database/impl/store/store_restore.ipp +++ b/include/bitcoin/database/impl/store/store_restore.ipp @@ -59,20 +59,21 @@ code CLASS::restore(const event_handler& handler) NOEXCEPT if (file::is_directory(primary)) { - // Clear invalid /heads, recover from /primary, clone to /primary. + // Clear invalid /heads and clone /primary (retained) to /heads. + // The snapshot is never vacated, so no fault leaves /heads as the + // only copy (which the recreation below would otherwise clear). ec = file::clear_directory_ex(heads); if (!ec) ec = file::remove_ex(heads); - if (!ec) ec = file::rename_ex(primary, heads); - if (!ec) ec = file::copy_directory_ex(heads, primary); + if (!ec) ec = file::copy_directory_ex(primary, heads); if (!ec) ec = file::discharge_directory_ex(primary); } else if (file::is_directory(secondary)) { - // Clear invalid /heads, recover from /secondary, clone to /primary. + // Clone /secondary to /heads and promote it to /primary (atomic). ec = file::clear_directory_ex(heads); if (!ec) ec = file::remove_ex(heads); - if (!ec) ec = file::rename_ex(secondary, heads); - if (!ec) ec = file::copy_directory_ex(heads, primary); + if (!ec) ec = file::copy_directory_ex(secondary, heads); + if (!ec) ec = file::rename_ex(secondary, primary); if (!ec) ec = file::discharge_directory_ex(primary); } else diff --git a/include/bitcoin/database/primitives/nomaps.hpp b/include/bitcoin/database/primitives/nomaps.hpp index 4af9cfd56..2d081f865 100644 --- a/include/bitcoin/database/primitives/nomaps.hpp +++ b/include/bitcoin/database/primitives/nomaps.hpp @@ -56,6 +56,7 @@ class nomaps /// Sizing. /// ----------------------------------------------------------------------- + size_t head_size() const NOEXCEPT; size_t body_size() const NOEXCEPT; Link count() const NOEXCEPT; Link allocate(const Link& count) NOEXCEPT; diff --git a/test/store/store_restore.cpp b/test/store/store_restore.cpp index 5d198fd9b..f42ea500d 100644 --- a/test/store/store_restore.cpp +++ b/test/store/store_restore.cpp @@ -231,4 +231,26 @@ BOOST_AUTO_TEST_CASE(store__restore__secondary_remnant__deleted) BOOST_REQUIRE(!instance.close(test::events)); } +BOOST_AUTO_TEST_CASE(store__restore__secondary_only__promoted_success) +{ + settings configuration{}; + configuration.path = TEST_DIRECTORY; + const auto primary = configuration.path / schema::dir::primary; + const auto secondary = configuration.path / schema::dir::secondary; + + test::map_store instance{ configuration }; + BOOST_REQUIRE(!instance.create(test::events)); + BOOST_REQUIRE(!instance.snapshot(test::events)); + BOOST_REQUIRE(!instance.close(test::events)); + + // Simulate crash between backup rotation and /primary promotion. + BOOST_REQUIRE(file::rename(primary, secondary)); + BOOST_REQUIRE(test::create(test::flush_lock_file(configuration.path))); + BOOST_REQUIRE(!instance.restore(test::events)); + BOOST_REQUIRE(test::folder(primary)); + BOOST_REQUIRE(!test::folder(secondary)); + BOOST_REQUIRE(test::folder(configuration.path / schema::dir::heads)); + BOOST_REQUIRE(!instance.close(test::events)); +} + BOOST_AUTO_TEST_SUITE_END()