Permalink
Fetching contributors…
Cannot retrieve contributors at this time
606 lines (529 sloc) 25.6 KB
/*
Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "dbconfigmysql.h"
#include "utils.h"
#include "akonadiserver_debug.h"
#include <private/standarddirs_p.h>
#include <private/xdgbasedirs_p.h>
#include <QDateTime>
#include <QDir>
#include <QProcess>
#include <QThread>
#include <QRegularExpression>
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlQuery>
#include <QCoreApplication>
using namespace Akonadi;
using namespace Akonadi::Server;
#define MYSQL_MIN_MAJOR 5
#define MYSQL_MIN_MINOR 1
#define MYSQL_VERSION_CHECK(major, minor, patch) ((major << 16) | (minor << 8) | patch)
DbConfigMysql::DbConfigMysql()
: mInternalServer(true)
, mDatabaseProcess(nullptr)
{
}
QString DbConfigMysql::driverName() const
{
return QStringLiteral("QMYSQL");
}
QString DbConfigMysql::databaseName() const
{
return mDatabaseName;
}
bool DbConfigMysql::init(QSettings &settings)
{
// determine default settings depending on the driver
QString defaultHostName;
QString defaultOptions;
QString defaultServerPath;
QString defaultCleanShutdownCommand;
#ifndef Q_OS_WIN
const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")));
#endif
const bool defaultInternalServer = true;
#ifdef MYSQLD_EXECUTABLE
if (QFile::exists(QStringLiteral(MYSQLD_EXECUTABLE))) {
defaultServerPath = QStringLiteral(MYSQLD_EXECUTABLE);
}
#endif
const QStringList mysqldSearchPath = QStringList()
<< QStringLiteral("/usr/bin")
<< QStringLiteral("/usr/sbin")
<< QStringLiteral("/usr/local/sbin")
<< QStringLiteral("/usr/local/libexec")
<< QStringLiteral("/usr/libexec")
<< QStringLiteral("/opt/mysql/libexec")
<< QStringLiteral("/opt/local/lib/mysql5/bin")
<< QStringLiteral("/opt/mysql/sbin");
if (defaultServerPath.isEmpty()) {
defaultServerPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysqld"), mysqldSearchPath);
}
const QString mysqladminPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysqladmin"), mysqldSearchPath);
if (!mysqladminPath.isEmpty()) {
#ifndef Q_OS_WIN
defaultCleanShutdownCommand = QStringLiteral("%1 --defaults-file=%2/mysql.conf --socket=%3/mysql.socket shutdown")
.arg(mysqladminPath, StandardDirs::saveDir("data"), socketDirectory);
#else
defaultCleanShutdownCommand = QString::fromLatin1("%1 shutdown --shared-memory").arg(mysqladminPath);
#endif
}
mMysqlInstallDbPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysql_install_db"), mysqldSearchPath);
qCDebug(AKONADISERVER_LOG) << "Found mysql_install_db: " << mMysqlInstallDbPath;
mMysqlCheckPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysqlcheck"), mysqldSearchPath);
qCDebug(AKONADISERVER_LOG) << "Found mysqlcheck: " << mMysqlCheckPath;
mInternalServer = settings.value(QStringLiteral("QMYSQL/StartServer"), defaultInternalServer).toBool();
#ifndef Q_OS_WIN
if (mInternalServer) {
defaultOptions = QStringLiteral("UNIX_SOCKET=%1/mysql.socket").arg(socketDirectory);
}
#endif
// read settings for current driver
settings.beginGroup(driverName());
mDatabaseName = settings.value(QStringLiteral("Name"), defaultDatabaseName()).toString();
mHostName = settings.value(QStringLiteral("Host"), defaultHostName).toString();
mUserName = settings.value(QStringLiteral("User")).toString();
mPassword = settings.value(QStringLiteral("Password")).toString();
mConnectionOptions = settings.value(QStringLiteral("Options"), defaultOptions).toString();
mMysqldPath = settings.value(QStringLiteral("ServerPath"), defaultServerPath).toString();
mCleanServerShutdownCommand = settings.value(QStringLiteral("CleanServerShutdownCommand"), defaultCleanShutdownCommand).toString();
settings.endGroup();
// verify settings and apply permanent changes (written out below)
if (mInternalServer) {
mConnectionOptions = defaultOptions;
// intentionally not namespaced as we are the only one in this db instance when using internal mode
mDatabaseName = QStringLiteral("akonadi");
}
if (mInternalServer && (mMysqldPath.isEmpty() || !QFile::exists(mMysqldPath))) {
mMysqldPath = defaultServerPath;
}
qCDebug(AKONADISERVER_LOG) << "Using mysqld:" << mMysqldPath;
// store back the default values
settings.beginGroup(driverName());
settings.setValue(QStringLiteral("Name"), mDatabaseName);
settings.setValue(QStringLiteral("Host"), mHostName);
settings.setValue(QStringLiteral("Options"), mConnectionOptions);
if (!mMysqldPath.isEmpty()) {
settings.setValue(QStringLiteral("ServerPath"), mMysqldPath);
}
settings.setValue(QStringLiteral("StartServer"), mInternalServer);
settings.endGroup();
settings.sync();
// apply temporary changes to the settings
if (mInternalServer) {
mHostName.clear();
mUserName.clear();
mPassword.clear();
}
return true;
}
void DbConfigMysql::apply(QSqlDatabase &database)
{
if (!mDatabaseName.isEmpty()) {
database.setDatabaseName(mDatabaseName);
}
if (!mHostName.isEmpty()) {
database.setHostName(mHostName);
}
if (!mUserName.isEmpty()) {
database.setUserName(mUserName);
}
if (!mPassword.isEmpty()) {
database.setPassword(mPassword);
}
database.setConnectOptions(mConnectionOptions);
// can we check that during init() already?
Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId));
}
bool DbConfigMysql::useInternalServer() const
{
return mInternalServer;
}
bool DbConfigMysql::startInternalServer()
{
bool success = true;
const QString akDir = StandardDirs::saveDir("data");
const QString dataDir = StandardDirs::saveDir("data", QStringLiteral("db_data"));
#ifndef Q_OS_WIN
const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")));
const QString socketFile = QStringLiteral("%1/mysql.socket").arg(socketDirectory);
const QString pidFileName = QStringLiteral("%1/mysql.pid").arg(socketDirectory);
#endif
// generate config file
const QString globalConfig = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-global.conf"));
const QString localConfig = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-local.conf"));
const QString actualConfig = StandardDirs::saveDir("data") + QLatin1String("/mysql.conf");
if (globalConfig.isEmpty()) {
qCCritical(AKONADISERVER_LOG) << "Did not find MySQL server default configuration (mysql-global.conf)";
return false;
}
#ifdef Q_OS_LINUX
// It is recommended to disable CoW feature when running on Btrfs to improve
// database performance. Disabling CoW only has effect on empty directory (since
// it affects only new files), so we check whether MySQL has not yet been initialized.
QDir dir(dataDir + QDir::separator() + QLatin1String("mysql"));
if (!dir.exists()) {
if (Utils::getDirectoryFileSystem(dataDir) == QLatin1String("btrfs")) {
Utils::disableCoW(dataDir);
}
}
#endif
if (mMysqldPath.isEmpty()) {
qCCritical(AKONADISERVER_LOG) << "mysqld not found. Please verify your installation";
return false;
}
// Get the version of the mysqld server that we'll be using.
// MySQL (but not MariaDB) deprecates and removes command line options in
// patch version releases, so we need to adjust the command line options accordingly
// when running the helper utilities or starting the server
const unsigned int localVersion = parseCommandLineToolsVersion();
if (localVersion == 0x000000) {
qCCritical(AKONADISERVER_LOG) << "Failed to detect mysqld version!";
}
// TODO: Parse "MariaDB" or "MySQL" from the version string instead of relying
// on the version numbers
const bool isMariaDB = localVersion >= MYSQL_VERSION_CHECK(10, 0, 0);
qCDebug(AKONADISERVER_LOG).nospace() << "mysqld reports version " << (localVersion >> 16) << "." << ((localVersion >> 8) & 0x0000FF) << "." << (localVersion & 0x0000FF)
<< " (" << (isMariaDB ? "MariaDB" : "Oracle MySQL") << ")";
bool confUpdate = false;
QFile actualFile(actualConfig);
// update conf only if either global (or local) is newer than actual
if ((QFileInfo(globalConfig).lastModified() > QFileInfo(actualFile).lastModified()) ||
(QFileInfo(localConfig).lastModified() > QFileInfo(actualFile).lastModified())) {
QFile globalFile(globalConfig);
QFile localFile(localConfig);
if (globalFile.open(QFile::ReadOnly) && actualFile.open(QFile::WriteOnly)) {
actualFile.write(globalFile.readAll());
if (!localConfig.isEmpty()) {
if (localFile.open(QFile::ReadOnly)) {
actualFile.write(localFile.readAll());
localFile.close();
}
}
globalFile.close();
actualFile.close();
confUpdate = true;
} else {
qCCritical(AKONADISERVER_LOG) << "Unable to create MySQL server configuration file.";
qCCritical(AKONADISERVER_LOG) << "This means that either the default configuration file (mysql-global.conf) was not readable";
qCCritical(AKONADISERVER_LOG) << "or the target file (mysql.conf) could not be written.";
return false;
}
}
// MySQL doesn't like world writeable config files (which makes sense), but
// our config file somehow ends up being world-writable on some systems for no
// apparent reason nevertheless, so fix that
const QFile::Permissions allowedPerms = actualFile.permissions()
& (QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther);
if (allowedPerms != actualFile.permissions()) {
actualFile.setPermissions(allowedPerms);
}
if (dataDir.isEmpty()) {
qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database data directory";
return false;
}
if (akDir.isEmpty()) {
qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database log directory";
return false;
}
#ifndef Q_OS_WIN
if (socketDirectory.isEmpty()) {
qCCritical(AKONADISERVER_LOG) << "Akonadi server was not able to create database misc directory";
return false;
}
// the socket path must not exceed 103 characters, so check for max dir length right away
if (socketDirectory.length() >= 90) {
qCCritical(AKONADISERVER_LOG) << "MySQL cannot deal with a socket path this long. Path was: " << socketDirectory;
return false;
}
// If mysql.socket file exists, check if also the server process is still running,
// else we can safely remove the socket file (cleanup after a system crash, etc.)
QFile pidFile(pidFileName);
if (QFile::exists(socketFile) && pidFile.open(QIODevice::ReadOnly)) {
qCDebug(AKONADISERVER_LOG) << "Found a mysqld pid file, checking whether the server is still running...";
QByteArray pid = pidFile.readLine().trimmed();
QFile proc(QString::fromLatin1("/proc/" + pid + "/stat"));
// Check whether the process with the PID from pidfile still exists and whether
// it's actually still mysqld or, whether the PID has been recycled in the meanwhile.
bool serverIsRunning = false;
if (proc.open(QIODevice::ReadOnly)) {
const QByteArray stat = proc.readAll();
const QList<QByteArray> stats = stat.split(' ');
if (stats.count() > 1) {
// Make sure the PID actually belongs to mysql process
if (stats[1] == "(mysqld)") {
// Yup, our mysqld is actually running, so pretend we started the server
// and try to connect to it
qCWarning(AKONADISERVER_LOG) << "mysqld for Akonadi is already running, trying to connect to it.";
serverIsRunning = true;
}
}
proc.close();
}
if (!serverIsRunning) {
qCDebug(AKONADISERVER_LOG) << "No mysqld process with specified PID is running. Removing the pidfile and starting a new instance...";
pidFile.close();
pidFile.remove();
QFile::remove(socketFile);
}
}
#endif
// synthesize the mysqld command
QStringList arguments;
arguments << QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir);
arguments << QStringLiteral("--datadir=%1/").arg(dataDir);
#ifndef Q_OS_WIN
arguments << QStringLiteral("--socket=%1").arg(socketFile);
arguments << QStringLiteral("--pid-file=%1").arg(pidFileName);
#else
arguments << QString::fromLatin1("--shared-memory");
#endif
#ifndef Q_OS_WIN
// If mysql.socket file does not exists, then we must start the server,
// otherwise we reconnect to it
if (!QFile::exists(socketFile)) {
// move mysql error log file out of the way
const QFileInfo errorLog(dataDir + QDir::separator() + QLatin1String("mysql.err"));
if (errorLog.exists()) {
QFile logFile(errorLog.absoluteFilePath());
QFile oldLogFile(dataDir + QDir::separator() + QLatin1String("mysql.err.old"));
if (logFile.open(QFile::ReadOnly) && oldLogFile.open(QFile::Append)) {
oldLogFile.write(logFile.readAll());
oldLogFile.close();
logFile.close();
logFile.remove();
} else {
qCCritical(AKONADISERVER_LOG) << "Failed to open MySQL error log.";
}
}
// first run, some MySQL versions need a mysql_install_db run for that
const QString confFile = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-global.conf"));
if (QDir(dataDir).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty() && !mMysqlInstallDbPath.isEmpty()) {
if (isMariaDB) {
initializeMariaDBDatabase(confFile, dataDir);
} else if (localVersion >= MYSQL_VERSION_CHECK(5, 7, 6)) {
initializeMySQL5_7_6Database(confFile, dataDir);
} else {
initializeMySQLDatabase(confFile, dataDir);
}
}
// clear mysql ib_logfile's in case innodb_log_file_size option changed in last confUpdate
if (confUpdate) {
QFile(dataDir + QDir::separator() + QLatin1String("ib_logfile0")).remove();
QFile(dataDir + QDir::separator() + QLatin1String("ib_logfile1")).remove();
}
qCDebug(AKONADISERVER_LOG) << "Executing:" << mMysqldPath << arguments.join(QLatin1Char(' '));
mDatabaseProcess = new QProcess;
mDatabaseProcess->start(mMysqldPath, arguments);
if (!mDatabaseProcess->waitForStarted()) {
qCCritical(AKONADISERVER_LOG) << "Could not start database server!";
qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath;
qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString();
return false;
}
connect(mDatabaseProcess, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished), this, &DbConfigMysql::processFinished);
// wait until mysqld has created the socket file (workaround for QTBUG-47475 in Qt5.5.0)
int counter = 50; // avoid an endless loop in case mysqld terminated
while ((counter-- > 0) && !QFileInfo::exists(socketFile)) {
QThread::msleep(100);
}
} else {
qCDebug(AKONADISERVER_LOG) << "Found mysql.socket file, reconnecting to the database";
}
#endif
const QLatin1String initCon("initConnection");
{
QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QMYSQL"), initCon);
apply(db);
db.setDatabaseName(QString()); // might not exist yet, then connecting to the actual db will fail
if (!db.isValid()) {
qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup";
return false;
}
bool opened = false;
for (int i = 0; i < 120; ++i) {
opened = db.open();
if (opened) {
break;
}
if (mDatabaseProcess && mDatabaseProcess->waitForFinished(500)) {
qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!";
qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath;
qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments;
qCCritical(AKONADISERVER_LOG) << "stdout:" << mDatabaseProcess->readAllStandardOutput();
qCCritical(AKONADISERVER_LOG) << "stderr:" << mDatabaseProcess->readAllStandardError();
qCCritical(AKONADISERVER_LOG) << "exit code:" << mDatabaseProcess->exitCode();
qCCritical(AKONADISERVER_LOG) << "process error:" << mDatabaseProcess->errorString();
return false;
}
}
if (opened) {
if (!mMysqlCheckPath.isEmpty()) {
execute(mMysqlCheckPath, { QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir),
QStringLiteral("--check-upgrade"),
QStringLiteral("--auto-repair"),
#ifndef Q_OS_WIN
QStringLiteral("--socket=%1/mysql.socket").arg(socketDirectory),
#endif
mDatabaseName
});
}
// Verify MySQL version
{
QSqlQuery query(db);
if (!query.exec(QStringLiteral("SELECT VERSION()")) || !query.first()) {
qCCritical(AKONADISERVER_LOG) << "Failed to verify database server version";
qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
return false;
}
const QString version = query.value(0).toString();
const QStringList versions = version.split(QLatin1Char('.'), QString::SkipEmptyParts);
if (versions.count() < 3) {
qCCritical(AKONADISERVER_LOG) << "Invalid database server version: " << version;
return false;
}
if (versions[0].toInt() < MYSQL_MIN_MAJOR || (versions[0].toInt() == MYSQL_MIN_MAJOR && versions[1].toInt() < MYSQL_MIN_MINOR)) {
qCCritical(AKONADISERVER_LOG) << "Unsupported MySQL version:";
qCCritical(AKONADISERVER_LOG) << "Current version:" << QStringLiteral("%1.%2").arg(versions[0], versions[1]);
qCCritical(AKONADISERVER_LOG) << "Minimum required version:" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR);
qCCritical(AKONADISERVER_LOG) << "Please update your MySQL database server";
return false;
} else {
qCDebug(AKONADISERVER_LOG) << "MySQL version OK"
<< "(required" << QStringLiteral("%1.%2").arg(MYSQL_MIN_MAJOR).arg(MYSQL_MIN_MINOR)
<< ", available" << QStringLiteral("%1.%2").arg(versions[0], versions[1]) << ")";
}
}
{
QSqlQuery query(db);
if (!query.exec(QStringLiteral("USE %1").arg(mDatabaseName))) {
qCDebug(AKONADISERVER_LOG) << "Failed to use database" << mDatabaseName;
qCDebug(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
qCDebug(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
qCDebug(AKONADISERVER_LOG) << "Trying to create database now...";
if (!query.exec(QStringLiteral("CREATE DATABASE akonadi"))) {
qCCritical(AKONADISERVER_LOG) << "Failed to create database";
qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
success = false;
}
}
} // make sure query is destroyed before we close the db
db.close();
} else {
qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!";
qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
success = false;
}
}
return success;
}
void DbConfigMysql::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
Q_UNUSED(exitCode);
Q_UNUSED(exitStatus);
qCCritical(AKONADISERVER_LOG) << "database server stopped unexpectedly";
#ifndef Q_OS_WIN
// when the server stopped unexpectedly, make sure to remove the stale socket file since otherwise
// it can not be started again
const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc")));
const QString socketFile = QStringLiteral("%1/mysql.socket").arg(socketDirectory);
QFile::remove(socketFile);
#endif
QCoreApplication::quit();
}
void DbConfigMysql::stopInternalServer()
{
if (!mDatabaseProcess) {
return;
}
// closing initConnection this late to work around QTBUG-63108
QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
disconnect(mDatabaseProcess, static_cast<void(QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished),
this, &DbConfigMysql::processFinished);
// first, try the nicest approach
if (!mCleanServerShutdownCommand.isEmpty()) {
QProcess::execute(mCleanServerShutdownCommand);
if (mDatabaseProcess->waitForFinished(3000)) {
return;
}
}
mDatabaseProcess->terminate();
const bool result = mDatabaseProcess->waitForFinished(3000);
// We've waited nicely for 3 seconds, to no avail, let's be rude.
if (!result) {
mDatabaseProcess->kill();
}
}
void DbConfigMysql::initSession(const QSqlDatabase &database)
{
QSqlQuery query(database);
query.exec(QStringLiteral("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"));
}
int DbConfigMysql::parseCommandLineToolsVersion() const
{
QProcess mysqldProcess;
mysqldProcess.start(mMysqldPath, { QStringLiteral("--version") });
mysqldProcess.waitForFinished(10000 /* 10 secs */);
const QString out = QString::fromLocal8Bit(mysqldProcess.readAllStandardOutput());
QRegularExpression regexp(QStringLiteral("Ver ([0-9]+)\\.([0-9]+)\\.([0-9]+)"));
auto match = regexp.match(out);
if (!match.hasMatch()) {
return 0;
}
return (match.capturedRef(1).toInt() << 16) | (match.capturedRef(2).toInt() << 8) | match.capturedRef(3).toInt();
}
bool DbConfigMysql::initializeMariaDBDatabase(const QString &confFile, const QString &dataDir) const
{
QFileInfo fi(mMysqlInstallDbPath);
QDir dir = fi.dir();
dir.cdUp();
const QString baseDir = dir.absolutePath();
return 0 == execute(mMysqlInstallDbPath, {
QStringLiteral("--defaults-file=%1").arg(confFile),
QStringLiteral("--force"),
QStringLiteral("--basedir=%1").arg(baseDir),
QStringLiteral("--datadir=%1/").arg(dataDir)
});
}
/**
* As of MySQL 5.7.6 mysql_install_db is deprecated and mysqld --initailize should be used instead
* See MySQL Reference Manual section 2.10.1.1 (Initializing the Data Directory Manually Using mysqld)
*/
bool DbConfigMysql::initializeMySQL5_7_6Database(const QString &confFile, const QString &dataDir) const
{
return 0 == execute(mMysqldPath, {
QStringLiteral("--defaults-file=%1").arg(confFile),
QStringLiteral("--initialize"),
QStringLiteral("--datadir=%1/").arg(dataDir)
});
}
bool DbConfigMysql::initializeMySQLDatabase(const QString &confFile, const QString &dataDir) const
{
QFileInfo fi(mMysqlInstallDbPath);
QDir dir = fi.dir();
dir.cdUp();
const QString baseDir = dir.absolutePath();
// Don't use --force, it has been removed in MySQL 5.7.5
return 0 == execute(mMysqlInstallDbPath, {
QStringLiteral("--defaults-file=%1").arg(confFile),
QStringLiteral("--basedir=%1").arg(baseDir),
QStringLiteral("--datadir=%1/").arg(dataDir)
});
}