From 2e90160b6c68abe19351691a06e201c66101dcb3 Mon Sep 17 00:00:00 2001 From: Christoph Erhardt Date: Thu, 16 Oct 2025 18:44:07 +0200 Subject: [PATCH 1/4] fix(storage): segfault during database migration The `akonadi-db-migrator` tool reproducibly crashes with a segmentation fault (or a failed assertion if built in debug mode) while executing `prepareDatabase()` on the destination configuration. The root cause is that both `DbInitializer` and `DbUpdater` expect `SchemaVersionTable` to be populated. This assumption is not met by a freshly created destination database: the table itself exists, but it is empty at this point. Consequently, `SchemaVersion::retrieveAll()` returns an empty list and the subsequent attempt to obtain its first element triggers an out-of-bounds read. Fix the issue by checking for an empty result list in both places. Refs: https://bugs.kde.org/show_bug.cgi?id=493393 --- src/server/storage/dbinitializer.cpp | 17 +++++++++++------ src/server/storage/dbupdater.cpp | 8 ++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/server/storage/dbinitializer.cpp b/src/server/storage/dbinitializer.cpp index 01b3359b7..3f430ae4d 100644 --- a/src/server/storage/dbinitializer.cpp +++ b/src/server/storage/dbinitializer.cpp @@ -79,12 +79,17 @@ bool DbInitializer::run() #ifndef DBINITIALIZER_UNITTEST // Now finally check and set the generation identifier if necessary auto store = DataStore::dataStoreForDatabase(mDatabase); - SchemaVersion version = SchemaVersion::retrieveAll(store).at(0); - if (version.generation() == 0) { - version.setGeneration(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); - version.update(store); - - qCDebug(AKONADISERVER_LOG) << "Generation:" << version.generation(); + const auto schemaVersions = SchemaVersion::retrieveAll(store); + if (schemaVersions.empty()) { + qCDebug(AKONADISERVER_LOG) << "DbInitializer: SchemaVersion not found; skipping generation update"; + } else { + auto version = schemaVersions.at(0); + if (version.generation() == 0) { + version.setGeneration(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); + version.update(store); + + qCDebug(AKONADISERVER_LOG) << "Generation:" << version.generation(); + } } #endif diff --git a/src/server/storage/dbupdater.cpp b/src/server/storage/dbupdater.cpp index 527cb4f65..38976e0ea 100644 --- a/src/server/storage/dbupdater.cpp +++ b/src/server/storage/dbupdater.cpp @@ -42,9 +42,13 @@ DbUpdater::DbUpdater(const QSqlDatabase &database, const QString &filename) bool DbUpdater::run() { - // TODO error handling auto store = DataStore::dataStoreForDatabase(m_database); - auto currentVersion = SchemaVersion::retrieveAll(store).at(0); + const auto schemaVersions = SchemaVersion::retrieveAll(store); + if (schemaVersions.empty()) { + qCDebug(AKONADISERVER_LOG) << "DbUpdater: SchemaVersion not found; skipping schema update"; + return true; + } + auto currentVersion = schemaVersions.at(0); UpdateSet::Map updates; -- GitLab From 63eb46ac4c77ed57a7b9afee2ecddfba9a3aa3a4 Mon Sep 17 00:00:00 2001 From: Christoph Erhardt Date: Fri, 17 Oct 2025 23:46:59 +0200 Subject: [PATCH 2/4] fix(dbmigrator): remove leftover entries in `db_migration` directory This avoids potential issues when `db_migration` already contains a stale database created with an old schema version. --- src/server/dbmigrator/dbmigrator.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/server/dbmigrator/dbmigrator.cpp b/src/server/dbmigrator/dbmigrator.cpp index 575a9da16..402e55452 100644 --- a/src/server/dbmigrator/dbmigrator.cpp +++ b/src/server/dbmigrator/dbmigrator.cpp @@ -326,6 +326,19 @@ std::unique_ptr dbConfigFromServerRc(const QString &configFile, bool o } const auto dbPath = overrideDbPath ? StandardDirs::saveDir("data", QStringLiteral("db_migration%1").arg(dbPathSuffix)) : QString{}; + + if (overrideDbPath) { + // Remove any directory entries left over from a previous run + try { + for (const auto &dirEntry : std::filesystem::directory_iterator(dbPath.toStdString())) { + std::filesystem::remove_all(dirEntry.path()); + } + } catch (const std::filesystem::filesystem_error &e) { + qCCritical(AKONADIDBMIGRATOR_LOG) << "Error: failed to clear migration directory " << dbPath << ": " << e.what(); + return {}; + } + } + config->init(settings, true, dbPath); return config; -- GitLab From 9a730ddf1938666d98552d592de1c9867ce6dc84 Mon Sep 17 00:00:00 2001 From: Christoph Erhardt Date: Thu, 16 Oct 2025 18:45:14 +0200 Subject: [PATCH 3/4] chore: remove unused `#include`s Reported by clang-tidy. --- src/server/collectionscheduler.cpp | 1 - src/server/storage/collectiontreecache.h | 2 -- src/server/storage/dbinitializer.cpp | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/server/collectionscheduler.cpp b/src/server/collectionscheduler.cpp index 41ca84ece..82098600d 100644 --- a/src/server/collectionscheduler.cpp +++ b/src/server/collectionscheduler.cpp @@ -9,7 +9,6 @@ #include "storage/datastore.h" #include "storage/selectquerybuilder.h" -#include "private/tristate_p.h" #include #include diff --git a/src/server/storage/collectiontreecache.h b/src/server/storage/collectiontreecache.h index d64e73cdb..cd741e749 100644 --- a/src/server/storage/collectiontreecache.h +++ b/src/server/storage/collectiontreecache.h @@ -9,8 +9,6 @@ #include "akthread.h" #include "entities.h" -#include "private/tristate_p.h" - #include #include #include diff --git a/src/server/storage/dbinitializer.cpp b/src/server/storage/dbinitializer.cpp index 3f430ae4d..3a58acf89 100644 --- a/src/server/storage/dbinitializer.cpp +++ b/src/server/storage/dbinitializer.cpp @@ -20,8 +20,6 @@ #include -#include "private/tristate_p.h" - using namespace Akonadi::Server; DbInitializer::Ptr DbInitializer::createInstance(const QSqlDatabase &database, Schema *schema) -- GitLab From 19740df0fd4eaf2c5bb6e3d53a1daa32496ee01d Mon Sep 17 00:00:00 2001 From: Christoph Erhardt Date: Thu, 16 Oct 2025 18:46:15 +0200 Subject: [PATCH 4/4] chore: use `QDateTime::currentSecsSinceEpoch()` Recommended by clazy. --- src/server/search/searchmanager.cpp | 2 +- src/server/storage/dbinitializer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/search/searchmanager.cpp b/src/server/search/searchmanager.cpp index 7f7d7d05d..17072f28d 100644 --- a/src/server/search/searchmanager.cpp +++ b/src/server/search/searchmanager.cpp @@ -294,7 +294,7 @@ void SearchManager::updateSearchImpl(const Collection &collection) } // Query all plugins for search results - const QByteArray id = "searchUpdate-" + QByteArray::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); + const QByteArray id = "searchUpdate-" + QByteArray::number(QDateTime::currentSecsSinceEpoch()); SearchRequest request(id, *this, mAgentSearchManager); request.setCollections(queryCollections); request.setMimeTypes(queryMimeTypes); diff --git a/src/server/storage/dbinitializer.cpp b/src/server/storage/dbinitializer.cpp index 3a58acf89..4e8d18a07 100644 --- a/src/server/storage/dbinitializer.cpp +++ b/src/server/storage/dbinitializer.cpp @@ -83,7 +83,7 @@ bool DbInitializer::run() } else { auto version = schemaVersions.at(0); if (version.generation() == 0) { - version.setGeneration(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); + version.setGeneration(QDateTime::currentSecsSinceEpoch()); version.update(store); qCDebug(AKONADISERVER_LOG) << "Generation:" << version.generation(); -- GitLab