From 2e90160b6c68abe19351691a06e201c66101dcb3 Mon Sep 17 00:00:00 2001
From: Christoph Erhardt <kde@sicherha.de>
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 <christoph.erhardt@sicherha.de>
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<DbConfig> 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 <kde@sicherha.de>
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 <chrono>
 
 #include <QDateTime>
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 <QHash>
 #include <QList>
 #include <QReadWriteLock>
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 <algorithm>
 
-#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 <kde@sicherha.de>
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

