7#include "dbconfigmysql.h"
8#include "akonadiserver_debug.h"
11#include "private/standarddirs_p.h"
13#include <QCoreApplication>
15#include <QRegularExpression>
19#include <QStandardPaths>
23using namespace Akonadi::Server;
25#define MYSQL_MIN_MAJOR 5
26#define MYSQL_MIN_MINOR 1
28#define MYSQL_VERSION_CHECK(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))
30static const QString s_mysqlSocketFileName = QStringLiteral(
"mysql.socket");
31static const QString s_initConnection = QStringLiteral(
"initConnectionMysql");
33DbConfigMysql::DbConfigMysql(
const QString &configFile)
38DbConfigMysql::~DbConfigMysql() =
default;
40QString DbConfigMysql::driverName()
const
42 return QStringLiteral(
"QMYSQL");
45QString DbConfigMysql::databaseName()
const
50QString DbConfigMysql::databasePath()
const
55void DbConfigMysql::setDatabasePath(
const QString &path, QSettings &settings)
59 settings.
setValue(QStringLiteral(
"DataDir"), mDataDir);
64static QString findExecutable(
const QString &bin)
66 static const QStringList mysqldSearchPath = {
67#ifdef MYSQLD_SCRIPTS_PATH
68 QStringLiteral(MYSQLD_SCRIPTS_PATH),
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"),
86bool DbConfigMysql::init(QSettings &settings,
bool storeSettings,
const QString &dbPathOverride)
89 QString defaultHostName;
90 QString defaultOptions;
91 QString defaultServerPath;
92 QString defaultCleanShutdownCommand;
95 const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir(
"data", QStringLiteral(
"db_misc")), s_mysqlSocketFileName.
length());
98 const bool defaultInternalServer =
true;
99#ifdef MYSQLD_EXECUTABLE
101 defaultServerPath = QStringLiteral(MYSQLD_EXECUTABLE);
104 if (defaultServerPath.
isEmpty()) {
105 defaultServerPath = findExecutable(QStringLiteral(
"mysqld"));
108 const QString mysqladminPath = findExecutable(QStringLiteral(
"mysqladmin"));
109 if (!mysqladminPath.
isEmpty()) {
111 defaultCleanShutdownCommand = QStringLiteral(
"%1 --defaults-file=%2/mysql.conf --socket=%3/%4 shutdown")
112 .
arg(mysqladminPath, StandardDirs::saveDir(
"data"), socketDirectory, s_mysqlSocketFileName);
118 const QString defaultDataDir = dbPathOverride.
isEmpty() ? StandardDirs::saveDir(
"data", QStringLiteral(
"db_data")) : dbPathOverride;
120 mMysqlInstallDbPath = findExecutable(QStringLiteral(
"mysql_install_db"));
121 qCDebug(AKONADISERVER_LOG) <<
"Found mysql_install_db: " << mMysqlInstallDbPath;
123 mMysqlCheckPath = findExecutable(QStringLiteral(
"mysqlcheck"));
124 qCDebug(AKONADISERVER_LOG) <<
"Found mysqlcheck: " << mMysqlCheckPath;
126 mMysqlUpgradePath = findExecutable(QStringLiteral(
"mysql_upgrade"));
127 qCDebug(AKONADISERVER_LOG) <<
"Found mysql_upgrade: " << mMysqlUpgradePath;
129 mInternalServer = settings.
value(QStringLiteral(
"QMYSQL/StartServer"), defaultInternalServer).
toBool();
131 if (mInternalServer) {
132 defaultOptions = QStringLiteral(
"UNIX_SOCKET=%1/%2").
arg(socketDirectory, s_mysqlSocketFileName);
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();
149 if (mInternalServer) {
150 mConnectionOptions = defaultOptions;
152 mDatabaseName = QStringLiteral(
"akonadi");
154 if (mInternalServer && (mMysqldPath.isEmpty() || !
QFile::exists(mMysqldPath))) {
155 mMysqldPath = defaultServerPath;
158 qCDebug(AKONADISERVER_LOG) <<
"Using mysqld:" << mMysqldPath;
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);
169 settings.
setValue(QStringLiteral(
"StartServer"), mInternalServer);
170 settings.
setValue(QStringLiteral(
"DataDir"), mDataDir);
176 if (mInternalServer) {
185bool DbConfigMysql::isAvailable(QSettings &settings)
191 if (!init(settings,
false)) {
195 if (mInternalServer && (mMysqldPath.isEmpty() || !
QFile::exists(mMysqldPath))) {
202void DbConfigMysql::apply(QSqlDatabase &database)
204 if (!mDatabaseName.isEmpty()) {
207 if (!mHostName.isEmpty()) {
210 if (!mUserName.isEmpty()) {
213 if (!mPassword.isEmpty()) {
223bool DbConfigMysql::useInternalServer()
const
225 return mInternalServer;
228bool DbConfigMysql::startInternalServer()
232 const QString akDir = StandardDirs::saveDir(
"data");
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);
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;
245 qCCritical(AKONADISERVER_LOG) <<
"Did not find MySQL server default configuration (mysql-global.conf)";
255 if (Utils::getDirectoryFileSystem(mDataDir) == QLatin1StringView(
"btrfs")) {
256 Utils::disableCoW(mDataDir);
261 if (mMysqldPath.isEmpty()) {
262 qCCritical(AKONADISERVER_LOG) <<
"mysqld not found. Please verify your installation";
270 const unsigned int localVersion = parseCommandLineToolsVersion();
271 if (localVersion == 0x000000) {
272 qCCritical(AKONADISERVER_LOG) <<
"Failed to detect mysqld version!";
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") <<
")";
280 QFile actualFile(actualConfig);
282 if ((QFileInfo(globalConfig).lastModified() > QFileInfo(actualFile).lastModified())
283 || (QFileInfo(localConfig).lastModified() > QFileInfo(actualFile).lastModified())) {
284 QFile globalFile(globalConfig);
285 QFile localFile(localConfig);
287 actualFile.write(globalFile.readAll());
290 actualFile.write(localFile.readAll());
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.";
309 if (allowedPerms != actualFile.permissions()) {
310 actualFile.setPermissions(allowedPerms);
313 if (mDataDir.isEmpty()) {
314 qCCritical(AKONADISERVER_LOG) <<
"Akonadi server was not able to create database data directory";
319 qCCritical(AKONADISERVER_LOG) <<
"Akonadi server was not able to create database log directory";
324 if (socketDirectory.
isEmpty()) {
325 qCCritical(AKONADISERVER_LOG) <<
"Akonadi server was not able to create database misc directory";
330 if (socketDirectory.
length() >= 90) {
331 qCCritical(AKONADISERVER_LOG) <<
"MySQL cannot deal with a socket path this long. Path was: " << socketDirectory;
337 QFile pidFile(pidFileName);
339 qCDebug(AKONADISERVER_LOG) <<
"Found a mysqld pid file, checking whether the server is still running...";
340 QByteArray pid = pidFile.readLine().trimmed();
344 bool serverIsRunning =
false;
346 const QByteArray
stat = proc.readAll();
347 const QList<QByteArray> stats =
stat.split(
' ');
348 if (stats.
count() > 1) {
352 const QString expectedProcName = QFileInfo(mMysqldPath).fileName().left(15);
356 qCWarning(AKONADISERVER_LOG) <<
"mysqld for Akonadi is already running, trying to connect to it.";
357 serverIsRunning =
true;
363 if (!serverIsRunning) {
364 qCDebug(AKONADISERVER_LOG) <<
"No mysqld process with specified PID is running. Removing the pidfile and starting a new instance...";
373 QStringList arguments;
374 arguments << QStringLiteral(
"--defaults-file=%1/mysql.conf").arg(akDir);
375 arguments << QStringLiteral(
"--datadir=%1/").arg(mDataDir);
377 arguments << QStringLiteral(
"--socket=%1").arg(socketFile);
378 arguments << QStringLiteral(
"--pid-file=%1").arg(pidFileName);
383 const QString errorLogFile = mDataDir +
QDir::separator() + QLatin1StringView(
"mysql.err");
389 const QFileInfo errorLog(errorLogFile);
390 if (errorLog.exists()) {
391 QFile logFile(errorLog.absoluteFilePath());
392 QFile oldLogFile(mDataDir +
QDir::separator() + QLatin1StringView(
"mysql.err.old"));
394 oldLogFile.write(logFile.readAll());
399 qCCritical(AKONADISERVER_LOG) <<
"Failed to open MySQL error log.";
404 const QString confFile = StandardDirs::locateResourceFile(
"config", QStringLiteral(
"mysql-global.conf"));
407 initializeMariaDBDatabase(confFile, mDataDir);
408 }
else if (localVersion >= MYSQL_VERSION_CHECK(5, 7, 6)) {
409 initializeMySQL5_7_6Database(confFile, mDataDir);
411 initializeMySQLDatabase(confFile, mDataDir);
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();
434 qCDebug(AKONADISERVER_LOG) <<
"Found " << qPrintable(s_mysqlSocketFileName) <<
" file, reconnecting to the database";
444 qCCritical(AKONADISERVER_LOG) <<
"Invalid database object during database server startup";
449 for (
int i = 0; i < 120; ++i) {
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";
468 if (!mMysqlCheckPath.isEmpty()) {
470 {QStringLiteral(
"--defaults-file=%1/mysql.conf").arg(akDir),
471 QStringLiteral(
"--check-upgrade"),
472 QStringLiteral(
"--auto-repair"),
474 QStringLiteral(
"--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName),
479 if (!mMysqlUpgradePath.isEmpty()) {
481 {QStringLiteral(
"--defaults-file=%1/mysql.conf").arg(akDir)
484 QStringLiteral(
"--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName)
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();
501 if (versions.
count() < 3) {
502 qCCritical(AKONADISERVER_LOG) <<
"Invalid database server version: " <<
version;
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";
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]) <<
")";
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();
536 qCCritical(AKONADISERVER_LOG) <<
"Failed to connect to database!";
537 qCCritical(AKONADISERVER_LOG) <<
"Database error:" << db.
lastError().
text();
550 qCCritical(AKONADISERVER_LOG) <<
"database server stopped unexpectedly";
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);
563void DbConfigMysql::stopInternalServer()
565 if (!mDatabaseProcess) {
575 if (!mCleanServerShutdownCommand.isEmpty()) {
577 if (mDatabaseProcess->waitForFinished(3000)) {
582 mDatabaseProcess->terminate();
583 const bool result = mDatabaseProcess->waitForFinished(3000);
586 mDatabaseProcess->kill();
590void DbConfigMysql::initSession(
const QSqlDatabase &database)
592 QSqlQuery
query(database);
593 query.exec(QStringLiteral(
"SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"));
596int DbConfigMysql::parseCommandLineToolsVersion()
const
598 QProcess mysqldProcess;
599 mysqldProcess.
start(mMysqldPath, {QStringLiteral(
"--version")});
603 QRegularExpression regexp(QStringLiteral(
"Ver ([0-9]+)\\.([0-9]+)\\.([0-9]+)"));
604 auto match = regexp.match(out);
605 if (!
match.hasMatch()) {
609 return (
match.capturedView(1).toInt() << 16) | (
match.capturedView(2).toInt() << 8) |
match.capturedView(3).toInt();
612bool DbConfigMysql::initializeMariaDBDatabase(
const QString &confFile,
const QString &dataDir)
const
617 if (mMysqlInstallDbPath.isEmpty()) {
618 return QDir().mkpath(dataDir);
621 QFileInfo fi(mMysqlInstallDbPath);
624 const QString baseDir =
dir.absolutePath();
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)});
637bool DbConfigMysql::initializeMySQL5_7_6Database(
const QString &confFile,
const QString &dataDir)
const
641 {QStringLiteral(
"--defaults-file=%1").arg(confFile), QStringLiteral(
"--initialize"), QStringLiteral(
"--datadir=%1/").arg(dataDir)});
644bool DbConfigMysql::initializeMySQLDatabase(
const QString &confFile,
const QString &dataDir)
const
648 if (mMysqlInstallDbPath.isEmpty()) {
649 return QDir().mkpath(dataDir);
652 QFileInfo fi(mMysqlInstallDbPath);
655 const QString baseDir =
dir.absolutePath();
661 {QStringLiteral(
"--defaults-file=%1").arg(confFile), QStringLiteral(
"--basedir=%1").arg(baseDir), QStringLiteral(
"--datadir=%1/").arg(dataDir)});
664bool DbConfigMysql::disableConstraintChecks(
const QSqlDatabase &db)
667 return query.exec(QStringLiteral(
"SET FOREIGN_KEY_CHECKS=0"));
670bool DbConfigMysql::enableConstraintChecks(
const QSqlDatabase &db)
673 return query.exec(QStringLiteral(
"SET FOREIGN_KEY_CHECKS=1"));
676#include "moc_dbconfigmysql.cpp"
A base class that provides an unique access layer to configuration and initialization of different da...
static QString defaultDatabaseName()
Returns the suggested default database name, if none is specified in the configuration already.
int execute(const QString &cmd, const QStringList &args) const
Calls QProcess::execute() and also prints the command and arguments via qCDebug()
Helper integration between Akonadi and Qt.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
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)
NETWORKMANAGERQT_EXPORT QString version()
bool exists() const const
bool exists() const const
qsizetype count() const const
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 setValue(QAnyStringView key, const QVariant &value)
QVariant value(QAnyStringView key) const const
QSqlDatabase addDatabase(QSqlDriver *driver, const QString &connectionName)
QSqlDriver * driver() const const
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
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
qsizetype length() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
void msleep(unsigned long msecs)
bool toBool() const const
QString toString() const const