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 }