Akonadi

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