File indexing completed on 2025-01-05 04:46:57

0001 /*
0002     SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dbconfigpostgresql.h"
0008 #include "akonadiserver_debug.h"
0009 #include "utils.h"
0010 
0011 #include "private/standarddirs_p.h"
0012 #include "shared/akranges.h"
0013 
0014 #include <QDir>
0015 #include <QDirIterator>
0016 #include <QProcess>
0017 #include <QRegularExpression>
0018 #include <QRegularExpressionMatch>
0019 #include <QSqlDriver>
0020 #include <QSqlError>
0021 #include <QSqlQuery>
0022 #include <QStandardPaths>
0023 
0024 #include <config-akonadi.h>
0025 #if HAVE_UNISTD_H
0026 #include <unistd.h>
0027 #endif
0028 #include <chrono>
0029 #include <filesystem>
0030 
0031 using namespace std::chrono_literals;
0032 
0033 using namespace Akonadi;
0034 using namespace Akonadi::Server;
0035 using namespace AkRanges;
0036 
0037 namespace
0038 {
0039 
0040 const QString s_initConnection = QStringLiteral("initConnectionPsql");
0041 
0042 } // namespace
0043 
0044 DbConfigPostgresql::DbConfigPostgresql(const QString &configFile)
0045     : DbConfig(configFile)
0046 {
0047 }
0048 
0049 QString DbConfigPostgresql::driverName() const
0050 {
0051     return QStringLiteral("QPSQL");
0052 }
0053 
0054 QString DbConfigPostgresql::databaseName() const
0055 {
0056     return mDatabaseName;
0057 }
0058 
0059 QString DbConfigPostgresql::databasePath() const
0060 {
0061     return mPgData;
0062 }
0063 
0064 void DbConfigPostgresql::setDatabasePath(const QString &path, QSettings &settings)
0065 {
0066     mPgData = path;
0067     settings.beginGroup(driverName());
0068     settings.setValue(QStringLiteral("PgData"), mPgData);
0069     settings.endGroup();
0070     settings.sync();
0071 }
0072 
0073 namespace
0074 {
0075 struct VersionCompare {
0076     bool operator()(const QFileInfo &lhsFi, const QFileInfo &rhsFi) const
0077     {
0078         const auto lhs = parseVersion(lhsFi.fileName());
0079         if (!lhs.has_value()) {
0080             return false;
0081         }
0082         const auto rhs = parseVersion(rhsFi.fileName());
0083         if (!rhs.has_value()) {
0084             return true;
0085         }
0086 
0087         return std::tie(lhs->major, lhs->minor) < std::tie(rhs->major, rhs->minor);
0088     }
0089 
0090 private:
0091     struct Version {
0092         int major;
0093         int minor;
0094     };
0095     std::optional<Version> parseVersion(const QString &name) const
0096     {
0097         const auto dotIdx = name.indexOf(QLatin1Char('.'));
0098         if (dotIdx == -1) {
0099             return {};
0100         }
0101         bool ok = false;
0102         const auto major = QStringView(name).left(dotIdx).toInt(&ok);
0103         if (!ok) {
0104             return {};
0105         }
0106         const auto minor = QStringView(name).mid(dotIdx + 1).toInt(&ok);
0107         if (!ok) {
0108             return {};
0109         }
0110         return Version{major, minor};
0111     }
0112 };
0113 
0114 } // namespace
0115 
0116 QStringList DbConfigPostgresql::postgresSearchPaths(const QString &versionedPath) const
0117 {
0118     QStringList paths;
0119 
0120 #ifdef POSTGRES_PATH
0121     const QString dir(QStringLiteral(POSTGRES_PATH));
0122     if (QDir(dir).exists()) {
0123         paths.push_back(QStringLiteral(POSTGRES_PATH));
0124     }
0125 #endif
0126     paths << QStringLiteral("/usr/bin") << QStringLiteral("/usr/sbin") << QStringLiteral("/usr/local/sbin");
0127 
0128     // Locate all versions in /usr/lib/postgresql (i.e. /usr/lib/postgresql/X.Y) in reversed
0129     // sorted order, so we search from the newest one to the oldest.
0130     QDir versionedDir(versionedPath);
0131     if (versionedDir.exists()) {
0132         auto versionedDirs = versionedDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
0133         qDebug() << versionedDirs;
0134         std::sort(versionedDirs.begin(), versionedDirs.end(), VersionCompare());
0135         std::reverse(versionedDirs.begin(), versionedDirs.end());
0136         paths += versionedDirs | Views::transform([](const auto &dir) -> QString {
0137                      return dir.absoluteFilePath() + QStringLiteral("/bin");
0138                  })
0139             | Actions::toQList;
0140     }
0141 
0142     return paths;
0143 }
0144 
0145 bool DbConfigPostgresql::init(QSettings &settings, bool storeSettings, const QString &dbPathOverride)
0146 {
0147     // determine default settings depending on the driver
0148     QString defaultHostName;
0149     QString defaultOptions;
0150     QString defaultServerPath;
0151     QString defaultInitDbPath;
0152     QString defaultPgUpgradePath;
0153     QString defaultPgData;
0154 
0155 #ifndef Q_WS_WIN // We assume that PostgreSQL is running as service on Windows
0156     const bool defaultInternalServer = true;
0157 #else
0158     const bool defaultInternalServer = false;
0159 #endif
0160 
0161     mInternalServer = settings.value(QStringLiteral("QPSQL/StartServer"), defaultInternalServer).toBool();
0162     if (mInternalServer) {
0163         const auto paths = postgresSearchPaths(QStringLiteral("/usr/lib/postgresql"));
0164 
0165         defaultServerPath = QStandardPaths::findExecutable(QStringLiteral("pg_ctl"), paths);
0166         defaultInitDbPath = QStandardPaths::findExecutable(QStringLiteral("initdb"), paths);
0167         defaultHostName = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")));
0168         defaultPgUpgradePath = QStandardPaths::findExecutable(QStringLiteral("pg_upgrade"), paths);
0169         defaultPgData = dbPathOverride.isEmpty() ? StandardDirs::saveDir("data", QStringLiteral("db_data")) : dbPathOverride;
0170     }
0171 
0172     // read settings for current driver
0173     settings.beginGroup(driverName());
0174     mDatabaseName = settings.value(QStringLiteral("Name"), defaultDatabaseName()).toString();
0175     if (mDatabaseName.isEmpty()) {
0176         mDatabaseName = defaultDatabaseName();
0177     }
0178     mHostName = settings.value(QStringLiteral("Host"), defaultHostName).toString();
0179     if (mHostName.isEmpty()) {
0180         mHostName = defaultHostName;
0181     }
0182     mHostPort = settings.value(QStringLiteral("Port")).toInt();
0183     // User, password and Options can be empty and still valid, so don't override them
0184     mUserName = settings.value(QStringLiteral("User")).toString();
0185     mPassword = settings.value(QStringLiteral("Password")).toString();
0186     mConnectionOptions = settings.value(QStringLiteral("Options"), defaultOptions).toString();
0187     mServerPath = settings.value(QStringLiteral("ServerPath"), defaultServerPath).toString();
0188     if (mInternalServer && mServerPath.isEmpty()) {
0189         mServerPath = defaultServerPath;
0190     }
0191     qCDebug(AKONADISERVER_LOG) << "Found pg_ctl:" << mServerPath;
0192     mInitDbPath = settings.value(QStringLiteral("InitDbPath"), defaultInitDbPath).toString();
0193     if (mInternalServer && mInitDbPath.isEmpty()) {
0194         mInitDbPath = defaultInitDbPath;
0195     }
0196     qCDebug(AKONADISERVER_LOG) << "Found initdb:" << mServerPath;
0197     mPgUpgradePath = settings.value(QStringLiteral("UpgradePath"), defaultPgUpgradePath).toString();
0198     if (mInternalServer && mPgUpgradePath.isEmpty()) {
0199         mPgUpgradePath = defaultPgUpgradePath;
0200     }
0201     qCDebug(AKONADISERVER_LOG) << "Found pg_upgrade:" << mPgUpgradePath;
0202     mPgData = settings.value(QStringLiteral("PgData"), defaultPgData).toString();
0203     if (mPgData.isEmpty()) {
0204         mPgData = defaultPgData;
0205     }
0206     settings.endGroup();
0207 
0208     if (storeSettings) {
0209         // store back the default values
0210         settings.beginGroup(driverName());
0211         settings.setValue(QStringLiteral("Name"), mDatabaseName);
0212         settings.setValue(QStringLiteral("Host"), mHostName);
0213         if (mHostPort) {
0214             settings.setValue(QStringLiteral("Port"), mHostPort);
0215         }
0216         settings.setValue(QStringLiteral("Options"), mConnectionOptions);
0217         settings.setValue(QStringLiteral("ServerPath"), mServerPath);
0218         settings.setValue(QStringLiteral("InitDbPath"), mInitDbPath);
0219         settings.setValue(QStringLiteral("StartServer"), mInternalServer);
0220         settings.setValue(QStringLiteral("PgData"), mPgData);
0221         settings.endGroup();
0222         settings.sync();
0223     }
0224 
0225     return true;
0226 }
0227 
0228 bool DbConfigPostgresql::isAvailable(QSettings &settings)
0229 {
0230     if (!QSqlDatabase::drivers().contains(driverName())) {
0231         return false;
0232     }
0233 
0234     if (!init(settings, false)) {
0235         return false;
0236     }
0237 
0238     if (mInternalServer) {
0239         if (mInitDbPath.isEmpty() || !QFile::exists(mInitDbPath)) {
0240             return false;
0241         }
0242 
0243         if (mServerPath.isEmpty() || !QFile::exists(mServerPath)) {
0244             return false;
0245         }
0246     }
0247 
0248     return true;
0249 }
0250 
0251 void DbConfigPostgresql::apply(QSqlDatabase &database)
0252 {
0253     if (!mDatabaseName.isEmpty()) {
0254         database.setDatabaseName(mDatabaseName);
0255     }
0256     if (!mHostName.isEmpty()) {
0257         database.setHostName(mHostName);
0258     }
0259     if (mHostPort > 0 && mHostPort < 65535) {
0260         database.setPort(mHostPort);
0261     }
0262     if (!mUserName.isEmpty()) {
0263         database.setUserName(mUserName);
0264     }
0265     if (!mPassword.isEmpty()) {
0266         database.setPassword(mPassword);
0267     }
0268 
0269     database.setConnectOptions(mConnectionOptions);
0270 
0271     // can we check that during init() already?
0272     Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId));
0273 }
0274 
0275 bool DbConfigPostgresql::useInternalServer() const
0276 {
0277     return mInternalServer;
0278 }
0279 
0280 std::optional<DbConfigPostgresql::Versions> DbConfigPostgresql::checkPgVersion() const
0281 {
0282     // Contains major version of Postgres that created the cluster
0283     QFile pgVersionFile(QStringLiteral("%1/PG_VERSION").arg(mPgData));
0284     if (!pgVersionFile.open(QIODevice::ReadOnly)) {
0285         return std::nullopt;
0286     }
0287     const auto clusterVersion = pgVersionFile.readAll().toInt();
0288 
0289     QProcess pgctl;
0290     pgctl.start(mServerPath, {QStringLiteral("--version")}, QIODevice::ReadOnly);
0291     if (!pgctl.waitForFinished()) {
0292         return std::nullopt;
0293     }
0294     // Looks like "pg_ctl (PostgreSQL) 11.2"
0295     const auto output = QString::fromUtf8(pgctl.readAll());
0296 
0297     // Get the major version from major.minor
0298     QRegularExpression re(QStringLiteral("\\(PostgreSQL\\) ([0-9]+).[0-9]+"));
0299     const auto match = re.match(output);
0300     if (!match.hasMatch()) {
0301         return std::nullopt;
0302     }
0303     const auto serverVersion = match.captured(1).toInt();
0304 
0305     qDebug(AKONADISERVER_LOG) << "Detected psql versions - cluster:" << clusterVersion << ", server:" << serverVersion;
0306     return {{clusterVersion, serverVersion}};
0307 }
0308 
0309 bool DbConfigPostgresql::runInitDb(const QString &newDbPath)
0310 {
0311     // Make sure the cluster directory exists
0312     if (!QDir(newDbPath).exists()) {
0313         if (!QDir().mkpath(newDbPath)) {
0314             return false;
0315         }
0316     }
0317 
0318 #ifdef Q_OS_LINUX
0319     // It is recommended to disable CoW feature when running on Btrfs to improve
0320     // database performance. This only has effect when done on empty directory,
0321     // so we only call this before calling initdb
0322     if (Utils::getDirectoryFileSystem(newDbPath) == QLatin1StringView("btrfs")) {
0323         Utils::disableCoW(newDbPath);
0324     }
0325 #endif
0326 
0327     // call 'initdb --pgdata=/home/user/.local/share/akonadi/data_db'
0328     return execute(mInitDbPath, {QStringLiteral("--pgdata=%1").arg(newDbPath), QStringLiteral("--encoding=UTF8"), QStringLiteral("--no-locale")}) == 0;
0329 }
0330 
0331 namespace
0332 {
0333 std::optional<QString> findBinPathForVersion(int version)
0334 {
0335     // First we need to find where the previous PostgreSQL version binaries are available
0336     const auto oldBinSearchPaths = {
0337         QStringLiteral("/usr/lib64/pgsql/postgresql-%1/bin").arg(version), // Fedora & friends
0338         QStringLiteral("/usr/lib/pgsql/postgresql-%1/bin").arg(version),
0339         QStringLiteral("/usr/lib/postgresql/%1/bin").arg(version), // Debian-based
0340         QStringLiteral("/opt/pgsql-%1/bin").arg(version), // Arch Linux
0341         // TODO: Check other distros as well, they might do things differently.
0342     };
0343 
0344     for (const auto &path : oldBinSearchPaths) {
0345         if (QDir(path).exists()) {
0346             return path;
0347         }
0348     }
0349 
0350     return std::nullopt;
0351 }
0352 
0353 bool checkAndRemoveTmpCluster(const QDir &baseDir, const QString &clusterName)
0354 {
0355     if (baseDir.exists(clusterName)) {
0356         qCInfo(AKONADISERVER_LOG) << "Postgres cluster update:" << clusterName << "cluster already exists, trying to remove it first";
0357         if (!QDir(baseDir.path() + QDir::separator() + clusterName).removeRecursively()) {
0358             qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to remove" << clusterName
0359                                          << "cluster from some previous run, not performing auto-upgrade";
0360             return false;
0361         }
0362     }
0363     return true;
0364 }
0365 
0366 bool runPgUpgrade(const QString &pgUpgrade,
0367                   const QDir &baseDir,
0368                   const QString &oldBinPath,
0369                   const QString &newBinPath,
0370                   const QString &oldDbData,
0371                   const QString &newDbData)
0372 {
0373     QProcess process;
0374     const QStringList args = {QString(QStringLiteral("--old-bindir=%1").arg(oldBinPath)),
0375                               QString(QStringLiteral("--new-bindir=%1").arg(newBinPath)),
0376                               QString(QStringLiteral("--old-datadir=%1").arg(oldDbData)),
0377                               QString(QStringLiteral("--new-datadir=%1").arg(newDbData))};
0378     qCInfo(AKONADISERVER_LOG) << "Postgres cluster update: starting pg_upgrade to upgrade your Akonadi DB cluster";
0379     qCDebug(AKONADISERVER_LOG) << "Executing pg_upgrade" << QStringList(args);
0380     process.setWorkingDirectory(baseDir.path());
0381     process.start(pgUpgrade, args);
0382     process.waitForFinished(std::chrono::milliseconds(1h).count());
0383     if (process.exitCode() != 0) {
0384         qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: pg_upgrade finished with exit code" << process.exitCode()
0385                                      << ", please run migration manually.";
0386         return false;
0387     }
0388 
0389     qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: pg_upgrade finished successfully.";
0390     return true;
0391 }
0392 
0393 bool swapClusters(QDir &baseDir, const QString &oldDbDataCluster, const QString &newDbDataCluster)
0394 {
0395     // If everything went fine, swap the old and new clusters
0396     if (!baseDir.rename(QStringLiteral("db_data"), oldDbDataCluster)) {
0397         qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to rename old db_data to" << oldDbDataCluster;
0398         return false;
0399     }
0400     if (!baseDir.rename(newDbDataCluster, QStringLiteral("db_data"))) {
0401         qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to rename" << newDbDataCluster << "to db_data, rolling back";
0402         if (!baseDir.rename(oldDbDataCluster, QStringLiteral("db_data"))) {
0403             qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to roll back from" << oldDbDataCluster << "to db_data.";
0404             return false;
0405         }
0406         qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: rollback successful.";
0407         return false;
0408     }
0409 
0410     return true;
0411 }
0412 
0413 } // namespace
0414 
0415 bool DbConfigPostgresql::upgradeCluster(int clusterVersion)
0416 {
0417     const auto oldDbDataCluster = QStringLiteral("old_db_data");
0418     const auto newDbDataCluster = QStringLiteral("new_db_data");
0419 
0420     QDir baseDir(mPgData); // db_data
0421     baseDir.cdUp(); // move to its parent folder
0422 
0423     const auto oldBinPath = findBinPathForVersion(clusterVersion);
0424     if (!oldBinPath.has_value()) {
0425         qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: failed to find Postgres server for version" << clusterVersion;
0426         return false;
0427     }
0428     const auto newBinPath = QFileInfo(mServerPath).path();
0429 
0430     if (!checkAndRemoveTmpCluster(baseDir, oldDbDataCluster)) {
0431         return false;
0432     }
0433     if (!checkAndRemoveTmpCluster(baseDir, newDbDataCluster)) {
0434         return false;
0435     }
0436 
0437     // Next, initialize a new cluster
0438     const QString newDbData = baseDir.path() + QDir::separator() + newDbDataCluster;
0439     qCInfo(AKONADISERVER_LOG) << "Postgres cluster upgrade: creating a new cluster for current Postgres server";
0440     if (!runInitDb(newDbData)) {
0441         qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to initialize new db cluster";
0442         return false;
0443     }
0444 
0445     // Now migrate the old cluster from the old version into the new cluster
0446     if (!runPgUpgrade(mPgUpgradePath, baseDir, *oldBinPath, newBinPath, mPgData, newDbData)) {
0447         return false;
0448     }
0449 
0450     if (!swapClusters(baseDir, oldDbDataCluster, newDbDataCluster)) {
0451         return false;
0452     }
0453 
0454     // Drop the old cluster
0455     if (!QDir(baseDir.path() + QDir::separator() + oldDbDataCluster).removeRecursively()) {
0456         qCInfo(AKONADISERVER_LOG) << "Postgres cluster update: failed to remove" << oldDbDataCluster << "cluster (not an issue, continuing)";
0457     }
0458 
0459     return true;
0460 }
0461 
0462 bool DbConfigPostgresql::startInternalServer()
0463 {
0464     // We defined the mHostName to the socket directory, during init
0465     const QString socketDir = mHostName;
0466     bool success = true;
0467 
0468     // Make sure the path exists, otherwise pg_ctl fails
0469     if (!QFile::exists(socketDir)) {
0470         QDir().mkpath(socketDir);
0471     }
0472 
0473 // TODO Windows support
0474 #ifndef Q_WS_WIN
0475     // If postmaster.pid exists, check whether the postgres process still exists too,
0476     // because normally we shouldn't be able to get this far if Akonadi is already
0477     // running. If postgres is not running, then the pidfile was left after a system
0478     // crash or something similar and we can remove it (otherwise pg_ctl won't start)
0479     QFile postmaster(QStringLiteral("%1/postmaster.pid").arg(mPgData));
0480     if (postmaster.exists() && postmaster.open(QIODevice::ReadOnly)) {
0481         qCDebug(AKONADISERVER_LOG) << "Found a postmaster.pid pidfile, checking whether the server is still running...";
0482         QByteArray pid = postmaster.readLine();
0483         // Remove newline character
0484         pid.chop(1);
0485         QFile proc(QString::fromLatin1("/proc/" + pid + "/stat"));
0486         // Check whether the process with the PID from pidfile still exists and whether
0487         // it's actually still postgres or, whether the PID has been recycled in the
0488         // meanwhile.
0489         if (proc.open(QIODevice::ReadOnly)) {
0490             const QByteArray stat = proc.readAll();
0491             const QList<QByteArray> stats = stat.split(' ');
0492             if (stats.count() > 1) {
0493                 // Make sure the PID actually belongs to postgres process
0494                 if (stats[1] == "(postgres)") {
0495                     // Yup, our PostgreSQL is actually running, so pretend we started the server
0496                     // and try to connect to it
0497                     qCWarning(AKONADISERVER_LOG) << "PostgreSQL for Akonadi is already running, trying to connect to it.";
0498                     return true;
0499                 }
0500             }
0501             proc.close();
0502         }
0503 
0504         qCDebug(AKONADISERVER_LOG) << "No postgres process with specified PID is running. Removing the pidfile and starting a new Postgres instance...";
0505         postmaster.close();
0506         postmaster.remove();
0507     }
0508 #endif
0509 
0510     // postgres data directory not initialized yet, so call initdb on it
0511     if (!QFile::exists(QStringLiteral("%1/PG_VERSION").arg(mPgData))) {
0512 #ifdef Q_OS_LINUX
0513         // It is recommended to disable CoW feature when running on Btrfs to improve
0514         // database performance. This only has effect when done on an empty directory,
0515         // so we call this before calling initdb.
0516         if (Utils::getDirectoryFileSystem(mPgData) == QLatin1StringView("btrfs")) {
0517             Utils::disableCoW(mPgData);
0518         }
0519 #endif
0520         // call 'initdb --pgdata=/home/user/.local/share/akonadi/db_data'
0521         execute(mInitDbPath, {QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--encoding=UTF8"), QStringLiteral("--no-locale")});
0522     } else {
0523         const auto versions = checkPgVersion();
0524         if (versions.has_value() && (versions->clusterVersion < versions->pgServerVersion)) {
0525             qCInfo(AKONADISERVER_LOG) << "Cluster PG_VERSION is" << versions->clusterVersion << ", PostgreSQL server is version " << versions->pgServerVersion
0526                                       << ", will attempt to upgrade the cluster";
0527             if (upgradeCluster(versions->clusterVersion)) {
0528                 qCInfo(AKONADISERVER_LOG) << "Successfully upgraded db cluster from Postgres" << versions->clusterVersion << "to" << versions->pgServerVersion;
0529             } else {
0530                 qCWarning(AKONADISERVER_LOG) << "Postgres db cluster upgrade failed, Akonadi will fail to start. Sorry.";
0531             }
0532         }
0533     }
0534 
0535     // synthesize the postgres command
0536     QStringList arguments;
0537     arguments << QStringLiteral("start") << QStringLiteral("-w") << QStringLiteral("--timeout=10") // default is 60 seconds.
0538               << QStringLiteral("--pgdata=%1").arg(mPgData)
0539               // These options are passed to postgres
0540               //  -k - directory for unix domain socket communication
0541               //  -h - disable listening for TCP/IP
0542               << QStringLiteral("-o \"-k%1\" -h ''").arg(socketDir);
0543 
0544     qCDebug(AKONADISERVER_LOG) << "Executing:" << mServerPath << arguments.join(QLatin1Char(' '));
0545     QProcess pgCtl;
0546     pgCtl.start(mServerPath, arguments);
0547     if (!pgCtl.waitForStarted()) {
0548         qCCritical(AKONADISERVER_LOG) << "Could not start database server!";
0549         qCCritical(AKONADISERVER_LOG) << "executable:" << mServerPath;
0550         qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
0551         qCCritical(AKONADISERVER_LOG) << "process error:" << pgCtl.errorString();
0552         return false;
0553     }
0554 
0555     {
0556         QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QPSQL"), s_initConnection);
0557         apply(db);
0558 
0559         // use the default database that is always available
0560         db.setDatabaseName(QStringLiteral("postgres"));
0561 
0562         if (!db.isValid()) {
0563             qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup";
0564             return false;
0565         }
0566 
0567         bool opened = false;
0568         for (int i = 0; i < 120; ++i) {
0569             opened = db.open();
0570             if (opened) {
0571                 break;
0572             }
0573 
0574             if (pgCtl.waitForFinished(500) && pgCtl.exitCode()) {
0575                 qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!";
0576                 qCCritical(AKONADISERVER_LOG) << "executable:" << mServerPath;
0577                 qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
0578                 qCCritical(AKONADISERVER_LOG) << "stdout:" << pgCtl.readAllStandardOutput();
0579                 qCCritical(AKONADISERVER_LOG) << "stderr:" << pgCtl.readAllStandardError();
0580                 qCCritical(AKONADISERVER_LOG) << "exit code:" << pgCtl.exitCode();
0581                 qCCritical(AKONADISERVER_LOG) << "process error:" << pgCtl.errorString();
0582                 return false;
0583             }
0584         }
0585 
0586         if (opened) {
0587             {
0588                 QSqlQuery query(db);
0589 
0590                 // check if the 'akonadi' database already exists
0591                 query.exec(QStringLiteral("SELECT 1 FROM pg_catalog.pg_database WHERE datname = '%1'").arg(mDatabaseName));
0592 
0593                 // if not, create it
0594                 if (!query.first()) {
0595                     if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(mDatabaseName))) {
0596                         qCCritical(AKONADISERVER_LOG) << "Failed to create database";
0597                         qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
0598                         qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
0599                         success = false;
0600                     }
0601                 }
0602             } // make sure query is destroyed before we close the db
0603             db.close();
0604         }
0605     }
0606     // Make sure pg_ctl has returned
0607     pgCtl.waitForFinished();
0608 
0609     QSqlDatabase::removeDatabase(s_initConnection);
0610     return success;
0611 }
0612 
0613 void DbConfigPostgresql::stopInternalServer()
0614 {
0615     if (!checkServerIsRunning()) {
0616         qCDebug(AKONADISERVER_LOG) << "Database is no longer running";
0617         return;
0618     }
0619 
0620     // first, try a FAST shutdown
0621     execute(mServerPath, {QStringLiteral("stop"), QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--mode=fast")});
0622     if (!checkServerIsRunning()) {
0623         return;
0624     }
0625 
0626     // second, try an IMMEDIATE shutdown
0627     execute(mServerPath, {QStringLiteral("stop"), QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--mode=immediate")});
0628     if (!checkServerIsRunning()) {
0629         return;
0630     }
0631 
0632     // third, pg_ctl couldn't terminate all the postgres processes, we have to
0633     // kill the master one. We don't want to do that, but we've passed the last
0634     // call. pg_ctl is used to send the kill signal (safe when kill is not
0635     // supported by OS)
0636     const QString pidFileName = QStringLiteral("%1/postmaster.pid").arg(mPgData);
0637     QFile pidFile(pidFileName);
0638     if (pidFile.open(QIODevice::ReadOnly)) {
0639         QString postmasterPid = QString::fromUtf8(pidFile.readLine(0).trimmed());
0640         qCCritical(AKONADISERVER_LOG) << "The postmaster is still running. Killing it.";
0641 
0642         execute(mServerPath, {QStringLiteral("kill"), QStringLiteral("ABRT"), postmasterPid});
0643     }
0644 }
0645 
0646 bool DbConfigPostgresql::checkServerIsRunning()
0647 {
0648     const QString command = mServerPath;
0649     QStringList arguments;
0650     arguments << QStringLiteral("status") << QStringLiteral("--pgdata=%1").arg(mPgData);
0651 
0652     QProcess pgCtl;
0653     pgCtl.start(command, arguments, QIODevice::ReadOnly);
0654     if (!pgCtl.waitForFinished(3000)) {
0655         // Error?
0656         return false;
0657     }
0658 
0659     // "pg_ctl status" exits with 0 when server is running and a non-zero code when not.
0660     return pgCtl.exitCode() == 0;
0661 }
0662 
0663 bool DbConfigPostgresql::disableConstraintChecks(const QSqlDatabase &db)
0664 {
0665     for (const auto &table : db.tables()) {
0666         qCDebug(AKONADISERVER_LOG) << "Disabling triggers on table" << table;
0667         QSqlQuery query(db);
0668         if (!query.exec(QStringLiteral("ALTER TABLE %1 DISABLE TRIGGER ALL").arg(table))) {
0669             qCWarning(AKONADISERVER_LOG) << "Failed to disable triggers on table" << table << ":" << query.lastError().databaseText();
0670             enableConstraintChecks(db);
0671             return false;
0672         }
0673     }
0674 
0675     return true;
0676 }
0677 
0678 bool DbConfigPostgresql::enableConstraintChecks(const QSqlDatabase &db)
0679 {
0680     for (const auto &table : db.tables()) {
0681         qCDebug(AKONADISERVER_LOG) << "Enabling triggers on table" << table;
0682         QSqlQuery query(db);
0683         if (!query.exec(QStringLiteral("ALTER TABLE %1 ENABLE TRIGGER ALL").arg(table))) {
0684             qCWarning(AKONADISERVER_LOG) << "Failed to enable triggers on table" << table << ":" << query.lastError().databaseText();
0685             // continue
0686         }
0687     }
0688 
0689     return true;
0690 }