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
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?
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
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);
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
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.)
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();
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#ifndef Q_OS_WIN
384 // If mysql socket file does not exists, then we must start the server,
385 // otherwise we reconnect to it
387 // move mysql error log file out of the way
388 const QFileInfo errorLog(mDataDir + QDir::separator() + QLatin1StringView("mysql.err"));
389 if (errorLog.exists()) {
390 QFile logFile(errorLog.absoluteFilePath());
391 QFile oldLogFile(mDataDir + QDir::separator() + QLatin1StringView("mysql.err.old"));
393 oldLogFile.write(logFile.readAll());
394 oldLogFile.close();
395 logFile.close();
396 logFile.remove();
397 } else {
398 qCCritical(AKONADISERVER_LOG) << "Failed to open MySQL error log.";
399 }
400 }
401
402 // first run, some MySQL versions need a mysql_install_db run for that
403 const QString confFile = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf"));
404 if (QDir(mDataDir).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) {
405 if (isMariaDB) {
406 initializeMariaDBDatabase(confFile, mDataDir);
407 } else if (localVersion >= MYSQL_VERSION_CHECK(5, 7, 6)) {
408 initializeMySQL5_7_6Database(confFile, mDataDir);
409 } else {
410 initializeMySQLDatabase(confFile, mDataDir);
411 }
412 }
413
414 qCDebug(AKONADISERVER_LOG) << "Executing:" << mMysqldPath << arguments.join(QLatin1Char(' '));
415 mDatabaseProcess = std::make_unique<QProcess>();
416 mDatabaseProcess->start(mMysqldPath, arguments);
417 if (!mDatabaseProcess->waitForStarted()) {
418 qCCritical(AKONADISERVER_LOG) << "Could not start database server!";
419 qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath;
420 qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
421 qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString();
422 return false;
423 }
424
425 connect(mDatabaseProcess.get(), &QProcess::finished, this, &DbConfigMysql::processFinished);
426
427 // wait until mysqld has created the socket file (workaround for QTBUG-47475 in Qt5.5.0)
428 int counter = 50; // avoid an endless loop in case mysqld terminated
429 while ((counter-- > 0) && !QFileInfo::exists(socketFile)) {
430 QThread::msleep(100);
431 }
432 } else {
433 qCDebug(AKONADISERVER_LOG) << "Found " << qPrintable(s_mysqlSocketFileName) << " file, reconnecting to the database";
434 }
435#endif
436
437 {
438 QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QMYSQL"), s_initConnection);
439 apply(db);
440
441 db.setDatabaseName(QString()); // might not exist yet, then connecting to the actual db will fail
442 if (!db.isValid()) {
443 qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup";
444 return false;
445 }
446
447 bool opened = false;
448 for (int i = 0; i < 120; ++i) {
449 opened = db.open();
450 if (opened) {
451 break;
452 }
453 if (mDatabaseProcess && mDatabaseProcess->waitForFinished(500)) {
454 qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!";
455 qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath;
456 qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
457 qCCritical(AKONADISERVER_LOG) << "stdout:" << mDatabaseProcess->readAllStandardOutput();
458 qCCritical(AKONADISERVER_LOG) << "stderr:" << mDatabaseProcess->readAllStandardError();
459 qCCritical(AKONADISERVER_LOG) << "exit code:" << mDatabaseProcess->exitCode();
460 qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString();
461 return false;
462 }
463 }
464
465 if (opened) {
466 if (!mMysqlCheckPath.isEmpty()) {
467 execute(mMysqlCheckPath,
468 {QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir),
469 QStringLiteral("--check-upgrade"),
470 QStringLiteral("--auto-repair"),
471#ifndef Q_OS_WIN
472 QStringLiteral("--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName),
473#endif
474 mDatabaseName});
475 }
476
477 if (!mMysqlUpgradePath.isEmpty()) {
478 execute(mMysqlUpgradePath,
479 {QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir)
480#ifndef Q_OS_WIN
481 ,
482 QStringLiteral("--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName)
483#endif
484 });
485 }
486
487 // Verify MySQL version
488 {
489 QSqlQuery query(db);
490 if (!query.exec(QStringLiteral("SELECT VERSION()")) || !query.first()) {
491 qCCritical(AKONADISERVER_LOG) << "Failed to verify database server version";
492 qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
493 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
494 return false;
495 }
496
497 const QString version = query.value(0).toString();
499 if (versions.count() < 3) {
500 qCCritical(AKONADISERVER_LOG) << "Invalid database server version: " << version;
501 return false;
502 }
503
504 if (versions[0].toInt() < MYSQL_MIN_MAJOR || (versions[0].toInt() == MYSQL_MIN_MAJOR && versions[1].toInt() < MYSQL_MIN_MINOR)) {
505 qCCritical(AKONADISERVER_LOG) << "Unsupported MySQL version:";
506 qCCritical(AKONADISERVER_LOG) << "Current version:" << QStringLiteral("%1.%2").arg(versions[0], versions[1]);
507 qCCritical(AKONADISERVER_LOG) << "Minimum required version:" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR);
508 qCCritical(AKONADISERVER_LOG) << "Please update your MySQL database server";
509 return false;
510 } else {
511 qCDebug(AKONADISERVER_LOG) << "MySQL version OK"
512 << "(required" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR) << ", available"
513 << QStringLiteral("%1.%2").arg(versions[0], versions[1]) << ")";
514 }
515 }
516
517 {
518 QSqlQuery query(db);
519 if (!query.exec(QStringLiteral("USE %1").arg(mDatabaseName))) {
520 qCDebug(AKONADISERVER_LOG) << "Failed to use database" << mDatabaseName;
521 qCDebug(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
522 qCDebug(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
523 qCDebug(AKONADISERVER_LOG) << "Trying to create database now...";
524 if (!query.exec(QStringLiteral("CREATE DATABASE akonadi"))) {
525 qCCritical(AKONADISERVER_LOG) << "Failed to create database";
526 qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
527 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
528 success = false;
529 }
530 }
531 } // make sure query is destroyed before we close the db
532 db.close();
533 } else {
534 qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!";
535 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
536 success = false;
537 }
538 }
539
540 return success;
541}
542
543void DbConfigMysql::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
544{
545 Q_UNUSED(exitCode)
546 Q_UNUSED(exitStatus)
547
548 qCCritical(AKONADISERVER_LOG) << "database server stopped unexpectedly";
549
550#ifndef Q_OS_WIN
551 // when the server stopped unexpectedly, make sure to remove the stale socket file since otherwise
552 // it can not be started again
553 const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length());
554 const QString socketFile = QStringLiteral("%1/%2").arg(socketDirectory, s_mysqlSocketFileName);
556#endif
557
559}
560
561void DbConfigMysql::stopInternalServer()
562{
563 if (!mDatabaseProcess) {
564 return;
565 }
566
567 // closing initConnection this late to work around QTBUG-63108
568 QSqlDatabase::removeDatabase(s_initConnection);
569
570 disconnect(mDatabaseProcess.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &DbConfigMysql::processFinished);
571
572 // first, try the nicest approach
573 if (!mCleanServerShutdownCommand.isEmpty()) {
574 QProcess::execute(mCleanServerShutdownCommand, QStringList());
575 if (mDatabaseProcess->waitForFinished(3000)) {
576 return;
577 }
578 }
579
580 mDatabaseProcess->terminate();
581 const bool result = mDatabaseProcess->waitForFinished(3000);
582 // We've waited nicely for 3 seconds, to no avail, let's be rude.
583 if (!result) {
584 mDatabaseProcess->kill();
585 }
586}
587
588void DbConfigMysql::initSession(const QSqlDatabase &database)
589{
590 QSqlQuery query(database);
591 query.exec(QStringLiteral("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"));
592}
593
594int DbConfigMysql::parseCommandLineToolsVersion() const
595{
597 mysqldProcess.start(mMysqldPath, {QStringLiteral("--version")});
598 mysqldProcess.waitForFinished(10000 /* 10 secs */);
599
600 const QString out = QString::fromLocal8Bit(mysqldProcess.readAllStandardOutput());
601 QRegularExpression regexp(QStringLiteral("Ver ([0-9]+)\\.([0-9]+)\\.([0-9]+)"));
602 auto match = regexp.match(out);
603 if (!match.hasMatch()) {
604 return 0;
605 }
606
607 return (match.capturedView(1).toInt() << 16) | (match.capturedView(2).toInt() << 8) | match.capturedView(3).toInt();
608}
609
610bool DbConfigMysql::initializeMariaDBDatabase(const QString &confFile, const QString &dataDir) const
611{
612 // KDE Neon (and possible others) don't ship mysql_install_db, but it seems
613 // that MariaDB can initialize itself automatically on first start, it only
614 // needs that the datadir directory exists
615 if (mMysqlInstallDbPath.isEmpty()) {
616 return QDir().mkpath(dataDir);
617 }
618
619 QFileInfo fi(mMysqlInstallDbPath);
620 QDir dir = fi.dir();
621 dir.cdUp();
622 const QString baseDir = dir.absolutePath();
623 return 0
624 == execute(mMysqlInstallDbPath,
625 {QStringLiteral("--defaults-file=%1").arg(confFile),
626 QStringLiteral("--force"),
627 QStringLiteral("--basedir=%1").arg(baseDir),
628 QStringLiteral("--datadir=%1/").arg(dataDir)});
629}
630
631/**
632 * As of MySQL 5.7.6 mysql_install_db is deprecated and mysqld --initailize should be used instead
633 * See MySQL Reference Manual section 2.10.1.1 (Initializing the Data Directory Manually Using mysqld)
634 */
635bool DbConfigMysql::initializeMySQL5_7_6Database(const QString &confFile, const QString &dataDir) const
636{
637 return 0
638 == execute(mMysqldPath,
639 {QStringLiteral("--defaults-file=%1").arg(confFile), QStringLiteral("--initialize"), QStringLiteral("--datadir=%1/").arg(dataDir)});
640}
641
642bool DbConfigMysql::initializeMySQLDatabase(const QString &confFile, const QString &dataDir) const
643{
644 // On FreeBSD MySQL 5.6 is also installed without mysql_install_db, so this
645 // might do the trick there as well.
646 if (mMysqlInstallDbPath.isEmpty()) {
647 return QDir().mkpath(dataDir);
648 }
649
650 QFileInfo fi(mMysqlInstallDbPath);
651 QDir dir = fi.dir();
652 dir.cdUp();
653 const QString baseDir = dir.absolutePath();
654
655 // Don't use --force, it has been removed in MySQL 5.7.5
656 return 0
657 == execute(
658 mMysqlInstallDbPath,
659 {QStringLiteral("--defaults-file=%1").arg(confFile), QStringLiteral("--basedir=%1").arg(baseDir), QStringLiteral("--datadir=%1/").arg(dataDir)});
660}
661
662bool DbConfigMysql::disableConstraintChecks(const QSqlDatabase &db)
663{
664 QSqlQuery query(db);
665 return query.exec(QStringLiteral("SET FOREIGN_KEY_CHECKS=0"));
666}
667
668bool DbConfigMysql::enableConstraintChecks(const QSqlDatabase &db)
669{
670 QSqlQuery query(db);
671 return query.exec(QStringLiteral("SET FOREIGN_KEY_CHECKS=1"));
672}
673
674#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)
NoDotAndDotDot
bool mkpath(const QString &dirPath) const const
QChar separator()
bool exists() const const
bool remove()
typedef Permissions
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)
T qobject_cast(QObject *object)
int execute(const QString &program, const QStringList &arguments)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
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 Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.