Akonadi

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

KDE's Doxygen guidelines are available online.