Akonadi

dbconfigmysql.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Tobias Koenig <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "dbconfigmysql.h"
8 #include "akonadiserver_debug.h"
9 #include "utils.h"
10 
11 #include <private/standarddirs_p.h>
12 
13 #include <QCoreApplication>
14 #include <QDir>
15 #include <QRegularExpression>
16 #include <QSqlDriver>
17 #include <QSqlError>
18 #include <QSqlQuery>
19 #include <QStandardPaths>
20 #include <QThread>
21 
22 using namespace Akonadi;
23 using namespace Akonadi::Server;
24 
25 #define MYSQL_MIN_MAJOR 5
26 #define MYSQL_MIN_MINOR 1
27 
28 #define MYSQL_VERSION_CHECK(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))
29 
30 static const QString s_mysqlSocketFileName = QStringLiteral("mysql.socket");
31 
32 DbConfigMysql::DbConfigMysql()
33  : mInternalServer(true)
34  , mDatabaseProcess(nullptr)
35 {
36 }
37 
38 QString DbConfigMysql::driverName() const
39 {
40  return QStringLiteral("QMYSQL");
41 }
42 
43 QString DbConfigMysql::databaseName() const
44 {
45  return mDatabaseName;
46 }
47 
48 static QString findExecutable(const QString &bin)
49 {
50  static const QStringList mysqldSearchPath = {
51 #ifdef MYSQLD_SCRIPTS_PATH
52  QStringLiteral(MYSQLD_SCRIPTS_PATH),
53 #endif
54  QStringLiteral("/usr/bin"),
55  QStringLiteral("/usr/sbin"),
56  QStringLiteral("/usr/local/sbin"),
57  QStringLiteral("/usr/local/libexec"),
58  QStringLiteral("/usr/libexec"),
59  QStringLiteral("/opt/mysql/libexec"),
60  QStringLiteral("/opt/local/lib/mysql5/bin"),
61  QStringLiteral("/opt/mysql/sbin"),
62  };
64  if (path.isEmpty()) { // No results in PATH; fall back to hardcoded list.
65  path = QStandardPaths::findExecutable(bin, mysqldSearchPath);
66  }
67  return path;
68 }
69 
70 bool DbConfigMysql::init(QSettings &settings, bool storeSettings)
71 {
72  // determine default settings depending on the driver
73  QString defaultHostName;
74  QString defaultOptions;
75  QString defaultServerPath;
76  QString defaultCleanShutdownCommand;
77 
78 #ifndef Q_OS_WIN
79  const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length());
80 #endif
81 
82  const bool defaultInternalServer = true;
83 #ifdef MYSQLD_EXECUTABLE
84  if (QFile::exists(QStringLiteral(MYSQLD_EXECUTABLE))) {
85  defaultServerPath = QStringLiteral(MYSQLD_EXECUTABLE);
86  }
87 #endif
88  if (defaultServerPath.isEmpty()) {
89  defaultServerPath = findExecutable(QStringLiteral("mysqld"));
90  }
91 
92  const QString mysqladminPath = findExecutable(QStringLiteral("mysqladmin"));
93  if (!mysqladminPath.isEmpty()) {
94 #ifndef Q_OS_WIN
95  defaultCleanShutdownCommand = QStringLiteral("%1 --defaults-file=%2/mysql.conf --socket=%3/%4 shutdown")
96  .arg(mysqladminPath, StandardDirs::saveDir("data"), socketDirectory, s_mysqlSocketFileName);
97 #else
98  defaultCleanShutdownCommand = QString::fromLatin1("%1 shutdown --shared-memory").arg(mysqladminPath);
99 #endif
100  }
101 
102  mMysqlInstallDbPath = findExecutable(QStringLiteral("mysql_install_db"));
103  qCDebug(AKONADISERVER_LOG) << "Found mysql_install_db: " << mMysqlInstallDbPath;
104 
105  mMysqlCheckPath = findExecutable(QStringLiteral("mysqlcheck"));
106  qCDebug(AKONADISERVER_LOG) << "Found mysqlcheck: " << mMysqlCheckPath;
107 
108  mInternalServer = settings.value(QStringLiteral("QMYSQL/StartServer"), defaultInternalServer).toBool();
109 #ifndef Q_OS_WIN
110  if (mInternalServer) {
111  defaultOptions = QStringLiteral("UNIX_SOCKET=%1/%2").arg(socketDirectory, s_mysqlSocketFileName);
112  }
113 #endif
114 
115  // read settings for current driver
116  settings.beginGroup(driverName());
117  mDatabaseName = settings.value(QStringLiteral("Name"), defaultDatabaseName()).toString();
118  mHostName = settings.value(QStringLiteral("Host"), defaultHostName).toString();
119  mUserName = settings.value(QStringLiteral("User")).toString();
120  mPassword = settings.value(QStringLiteral("Password")).toString();
121  mConnectionOptions = settings.value(QStringLiteral("Options"), defaultOptions).toString();
122  mMysqldPath = settings.value(QStringLiteral("ServerPath"), defaultServerPath).toString();
123  mCleanServerShutdownCommand = settings.value(QStringLiteral("CleanServerShutdownCommand"), defaultCleanShutdownCommand).toString();
124  settings.endGroup();
125 
126  // verify settings and apply permanent changes (written out below)
127  if (mInternalServer) {
128  mConnectionOptions = defaultOptions;
129  // intentionally not namespaced as we are the only one in this db instance when using internal mode
130  mDatabaseName = QStringLiteral("akonadi");
131  }
132  if (mInternalServer && (mMysqldPath.isEmpty() || !QFile::exists(mMysqldPath))) {
133  mMysqldPath = defaultServerPath;
134  }
135 
136  qCDebug(AKONADISERVER_LOG) << "Using mysqld:" << mMysqldPath;
137 
138  if (storeSettings) {
139  // store back the default values
140  settings.beginGroup(driverName());
141  settings.setValue(QStringLiteral("Name"), mDatabaseName);
142  settings.setValue(QStringLiteral("Host"), mHostName);
143  settings.setValue(QStringLiteral("Options"), mConnectionOptions);
144  if (!mMysqldPath.isEmpty()) {
145  settings.setValue(QStringLiteral("ServerPath"), mMysqldPath);
146  }
147  settings.setValue(QStringLiteral("StartServer"), mInternalServer);
148  settings.endGroup();
149  settings.sync();
150  }
151 
152  // apply temporary changes to the settings
153  if (mInternalServer) {
154  mHostName.clear();
155  mUserName.clear();
156  mPassword.clear();
157  }
158 
159  return true;
160 }
161 
162 bool DbConfigMysql::isAvailable(QSettings &settings)
163 {
164  if (!QSqlDatabase::drivers().contains(driverName())) {
165  return false;
166  }
167 
168  if (!init(settings, false)) {
169  return false;
170  }
171 
172  if (mInternalServer && (mMysqldPath.isEmpty() || !QFile::exists(mMysqldPath))) {
173  return false;
174  }
175 
176  return true;
177 }
178 
179 void DbConfigMysql::apply(QSqlDatabase &database)
180 {
181  if (!mDatabaseName.isEmpty()) {
182  database.setDatabaseName(mDatabaseName);
183  }
184  if (!mHostName.isEmpty()) {
185  database.setHostName(mHostName);
186  }
187  if (!mUserName.isEmpty()) {
188  database.setUserName(mUserName);
189  }
190  if (!mPassword.isEmpty()) {
191  database.setPassword(mPassword);
192  }
193 
194  database.setConnectOptions(mConnectionOptions);
195 
196  // can we check that during init() already?
197  Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId));
198 }
199 
200 bool DbConfigMysql::useInternalServer() const
201 {
202  return mInternalServer;
203 }
204 
205 bool DbConfigMysql::startInternalServer()
206 {
207  bool success = true;
208 
209  const QString akDir = StandardDirs::saveDir("data");
210  const QString dataDir = StandardDirs::saveDir("data", QStringLiteral("db_data"));
211 #ifndef Q_OS_WIN
212  const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length());
213  const QString socketFile = QStringLiteral("%1/%2").arg(socketDirectory, s_mysqlSocketFileName);
214  const QString pidFileName = QStringLiteral("%1/mysql.pid").arg(socketDirectory);
215 #endif
216 
217  // generate config file
218  const QString globalConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf"));
219  const QString localConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-local.conf"));
220  const QString actualConfig = StandardDirs::saveDir("data") + QLatin1String("/mysql.conf");
221  if (globalConfig.isEmpty()) {
222  qCCritical(AKONADISERVER_LOG) << "Did not find MySQL server default configuration (mysql-global.conf)";
223  return false;
224  }
225 
226 #ifdef Q_OS_LINUX
227  // It is recommended to disable CoW feature when running on Btrfs to improve
228  // database performance. Disabling CoW only has effect on empty directory (since
229  // it affects only new files), so we check whether MySQL has not yet been initialized.
230  QDir dir(dataDir + QDir::separator() + QLatin1String("mysql"));
231  if (!dir.exists()) {
232  if (Utils::getDirectoryFileSystem(dataDir) == QLatin1String("btrfs")) {
233  Utils::disableCoW(dataDir);
234  }
235  }
236 #endif
237 
238  if (mMysqldPath.isEmpty()) {
239  qCCritical(AKONADISERVER_LOG) << "mysqld not found. Please verify your installation";
240  return false;
241  }
242 
243  // Get the version of the mysqld server that we'll be using.
244  // MySQL (but not MariaDB) deprecates and removes command line options in
245  // patch version releases, so we need to adjust the command line options accordingly
246  // when running the helper utilities or starting the server
247  const unsigned int localVersion = parseCommandLineToolsVersion();
248  if (localVersion == 0x000000) {
249  qCCritical(AKONADISERVER_LOG) << "Failed to detect mysqld version!";
250  }
251  // TODO: Parse "MariaDB" or "MySQL" from the version string instead of relying
252  // on the version numbers
253  const bool isMariaDB = localVersion >= MYSQL_VERSION_CHECK(10, 0, 0);
254  qCDebug(AKONADISERVER_LOG).nospace() << "mysqld reports version " << (localVersion >> 16) << "." << ((localVersion >> 8) & 0x0000FF) << "."
255  << (localVersion & 0x0000FF) << " (" << (isMariaDB ? "MariaDB" : "Oracle MySQL") << ")";
256 
257  QFile actualFile(actualConfig);
258  // update conf only if either global (or local) is newer than actual
259  if ((QFileInfo(globalConfig).lastModified() > QFileInfo(actualFile).lastModified())
260  || (QFileInfo(localConfig).lastModified() > QFileInfo(actualFile).lastModified())) {
261  QFile globalFile(globalConfig);
262  QFile localFile(localConfig);
263  if (globalFile.open(QFile::ReadOnly) && actualFile.open(QFile::WriteOnly)) {
264  actualFile.write(globalFile.readAll());
265  if (!localConfig.isEmpty()) {
266  if (localFile.open(QFile::ReadOnly)) {
267  actualFile.write(localFile.readAll());
268  localFile.close();
269  }
270  }
271  globalFile.close();
272  actualFile.close();
273  } else {
274  qCCritical(AKONADISERVER_LOG) << "Unable to create MySQL server configuration file.";
275  qCCritical(AKONADISERVER_LOG) << "This means that either the default configuration file (mysql-global.conf) was not readable";
276  qCCritical(AKONADISERVER_LOG) << "or the target file (mysql.conf) could not be written.";
277  return false;
278  }
279  }
280 
281  // MySQL doesn't like world writeable config files (which makes sense), but
282  // our config file somehow ends up being world-writable on some systems for no
283  // apparent reason nevertheless, so fix that
284  const QFile::Permissions allowedPerms =
286  if (allowedPerms != actualFile.permissions()) {
287  actualFile.setPermissions(allowedPerms);
288  }
289 
290  if (dataDir.isEmpty()) {
291  qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database data directory";
292  return false;
293  }
294 
295  if (akDir.isEmpty()) {
296  qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database log directory";
297  return false;
298  }
299 
300 #ifndef Q_OS_WIN
301  if (socketDirectory.isEmpty()) {
302  qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database misc directory";
303  return false;
304  }
305 
306  // the socket path must not exceed 103 characters, so check for max dir length right away
307  if (socketDirectory.length() >= 90) {
308  qCCritical(AKONADISERVER_LOG) << "MySQL cannot deal with a socket path this long. Path was: " << socketDirectory;
309  return false;
310  }
311 
312  // If mysql socket file exists, check if also the server process is still running,
313  // else we can safely remove the socket file (cleanup after a system crash, etc.)
314  QFile pidFile(pidFileName);
315  if (QFile::exists(socketFile) && pidFile.open(QIODevice::ReadOnly)) {
316  qCDebug(AKONADISERVER_LOG) << "Found a mysqld pid file, checking whether the server is still running...";
317  QByteArray pid = pidFile.readLine().trimmed();
318  QFile proc(QString::fromLatin1("/proc/" + pid + "/stat"));
319  // Check whether the process with the PID from pidfile still exists and whether
320  // it's actually still mysqld or, whether the PID has been recycled in the meanwhile.
321  bool serverIsRunning = false;
322  if (proc.open(QIODevice::ReadOnly)) {
323  const QByteArray stat = proc.readAll();
324  const QList<QByteArray> stats = stat.split(' ');
325  if (stats.count() > 1) {
326  // Make sure the PID actually belongs to mysql process
327 
328  // Linux trims executable name in /proc filesystem to 15 characters
329  const QString expectedProcName = QFileInfo(mMysqldPath).fileName().left(15);
330  if (QString::fromLatin1(stats[1]) == QStringLiteral("(%1)").arg(expectedProcName)) {
331  // Yup, our mysqld is actually running, so pretend we started the server
332  // and try to connect to it
333  qCWarning(AKONADISERVER_LOG) << "mysqld for Akonadi is already running, trying to connect to it.";
334  serverIsRunning = true;
335  }
336  }
337  proc.close();
338  }
339 
340  if (!serverIsRunning) {
341  qCDebug(AKONADISERVER_LOG) << "No mysqld process with specified PID is running. Removing the pidfile and starting a new instance...";
342  pidFile.close();
343  pidFile.remove();
344  QFile::remove(socketFile);
345  }
346  }
347 #endif
348 
349  // synthesize the mysqld command
350  QStringList arguments;
351  arguments << QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir);
352  arguments << QStringLiteral("--datadir=%1/").arg(dataDir);
353 #ifndef Q_OS_WIN
354  arguments << QStringLiteral("--socket=%1").arg(socketFile);
355  arguments << QStringLiteral("--pid-file=%1").arg(pidFileName);
356 #else
357  arguments << QString::fromLatin1("--shared-memory");
358 #endif
359 
360 #ifndef Q_OS_WIN
361  // If mysql socket file does not exists, then we must start the server,
362  // otherwise we reconnect to it
363  if (!QFile::exists(socketFile)) {
364  // move mysql error log file out of the way
365  const QFileInfo errorLog(dataDir + QDir::separator() + QLatin1String("mysql.err"));
366  if (errorLog.exists()) {
367  QFile logFile(errorLog.absoluteFilePath());
368  QFile oldLogFile(dataDir + QDir::separator() + QLatin1String("mysql.err.old"));
369  if (logFile.open(QFile::ReadOnly) && oldLogFile.open(QFile::Append)) {
370  oldLogFile.write(logFile.readAll());
371  oldLogFile.close();
372  logFile.close();
373  logFile.remove();
374  } else {
375  qCCritical(AKONADISERVER_LOG) << "Failed to open MySQL error log.";
376  }
377  }
378 
379  // first run, some MySQL versions need a mysql_install_db run for that
380  const QString confFile = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf"));
381  if (QDir(dataDir).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) {
382  if (isMariaDB) {
383  initializeMariaDBDatabase(confFile, dataDir);
384  } else if (localVersion >= MYSQL_VERSION_CHECK(5, 7, 6)) {
385  initializeMySQL5_7_6Database(confFile, dataDir);
386  } else {
387  initializeMySQLDatabase(confFile, dataDir);
388  }
389  }
390 
391  qCDebug(AKONADISERVER_LOG) << "Executing:" << mMysqldPath << arguments.join(QLatin1Char(' '));
392  mDatabaseProcess = new QProcess;
393  mDatabaseProcess->start(mMysqldPath, arguments);
394  if (!mDatabaseProcess->waitForStarted()) {
395  qCCritical(AKONADISERVER_LOG) << "Could not start database server!";
396  qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath;
397  qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
398  qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString();
399  return false;
400  }
401 
402  connect(mDatabaseProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, &DbConfigMysql::processFinished);
403 
404  // wait until mysqld has created the socket file (workaround for QTBUG-47475 in Qt5.5.0)
405  int counter = 50; // avoid an endless loop in case mysqld terminated
406  while ((counter-- > 0) && !QFileInfo::exists(socketFile)) {
407  QThread::msleep(100);
408  }
409  } else {
410  qCDebug(AKONADISERVER_LOG) << "Found " << qPrintable(s_mysqlSocketFileName) << " file, reconnecting to the database";
411  }
412 #endif
413 
414  const QLatin1String initCon("initConnection");
415  {
416  QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QMYSQL"), initCon);
417  apply(db);
418 
419  db.setDatabaseName(QString()); // might not exist yet, then connecting to the actual db will fail
420  if (!db.isValid()) {
421  qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup";
422  return false;
423  }
424 
425  bool opened = false;
426  for (int i = 0; i < 120; ++i) {
427  opened = db.open();
428  if (opened) {
429  break;
430  }
431  if (mDatabaseProcess && mDatabaseProcess->waitForFinished(500)) {
432  qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!";
433  qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath;
434  qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
435  qCCritical(AKONADISERVER_LOG) << "stdout:" << mDatabaseProcess->readAllStandardOutput();
436  qCCritical(AKONADISERVER_LOG) << "stderr:" << mDatabaseProcess->readAllStandardError();
437  qCCritical(AKONADISERVER_LOG) << "exit code:" << mDatabaseProcess->exitCode();
438  qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString();
439  return false;
440  }
441  }
442 
443  if (opened) {
444  if (!mMysqlCheckPath.isEmpty()) {
445  execute(mMysqlCheckPath,
446  {QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir),
447  QStringLiteral("--check-upgrade"),
448  QStringLiteral("--auto-repair"),
449 #ifndef Q_OS_WIN
450  QStringLiteral("--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName),
451 #endif
452  mDatabaseName});
453  }
454 
455  // Verify MySQL version
456  {
457  QSqlQuery query(db);
458  if (!query.exec(QStringLiteral("SELECT VERSION()")) || !query.first()) {
459  qCCritical(AKONADISERVER_LOG) << "Failed to verify database server version";
460  qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
461  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
462  return false;
463  }
464 
465  const QString version = query.value(0).toString();
466  const QStringList versions = version.split(QLatin1Char('.'), Qt::SkipEmptyParts);
467  if (versions.count() < 3) {
468  qCCritical(AKONADISERVER_LOG) << "Invalid database server version: " << version;
469  return false;
470  }
471 
472  if (versions[0].toInt() < MYSQL_MIN_MAJOR || (versions[0].toInt() == MYSQL_MIN_MAJOR && versions[1].toInt() < MYSQL_MIN_MINOR)) {
473  qCCritical(AKONADISERVER_LOG) << "Unsupported MySQL version:";
474  qCCritical(AKONADISERVER_LOG) << "Current version:" << QStringLiteral("%1.%2").arg(versions[0], versions[1]);
475  qCCritical(AKONADISERVER_LOG) << "Minimum required version:" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR);
476  qCCritical(AKONADISERVER_LOG) << "Please update your MySQL database server";
477  return false;
478  } else {
479  qCDebug(AKONADISERVER_LOG) << "MySQL version OK"
480  << "(required" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR) << ", available"
481  << QStringLiteral("%1.%2").arg(versions[0], versions[1]) << ")";
482  }
483  }
484 
485  {
486  QSqlQuery query(db);
487  if (!query.exec(QStringLiteral("USE %1").arg(mDatabaseName))) {
488  qCDebug(AKONADISERVER_LOG) << "Failed to use database" << mDatabaseName;
489  qCDebug(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
490  qCDebug(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
491  qCDebug(AKONADISERVER_LOG) << "Trying to create database now...";
492  if (!query.exec(QStringLiteral("CREATE DATABASE akonadi"))) {
493  qCCritical(AKONADISERVER_LOG) << "Failed to create database";
494  qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
495  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
496  success = false;
497  }
498  }
499  } // make sure query is destroyed before we close the db
500  db.close();
501  } else {
502  qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!";
503  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
504  success = false;
505  }
506  }
507 
508  return success;
509 }
510 
511 void DbConfigMysql::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
512 {
513  Q_UNUSED(exitCode)
514  Q_UNUSED(exitStatus)
515 
516  qCCritical(AKONADISERVER_LOG) << "database server stopped unexpectedly";
517 
518 #ifndef Q_OS_WIN
519  // when the server stopped unexpectedly, make sure to remove the stale socket file since otherwise
520  // it can not be started again
521  const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length());
522  const QString socketFile = QStringLiteral("%1/%2").arg(socketDirectory, s_mysqlSocketFileName);
523  QFile::remove(socketFile);
524 #endif
525 
527 }
528 
529 void DbConfigMysql::stopInternalServer()
530 {
531  if (!mDatabaseProcess) {
532  return;
533  }
534 
535  // closing initConnection this late to work around QTBUG-63108
536  QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
537 
538  disconnect(mDatabaseProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &DbConfigMysql::processFinished);
539 
540  // first, try the nicest approach
541  if (!mCleanServerShutdownCommand.isEmpty()) {
542  QProcess::execute(mCleanServerShutdownCommand, QStringList());
543  if (mDatabaseProcess->waitForFinished(3000)) {
544  return;
545  }
546  }
547 
548  mDatabaseProcess->terminate();
549  const bool result = mDatabaseProcess->waitForFinished(3000);
550  // We've waited nicely for 3 seconds, to no avail, let's be rude.
551  if (!result) {
552  mDatabaseProcess->kill();
553  }
554 }
555 
556 void DbConfigMysql::initSession(const QSqlDatabase &database)
557 {
558  QSqlQuery query(database);
559  query.exec(QStringLiteral("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"));
560 }
561 
562 int DbConfigMysql::parseCommandLineToolsVersion() const
563 {
564  QProcess mysqldProcess;
565  mysqldProcess.start(mMysqldPath, {QStringLiteral("--version")});
566  mysqldProcess.waitForFinished(10000 /* 10 secs */);
567 
568  const QString out = QString::fromLocal8Bit(mysqldProcess.readAllStandardOutput());
569  QRegularExpression regexp(QStringLiteral("Ver ([0-9]+)\\.([0-9]+)\\.([0-9]+)"));
570  auto match = regexp.match(out);
571  if (!match.hasMatch()) {
572  return 0;
573  }
574 
575  return (match.capturedView(1).toInt() << 16) | (match.capturedView(2).toInt() << 8) | match.capturedView(3).toInt();
576 }
577 
578 bool DbConfigMysql::initializeMariaDBDatabase(const QString &confFile, const QString &dataDir) const
579 {
580  // KDE Neon (and possible others) don't ship mysql_install_db, but it seems
581  // that MariaDB can initialize itself automatically on first start, it only
582  // needs that the datadir directory exists
583  if (mMysqlInstallDbPath.isEmpty()) {
584  return QDir().mkpath(dataDir);
585  }
586 
587  QFileInfo fi(mMysqlInstallDbPath);
588  QDir dir = fi.dir();
589  dir.cdUp();
590  const QString baseDir = dir.absolutePath();
591  return 0
592  == execute(mMysqlInstallDbPath,
593  {QStringLiteral("--defaults-file=%1").arg(confFile),
594  QStringLiteral("--force"),
595  QStringLiteral("--basedir=%1").arg(baseDir),
596  QStringLiteral("--datadir=%1/").arg(dataDir)});
597 }
598 
599 /**
600  * As of MySQL 5.7.6 mysql_install_db is deprecated and mysqld --initailize should be used instead
601  * See MySQL Reference Manual section 2.10.1.1 (Initializing the Data Directory Manually Using mysqld)
602  */
603 bool DbConfigMysql::initializeMySQL5_7_6Database(const QString &confFile, const QString &dataDir) const
604 {
605  return 0
606  == execute(mMysqldPath,
607  {QStringLiteral("--defaults-file=%1").arg(confFile), QStringLiteral("--initialize"), QStringLiteral("--datadir=%1/").arg(dataDir)});
608 }
609 
610 bool DbConfigMysql::initializeMySQLDatabase(const QString &confFile, const QString &dataDir) const
611 {
612  // On FreeBSD MySQL 5.6 is also installed without mysql_install_db, so this
613  // might do the trick there as well.
614  if (mMysqlInstallDbPath.isEmpty()) {
615  return QDir().mkpath(dataDir);
616  }
617 
618  QFileInfo fi(mMysqlInstallDbPath);
619  QDir dir = fi.dir();
620  dir.cdUp();
621  const QString baseDir = dir.absolutePath();
622 
623  // Don't use --force, it has been removed in MySQL 5.7.5
624  return 0
625  == execute(
626  mMysqlInstallDbPath,
627  {QStringLiteral("--defaults-file=%1").arg(confFile), QStringLiteral("--basedir=%1").arg(baseDir), QStringLiteral("--datadir=%1/").arg(dataDir)});
628 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
T & first()
void msleep(unsigned long msecs)
void sync()
bool waitForFinished(int msecs)
void beginGroup(const QString &prefix)
bool remove()
KGuiItem apply()
QCA_EXPORT void init()
int count(const T &value) const const
QChar separator()
void setHostName(const QString &host)
void removeDatabase(const QString &connectionName)
QByteArray readAllStandardOutput()
QByteArray trimmed() const const
NoDotAndDotDot
void finished(int exitCode)
bool exists() 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)
QString fromLocal8Bit(const char *str, int size)
bool isValid() const const
void setValue(const QString &key, const QVariant &value)
void setDatabaseName(const QString &name)
SkipEmptyParts
bool isEmpty() const const
int execute(const QString &program, const QStringList &arguments)
int length() const const
bool mkpath(const QString &dirPath) const const
QVariant value(const QString &key, const QVariant &defaultValue) const const
void setPassword(const QString &password)
void setUserName(const QString &name)
typedef Permissions
QString join(const QString &separator) const const
QString fileName() const const
bool toBool() const const
QString & remove(int position, int n)
void endGroup()
QString text() const const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
unsigned int version()
QString left(int n) const const
QString fromLatin1(const char *str, int size)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QSqlDriver * driver() const const
void setConnectOptions(const QString &options)
QSqlError lastError() const const
QStringList drivers()
T value(int i) const const
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 Jun 25 2022 06:00:32 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.