File indexing completed on 2025-01-05 04:46:57
0001 /* 0002 SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "dbconfigmysql.h" 0008 #include "akonadiserver_debug.h" 0009 #include "utils.h" 0010 0011 #include "private/standarddirs_p.h" 0012 0013 #include <QCoreApplication> 0014 #include <QDir> 0015 #include <QRegularExpression> 0016 #include <QSqlDriver> 0017 #include <QSqlError> 0018 #include <QSqlQuery> 0019 #include <QStandardPaths> 0020 #include <QThread> 0021 0022 using namespace Akonadi; 0023 using namespace Akonadi::Server; 0024 0025 #define MYSQL_MIN_MAJOR 5 0026 #define MYSQL_MIN_MINOR 1 0027 0028 #define MYSQL_VERSION_CHECK(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) 0029 0030 static const QString s_mysqlSocketFileName = QStringLiteral("mysql.socket"); 0031 static const QString s_initConnection = QStringLiteral("initConnectionMysql"); 0032 0033 DbConfigMysql::DbConfigMysql(const QString &configFile) 0034 : DbConfig(configFile) 0035 { 0036 } 0037 0038 DbConfigMysql::~DbConfigMysql() = default; 0039 0040 QString DbConfigMysql::driverName() const 0041 { 0042 return QStringLiteral("QMYSQL"); 0043 } 0044 0045 QString DbConfigMysql::databaseName() const 0046 { 0047 return mDatabaseName; 0048 } 0049 0050 QString DbConfigMysql::databasePath() const 0051 { 0052 return mDataDir; 0053 } 0054 0055 void DbConfigMysql::setDatabasePath(const QString &path, QSettings &settings) 0056 { 0057 mDataDir = path; 0058 settings.beginGroup(driverName()); 0059 settings.setValue(QStringLiteral("DataDir"), mDataDir); 0060 settings.endGroup(); 0061 settings.sync(); 0062 } 0063 0064 static QString findExecutable(const QString &bin) 0065 { 0066 static const QStringList mysqldSearchPath = { 0067 #ifdef MYSQLD_SCRIPTS_PATH 0068 QStringLiteral(MYSQLD_SCRIPTS_PATH), 0069 #endif 0070 QStringLiteral("/usr/bin"), 0071 QStringLiteral("/usr/sbin"), 0072 QStringLiteral("/usr/local/sbin"), 0073 QStringLiteral("/usr/local/libexec"), 0074 QStringLiteral("/usr/libexec"), 0075 QStringLiteral("/opt/mysql/libexec"), 0076 QStringLiteral("/opt/local/lib/mysql5/bin"), 0077 QStringLiteral("/opt/mysql/sbin"), 0078 }; 0079 QString path = QStandardPaths::findExecutable(bin); 0080 if (path.isEmpty()) { // No results in PATH; fall back to hardcoded list. 0081 path = QStandardPaths::findExecutable(bin, mysqldSearchPath); 0082 } 0083 return path; 0084 } 0085 0086 bool DbConfigMysql::init(QSettings &settings, bool storeSettings, const QString &dbPathOverride) 0087 { 0088 // determine default settings depending on the driver 0089 QString defaultHostName; 0090 QString defaultOptions; 0091 QString defaultServerPath; 0092 QString defaultCleanShutdownCommand; 0093 0094 #ifndef Q_OS_WIN 0095 const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length()); 0096 #endif 0097 0098 const bool defaultInternalServer = true; 0099 #ifdef MYSQLD_EXECUTABLE 0100 if (QFile::exists(QStringLiteral(MYSQLD_EXECUTABLE))) { 0101 defaultServerPath = QStringLiteral(MYSQLD_EXECUTABLE); 0102 } 0103 #endif 0104 if (defaultServerPath.isEmpty()) { 0105 defaultServerPath = findExecutable(QStringLiteral("mysqld")); 0106 } 0107 0108 const QString mysqladminPath = findExecutable(QStringLiteral("mysqladmin")); 0109 if (!mysqladminPath.isEmpty()) { 0110 #ifndef Q_OS_WIN 0111 defaultCleanShutdownCommand = QStringLiteral("%1 --defaults-file=%2/mysql.conf --socket=%3/%4 shutdown") 0112 .arg(mysqladminPath, StandardDirs::saveDir("data"), socketDirectory, s_mysqlSocketFileName); 0113 #else 0114 defaultCleanShutdownCommand = QString::fromLatin1("%1 shutdown --shared-memory").arg(mysqladminPath); 0115 #endif 0116 } 0117 0118 const QString defaultDataDir = dbPathOverride.isEmpty() ? StandardDirs::saveDir("data", QStringLiteral("db_data")) : dbPathOverride; 0119 0120 mMysqlInstallDbPath = findExecutable(QStringLiteral("mysql_install_db")); 0121 qCDebug(AKONADISERVER_LOG) << "Found mysql_install_db: " << mMysqlInstallDbPath; 0122 0123 mMysqlCheckPath = findExecutable(QStringLiteral("mysqlcheck")); 0124 qCDebug(AKONADISERVER_LOG) << "Found mysqlcheck: " << mMysqlCheckPath; 0125 0126 mMysqlUpgradePath = findExecutable(QStringLiteral("mysql_upgrade")); 0127 qCDebug(AKONADISERVER_LOG) << "Found mysql_upgrade: " << mMysqlUpgradePath; 0128 0129 mInternalServer = settings.value(QStringLiteral("QMYSQL/StartServer"), defaultInternalServer).toBool(); 0130 #ifndef Q_OS_WIN 0131 if (mInternalServer) { 0132 defaultOptions = QStringLiteral("UNIX_SOCKET=%1/%2").arg(socketDirectory, s_mysqlSocketFileName); 0133 } 0134 #endif 0135 0136 // read settings for current driver 0137 settings.beginGroup(driverName()); 0138 mDatabaseName = settings.value(QStringLiteral("Name"), defaultDatabaseName()).toString(); 0139 mHostName = settings.value(QStringLiteral("Host"), defaultHostName).toString(); 0140 mUserName = settings.value(QStringLiteral("User")).toString(); 0141 mPassword = settings.value(QStringLiteral("Password")).toString(); 0142 mConnectionOptions = settings.value(QStringLiteral("Options"), defaultOptions).toString(); 0143 mDataDir = settings.value(QStringLiteral("DataDir"), defaultDataDir).toString(); 0144 mMysqldPath = settings.value(QStringLiteral("ServerPath"), defaultServerPath).toString(); 0145 mCleanServerShutdownCommand = settings.value(QStringLiteral("CleanServerShutdownCommand"), defaultCleanShutdownCommand).toString(); 0146 settings.endGroup(); 0147 0148 // verify settings and apply permanent changes (written out below) 0149 if (mInternalServer) { 0150 mConnectionOptions = defaultOptions; 0151 // intentionally not namespaced as we are the only one in this db instance when using internal mode 0152 mDatabaseName = QStringLiteral("akonadi"); 0153 } 0154 if (mInternalServer && (mMysqldPath.isEmpty() || !QFile::exists(mMysqldPath))) { 0155 mMysqldPath = defaultServerPath; 0156 } 0157 0158 qCDebug(AKONADISERVER_LOG) << "Using mysqld:" << mMysqldPath; 0159 0160 if (storeSettings) { 0161 // store back the default values 0162 settings.beginGroup(driverName()); 0163 settings.setValue(QStringLiteral("Name"), mDatabaseName); 0164 settings.setValue(QStringLiteral("Host"), mHostName); 0165 settings.setValue(QStringLiteral("Options"), mConnectionOptions); 0166 if (!mMysqldPath.isEmpty()) { 0167 settings.setValue(QStringLiteral("ServerPath"), mMysqldPath); 0168 } 0169 settings.setValue(QStringLiteral("StartServer"), mInternalServer); 0170 settings.setValue(QStringLiteral("DataDir"), mDataDir); 0171 settings.endGroup(); 0172 settings.sync(); 0173 } 0174 0175 // apply temporary changes to the settings 0176 if (mInternalServer) { 0177 mHostName.clear(); 0178 mUserName.clear(); 0179 mPassword.clear(); 0180 } 0181 0182 return true; 0183 } 0184 0185 bool DbConfigMysql::isAvailable(QSettings &settings) 0186 { 0187 if (!QSqlDatabase::drivers().contains(driverName())) { 0188 return false; 0189 } 0190 0191 if (!init(settings, false)) { 0192 return false; 0193 } 0194 0195 if (mInternalServer && (mMysqldPath.isEmpty() || !QFile::exists(mMysqldPath))) { 0196 return false; 0197 } 0198 0199 return true; 0200 } 0201 0202 void DbConfigMysql::apply(QSqlDatabase &database) 0203 { 0204 if (!mDatabaseName.isEmpty()) { 0205 database.setDatabaseName(mDatabaseName); 0206 } 0207 if (!mHostName.isEmpty()) { 0208 database.setHostName(mHostName); 0209 } 0210 if (!mUserName.isEmpty()) { 0211 database.setUserName(mUserName); 0212 } 0213 if (!mPassword.isEmpty()) { 0214 database.setPassword(mPassword); 0215 } 0216 0217 database.setConnectOptions(mConnectionOptions); 0218 0219 // can we check that during init() already? 0220 Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId)); 0221 } 0222 0223 bool DbConfigMysql::useInternalServer() const 0224 { 0225 return mInternalServer; 0226 } 0227 0228 bool DbConfigMysql::startInternalServer() 0229 { 0230 bool success = true; 0231 0232 const QString akDir = StandardDirs::saveDir("data"); 0233 #ifndef Q_OS_WIN 0234 const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length()); 0235 const QString socketFile = QStringLiteral("%1/%2").arg(socketDirectory, s_mysqlSocketFileName); 0236 const QString pidFileName = QStringLiteral("%1/mysql.pid").arg(socketDirectory); 0237 #endif 0238 0239 // generate config file 0240 const QString globalConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf")); 0241 const QString localConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-local.conf")); 0242 const QString actualConfig = StandardDirs::saveDir("data") + QLatin1StringView("/mysql.conf"); 0243 qCDebug(AKONADISERVER_LOG) << " globalConfig : " << globalConfig << " localConfig : " << localConfig << " actualConfig : " << actualConfig; 0244 if (globalConfig.isEmpty()) { 0245 qCCritical(AKONADISERVER_LOG) << "Did not find MySQL server default configuration (mysql-global.conf)"; 0246 return false; 0247 } 0248 0249 #ifdef Q_OS_LINUX 0250 // It is recommended to disable CoW feature when running on Btrfs to improve 0251 // database performance. Disabling CoW only has effect on empty directory (since 0252 // it affects only new files), so we check whether MySQL has not yet been initialized. 0253 QDir dir(mDataDir + QDir::separator() + QLatin1StringView("mysql")); 0254 if (!dir.exists()) { 0255 if (Utils::getDirectoryFileSystem(mDataDir) == QLatin1StringView("btrfs")) { 0256 Utils::disableCoW(mDataDir); 0257 } 0258 } 0259 #endif 0260 0261 if (mMysqldPath.isEmpty()) { 0262 qCCritical(AKONADISERVER_LOG) << "mysqld not found. Please verify your installation"; 0263 return false; 0264 } 0265 0266 // Get the version of the mysqld server that we'll be using. 0267 // MySQL (but not MariaDB) deprecates and removes command line options in 0268 // patch version releases, so we need to adjust the command line options accordingly 0269 // when running the helper utilities or starting the server 0270 const unsigned int localVersion = parseCommandLineToolsVersion(); 0271 if (localVersion == 0x000000) { 0272 qCCritical(AKONADISERVER_LOG) << "Failed to detect mysqld version!"; 0273 } 0274 // TODO: Parse "MariaDB" or "MySQL" from the version string instead of relying 0275 // on the version numbers 0276 const bool isMariaDB = localVersion >= MYSQL_VERSION_CHECK(10, 0, 0); 0277 qCDebug(AKONADISERVER_LOG).nospace() << "mysqld reports version " << (localVersion >> 16) << "." << ((localVersion >> 8) & 0x0000FF) << "." 0278 << (localVersion & 0x0000FF) << " (" << (isMariaDB ? "MariaDB" : "Oracle MySQL") << ")"; 0279 0280 QFile actualFile(actualConfig); 0281 // update conf only if either global (or local) is newer than actual 0282 if ((QFileInfo(globalConfig).lastModified() > QFileInfo(actualFile).lastModified()) 0283 || (QFileInfo(localConfig).lastModified() > QFileInfo(actualFile).lastModified())) { 0284 QFile globalFile(globalConfig); 0285 QFile localFile(localConfig); 0286 if (globalFile.open(QFile::ReadOnly) && actualFile.open(QFile::WriteOnly)) { 0287 actualFile.write(globalFile.readAll()); 0288 if (!localConfig.isEmpty()) { 0289 if (localFile.open(QFile::ReadOnly)) { 0290 actualFile.write(localFile.readAll()); 0291 localFile.close(); 0292 } 0293 } 0294 globalFile.close(); 0295 actualFile.close(); 0296 } else { 0297 qCCritical(AKONADISERVER_LOG) << "Unable to create MySQL server configuration file."; 0298 qCCritical(AKONADISERVER_LOG) << "This means that either the default configuration file (mysql-global.conf) was not readable"; 0299 qCCritical(AKONADISERVER_LOG) << "or the target file (mysql.conf) could not be written."; 0300 return false; 0301 } 0302 } 0303 0304 // MySQL doesn't like world writeable config files (which makes sense), but 0305 // our config file somehow ends up being world-writable on some systems for no 0306 // apparent reason nevertheless, so fix that 0307 const QFile::Permissions allowedPerms = 0308 actualFile.permissions() & (QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther); 0309 if (allowedPerms != actualFile.permissions()) { 0310 actualFile.setPermissions(allowedPerms); 0311 } 0312 0313 if (mDataDir.isEmpty()) { 0314 qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database data directory"; 0315 return false; 0316 } 0317 0318 if (akDir.isEmpty()) { 0319 qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database log directory"; 0320 return false; 0321 } 0322 0323 #ifndef Q_OS_WIN 0324 if (socketDirectory.isEmpty()) { 0325 qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database misc directory"; 0326 return false; 0327 } 0328 0329 // the socket path must not exceed 103 characters, so check for max dir length right away 0330 if (socketDirectory.length() >= 90) { 0331 qCCritical(AKONADISERVER_LOG) << "MySQL cannot deal with a socket path this long. Path was: " << socketDirectory; 0332 return false; 0333 } 0334 0335 // If mysql socket file exists, check if also the server process is still running, 0336 // else we can safely remove the socket file (cleanup after a system crash, etc.) 0337 QFile pidFile(pidFileName); 0338 if (QFile::exists(socketFile) && pidFile.open(QIODevice::ReadOnly)) { 0339 qCDebug(AKONADISERVER_LOG) << "Found a mysqld pid file, checking whether the server is still running..."; 0340 QByteArray pid = pidFile.readLine().trimmed(); 0341 QFile proc(QString::fromLatin1("/proc/" + pid + "/stat")); 0342 // Check whether the process with the PID from pidfile still exists and whether 0343 // it's actually still mysqld or, whether the PID has been recycled in the meanwhile. 0344 bool serverIsRunning = false; 0345 if (proc.open(QIODevice::ReadOnly)) { 0346 const QByteArray stat = proc.readAll(); 0347 const QList<QByteArray> stats = stat.split(' '); 0348 if (stats.count() > 1) { 0349 // Make sure the PID actually belongs to mysql process 0350 0351 // Linux trims executable name in /proc filesystem to 15 characters 0352 const QString expectedProcName = QFileInfo(mMysqldPath).fileName().left(15); 0353 if (QString::fromLatin1(stats[1]) == QStringLiteral("(%1)").arg(expectedProcName)) { 0354 // Yup, our mysqld is actually running, so pretend we started the server 0355 // and try to connect to it 0356 qCWarning(AKONADISERVER_LOG) << "mysqld for Akonadi is already running, trying to connect to it."; 0357 serverIsRunning = true; 0358 } 0359 } 0360 proc.close(); 0361 } 0362 0363 if (!serverIsRunning) { 0364 qCDebug(AKONADISERVER_LOG) << "No mysqld process with specified PID is running. Removing the pidfile and starting a new instance..."; 0365 pidFile.close(); 0366 pidFile.remove(); 0367 QFile::remove(socketFile); 0368 } 0369 } 0370 #endif 0371 0372 // synthesize the mysqld command 0373 QStringList arguments; 0374 arguments << QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir); 0375 arguments << QStringLiteral("--datadir=%1/").arg(mDataDir); 0376 #ifndef Q_OS_WIN 0377 arguments << QStringLiteral("--socket=%1").arg(socketFile); 0378 arguments << QStringLiteral("--pid-file=%1").arg(pidFileName); 0379 #else 0380 arguments << QString::fromLatin1("--shared-memory"); 0381 #endif 0382 0383 #ifndef Q_OS_WIN 0384 // If mysql socket file does not exists, then we must start the server, 0385 // otherwise we reconnect to it 0386 if (!QFile::exists(socketFile)) { 0387 // move mysql error log file out of the way 0388 const QFileInfo errorLog(mDataDir + QDir::separator() + QLatin1StringView("mysql.err")); 0389 if (errorLog.exists()) { 0390 QFile logFile(errorLog.absoluteFilePath()); 0391 QFile oldLogFile(mDataDir + QDir::separator() + QLatin1StringView("mysql.err.old")); 0392 if (logFile.open(QFile::ReadOnly) && oldLogFile.open(QFile::WriteOnly)) { 0393 oldLogFile.write(logFile.readAll()); 0394 oldLogFile.close(); 0395 logFile.close(); 0396 logFile.remove(); 0397 } else { 0398 qCCritical(AKONADISERVER_LOG) << "Failed to open MySQL error log."; 0399 } 0400 } 0401 0402 // first run, some MySQL versions need a mysql_install_db run for that 0403 const QString confFile = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf")); 0404 if (QDir(mDataDir).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) { 0405 if (isMariaDB) { 0406 initializeMariaDBDatabase(confFile, mDataDir); 0407 } else if (localVersion >= MYSQL_VERSION_CHECK(5, 7, 6)) { 0408 initializeMySQL5_7_6Database(confFile, mDataDir); 0409 } else { 0410 initializeMySQLDatabase(confFile, mDataDir); 0411 } 0412 } 0413 0414 qCDebug(AKONADISERVER_LOG) << "Executing:" << mMysqldPath << arguments.join(QLatin1Char(' ')); 0415 mDatabaseProcess = std::make_unique<QProcess>(); 0416 mDatabaseProcess->start(mMysqldPath, arguments); 0417 if (!mDatabaseProcess->waitForStarted()) { 0418 qCCritical(AKONADISERVER_LOG) << "Could not start database server!"; 0419 qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath; 0420 qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments; 0421 qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString(); 0422 return false; 0423 } 0424 0425 connect(mDatabaseProcess.get(), &QProcess::finished, this, &DbConfigMysql::processFinished); 0426 0427 // wait until mysqld has created the socket file (workaround for QTBUG-47475 in Qt5.5.0) 0428 int counter = 50; // avoid an endless loop in case mysqld terminated 0429 while ((counter-- > 0) && !QFileInfo::exists(socketFile)) { 0430 QThread::msleep(100); 0431 } 0432 } else { 0433 qCDebug(AKONADISERVER_LOG) << "Found " << qPrintable(s_mysqlSocketFileName) << " file, reconnecting to the database"; 0434 } 0435 #endif 0436 0437 { 0438 QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QMYSQL"), s_initConnection); 0439 apply(db); 0440 0441 db.setDatabaseName(QString()); // might not exist yet, then connecting to the actual db will fail 0442 if (!db.isValid()) { 0443 qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup"; 0444 return false; 0445 } 0446 0447 bool opened = false; 0448 for (int i = 0; i < 120; ++i) { 0449 opened = db.open(); 0450 if (opened) { 0451 break; 0452 } 0453 if (mDatabaseProcess && mDatabaseProcess->waitForFinished(500)) { 0454 qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!"; 0455 qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath; 0456 qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments; 0457 qCCritical(AKONADISERVER_LOG) << "stdout:" << mDatabaseProcess->readAllStandardOutput(); 0458 qCCritical(AKONADISERVER_LOG) << "stderr:" << mDatabaseProcess->readAllStandardError(); 0459 qCCritical(AKONADISERVER_LOG) << "exit code:" << mDatabaseProcess->exitCode(); 0460 qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString(); 0461 return false; 0462 } 0463 } 0464 0465 if (opened) { 0466 if (!mMysqlCheckPath.isEmpty()) { 0467 execute(mMysqlCheckPath, 0468 {QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir), 0469 QStringLiteral("--check-upgrade"), 0470 QStringLiteral("--auto-repair"), 0471 #ifndef Q_OS_WIN 0472 QStringLiteral("--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName), 0473 #endif 0474 mDatabaseName}); 0475 } 0476 0477 if (!mMysqlUpgradePath.isEmpty()) { 0478 execute(mMysqlUpgradePath, 0479 {QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir) 0480 #ifndef Q_OS_WIN 0481 , 0482 QStringLiteral("--socket=%1/%2").arg(socketDirectory, s_mysqlSocketFileName) 0483 #endif 0484 }); 0485 } 0486 0487 // Verify MySQL version 0488 { 0489 QSqlQuery query(db); 0490 if (!query.exec(QStringLiteral("SELECT VERSION()")) || !query.first()) { 0491 qCCritical(AKONADISERVER_LOG) << "Failed to verify database server version"; 0492 qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); 0493 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); 0494 return false; 0495 } 0496 0497 const QString version = query.value(0).toString(); 0498 const QStringList versions = version.split(QLatin1Char('.'), Qt::SkipEmptyParts); 0499 if (versions.count() < 3) { 0500 qCCritical(AKONADISERVER_LOG) << "Invalid database server version: " << version; 0501 return false; 0502 } 0503 0504 if (versions[0].toInt() < MYSQL_MIN_MAJOR || (versions[0].toInt() == MYSQL_MIN_MAJOR && versions[1].toInt() < MYSQL_MIN_MINOR)) { 0505 qCCritical(AKONADISERVER_LOG) << "Unsupported MySQL version:"; 0506 qCCritical(AKONADISERVER_LOG) << "Current version:" << QStringLiteral("%1.%2").arg(versions[0], versions[1]); 0507 qCCritical(AKONADISERVER_LOG) << "Minimum required version:" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR); 0508 qCCritical(AKONADISERVER_LOG) << "Please update your MySQL database server"; 0509 return false; 0510 } else { 0511 qCDebug(AKONADISERVER_LOG) << "MySQL version OK" 0512 << "(required" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR) << ", available" 0513 << QStringLiteral("%1.%2").arg(versions[0], versions[1]) << ")"; 0514 } 0515 } 0516 0517 { 0518 QSqlQuery query(db); 0519 if (!query.exec(QStringLiteral("USE %1").arg(mDatabaseName))) { 0520 qCDebug(AKONADISERVER_LOG) << "Failed to use database" << mDatabaseName; 0521 qCDebug(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); 0522 qCDebug(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); 0523 qCDebug(AKONADISERVER_LOG) << "Trying to create database now..."; 0524 if (!query.exec(QStringLiteral("CREATE DATABASE akonadi"))) { 0525 qCCritical(AKONADISERVER_LOG) << "Failed to create database"; 0526 qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); 0527 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); 0528 success = false; 0529 } 0530 } 0531 } // make sure query is destroyed before we close the db 0532 db.close(); 0533 } else { 0534 qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!"; 0535 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); 0536 success = false; 0537 } 0538 } 0539 0540 return success; 0541 } 0542 0543 void DbConfigMysql::processFinished(int exitCode, QProcess::ExitStatus exitStatus) 0544 { 0545 Q_UNUSED(exitCode) 0546 Q_UNUSED(exitStatus) 0547 0548 qCCritical(AKONADISERVER_LOG) << "database server stopped unexpectedly"; 0549 0550 #ifndef Q_OS_WIN 0551 // when the server stopped unexpectedly, make sure to remove the stale socket file since otherwise 0552 // it can not be started again 0553 const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")), s_mysqlSocketFileName.length()); 0554 const QString socketFile = QStringLiteral("%1/%2").arg(socketDirectory, s_mysqlSocketFileName); 0555 QFile::remove(socketFile); 0556 #endif 0557 0558 QCoreApplication::quit(); 0559 } 0560 0561 void DbConfigMysql::stopInternalServer() 0562 { 0563 if (!mDatabaseProcess) { 0564 return; 0565 } 0566 0567 // closing initConnection this late to work around QTBUG-63108 0568 QSqlDatabase::removeDatabase(s_initConnection); 0569 0570 disconnect(mDatabaseProcess.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &DbConfigMysql::processFinished); 0571 0572 // first, try the nicest approach 0573 if (!mCleanServerShutdownCommand.isEmpty()) { 0574 QProcess::execute(mCleanServerShutdownCommand, QStringList()); 0575 if (mDatabaseProcess->waitForFinished(3000)) { 0576 return; 0577 } 0578 } 0579 0580 mDatabaseProcess->terminate(); 0581 const bool result = mDatabaseProcess->waitForFinished(3000); 0582 // We've waited nicely for 3 seconds, to no avail, let's be rude. 0583 if (!result) { 0584 mDatabaseProcess->kill(); 0585 } 0586 } 0587 0588 void DbConfigMysql::initSession(const QSqlDatabase &database) 0589 { 0590 QSqlQuery query(database); 0591 query.exec(QStringLiteral("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")); 0592 } 0593 0594 int DbConfigMysql::parseCommandLineToolsVersion() const 0595 { 0596 QProcess mysqldProcess; 0597 mysqldProcess.start(mMysqldPath, {QStringLiteral("--version")}); 0598 mysqldProcess.waitForFinished(10000 /* 10 secs */); 0599 0600 const QString out = QString::fromLocal8Bit(mysqldProcess.readAllStandardOutput()); 0601 QRegularExpression regexp(QStringLiteral("Ver ([0-9]+)\\.([0-9]+)\\.([0-9]+)")); 0602 auto match = regexp.match(out); 0603 if (!match.hasMatch()) { 0604 return 0; 0605 } 0606 0607 return (match.capturedView(1).toInt() << 16) | (match.capturedView(2).toInt() << 8) | match.capturedView(3).toInt(); 0608 } 0609 0610 bool DbConfigMysql::initializeMariaDBDatabase(const QString &confFile, const QString &dataDir) const 0611 { 0612 // KDE Neon (and possible others) don't ship mysql_install_db, but it seems 0613 // that MariaDB can initialize itself automatically on first start, it only 0614 // needs that the datadir directory exists 0615 if (mMysqlInstallDbPath.isEmpty()) { 0616 return QDir().mkpath(dataDir); 0617 } 0618 0619 QFileInfo fi(mMysqlInstallDbPath); 0620 QDir dir = fi.dir(); 0621 dir.cdUp(); 0622 const QString baseDir = dir.absolutePath(); 0623 return 0 0624 == execute(mMysqlInstallDbPath, 0625 {QStringLiteral("--defaults-file=%1").arg(confFile), 0626 QStringLiteral("--force"), 0627 QStringLiteral("--basedir=%1").arg(baseDir), 0628 QStringLiteral("--datadir=%1/").arg(dataDir)}); 0629 } 0630 0631 /** 0632 * As of MySQL 5.7.6 mysql_install_db is deprecated and mysqld --initailize should be used instead 0633 * See MySQL Reference Manual section 2.10.1.1 (Initializing the Data Directory Manually Using mysqld) 0634 */ 0635 bool DbConfigMysql::initializeMySQL5_7_6Database(const QString &confFile, const QString &dataDir) const 0636 { 0637 return 0 0638 == execute(mMysqldPath, 0639 {QStringLiteral("--defaults-file=%1").arg(confFile), QStringLiteral("--initialize"), QStringLiteral("--datadir=%1/").arg(dataDir)}); 0640 } 0641 0642 bool DbConfigMysql::initializeMySQLDatabase(const QString &confFile, const QString &dataDir) const 0643 { 0644 // On FreeBSD MySQL 5.6 is also installed without mysql_install_db, so this 0645 // might do the trick there as well. 0646 if (mMysqlInstallDbPath.isEmpty()) { 0647 return QDir().mkpath(dataDir); 0648 } 0649 0650 QFileInfo fi(mMysqlInstallDbPath); 0651 QDir dir = fi.dir(); 0652 dir.cdUp(); 0653 const QString baseDir = dir.absolutePath(); 0654 0655 // Don't use --force, it has been removed in MySQL 5.7.5 0656 return 0 0657 == execute( 0658 mMysqlInstallDbPath, 0659 {QStringLiteral("--defaults-file=%1").arg(confFile), QStringLiteral("--basedir=%1").arg(baseDir), QStringLiteral("--datadir=%1/").arg(dataDir)}); 0660 } 0661 0662 bool DbConfigMysql::disableConstraintChecks(const QSqlDatabase &db) 0663 { 0664 QSqlQuery query(db); 0665 return query.exec(QStringLiteral("SET FOREIGN_KEY_CHECKS=0")); 0666 } 0667 0668 bool DbConfigMysql::enableConstraintChecks(const QSqlDatabase &db) 0669 { 0670 QSqlQuery query(db); 0671 return query.exec(QStringLiteral("SET FOREIGN_KEY_CHECKS=1")); 0672 } 0673 0674 #include "moc_dbconfigmysql.cpp"