diff --git a/CHANGELOG.md b/CHANGELOG.md index dd29dd712c3..54eae0ed7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Internals * Update libuv used in object store tests from v1.35.0 to v1.48.0 ([PR #7508](https://github.com/realm/realm-core/pull/7508)). * Made `set_default_logger` nullable in the bindgen spec.yml (PR [#7515](https://github.com/realm/realm-core/pull/7515)). +* Recreating the sync metadata Realm when the encryption key changes is now done in a multi-process safe manner ([PR #7526](https://github.com/realm/realm-core/pull/7526)). * Added `App::default_base_url()` static accessor for SDKs to retrieve the default base URL from Core. ([PR #7534](https://github.com/realm/realm-core/pull/7534)) * Realm2JSON tool will now correctly upgrade file to current fileformat. diff --git a/src/realm/alloc_slab.cpp b/src/realm/alloc_slab.cpp index d79b4724c70..60b441a940b 100644 --- a/src/realm/alloc_slab.cpp +++ b/src/realm/alloc_slab.cpp @@ -813,17 +813,39 @@ ref_type SlabAlloc::attach_file(const std::string& path, Config& cfg, util::Writ // the call below to set_encryption_key. m_file.set_encryption_key(cfg.encryption_key); + note_reader_start(this); + util::ScopeExit reader_end_guard([this]() noexcept { + note_reader_end(this); + }); size_t size = 0; // The size of a database file must not exceed what can be encoded in // size_t. if (REALM_UNLIKELY(int_cast_with_overflow_detect(m_file.get_size(), size))) throw InvalidDatabase("Realm file too large", path); - if (cfg.encryption_key && size == 0 && physical_file_size != 0) { + if (cfg.clear_file_on_error && cfg.session_initiator) { + if (size == 0 && physical_file_size != 0) { + cfg.clear_file = true; + } + else if (size > 0) { + try { + read_and_validate_header(m_file, path, size, cfg.session_initiator, m_write_observer); + } + catch (const InvalidDatabase&) { + cfg.clear_file = true; + } + } + } + if (cfg.clear_file) { + m_file.resize(0); + size = 0; + physical_file_size = 0; + } + else if (cfg.encryption_key && !cfg.clear_file && size == 0 && physical_file_size != 0) { // The opened file holds data, but is so small it cannot have // been created with encryption throw InvalidDatabase("Attempt to open unencrypted file with encryption key", path); } - if (size == 0 || cfg.clear_file) { + if (size == 0) { if (REALM_UNLIKELY(cfg.read_only)) throw InvalidDatabase("Read-only access to empty Realm file", path); @@ -833,7 +855,7 @@ ref_type SlabAlloc::attach_file(const std::string& path, Config& cfg, util::Writ // mappings_for_file in the encryption layer. So the prealloc() is required before // interacting with the encryption layer in File::write(). // Pre-alloc initial space - m_file.prealloc(initial_size); // Throws + m_file.prealloc(initial_size); // Throws // seek() back to the start of the file in preparation for writing the header // This sequence of File operations is protected from races by // DB::m_controlmutex, so we know we are the only ones operating on the file @@ -847,65 +869,9 @@ ref_type SlabAlloc::attach_file(const std::string& path, Config& cfg, util::Writ size = initial_size; } - ref_type top_ref; - note_reader_start(this); - util::ScopeExit reader_end_guard([this]() noexcept { - note_reader_end(this); - }); - - try { - // we'll read header and (potentially) footer - File::Map map_header(m_file, File::access_ReadOnly, sizeof(Header), 0, m_write_observer); - realm::util::encryption_read_barrier(map_header, 0, sizeof(Header)); - auto header = reinterpret_cast(map_header.get_addr()); - File::Map map_footer; - const StreamingFooter* footer = nullptr; - if (is_file_on_streaming_form(*header) && size >= sizeof(StreamingFooter) + sizeof(Header)) { - size_t footer_ref = size - sizeof(StreamingFooter); - size_t footer_page_base = footer_ref & ~(page_size() - 1); - size_t footer_offset = footer_ref - footer_page_base; - map_footer = File::Map(m_file, footer_page_base, File::access_ReadOnly, - sizeof(StreamingFooter) + footer_offset, 0, m_write_observer); - realm::util::encryption_read_barrier(map_footer, footer_offset, sizeof(StreamingFooter)); - footer = reinterpret_cast(map_footer.get_addr() + footer_offset); - } - - top_ref = validate_header(header, footer, size, path, cfg.encryption_key != nullptr); // Throws - m_attach_mode = cfg.is_shared ? attach_SharedFile : attach_UnsharedFile; - m_data = map_header.get_addr(); // <-- needed below - - if (cfg.session_initiator && is_file_on_streaming_form(*header)) { - // Don't compare file format version fields as they are allowed to differ. - // Also don't compare reserved fields. - REALM_ASSERT_EX(header->m_flags == 0, header->m_flags, get_file_path_for_assertions()); - REALM_ASSERT_EX(header->m_mnemonic[0] == uint8_t('T'), header->m_mnemonic[0], - get_file_path_for_assertions()); - REALM_ASSERT_EX(header->m_mnemonic[1] == uint8_t('-'), header->m_mnemonic[1], - get_file_path_for_assertions()); - REALM_ASSERT_EX(header->m_mnemonic[2] == uint8_t('D'), header->m_mnemonic[2], - get_file_path_for_assertions()); - REALM_ASSERT_EX(header->m_mnemonic[3] == uint8_t('B'), header->m_mnemonic[3], - get_file_path_for_assertions()); - REALM_ASSERT_EX(header->m_top_ref[0] == 0xFFFFFFFFFFFFFFFFULL, header->m_top_ref[0], - get_file_path_for_assertions()); - REALM_ASSERT_EX(header->m_top_ref[1] == 0, header->m_top_ref[1], get_file_path_for_assertions()); - REALM_ASSERT_EX(footer->m_magic_cookie == footer_magic_cookie, footer->m_magic_cookie, - get_file_path_for_assertions()); - } - } - catch (const InvalidDatabase&) { - throw; - } - catch (const DecryptionFailed& e) { - throw InvalidDatabase(util::format("Realm file decryption failed (%1)", e.what()), path); - } - catch (const std::exception& e) { - throw InvalidDatabase(e.what(), path); - } - catch (...) { - throw InvalidDatabase("unknown error", path); - } + ref_type top_ref = read_and_validate_header(m_file, path, size, cfg.session_initiator, m_write_observer); + m_attach_mode = cfg.is_shared ? attach_SharedFile : attach_UnsharedFile; // m_data not valid at this point! m_baseline = 0; // make sure that any call to begin_read cause any slab to be placed in free @@ -1040,6 +1006,57 @@ void SlabAlloc::attach_empty() m_ref_translation_ptr = new RefTranslation[1]; } +ref_type SlabAlloc::read_and_validate_header(util::File& file, const std::string& path, size_t size, + bool session_initiator, util::WriteObserver* write_observer) +{ + try { + // we'll read header and (potentially) footer + File::Map map_header(file, File::access_ReadOnly, sizeof(Header), 0, write_observer); + realm::util::encryption_read_barrier(map_header, 0, sizeof(Header)); + auto header = reinterpret_cast(map_header.get_addr()); + + File::Map map_footer; + const StreamingFooter* footer = nullptr; + if (is_file_on_streaming_form(*header) && size >= sizeof(StreamingFooter) + sizeof(Header)) { + size_t footer_ref = size - sizeof(StreamingFooter); + size_t footer_page_base = footer_ref & ~(page_size() - 1); + size_t footer_offset = footer_ref - footer_page_base; + map_footer = File::Map(file, footer_page_base, File::access_ReadOnly, + sizeof(StreamingFooter) + footer_offset, 0, write_observer); + realm::util::encryption_read_barrier(map_footer, footer_offset, sizeof(StreamingFooter)); + footer = reinterpret_cast(map_footer.get_addr() + footer_offset); + } + + auto top_ref = validate_header(header, footer, size, path, file.get_encryption_key() != nullptr); // Throws + + if (session_initiator && is_file_on_streaming_form(*header)) { + // Don't compare file format version fields as they are allowed to differ. + // Also don't compare reserved fields. + REALM_ASSERT_EX(header->m_flags == 0, header->m_flags, path); + REALM_ASSERT_EX(header->m_mnemonic[0] == uint8_t('T'), header->m_mnemonic[0], path); + REALM_ASSERT_EX(header->m_mnemonic[1] == uint8_t('-'), header->m_mnemonic[1], path); + REALM_ASSERT_EX(header->m_mnemonic[2] == uint8_t('D'), header->m_mnemonic[2], path); + REALM_ASSERT_EX(header->m_mnemonic[3] == uint8_t('B'), header->m_mnemonic[3], path); + REALM_ASSERT_EX(header->m_top_ref[0] == 0xFFFFFFFFFFFFFFFFULL, header->m_top_ref[0], path); + REALM_ASSERT_EX(header->m_top_ref[1] == 0, header->m_top_ref[1], path); + REALM_ASSERT_EX(footer->m_magic_cookie == footer_magic_cookie, footer->m_magic_cookie, path); + } + return top_ref; + } + catch (const InvalidDatabase&) { + throw; + } + catch (const DecryptionFailed& e) { + throw InvalidDatabase(util::format("Realm file decryption failed (%1)", e.what()), path); + } + catch (const std::exception& e) { + throw InvalidDatabase(e.what(), path); + } + catch (...) { + throw InvalidDatabase("unknown error", path); + } +} + void SlabAlloc::throw_header_exception(std::string msg, const Header& header, const std::string& path) { char buf[256]; diff --git a/src/realm/alloc_slab.hpp b/src/realm/alloc_slab.hpp index 7e9ce1f115b..e1ad8a0ca9f 100644 --- a/src/realm/alloc_slab.hpp +++ b/src/realm/alloc_slab.hpp @@ -108,15 +108,20 @@ class SlabAlloc : public Allocator { /// Always initialize the file as if it was a newly /// created file and ignore any pre-existing contents. Requires that /// Config::session_initiator be true as well. + /// + /// \var Config::clear_file_on_error + /// If the file being opened is not a valid Realm file (possibly due to a + /// decryption failure), reinitialize it as if clear_file was set. struct Config { + const char* encryption_key = nullptr; bool is_shared = false; bool read_only = false; bool no_create = false; bool skip_validate = false; bool session_initiator = false; bool clear_file = false; + bool clear_file_on_error = false; bool disable_sync = false; - const char* encryption_key = nullptr; }; struct Retry {}; @@ -363,6 +368,11 @@ class SlabAlloc : public Allocator { void note_reader_start(const void* reader_id); void note_reader_end(const void* reader_id) noexcept; + /// Read the header (and possibly footer) from the file, returning the top ref if it's valid and throwing + /// InvalidDatabase otherwise. + static ref_type read_and_validate_header(util::File& file, const std::string& path, size_t size, + bool session_initiator, util::WriteObserver* write_observer); + void verify() const override; #ifdef REALM_DEBUG void enable_debug(bool enable) @@ -703,10 +713,10 @@ class SlabAlloc : public Allocator { /// corrupted, or if the specified encryption key is incorrect. This /// function will not detect all forms of corruption, though. /// Returns the top_ref for the latest commit. - ref_type validate_header(const char* data, size_t len, const std::string& path); - ref_type validate_header(const Header* header, const StreamingFooter* footer, size_t size, - const std::string& path, bool is_encrypted = false); - void throw_header_exception(std::string msg, const Header& header, const std::string& path); + static ref_type validate_header(const char* data, size_t len, const std::string& path); + static ref_type validate_header(const Header* header, const StreamingFooter* footer, size_t size, + const std::string& path, bool is_encrypted = false); + static void throw_header_exception(std::string msg, const Header& header, const std::string& path); static bool is_file_on_streaming_form(const Header& header); /// Read the top_ref from the given buffer and set m_file_on_streaming_form diff --git a/src/realm/db.cpp b/src/realm/db.cpp index f982be15652..ac2967bfdb9 100644 --- a/src/realm/db.cpp +++ b/src/realm/db.cpp @@ -898,7 +898,7 @@ std::string DBOptions::sys_tmp_dir = getenv("TMPDIR") ? getenv("TMPDIR") : ""; // initializing process crashes and leaves the shared memory in an // undefined state. -void DB::open(const std::string& path, bool no_create_file, const DBOptions& options) +void DB::open(const std::string& path, const DBOptions& options) { // Exception safety: Since do_open() is called from constructors, if it // throws, it must leave the file closed. @@ -1144,10 +1144,11 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions& opt cfg.read_only = false; cfg.skip_validate = !begin_new_session; cfg.disable_sync = options.durability == Durability::MemOnly || options.durability == Durability::Unsafe; + cfg.clear_file_on_error = options.clear_on_invalid_file; // only the session initiator is allowed to create the database, all other // must assume that it already exists. - cfg.no_create = (begin_new_session ? no_create_file : true); + cfg.no_create = (begin_new_session ? options.no_create : true); // if we're opening a MemOnly file that isn't already opened by // someone else then it's a file which should have been deleted on @@ -1499,8 +1500,7 @@ void DB::open(Replication& repl, const std::string& file, const DBOptions& optio set_replication(&repl); - bool no_create = false; - open(file, no_create, options); // Throws + open(file, options); // Throws } class DBLogger : public Logger { @@ -1532,7 +1532,7 @@ void DB::set_logger(const std::shared_ptr& logger) noexcept m_logger = std::make_shared(logger, m_log_id); } -void DB::open(Replication& repl, const DBOptions options) +void DB::open(Replication& repl, const DBOptions& options) { REALM_ASSERT(!is_attached()); repl.initialize(*this); // Throws @@ -2808,10 +2808,10 @@ inline DB::DB(Private, const DBOptions& options) } } -DBRef DB::create(const std::string& file, bool no_create, const DBOptions& options) NO_THREAD_SAFETY_ANALYSIS +DBRef DB::create(const std::string& file, const DBOptions& options) NO_THREAD_SAFETY_ANALYSIS { DBRef retval = std::make_shared(Private(), options); - retval->open(file, no_create, options); + retval->open(file, options); return retval; } diff --git a/src/realm/db.hpp b/src/realm/db.hpp index ac0b555b32e..af8fc23633c 100644 --- a/src/realm/db.hpp +++ b/src/realm/db.hpp @@ -128,7 +128,7 @@ class DB : public std::enable_shared_from_this { // calling DB::close(), but after that no new association can be established. To reopen the // file (or another file), a new DB object is needed. The specified Replication instance, if // any, must remain in existence for as long as the DB. - static DBRef create(const std::string& file, bool no_create = false, const DBOptions& options = DBOptions()); + static DBRef create(const std::string& file, const DBOptions& options = DBOptions()); static DBRef create(Replication& repl, const std::string& file, const DBOptions& options = DBOptions()); static DBRef create(std::unique_ptr repl, const std::string& file, const DBOptions& options = DBOptions()); @@ -534,10 +534,6 @@ class DB : public std::enable_shared_from_this { /// /// \param file Filesystem path to a Realm database file. /// - /// \param no_create If the database file does not already exist, it will be - /// created (unless this is set to true.) When multiple threads are involved, - /// it is safe to let the first thread, that gets to it, create the file. - /// /// \param options See DBOptions for details of each option. /// Sensible defaults are provided if this parameter is left out. /// @@ -553,13 +549,12 @@ class DB : public std::enable_shared_from_this { /// \throw UnsupportedFileFormatVersion if the file format version or /// history schema version is one which this version of Realm does not know /// how to migrate from. - void open(const std::string& file, bool no_create = false, const DBOptions& options = DBOptions()) - REQUIRES(!m_mutex); + void open(const std::string& file, const DBOptions& options = DBOptions()) REQUIRES(!m_mutex); void open(BinaryData, bool take_ownership = true) REQUIRES(!m_mutex); void open(Replication&, const std::string& file, const DBOptions& options = DBOptions()) REQUIRES(!m_mutex); - void open(Replication& repl, const DBOptions options = DBOptions()) REQUIRES(!m_mutex); + void open(Replication& repl, const DBOptions& options = DBOptions()) REQUIRES(!m_mutex); - void do_open(const std::string& file, bool no_create, const DBOptions& options); + void do_open(const std::string& file, const DBOptions& options); Replication* const* get_repl() const noexcept { diff --git a/src/realm/db_options.hpp b/src/realm/db_options.hpp index 781e561a96a..a11eded2352 100644 --- a/src/realm/db_options.hpp +++ b/src/realm/db_options.hpp @@ -88,6 +88,9 @@ struct DBOptions { /// Disable automatic backup at file format upgrade by setting to false bool backup_at_file_format_change = true; + /// Disable creating new files if the DB to open does not already exist. + bool no_create = false; + /// List of versions we can upgrade from BackupHandler::VersionList accepted_versions = BackupHandler::accepted_versions_; @@ -99,6 +102,10 @@ struct DBOptions { /// a performance impact. bool enable_async_writes = false; + /// If set, opening a file which is not a Realm file or cannot be decrypted + /// will clear and reinitialize the file. + bool clear_on_invalid_file = false; + /// sys_tmp_dir will be used if the temp_dir is empty when creating DBOptions. /// It must be writable and allowed to create pipe/fifo file on it. /// set_sys_tmp_dir is not a thread-safe call and it is only supposed to be called once diff --git a/src/realm/object-store/impl/realm_coordinator.cpp b/src/realm/object-store/impl/realm_coordinator.cpp index 552a5cee04d..00f0a191655 100644 --- a/src/realm/object-store/impl/realm_coordinator.cpp +++ b/src/realm/object-store/impl/realm_coordinator.cpp @@ -479,6 +479,7 @@ bool RealmCoordinator::open_db() } options.encryption_key = m_config.encryption_key.data(); options.allow_file_format_upgrade = !m_config.disable_format_upgrade && !schema_mode_reset_file; + options.clear_on_invalid_file = m_config.clear_on_invalid_file; if (history) { options.backup_at_file_format_change = m_config.backup_at_file_format_change; #ifdef __EMSCRIPTEN__ @@ -496,7 +497,8 @@ bool RealmCoordinator::open_db() #endif } else { - m_db = DB::create(m_config.path, true, options); + options.no_create = true; + m_db = DB::create(m_config.path, options); } } catch (realm::FileFormatUpgradeRequired const&) { diff --git a/src/realm/object-store/shared_realm.hpp b/src/realm/object-store/shared_realm.hpp index 5031b0f19d4..5e98c96149f 100644 --- a/src/realm/object-store/shared_realm.hpp +++ b/src/realm/object-store/shared_realm.hpp @@ -184,6 +184,13 @@ struct RealmConfig { // everything can be done deterministically on one thread, and // speeds up tests that don't need notifications. bool automatic_change_notifications = true; + + // For internal use and should not be exposed by SDKs. + // + // If the file is invalid or can't be decrypted with the given encryption + // key, clear it and reinitialize it as a new file. This is used for the + // sync metadata realm which is automatically deleted if it can't be used. + bool clear_on_invalid_file = false; }; class Realm : public std::enable_shared_from_this { diff --git a/src/realm/object-store/sync/impl/sync_metadata.cpp b/src/realm/object-store/sync/impl/sync_metadata.cpp index 81cafc2e97d..af75fc0a55b 100644 --- a/src/realm/object-store/sync/impl/sync_metadata.cpp +++ b/src/realm/object-store/sync/impl/sync_metadata.cpp @@ -527,12 +527,7 @@ std::shared_ptr SyncMetadataManager::try_get_realm() const std::shared_ptr SyncMetadataManager::open_realm(bool should_encrypt, bool caller_supplied_key) { if (caller_supplied_key || !should_encrypt || !REALM_PLATFORM_APPLE) { - if (auto realm = try_get_realm()) - return realm; - - // Encryption key changed, so delete the existing metadata realm and - // recreate it - util::File::remove(m_metadata_config.path); + m_metadata_config.clear_on_invalid_file = true; return get_realm(); } @@ -560,8 +555,8 @@ std::shared_ptr SyncMetadataManager::open_realm(bool should_encrypt, bool return realm; // We weren't able to open the existing file with either the stored key - // or no key, so just delete it. - util::File::remove(m_metadata_config.path); + // or no key, so just recreate it + m_metadata_config.clear_on_invalid_file = true; } // We now have no metadata Realm. If we don't have an existing stored key, diff --git a/test/benchmark-common-tasks/main.cpp b/test/benchmark-common-tasks/main.cpp index b4872dcf6e3..234b4ed4a52 100644 --- a/test/benchmark-common-tasks/main.cpp +++ b/test/benchmark-common-tasks/main.cpp @@ -2021,7 +2021,7 @@ struct BenchmarkNonInitiatorOpen : Benchmark { DBRef do_open() { - return DB::create(*path, false, DBOptions(m_durability, m_encryption_key)); + return DB::create(*path, DBOptions(m_durability, m_encryption_key)); } void before_all(DBRef) @@ -2502,7 +2502,7 @@ void run_benchmark(BenchmarkResults& results, bool force_full = false) realm::test_util::DBTestPathGuard realm_path( test_util::get_test_path("benchmark_common_tasks_" + ident, ".realm")); DBRef group; - group = DB::create(realm_path, false, DBOptions(level, key)); + group = DB::create(realm_path, DBOptions(level, key)); benchmark.before_all(group); // Warm-up and initial measuring: diff --git a/test/test_client_reset.cpp b/test/test_client_reset.cpp index c6beed04601..077fa5626d6 100644 --- a/test/test_client_reset.cpp +++ b/test/test_client_reset.cpp @@ -975,7 +975,8 @@ TEST(ClientReset_NoChanges) db->write_copy(path_backup, nullptr); DBOptions options; options.is_immutable = true; - auto backup_db = DB::create(path_backup, true, options); + options.no_create = true; + auto backup_db = DB::create(path_backup, options); const ClientResyncMode modes[] = {ClientResyncMode::Recover, ClientResyncMode::DiscardLocal, ClientResyncMode::RecoverOrDiscard}; diff --git a/test/test_lang_bind_helper.cpp b/test/test_lang_bind_helper.cpp index 75d4a7e2a68..d8fdb175a4c 100644 --- a/test/test_lang_bind_helper.cpp +++ b/test/test_lang_bind_helper.cpp @@ -5195,7 +5195,7 @@ TEST(LangBindHelper_SessionHistoryConsistency) // session participants must still agree { // No history - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); // Out-of-Realm history std::unique_ptr hist = realm::make_in_realm_history(); @@ -5224,7 +5224,7 @@ TEST(LangBindHelper_InRealmHistory_Upgrade) SHARED_GROUP_TEST_PATH(path_2); { // No history - DBRef sg = DB::create(path_2, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path_2, DBOptions(crypt_key())); WriteTransaction wt(sg); wt.commit(); } @@ -5249,7 +5249,7 @@ TEST(LangBindHelper_InRealmHistory_Downgrade) } { // No history - CHECK_THROW(DB::create(path, false, DBOptions(crypt_key())), IncompatibleHistories); + CHECK_THROW(DB::create(path, DBOptions(crypt_key())), IncompatibleHistories); } } diff --git a/test/test_shared.cpp b/test/test_shared.cpp index 91a14ab5ff2..4a3cab6cabc 100644 --- a/test/test_shared.cpp +++ b/test/test_shared.cpp @@ -218,7 +218,9 @@ void writer(DBRef sg, uint64_t id) void killer(TestContext& test_context, int pid, std::string path, int id) { { - DBRef sg = DB::create(path, true, DBOptions(crypt_key())); + DBOptions options(crypt_key()); + options.no_create = true; + DBRef sg = DB::create(path, options); bool done = false; do { sched_yield(); @@ -254,7 +256,9 @@ void killer(TestContext& test_context, int pid, std::string path, int id) CHECK_EQUAL(0, child_exit_status); { // Verify that we surely did kill the process before it could do all it's commits. - DBRef sg = DB::create(path, true); + DBOptions options; + options.no_create = true; + DBRef sg = DB::create(path, options); ReadTransaction rt(sg); rt.get_group().verify(); auto t1 = rt.get_table("test"); @@ -285,7 +289,7 @@ TEST_IF(Shared_PipelinedWritesWithKills, false) CHECK(RobustMutex::is_robust_on_this_platform); const int num_processes = 50; SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); { // Create table entries WriteTransaction wt(sg); @@ -351,7 +355,7 @@ ONLY(Shared_DiskSpace) std::string path = "test.realm"; - SharedGroup sg(path, false, DBOptions("1234567890123456789012345678901123456789012345678901234567890123")); + SharedGroup sg(path, DBOptions("1234567890123456789012345678901123456789012345678901234567890123")); // SharedGroup sg(path, false, SharedGroupOptions(nullptr)); int seed = time(0); @@ -641,7 +645,7 @@ TEST(Shared_Initial) auto key_str = crypt_key(); { // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(key_str)); + DBRef sg = DB::create(path, DBOptions(key_str)); // Verify that new group is empty { @@ -661,8 +665,7 @@ TEST(Shared_InitialMem) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - bool no_create = false; - DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + DBRef sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); // Verify that new group is empty { @@ -687,10 +690,7 @@ TEST(Shared_InitialMem_StaleFile) // file open // Create a MemOnly realm at the path so that a lock file gets initialized - { - bool no_create = false; - DBRef r = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); - } + DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); CHECK(!File::exists(path)); CHECK(File::exists(path.get_lock_path())); @@ -706,8 +706,7 @@ TEST(Shared_InitialMem_StaleFile) // Verify that we can still open the path as a MemOnly SharedGroup and that // it's cleaned up afterwards { - bool no_create = false; - DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + DBRef db = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); CHECK(File::exists(path)); } CHECK(!File::exists(path)); @@ -720,11 +719,11 @@ TEST(Shared_Initial2) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); { // Open the same db again (in empty state) - DBRef sg2 = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg2 = DB::create(path, DBOptions(crypt_key())); // Verify that new group is empty { @@ -765,12 +764,11 @@ TEST(Shared_Initial2_Mem) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - bool no_create = false; - DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + DBRef sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); { // Open the same db again (in empty state) - DBRef sg2 = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + DBRef sg2 = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); // Verify that new group is empty { @@ -810,7 +808,7 @@ TEST(Shared_1) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); Timestamp first_timestamp_value{1, 1}; std::vector cols; @@ -911,7 +909,7 @@ TEST(Shared_try_begin_write) { SHARED_GROUP_TEST_PATH(path); // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); std::mutex thread_obtains_write_lock; std::condition_variable cv; std::mutex cv_lock; @@ -990,7 +988,7 @@ TEST(Shared_Rollback) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); std::vector cols; // Create first table in group (but rollback) @@ -1062,7 +1060,7 @@ TEST(Shared_Writes) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); std::vector cols; // Create first table in group @@ -1139,8 +1137,7 @@ TEST(Shared_ManyReaders) SHARED_GROUP_TEST_PATH(path); - bool no_create = false; - auto root_sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + auto root_sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); // Add two tables { @@ -1162,7 +1159,7 @@ TEST(Shared_ManyReaders) // Create 8*N shared group accessors for (int i = 0; i < 8 * N; ++i) - shared_groups[i] = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + shared_groups[i] = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); // Initiate 2*N read transactions with progressive changes for (int i = 0; i < 2 * N; ++i) { @@ -1364,7 +1361,7 @@ TEST(Shared_ManyReaders) // Check final state via new shared group { - DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly)); + DBRef sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly)); ReadTransaction rt(sg); #if !defined(_WIN32) || TEST_DURATION > 0 rt.get_group().verify(); @@ -1411,9 +1408,8 @@ TEST(Many_ConcurrentReaders) DBOptions options; options.logger = std::make_shared(logs); options.logger->set_level_threshold(Logger::Level::all); - constexpr bool no_create = false; for (int i = 0; i < 1000; ++i) { - DBRef sg_r = DB::create(path_str, no_create, options); + DBRef sg_r = DB::create(path_str, options); ReadTransaction rt(sg_r); ConstTableRef t = rt.get_table("table"); auto col_key = t->get_column_key("column"); @@ -1444,7 +1440,7 @@ TEST(Many_ConcurrentReaders) TEST(Shared_WritesSpecialOrder) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); const int num_rows = 5; // FIXME: Should be strictly greater than REALM_MAX_BPNODE_SIZE, but that takes too long time. @@ -1531,7 +1527,7 @@ TEST(Shared_WriterThreads) SHARED_GROUP_TEST_PATH(path); { // Create a new shared db - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); const int thread_count = 10; // Create first table in group @@ -1605,7 +1601,7 @@ TEST(Shared_RobustAgainstDeathDuringWrite) REALM_TERMINATE("fork() failed"); if (pid == 0) { // Child - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); WriteTransaction wt(sg); wt.get_group().verify(); wt.get_or_add_table("alpha"); @@ -1626,7 +1622,7 @@ TEST(Shared_RobustAgainstDeathDuringWrite) // Check that we can continue without dead-locking { - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); WriteTransaction wt(sg); wt.get_group().verify(); TableRef table = wt.get_or_add_table("beta"); @@ -1640,7 +1636,7 @@ TEST(Shared_RobustAgainstDeathDuringWrite) } { - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); ReadTransaction rt(sg); rt.get_group().verify(); CHECK(!rt.has_table("alpha")); @@ -1669,7 +1665,7 @@ TEST(Shared_SpaceOveruse) // Many transactions SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); // Do a lot of sequential transactions for (int i = 0; i != n_outer; ++i) { @@ -1712,7 +1708,7 @@ TEST(Shared_Notifications) { // Create a new shared db SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); TransactionRef tr1 = sg->start_read(); // No other instance have changed db since last transaction @@ -1720,7 +1716,7 @@ TEST(Shared_Notifications) { // Open the same db again (in empty state) - DBRef sg2 = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg2 = DB::create(path, DBOptions(crypt_key())); // Verify that new group is empty { @@ -1778,7 +1774,7 @@ TEST(Shared_FromSerialized) } // Open same file as shared group - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); // Verify that contents is there when shared { @@ -1798,7 +1794,7 @@ TEST(Shared_FromSerialized) TEST_IF(Shared_StringIndexBug1, TEST_DURATION >= 1) { SHARED_GROUP_TEST_PATH(path); - DBRef db = DB::create(path, false, DBOptions(crypt_key())); + DBRef db = DB::create(path, DBOptions(crypt_key())); { auto tr = db->start_write(); @@ -1824,7 +1820,7 @@ TEST_IF(Shared_StringIndexBug1, TEST_DURATION >= 1) TEST(Shared_StringIndexBug2) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); { WriteTransaction wt(sg); @@ -1856,7 +1852,7 @@ void rand_str(Random& random, char* res, size_t len) TEST(Shared_StringIndexBug3) { SHARED_GROUP_TEST_PATH(path); - DBRef db = DB::create(path, false, DBOptions(crypt_key())); + DBRef db = DB::create(path, DBOptions(crypt_key())); ColKey col; { auto tr = db->start_write(); @@ -1904,7 +1900,7 @@ TEST(Shared_ClearColumnWithBasicArrayRootLeaf) { SHARED_GROUP_TEST_PATH(path); { - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); WriteTransaction wt(sg); TableRef test = wt.add_table("Test"); auto col = test->add_column(type_Double, "foo"); @@ -1913,7 +1909,7 @@ TEST(Shared_ClearColumnWithBasicArrayRootLeaf) wt.commit(); } { - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); ReadTransaction rt(sg); ConstTableRef test = rt.get_table("Test"); auto col = test->get_column_key("foo"); @@ -1925,7 +1921,7 @@ TEST(Shared_ClearColumnWithLinksToSelf) { // Reproduction of issue found by fuzzer SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); { WriteTransaction wt(sg); TableRef test = wt.add_table("Test"); @@ -2045,7 +2041,7 @@ TEST(Shared_WaitForChange) for (int j = 0; j < num_threads; j++) shared_state[j] = 0; SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false); + DBRef sg = DB::create(path); auto waiter = [&](DBRef db, int i) { TransactionRef tr; @@ -2174,8 +2170,8 @@ TEST(Shared_MultipleSharersOfStreamingFormat) } { // See if we can handle overlapped accesses through multiple shared groups - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); - DBRef sg2 = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); + DBRef sg2 = DB::create(path, DBOptions(crypt_key())); { ReadTransaction rt(sg); rt.get_group().verify(); @@ -2204,9 +2200,9 @@ TEST(Shared_MultipleSharersOfStreamingFormat) TEST(Shared_EncryptionKeyCheck) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key(true))); - CHECK_THROW(DB::create(path, false, DBOptions()), InvalidDatabase); - DBRef sg3 = DB::create(path, false, DBOptions(crypt_key(true))); + DBRef sg = DB::create(path, DBOptions(crypt_key(true))); + CHECK_THROW(DB::create(path, DBOptions()), InvalidDatabase); + DBRef sg3 = DB::create(path, DBOptions(crypt_key(true))); } // opposite - if opened unencrypted, attempt to share it encrypted @@ -2214,9 +2210,9 @@ TEST(Shared_EncryptionKeyCheck) TEST(Shared_EncryptionKeyCheck_2) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions()); - CHECK_THROW(DB::create(path, false, DBOptions(crypt_key(true))), InvalidDatabase); - DBRef sg3 = DB::create(path, false, DBOptions()); + DBRef sg = DB::create(path, DBOptions()); + CHECK_THROW(DB::create(path, DBOptions(crypt_key(true))), InvalidDatabase); + DBRef sg3 = DB::create(path, DBOptions()); CHECK(sg3); } @@ -2228,9 +2224,9 @@ TEST(Shared_EncryptionKeyCheck_3) char second_key[64]; memcpy(second_key, first_key, 64); second_key[3] = ~second_key[3]; - DBRef sg = DB::create(path, false, DBOptions(first_key)); - CHECK_THROW(DB::create(path, false, DBOptions(second_key)), InvalidDatabase); - DBRef sg3 = DB::create(path, false, DBOptions(first_key)); + DBRef sg = DB::create(path, DBOptions(first_key)); + CHECK_THROW(DB::create(path, DBOptions(second_key)), InvalidDatabase); + DBRef sg3 = DB::create(path, DBOptions(first_key)); } TEST(Shared_EncryptionPageReadFailure) @@ -2238,7 +2234,7 @@ TEST(Shared_EncryptionPageReadFailure) SHARED_GROUP_TEST_PATH(path); constexpr size_t num_objects = 4096; { - DBRef sg = DB::create(path, false, DBOptions(crypt_key(true))); + DBRef sg = DB::create(path, DBOptions(crypt_key(true))); WriteTransaction wt(sg); TableRef table = wt.get_group().add_table_with_primary_key("foo", type_ObjectId, "pk"); auto str_col = table->add_column(type_String, "string"); @@ -2261,7 +2257,7 @@ TEST(Shared_EncryptionPageReadFailure) { bool did_throw = false; try { - DBRef sg = DB::create(path, false, DBOptions(crypt_key(true))); + DBRef sg = DB::create(path, DBOptions(crypt_key(true))); WriteTransaction wt(sg); TableRef table = wt.get_group().get_table("foo"); CHECK_EQUAL(table->size(), num_objects); @@ -2308,7 +2304,7 @@ TEST(Shared_VersionCount) TEST(Shared_MultipleRollbacks) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); TransactionRef wt = sg->start_write(); wt->rollback(); wt->rollback(); @@ -2318,7 +2314,7 @@ TEST(Shared_MultipleRollbacks) TEST(Shared_MultipleEndReads) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); TransactionRef reader = sg->start_read(); reader->end_read(); reader->end_read(); @@ -2330,7 +2326,7 @@ TEST(Shared_ReserveDiskSpace) { SHARED_GROUP_TEST_PATH(path); { - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); size_t orig_file_size = size_t(File(path).get_size()); // Check that reserve() does not change the file size if the @@ -2413,7 +2409,7 @@ TEST(Shared_MovingSearchIndex) // adjusted when columns are inserted or removed at a lower column_index. SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); // Create an int column, regular string column, and an enumeration strings // column, and equip them with search indexes. @@ -2534,12 +2530,11 @@ TEST(Shared_SessionDurabilityConsistency) SHARED_GROUP_TEST_PATH(path); { - bool no_create = false; DBOptions::Durability durability_1 = DBOptions::Durability::Full; - DBRef sg = DB::create(path, no_create, DBOptions(durability_1)); + DBRef sg = DB::create(path, DBOptions(durability_1)); DBOptions::Durability durability_2 = DBOptions::Durability::MemOnly; - CHECK_RUNTIME_ERROR(DB::create(path, no_create, DBOptions(durability_2)), ErrorCodes::IncompatibleSession); + CHECK_RUNTIME_ERROR(DB::create(path, DBOptions(durability_2)), ErrorCodes::IncompatibleSession); } } @@ -2748,7 +2743,7 @@ TEST_IF(Shared_encrypted_pin_and_write, false) SHARED_GROUP_TEST_PATH(path); { // initial table structure setup on main thread - DBRef sg = DB::create(path, false, DBOptions(crypt_key(true))); + DBRef sg = DB::create(path, DBOptions(crypt_key(true))); WriteTransaction wt(sg); Group& group = wt.get_group(); TableRef t = group.add_table("table"); @@ -2759,12 +2754,12 @@ TEST_IF(Shared_encrypted_pin_and_write, false) wt.commit(); } - DBRef sg_reader = DB::create(path, false, DBOptions(crypt_key(true))); + DBRef sg_reader = DB::create(path, DBOptions(crypt_key(true))); ReadTransaction rt(sg_reader); // hold first version auto do_many_writes = [&]() { - DBRef sg = DB::create(path, false, DBOptions(crypt_key(true))); + DBRef sg = DB::create(path, DBOptions(crypt_key(true))); const size_t base_size = 100000; std::string base(base_size, 'a'); // write many transactions to grow the file @@ -2806,7 +2801,7 @@ NONCONCURRENT_TEST(Shared_BigAllocations) { size_t string_length = 64 * 1024; SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); std::string long_string(string_length, 'a'); { WriteTransaction wt(sg); @@ -2844,7 +2839,7 @@ TEST_IF(Shared_CompactEncrypt, REALM_ENABLE_ENCRYPTION) const char* key1 = "KdrL2ieWyspILXIPetpkLD6rQYKhYnS6lvGsgk4qsJAMr1adQnKsYo3oTEYJDIfa"; const char* key2 = "ti6rOKviXrwxSGMPVk35Dp9Q4eku8Cu8YTtnnZKAejOTNIEv7TvXrYdjOPSNexMR"; { - auto db = DB::create(path, false, DBOptions(key1)); + auto db = DB::create(path, DBOptions(key1)); auto tr = db->start_write(); TableRef t = tr->add_table("table"); auto col = t->add_column(type_String, "Strings"); @@ -2874,7 +2869,9 @@ TEST_IF(Shared_CompactEncrypt, REALM_ENABLE_ENCRYPTION) } } { - auto db = DB::create(path, true, DBOptions()); + DBOptions options; + options.no_create = true; + auto db = DB::create(path, options); { auto rt = db->start_read(); CHECK(rt->has_table("table")); @@ -2890,7 +2887,7 @@ NONCONCURRENT_TEST(Shared_BigAllocationsMinimized) size_t string_length = 4 * 1024; SHARED_GROUP_TEST_PATH(path); std::string long_string(string_length, 'a'); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); { { WriteTransaction wt(sg); @@ -2927,7 +2924,7 @@ NONCONCURRENT_TEST(Shared_BigAllocationsMinimized) NONCONCURRENT_TEST(Shared_TopSizeNotEqualNine) { SHARED_GROUP_TEST_PATH(path); - DBRef sg = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg = DB::create(path, DBOptions(crypt_key())); { TransactionRef writer = sg->start_write(); @@ -2938,13 +2935,13 @@ NONCONCURRENT_TEST(Shared_TopSizeNotEqualNine) writer->commit(); } REALM_ASSERT_RELEASE(sg->compact()); - DBRef sg2 = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg2 = DB::create(path, DBOptions(crypt_key())); { TransactionRef writer = sg2->start_write(); writer->commit(); } TransactionRef reader2 = sg2->start_read(); - DBRef sg3 = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg3 = DB::create(path, DBOptions(crypt_key())); TransactionRef reader3 = sg3->start_read(); TransactionRef reader = sg->start_read(); } @@ -2955,7 +2952,7 @@ NONCONCURRENT_TEST(Shared_TopSizeNotEqualNine) TEST(Shared_Bptree_insert_failure) { SHARED_GROUP_TEST_PATH(path); - DBRef sg_w = DB::create(path, false, DBOptions(crypt_key())); + DBRef sg_w = DB::create(path, DBOptions(crypt_key())); TransactionRef writer = sg_w->start_write(); auto tk = writer->add_table("")->get_key(); @@ -2968,7 +2965,7 @@ TEST(Shared_Bptree_insert_failure) { // This intervening sg can do the same operation as the one doing compact, // but without failing: - DB sg2(path, false, DBOptions(crypt_key())); + DB sg2(path, DBOptions(crypt_key())); Group& g2 = const_cast(sg2.begin_write()); g2.get_table(tk)->add_empty_row(396); } @@ -3011,10 +3008,9 @@ TEST(Shared_LockFileInitSpinsOnZeroSize) { SHARED_GROUP_TEST_PATH(path); - bool no_create = false; DBOptions options; options.encryption_key = crypt_key(); - DBRef sg = DB::create(path, no_create, options); + DBRef sg = DB::create(path, options); sg->close(); CHECK(File::exists(path)); @@ -3046,7 +3042,7 @@ TEST(Shared_LockFileInitSpinsOnZeroSize) wait_for(1, mutex, test_stage); // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves - sg = DB::create(path, no_create, options); + sg = DB::create(path, options); CHECK(sg->is_attached()); sg->close(); @@ -3058,10 +3054,9 @@ TEST(Shared_LockFileSpinsOnInitComplete) { SHARED_GROUP_TEST_PATH(path); - bool no_create = false; DBOptions options; options.encryption_key = crypt_key(); - DBRef sg = DB::create(path, no_create, options); + DBRef sg = DB::create(path, options); sg->close(); CHECK(File::exists(path)); @@ -3093,7 +3088,7 @@ TEST(Shared_LockFileSpinsOnInitComplete) wait_for(1, mutex, test_stage); // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves - sg = DB::create(path, no_create, options); + sg = DB::create(path, options); CHECK(sg->is_attached()); sg->close(); @@ -3109,10 +3104,9 @@ TEST(Shared_LockFileOfWrongSizeThrows) SHARED_GROUP_TEST_PATH(path); - bool no_create = false; DBOptions options; options.encryption_key = crypt_key(); - DBRef sg = DB::create(path, no_create, options); + DBRef sg = DB::create(path, options); sg->close(); CHECK(File::exists(path)); @@ -3157,7 +3151,7 @@ TEST(Shared_LockFileOfWrongSizeThrows) // we expect to throw if init_complete = 1 but the file is not the expected size (< sizeof(SharedInfo)) // we go through 10 retry attempts before throwing - CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile); + CHECK_THROW(DB::create(path, options), IncompatibleLockFile); CHECK(!sg->is_attached()); mutex.lock(); @@ -3172,10 +3166,9 @@ TEST(Shared_LockFileOfWrongVersionThrows) { SHARED_GROUP_TEST_PATH(path); - bool no_create = false; DBOptions options; options.encryption_key = crypt_key(); - DBRef sg = DB::create(path, no_create, options); + DBRef sg = DB::create(path, options); CHECK(File::exists(path)); CHECK(File::exists(path.get_lock_path())); @@ -3212,7 +3205,7 @@ TEST(Shared_LockFileOfWrongVersionThrows) sg->close(); // we expect to throw if info->shared_info_version != g_shared_info_version - CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile); + CHECK_THROW(DB::create(path, options), IncompatibleLockFile); CHECK(!sg->is_attached()); mutex.lock(); @@ -3227,10 +3220,9 @@ TEST(Shared_LockFileOfWrongMutexSizeThrows) { SHARED_GROUP_TEST_PATH(path); - bool no_create = false; DBOptions options; options.encryption_key = crypt_key(); - DBRef sg = DB::create(path, no_create, options); + DBRef sg = DB::create(path, options); CHECK(File::exists(path)); CHECK(File::exists(path.get_lock_path())); @@ -3266,7 +3258,7 @@ TEST(Shared_LockFileOfWrongMutexSizeThrows) sg->close(); // we expect to throw if the mutex size is incorrect - CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile); + CHECK_THROW(DB::create(path, options), IncompatibleLockFile); CHECK(!sg->is_attached()); mutex.lock(); @@ -3281,10 +3273,9 @@ TEST(Shared_LockFileOfWrongCondvarSizeThrows) { SHARED_GROUP_TEST_PATH(path); - bool no_create = false; DBOptions options; options.encryption_key = crypt_key(); - DBRef sg = DB::create(path, no_create, options); + DBRef sg = DB::create(path, options); CHECK(File::exists(path)); CHECK(File::exists(path.get_lock_path())); @@ -3319,7 +3310,7 @@ TEST(Shared_LockFileOfWrongCondvarSizeThrows) sg->close(); // we expect to throw if the condvar size is incorrect - CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile); + CHECK_THROW(DB::create(path, options), IncompatibleLockFile); CHECK(!sg->is_attached()); mutex.lock(); @@ -3430,8 +3421,8 @@ TEST_IF(Shared_DecryptExisting, REALM_ENABLE_ENCRYPTION) #if 0 // set to 1 to generate the .realm file { File::try_remove(path); - //DB db(path, false, DBOptions(crypt_key(true))); - auto db = DB::create(path, false, DBOptions(crypt_key(true))); + //DB db(path, DBOptions(crypt_key(true))); + auto db = DB::create(path, DBOptions(crypt_key(true))); auto rt = db->start_write(); //Group& group = db.begin_write(); TableRef table = rt->add_table("table"); @@ -3508,7 +3499,9 @@ TEST(Shared_OpenAfterClose) wt = nullptr; db_w->close(); - db_w = DB::create(path, true, DBOptions(key)); + DBOptions options(key); + options.no_create = true; + db_w = DB::create(path, options); wt = db_w->start_write(); wt = nullptr; db_w->close(); @@ -3710,7 +3703,7 @@ TEST_IF(Shared_LinksToSameCluster, REALM_ENABLE_ENCRYPTION) TEST(Shared_GetCommitSize) { SHARED_GROUP_TEST_PATH(path); - DBRef db = DB::create(path, false, DBOptions(crypt_key())); + DBRef db = DB::create(path, DBOptions(crypt_key())); size_t size_before; size_t commit_size; { @@ -3769,7 +3762,7 @@ TEST_IF(Shared_LargeFile, TEST_DURATION > 0 && !REALM_ANDROID) SHARED_GROUP_TEST_PATH(path); DBOptions options; options.durability = DBOptions::Durability::MemOnly; - DBRef db = DB::create(path, false, options); + DBRef db = DB::create(path, options); auto tr = db->start_write(); @@ -3830,7 +3823,7 @@ TEST(Shared_EncryptionBug) DBOptions options; options.encryption_key = crypt_key(true); { - DBRef db = DB::create(path, false, options); + DBRef db = DB::create(path, options); { WriteTransaction wt(db); auto foo = wt.add_table("foo"); @@ -3851,7 +3844,7 @@ TEST(Shared_EncryptionBug) } { - DBRef db = DB::create(path, false, options); + DBRef db = DB::create(path, options); db->start_read()->verify(); } } @@ -4453,4 +4446,113 @@ NONCONCURRENT_TEST_IF(Shared_LockFileConcurrentInit, testing_supports_spawn_proc } } +TEST(Shared_ClearOnError_ReopenValidFile) +{ + SHARED_GROUP_TEST_PATH(path); + DBOptions options(crypt_key()); + options.clear_on_invalid_file = true; + + { + auto db = DB::create(path, options); + WriteTransaction wt(db); + wt.add_table("table"); + wt.commit(); + } + + { + // The file should not have been cleared + auto db = DB::create(path, options); + CHECK(db->start_read()->get_table("table")); + } +} + +TEST(Shared_ClearOnError_ResetInvalidFile) +{ + SHARED_GROUP_TEST_PATH(path); + DBOptions options; + options.clear_on_invalid_file = true; + + { + auto db = DB::create(path, options); + WriteTransaction wt(db); + wt.add_table("table"); + wt.commit(); + } + + { + // Overwrite the first byte of the mnemonic so that this isn't a valid file + util::File file(path, File::mode_Update); + file.seek(8); + file.write("\0", 1); + } + + { + // The file should have been cleared + auto db = DB::create(path, options); + CHECK_NOT(db->start_read()->get_table("table")); + } +} + +#if REALM_ENABLE_ENCRYPTION +TEST(Shared_ClearOnError_ChangeEncryptionKey) +{ + auto key_1 = "1234567890123456789012345678901123456789012345678901234567890123"; + auto key_2 = "2234567890123456789012345678901123456789012345678901234567890123"; + + SHARED_GROUP_TEST_PATH(path); + DBOptions options; + options.clear_on_invalid_file = true; + options.encryption_key = key_1; + + { + auto db = DB::create(path, options); + WriteTransaction wt(db); + wt.add_table("table"); + wt.commit(); + } + + { // change from first key to second + options.encryption_key = key_2; + auto db = DB::create(path, options); + WriteTransaction wt(db); + CHECK_NOT(wt.get_table("table")); + wt.add_table("table 2"); + wt.commit(); + } + + { // change from encrypted to unencrypted + options.encryption_key = nullptr; + auto db = DB::create(path, options); + WriteTransaction wt(db); + CHECK_NOT(wt.get_table("table 2")); + wt.add_table("table 3"); + wt.commit(); + } + + { // change from unencrypted to encrypted + options.encryption_key = key_1; + auto db = DB::create(path, options); + WriteTransaction wt(db); + CHECK_NOT(wt.get_table("table 3")); + wt.add_table("table 4"); + wt.commit(); + } + + { // sanity check that reopening encrypted with the same key works + auto db = DB::create(path, options); + CHECK(db->start_read()->get_table("table 4")); + } +} + +TEST(Shared_ClearOnError_CannotClearWhileFileIsOpen) +{ + SHARED_GROUP_TEST_PATH(path); + DBOptions options; + options.clear_on_invalid_file = true; + auto db = DB::create(make_in_realm_history(), path, options); + options.encryption_key = crypt_key(true); + CHECK_THROW(DB::create(make_in_realm_history(), path, options), InvalidDatabase); +} +#endif + #endif // TEST_SHARED diff --git a/test/test_upgrade_database.cpp b/test/test_upgrade_database.cpp index 788438cf7b7..da76d0542bf 100644 --- a/test/test_upgrade_database.cpp +++ b/test/test_upgrade_database.cpp @@ -83,12 +83,13 @@ TEST_IF(Upgrade_DatabaseWithCallback, REALM_MAX_BPNODE_SIZE == 1000) bool did_upgrade = false; DBOptions options; + options.no_create = true; options.upgrade_callback = [&](int old_version, int new_version) { did_upgrade = true; CHECK_EQUAL(old_version, 20); CHECK_EQUAL(new_version, Group::get_current_file_format_version()); }; - DB::create(temp_copy, true, options); + DB::create(temp_copy, options); CHECK(did_upgrade); } @@ -102,10 +103,11 @@ TEST_IF(Upgrade_DatabaseWithCallbackWithException, REALM_MAX_BPNODE_SIZE == 1000 // Callback that throws should revert the upgrade DBOptions options; + options.no_create = true; options.upgrade_callback = [&](int, int) { throw 123; }; - CHECK_THROW(DB::create(temp_copy, true, options), int); + CHECK_THROW(DB::create(temp_copy, options), int); // Callback should be triggered here because the file still needs to be upgraded bool did_upgrade = false; @@ -114,12 +116,12 @@ TEST_IF(Upgrade_DatabaseWithCallbackWithException, REALM_MAX_BPNODE_SIZE == 1000 CHECK_EQUAL(old_version, 20); CHECK_EQUAL(new_version, Group::get_current_file_format_version()); }; - DB::create(temp_copy, true, options); + DB::create(temp_copy, options); CHECK(did_upgrade); // Callback should not be triggered here because the file is already upgraded did_upgrade = false; - DB::create(temp_copy, true, options); + DB::create(temp_copy, options); CHECK_NOT(did_upgrade); }