Akonadi

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

KDE's Doxygen guidelines are available online.