Akonadi

dbconfigpostgresql.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Tobias Koenig <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "dbconfigpostgresql.h"
8 #include "akonadiserver_debug.h"
9 #include "utils.h"
10 
11 #include <private/standarddirs_p.h>
12 #include <shared/akranges.h>
13 
14 #include <QDir>
15 #include <QProcess>
16 #include <QRegularExpression>
17 #include <QRegularExpressionMatch>
18 #include <QSqlDriver>
19 #include <QSqlError>
20 #include <QSqlQuery>
21 #include <QStandardPaths>
22 
23 #include <config-akonadi.h>
24 #if HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif
27 #include <chrono>
28 
29 using namespace std::chrono_literals;
30 
31 using namespace Akonadi;
32 using namespace Akonadi::Server;
33 using namespace AkRanges;
34 
35 DbConfigPostgresql::DbConfigPostgresql()
36  : mHostPort(0)
37  , mInternalServer(true)
38 {
39 }
40 
41 QString DbConfigPostgresql::driverName() const
42 {
43  return QStringLiteral("QPSQL");
44 }
45 
46 QString DbConfigPostgresql::databaseName() const
47 {
48  return mDatabaseName;
49 }
50 
51 namespace
52 {
53 struct VersionCompare {
54  bool operator()(const QFileInfo &lhsFi, const QFileInfo &rhsFi) const
55  {
56  const auto lhs = parseVersion(lhsFi.fileName());
57  if (!lhs.has_value()) {
58  return false;
59  }
60  const auto rhs = parseVersion(rhsFi.fileName());
61  if (!rhs.has_value()) {
62  return true;
63  }
64 
65  return std::tie(lhs->major, lhs->minor) < std::tie(rhs->major, rhs->minor);
66  }
67 
68 private:
69  struct Version {
70  int major;
71  int minor;
72  };
73  std::optional<Version> parseVersion(const QString &name) const
74  {
75  const auto dotIdx = name.indexOf(QLatin1Char('.'));
76  if (dotIdx == -1) {
77  return {};
78  }
79  bool ok = false;
80  const auto major = QStringView(name).left(dotIdx).toInt(&ok);
81  if (!ok) {
82  return {};
83  }
84  const auto minor = QStringView(name).mid(dotIdx + 1).toInt(&ok);
85  if (!ok) {
86  return {};
87  }
88  return Version{major, minor};
89  }
90 };
91 
92 } // namespace
93 
94 QStringList DbConfigPostgresql::postgresSearchPaths(const QString &versionedPath) const
95 {
96  QStringList paths;
97 
98 #ifdef POSTGRES_PATH
99  const QString dir(QStringLiteral(POSTGRES_PATH));
100  if (QDir(dir).exists()) {
101  paths.push_back(QStringLiteral(POSTGRES_PATH));
102  }
103 #endif
104  paths << QStringLiteral("/usr/bin") << QStringLiteral("/usr/sbin") << QStringLiteral("/usr/local/sbin");
105 
106  // Locate all versions in /usr/lib/postgresql (i.e. /usr/lib/postgresql/X.Y) in reversed
107  // sorted order, so we search from the newest one to the oldest.
108  QDir versionedDir(versionedPath);
109  if (versionedDir.exists()) {
110  auto versionedDirs = versionedDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
111  qDebug() << versionedDirs;
112  std::sort(versionedDirs.begin(), versionedDirs.end(), VersionCompare());
113  std::reverse(versionedDirs.begin(), versionedDirs.end());
114  paths += versionedDirs | Views::transform([](const auto &dir) -> QString {
115  return dir.absoluteFilePath() + QStringLiteral("/bin");
116  })
117  | Actions::toQList;
118  }
119 
120  return paths;
121 }
122 
123 bool DbConfigPostgresql::init(QSettings &settings, bool storeSettings)
124 {
125  // determine default settings depending on the driver
126  QString defaultHostName;
127  QString defaultOptions;
128  QString defaultServerPath;
129  QString defaultInitDbPath;
130  QString defaultPgUpgradePath;
131  QString defaultPgData;
132 
133 #ifndef Q_WS_WIN // We assume that PostgreSQL is running as service on Windows
134  const bool defaultInternalServer = true;
135 #else
136  const bool defaultInternalServer = false;
137 #endif
138 
139  mInternalServer = settings.value(QStringLiteral("QPSQL/StartServer"), defaultInternalServer).toBool();
140  if (mInternalServer) {
141  const auto paths = postgresSearchPaths(QStringLiteral("/usr/lib/postgresql"));
142 
143  defaultServerPath = QStandardPaths::findExecutable(QStringLiteral("pg_ctl"), paths);
144  defaultInitDbPath = QStandardPaths::findExecutable(QStringLiteral("initdb"), paths);
145  defaultHostName = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")));
146  defaultPgUpgradePath = QStandardPaths::findExecutable(QStringLiteral("pg_upgrade"), paths);
147  defaultPgData = StandardDirs::saveDir("data", QStringLiteral("db_data"));
148  }
149 
150  // read settings for current driver
151  settings.beginGroup(driverName());
152  mDatabaseName = settings.value(QStringLiteral("Name"), defaultDatabaseName()).toString();
153  if (mDatabaseName.isEmpty()) {
154  mDatabaseName = defaultDatabaseName();
155  }
156  mHostName = settings.value(QStringLiteral("Host"), defaultHostName).toString();
157  if (mHostName.isEmpty()) {
158  mHostName = defaultHostName;
159  }
160  mHostPort = settings.value(QStringLiteral("Port")).toInt();
161  // User, password and Options can be empty and still valid, so don't override them
162  mUserName = settings.value(QStringLiteral("User")).toString();
163  mPassword = settings.value(QStringLiteral("Password")).toString();
164  mConnectionOptions = settings.value(QStringLiteral("Options"), defaultOptions).toString();
165  mServerPath = settings.value(QStringLiteral("ServerPath"), defaultServerPath).toString();
166  if (mInternalServer && mServerPath.isEmpty()) {
167  mServerPath = defaultServerPath;
168  }
169  qCDebug(AKONADISERVER_LOG) << "Found pg_ctl:" << mServerPath;
170  mInitDbPath = settings.value(QStringLiteral("InitDbPath"), defaultInitDbPath).toString();
171  if (mInternalServer && mInitDbPath.isEmpty()) {
172  mInitDbPath = defaultInitDbPath;
173  }
174  qCDebug(AKONADISERVER_LOG) << "Found initdb:" << mServerPath;
175  mPgUpgradePath = settings.value(QStringLiteral("UpgradePath"), defaultPgUpgradePath).toString();
176  if (mInternalServer && mPgUpgradePath.isEmpty()) {
177  mPgUpgradePath = defaultPgUpgradePath;
178  }
179  qCDebug(AKONADISERVER_LOG) << "Found pg_upgrade:" << mPgUpgradePath;
180  mPgData = settings.value(QStringLiteral("PgData"), defaultPgData).toString();
181  if (mPgData.isEmpty()) {
182  mPgData = defaultPgData;
183  }
184  settings.endGroup();
185 
186  if (storeSettings) {
187  // store back the default values
188  settings.beginGroup(driverName());
189  settings.setValue(QStringLiteral("Name"), mDatabaseName);
190  settings.setValue(QStringLiteral("Host"), mHostName);
191  if (mHostPort) {
192  settings.setValue(QStringLiteral("Port"), mHostPort);
193  }
194  settings.setValue(QStringLiteral("Options"), mConnectionOptions);
195  settings.setValue(QStringLiteral("ServerPath"), mServerPath);
196  settings.setValue(QStringLiteral("InitDbPath"), mInitDbPath);
197  settings.setValue(QStringLiteral("StartServer"), mInternalServer);
198  settings.endGroup();
199  settings.sync();
200  }
201 
202  return true;
203 }
204 
205 bool DbConfigPostgresql::isAvailable(QSettings &settings)
206 {
207  if (!QSqlDatabase::drivers().contains(driverName())) {
208  return false;
209  }
210 
211  if (!init(settings, false)) {
212  return false;
213  }
214 
215  if (mInternalServer) {
216  if (mInitDbPath.isEmpty() || !QFile::exists(mInitDbPath)) {
217  return false;
218  }
219 
220  if (mServerPath.isEmpty() || !QFile::exists(mServerPath)) {
221  return false;
222  }
223  }
224 
225  return true;
226 }
227 
228 void DbConfigPostgresql::apply(QSqlDatabase &database)
229 {
230  if (!mDatabaseName.isEmpty()) {
231  database.setDatabaseName(mDatabaseName);
232  }
233  if (!mHostName.isEmpty()) {
234  database.setHostName(mHostName);
235  }
236  if (mHostPort > 0 && mHostPort < 65535) {
237  database.setPort(mHostPort);
238  }
239  if (!mUserName.isEmpty()) {
240  database.setUserName(mUserName);
241  }
242  if (!mPassword.isEmpty()) {
243  database.setPassword(mPassword);
244  }
245 
246  database.setConnectOptions(mConnectionOptions);
247 
248  // can we check that during init() already?
249  Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId));
250 }
251 
252 bool DbConfigPostgresql::useInternalServer() const
253 {
254  return mInternalServer;
255 }
256 
257 std::optional<DbConfigPostgresql::Versions> DbConfigPostgresql::checkPgVersion() const
258 {
259  // Contains major version of Postgres that created the cluster
260  QFile pgVersionFile(QStringLiteral("%1/PG_VERSION").arg(mPgData));
261  if (!pgVersionFile.open(QIODevice::ReadOnly)) {
262  return std::nullopt;
263  }
264  const auto clusterVersion = pgVersionFile.readAll().toInt();
265 
266  QProcess pgctl;
267  pgctl.start(mServerPath, {QStringLiteral("--version")}, QIODevice::ReadOnly);
268  if (!pgctl.waitForFinished()) {
269  return std::nullopt;
270  }
271  // Looks like "pg_ctl (PostgreSQL) 11.2"
272  const auto output = QString::fromUtf8(pgctl.readAll());
273 
274  // Get the major version from major.minor
275  QRegularExpression re(QStringLiteral("\\(PostgreSQL\\) ([0-9]+).[0-9]+"));
276  const auto match = re.match(output);
277  if (!match.hasMatch()) {
278  return std::nullopt;
279  }
280  const auto serverVersion = match.captured(1).toInt();
281 
282  qDebug(AKONADISERVER_LOG) << "Detected psql versions - cluster:" << clusterVersion << ", server:" << serverVersion;
283  return {{clusterVersion, serverVersion}};
284 }
285 
286 bool DbConfigPostgresql::runInitDb(const QString &newDbPath)
287 {
288  // Make sure the cluster directory exists
289  if (!QDir(newDbPath).exists()) {
290  if (!QDir().mkpath(newDbPath)) {
291  return false;
292  }
293  }
294 
295 #ifdef Q_OS_LINUX
296  // It is recommended to disable CoW feature when running on Btrfs to improve
297  // database performance. This only has effect when done on empty directory,
298  // so we only call this before calling initdb
299  if (Utils::getDirectoryFileSystem(newDbPath) == QLatin1String("btrfs")) {
300  Utils::disableCoW(newDbPath);
301  }
302 #endif
303 
304  // call 'initdb --pgdata=/home/user/.local/share/akonadi/data_db'
305  return execute(mInitDbPath, {QStringLiteral("--pgdata=%1").arg(newDbPath), QStringLiteral("--encoding=UTF8"), QStringLiteral("--no-locale")}) == 0;
306 }
307 
308 namespace
309 {
310 std::optional<QString> findBinPathForVersion(int version)
311 {
312  // First we need to find where the previous PostgreSQL version binaries are available
313  const auto oldBinSearchPaths = {
314  QStringLiteral("/usr/lib64/pgsql/postgresql-%1/bin").arg(version), // Fedora & friends
315  QStringLiteral("/usr/lib/pgsql/postgresql-%1/bin").arg(version),
316  QStringLiteral("/usr/lib/postgresql/%1/bin").arg(version), // Debian-based
317  QStringLiteral("/opt/pgsql-%1/bin").arg(version), // Arch Linux
318  // TODO: Check other distros as well, they might do things differently.
319  };
320 
321  for (const auto &path : oldBinSearchPaths) {
322  if (QDir(path).exists()) {
323  return path;
324  }
325  }
326 
327  return std::nullopt;
328 }
329 
330 bool checkAndRemoveTmpCluster(const QDir &baseDir, const QString &clusterName)
331 {
332  if (baseDir.exists(clusterName)) {
333  qCInfo(AKONADISERVER_LOG) << "Postgres cluster update:" << clusterName << "cluster already exists, trying to remove it first";
334  if (!QDir(baseDir.path() + QDir::separator() + clusterName).removeRecursively()) {
335  qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to remove" << clusterName
336  << "cluster from some previous run, not performing auto-upgrade";
337  return false;
338  }
339  }
340  return true;
341 }
342 
343 bool runPgUpgrade(const QString &pgUpgrade,
344  const QDir &baseDir,
345  const QString &oldBinPath,
346  const QString &newBinPath,
347  const QString &oldDbData,
348  const QString &newDbData)
349 {
350  QProcess process;
351  const QStringList args = {QString(QStringLiteral("--old-bindir=%1").arg(oldBinPath)),
352  QString(QStringLiteral("--new-bindir=%1").arg(newBinPath)),
353  QString(QStringLiteral("--old-datadir=%1").arg(oldDbData)),
354  QString(QStringLiteral("--new-datadir=%1").arg(newDbData))};
355  qCInfo(AKONADISERVER_LOG) << "Postgres cluster update: starting pg_upgrade to upgrade your Akonadi DB cluster";
356  qCDebug(AKONADISERVER_LOG) << "Executing pg_upgrade" << QStringList(args);
357  process.setWorkingDirectory(baseDir.path());
358  process.start(pgUpgrade, args);
359  process.waitForFinished(std::chrono::milliseconds(1h).count());
360  if (process.exitCode() != 0) {
361  qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: pg_upgrade finished with exit code" << process.exitCode()
362  << ", please run migration manually.";
363  return false;
364  }
365 
366  qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: pg_upgrade finished successfully.";
367  return true;
368 }
369 
370 bool swapClusters(QDir &baseDir, const QString &oldDbDataCluster, const QString &newDbDataCluster)
371 {
372  // If everything went fine, swap the old and new clusters
373  if (!baseDir.rename(QStringLiteral("db_data"), oldDbDataCluster)) {
374  qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to rename old db_data to" << oldDbDataCluster;
375  return false;
376  }
377  if (!baseDir.rename(newDbDataCluster, QStringLiteral("db_data"))) {
378  qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to rename" << newDbDataCluster << "to db_data, rolling back";
379  if (!baseDir.rename(oldDbDataCluster, QStringLiteral("db_data"))) {
380  qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to roll back from" << oldDbDataCluster << "to db_data.";
381  return false;
382  }
383  qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: rollback successful.";
384  return false;
385  }
386 
387  return true;
388 }
389 
390 } // namespace
391 
392 bool DbConfigPostgresql::upgradeCluster(int clusterVersion)
393 {
394  const auto oldDbDataCluster = QStringLiteral("old_db_data");
395  const auto newDbDataCluster = QStringLiteral("new_db_data");
396 
397  QDir baseDir(mPgData); // db_data
398  baseDir.cdUp(); // move to its parent folder
399 
400  const auto oldBinPath = findBinPathForVersion(clusterVersion);
401  if (!oldBinPath.has_value()) {
402  qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: failed to find Postgres server for version" << clusterVersion;
403  return false;
404  }
405  const auto newBinPath = QFileInfo(mServerPath).path();
406 
407  if (!checkAndRemoveTmpCluster(baseDir, oldDbDataCluster)) {
408  return false;
409  }
410  if (!checkAndRemoveTmpCluster(baseDir, newDbDataCluster)) {
411  return false;
412  }
413 
414  // Next, initialize a new cluster
415  const QString newDbData = baseDir.path() + QDir::separator() + newDbDataCluster;
416  qCInfo(AKONADISERVER_LOG) << "Postgres cluster upgrade: creating a new cluster for current Postgres server";
417  if (!runInitDb(newDbData)) {
418  qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to initialize new db cluster";
419  return false;
420  }
421 
422  // Now migrate the old cluster from the old version into the new cluster
423  if (!runPgUpgrade(mPgUpgradePath, baseDir, *oldBinPath, newBinPath, mPgData, newDbData)) {
424  return false;
425  }
426 
427  if (!swapClusters(baseDir, oldDbDataCluster, newDbDataCluster)) {
428  return false;
429  }
430 
431  // Drop the old cluster
432  if (!QDir(baseDir.path() + QDir::separator() + oldDbDataCluster).removeRecursively()) {
433  qCInfo(AKONADISERVER_LOG) << "Postgres cluster update: failed to remove" << oldDbDataCluster << "cluster (not an issue, continuing)";
434  }
435 
436  return true;
437 }
438 
439 bool DbConfigPostgresql::startInternalServer()
440 {
441  // We defined the mHostName to the socket directory, during init
442  const QString socketDir = mHostName;
443  bool success = true;
444 
445  // Make sure the path exists, otherwise pg_ctl fails
446  if (!QFile::exists(socketDir)) {
447  QDir().mkpath(socketDir);
448  }
449 
450 // TODO Windows support
451 #ifndef Q_WS_WIN
452  // If postmaster.pid exists, check whether the postgres process still exists too,
453  // because normally we shouldn't be able to get this far if Akonadi is already
454  // running. If postgres is not running, then the pidfile was left after a system
455  // crash or something similar and we can remove it (otherwise pg_ctl won't start)
456  QFile postmaster(QStringLiteral("%1/postmaster.pid").arg(mPgData));
457  if (postmaster.exists() && postmaster.open(QIODevice::ReadOnly)) {
458  qCDebug(AKONADISERVER_LOG) << "Found a postmaster.pid pidfile, checking whether the server is still running...";
459  QByteArray pid = postmaster.readLine();
460  // Remove newline character
461  pid.chop(1);
462  QFile proc(QString::fromLatin1("/proc/" + pid + "/stat"));
463  // Check whether the process with the PID from pidfile still exists and whether
464  // it's actually still postgres or, whether the PID has been recycled in the
465  // meanwhile.
466  if (proc.open(QIODevice::ReadOnly)) {
467  const QByteArray stat = proc.readAll();
468  const QList<QByteArray> stats = stat.split(' ');
469  if (stats.count() > 1) {
470  // Make sure the PID actually belongs to postgres process
471  if (stats[1] == "(postgres)") {
472  // Yup, our PostgreSQL is actually running, so pretend we started the server
473  // and try to connect to it
474  qCWarning(AKONADISERVER_LOG) << "PostgreSQL for Akonadi is already running, trying to connect to it.";
475  return true;
476  }
477  }
478  proc.close();
479  }
480 
481  qCDebug(AKONADISERVER_LOG) << "No postgres process with specified PID is running. Removing the pidfile and starting a new Postgres instance...";
482  postmaster.close();
483  postmaster.remove();
484  }
485 #endif
486 
487  // postgres data directory not initialized yet, so call initdb on it
488  if (!QFile::exists(QStringLiteral("%1/PG_VERSION").arg(mPgData))) {
489 #ifdef Q_OS_LINUX
490  // It is recommended to disable CoW feature when running on Btrfs to improve
491  // database performance. This only has effect when done on an empty directory,
492  // so we call this before calling initdb.
493  if (Utils::getDirectoryFileSystem(mPgData) == QLatin1String("btrfs")) {
494  Utils::disableCoW(mPgData);
495  }
496 #endif
497  // call 'initdb --pgdata=/home/user/.local/share/akonadi/db_data'
498  execute(mInitDbPath, {QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--encoding=UTF8"), QStringLiteral("--no-locale")});
499  } else {
500  const auto versions = checkPgVersion();
501  if (versions.has_value() && (versions->clusterVersion < versions->pgServerVersion)) {
502  qCInfo(AKONADISERVER_LOG) << "Cluster PG_VERSION is" << versions->clusterVersion << ", PostgreSQL server is version " << versions->pgServerVersion
503  << ", will attempt to upgrade the cluster";
504  if (upgradeCluster(versions->clusterVersion)) {
505  qCInfo(AKONADISERVER_LOG) << "Successfully upgraded db cluster from Postgres" << versions->clusterVersion << "to" << versions->pgServerVersion;
506  } else {
507  qCWarning(AKONADISERVER_LOG) << "Postgres db cluster upgrade failed, Akonadi will fail to start. Sorry.";
508  }
509  }
510  }
511 
512  // synthesize the postgres command
513  QStringList arguments;
514  arguments << QStringLiteral("start") << QStringLiteral("-w") << QStringLiteral("--timeout=10") // default is 60 seconds.
515  << QStringLiteral("--pgdata=%1").arg(mPgData)
516  // These options are passed to postgres
517  // -k - directory for unix domain socket communication
518  // -h - disable listening for TCP/IP
519  << QStringLiteral("-o \"-k%1\" -h ''").arg(socketDir);
520 
521  qCDebug(AKONADISERVER_LOG) << "Executing:" << mServerPath << arguments.join(QLatin1Char(' '));
522  QProcess pgCtl;
523  pgCtl.start(mServerPath, arguments);
524  if (!pgCtl.waitForStarted()) {
525  qCCritical(AKONADISERVER_LOG) << "Could not start database server!";
526  qCCritical(AKONADISERVER_LOG) << "executable:" << mServerPath;
527  qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
528  qCCritical(AKONADISERVER_LOG) << "process error:" << pgCtl.errorString();
529  return false;
530  }
531 
532  const QLatin1String initCon("initConnection");
533  {
534  QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QPSQL"), initCon);
535  apply(db);
536 
537  // use the default database that is always available
538  db.setDatabaseName(QStringLiteral("postgres"));
539 
540  if (!db.isValid()) {
541  qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup";
542  return false;
543  }
544 
545  bool opened = false;
546  for (int i = 0; i < 120; ++i) {
547  opened = db.open();
548  if (opened) {
549  break;
550  }
551 
552  if (pgCtl.waitForFinished(500) && pgCtl.exitCode()) {
553  qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!";
554  qCCritical(AKONADISERVER_LOG) << "executable:" << mServerPath;
555  qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
556  qCCritical(AKONADISERVER_LOG) << "stdout:" << pgCtl.readAllStandardOutput();
557  qCCritical(AKONADISERVER_LOG) << "stderr:" << pgCtl.readAllStandardError();
558  qCCritical(AKONADISERVER_LOG) << "exit code:" << pgCtl.exitCode();
559  qCCritical(AKONADISERVER_LOG) << "process error:" << pgCtl.errorString();
560  return false;
561  }
562  }
563 
564  if (opened) {
565  {
566  QSqlQuery query(db);
567 
568  // check if the 'akonadi' database already exists
569  query.exec(QStringLiteral("SELECT 1 FROM pg_catalog.pg_database WHERE datname = '%1'").arg(mDatabaseName));
570 
571  // if not, create it
572  if (!query.first()) {
573  if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(mDatabaseName))) {
574  qCCritical(AKONADISERVER_LOG) << "Failed to create database";
575  qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
576  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
577  success = false;
578  }
579  }
580  } // make sure query is destroyed before we close the db
581  db.close();
582  }
583  }
584  // Make sure pg_ctl has returned
585  pgCtl.waitForFinished();
586 
588  return success;
589 }
590 
591 void DbConfigPostgresql::stopInternalServer()
592 {
593  if (!checkServerIsRunning()) {
594  qCDebug(AKONADISERVER_LOG) << "Database is no longer running";
595  return;
596  }
597 
598  // first, try a FAST shutdown
599  execute(mServerPath, {QStringLiteral("stop"), QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--mode=fast")});
600  if (!checkServerIsRunning()) {
601  return;
602  }
603 
604  // second, try an IMMEDIATE shutdown
605  execute(mServerPath, {QStringLiteral("stop"), QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--mode=immediate")});
606  if (!checkServerIsRunning()) {
607  return;
608  }
609 
610  // third, pg_ctl couldn't terminate all the postgres processes, we have to
611  // kill the master one. We don't want to do that, but we've passed the last
612  // call. pg_ctl is used to send the kill signal (safe when kill is not
613  // supported by OS)
614  const QString pidFileName = QStringLiteral("%1/postmaster.pid").arg(mPgData);
615  QFile pidFile(pidFileName);
616  if (pidFile.open(QIODevice::ReadOnly)) {
617  QString postmasterPid = QString::fromUtf8(pidFile.readLine(0).trimmed());
618  qCCritical(AKONADISERVER_LOG) << "The postmaster is still running. Killing it.";
619 
620  execute(mServerPath, {QStringLiteral("kill"), QStringLiteral("ABRT"), postmasterPid});
621  }
622 }
623 
624 bool DbConfigPostgresql::checkServerIsRunning()
625 {
626  const QString command = mServerPath;
627  QStringList arguments;
628  arguments << QStringLiteral("status") << QStringLiteral("--pgdata=%1").arg(mPgData);
629 
630  QProcess pgCtl;
631  pgCtl.start(command, arguments, QIODevice::ReadOnly);
632  if (!pgCtl.waitForFinished(3000)) {
633  // Error?
634  return false;
635  }
636 
637  // "pg_ctl status" exits with 0 when server is running and a non-zero code when not.
638  return pgCtl.exitCode() == 0;
639 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
T & first()
QString path() const const
QString errorString() const const
bool exists() const const
bool cdUp()
QString fromUtf8(const char *str, int size)
void sync()
bool waitForFinished(int msecs)
void beginGroup(const QString &prefix)
void setWorkingDirectory(const QString &dir)
KGuiItem apply()
void setPort(int port)
QCA_EXPORT void init()
int count(const T &value) const const
QChar separator()
QStringView mid(qsizetype start) const const
void setHostName(const QString &host)
void removeDatabase(const QString &connectionName)
QByteArray readAllStandardOutput()
void push_back(const T &value)
QStringView left(qsizetype length) const const
bool exists() const const
virtual bool hasFeature(QSqlDriver::DriverFeature feature) const const=0
QString findExecutable(const QString &executableName, const QStringList &paths)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
void chop(int n)
bool isValid() const const
void setValue(const QString &key, const QVariant &value)
void setDatabaseName(const QString &name)
bool waitForStarted(int msecs)
bool mkpath(const QString &dirPath) const const
QVariant value(const QString &key, const QVariant &defaultValue) const const
int toInt(bool *ok) const const
void setPassword(const QString &password)
void setUserName(const QString &name)
QString join(const QString &separator) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
QString fileName() const const
QString path() const const
bool toBool() const const
void endGroup()
QByteArray readAllStandardError()
QString text() const const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString name(StandardShortcut id)
QSqlDriver * driver() const const
QByteArray readAll()
void setConnectOptions(const QString &options)
QSqlError lastError() const const
bool rename(const QString &oldName, const QString &newName)
QStringList drivers()
int exitCode() const const
bool removeRecursively()
QString toString() const const
Helper integration between Akonadi and Qt.
int stat(const QString &path, KDE_struct_stat *buf)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jul 2 2022 06:41:47 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.