diff --git a/src/backend/corebackendmanager.cpp b/src/backend/corebackendmanager.cpp index 195bbf6..3f9b817 100644 --- a/src/backend/corebackendmanager.cpp +++ b/src/backend/corebackendmanager.cpp @@ -1,131 +1,133 @@ /************************************************************************* * Copyright (C) 2010 by Volker Lanz * * Copyright (C) 2015 by Teo Mrnjavac * * Copyright (C) 2016 by Andrius Štikonas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* *************************************************************************/ #include "backend/corebackendmanager.h" #include "backend/corebackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include CoreBackendManager::CoreBackendManager() : m_Backend(nullptr) { m_KAuthThread = new QThread(); kauthThread()->start(); } CoreBackendManager* CoreBackendManager::self() { static CoreBackendManager* instance = nullptr; if (instance == nullptr) instance = new CoreBackendManager; return instance; } QVector CoreBackendManager::list() const { auto filter = [&](const KPluginMetaData &metaData) { return metaData.serviceTypes().contains(QStringLiteral("PartitionManager/Plugin")) && metaData.category().contains(QStringLiteral("BackendPlugin")); }; // find backend plugins in standard path (e.g. /usr/lib64/qt5/plugins) using filter from above return KPluginLoader::findPlugins(QString(), filter); } void CoreBackendManager::startExternalCommandHelper() { + stopExternalCommandHelper(); + KAuth::Action action = KAuth::Action(QStringLiteral("org.kde.kpmcore.externalcommand.init")); action.setHelperId(QStringLiteral("org.kde.kpmcore.externalcommand")); action.setTimeout(10 * 24 * 3600 * 1000); // 10 days QVariantMap arguments; m_Uuid = QUuid::createUuid().toString(); arguments.insert(QStringLiteral("callerUuid"), Uuid()); action.setArguments(arguments); m_job = action.execute(); job()->start(); QEventLoop loop; auto exitLoop = [&] () {loop.exit();}; auto conn = QObject::connect(job(), &KAuth::ExecuteJob::newData, exitLoop); loop.exec(); QObject::disconnect(conn); } void CoreBackendManager::stopExternalCommandHelper() { QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); if (iface.isValid()) iface.call(QStringLiteral("exit"), CoreBackendManager::self()->Uuid()); } KAuth::ExecuteJob* CoreBackendManager::job() { return m_job; } bool CoreBackendManager::load(const QString& name) { if (backend()) unload(); KPluginLoader loader(name); KPluginFactory* factory = loader.factory(); if (factory != nullptr) { m_Backend = factory->create(nullptr); QString id = loader.metaData().toVariantMap().value(QStringLiteral("MetaData")) .toMap().value(QStringLiteral("KPlugin")).toMap().value(QStringLiteral("Id")).toString(); QString version = loader.metaData().toVariantMap().value(QStringLiteral("MetaData")) .toMap().value(QStringLiteral("KPlugin")).toMap().value(QStringLiteral("Version")).toString(); if (id.isEmpty()) return false; backend()->setId(id); backend()->setVersion(version); qDebug() << "Loaded backend plugin: " << backend()->id(); startExternalCommandHelper(); return true; } qWarning() << "Could not load plugin for core backend " << name << ": " << loader.errorString(); return false; } void CoreBackendManager::unload() { delete m_Backend; m_Backend = nullptr; } diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp index 1400cfb..c77ad36 100644 --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -1,246 +1,270 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016 by Andrius Štikonas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* *************************************************************************/ #include "backend/corebackendmanager.h" #include "core/device.h" #include "core/copysource.h" #include "core/copytarget.h" #include "core/copysourcedevice.h" #include "core/copytargetdevice.h" #include "util/externalcommand.h" #include "util/report.h" #include #include #include #include #include #include #include #include #include #include #include #include ExternalCommand::ExternalCommand(CopySource& source, CopyTarget& target,const QProcess::ProcessChannelMode processChannelMode) : m_ExitCode(-1), m_Source(&source), m_Target(&target) { setup(processChannelMode); } /** Starts copyBlocks command. */ bool ExternalCommand::startCopyBlocks() { this->moveToThread(CoreBackendManager::self()->kauthThread()); QTimer::singleShot(0, this, &ExternalCommand::copyBlocks); QEventLoop loop; connect(this, &ExternalCommand::finished, &loop, &QEventLoop::quit); loop.exec(); return true; } bool ExternalCommand::copyBlocks() { bool rval = true; const qint64 blockSize = 10 * 1024 * 1024; // number of bytes per block to copy if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus system bus"; return false; } // TODO KF6:Use new signal-slot syntax connect(CoreBackendManager::self()->job(), SIGNAL(percent(KJob*, unsigned long)), this, SLOT(emitProgress(KJob*, unsigned long))); connect(CoreBackendManager::self()->job(), &KAuth::ExecuteJob::newData, this, &ExternalCommand::emitReport); QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); iface.setTimeout(10 * 24 * 3600 * 1000); // 10 days if (iface.isValid()) { QDBusPendingCall pcall= iface.asyncCall(QStringLiteral("copyblocks"), CoreBackendManager::self()->Uuid(), m_Source->path(), m_Source->firstByte(), m_Source->length(), m_Target->path(), m_Target->firstByte(), blockSize); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QEventLoop loop; auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { loop.exit(); if (watcher->isError()) { qWarning() << watcher->error(); } else { QDBusPendingReply reply = *watcher; rval = reply.argumentAt<0>(); } emit finished(); setExitCode(!rval); }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); } return rval; } /** Creates a new ExternalCommand instance without Report. @param cmd the command to run @param args the arguments to pass to the command */ ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args, const QProcess::ProcessChannelMode processChannelMode) : m_Report(nullptr), m_Command(cmd), m_Args(args), m_ExitCode(-1), m_Output() { setup(processChannelMode); } /** Creates a new ExternalCommand instance with Report. @param report the Report to write output to. @param cmd the command to run @param args the arguments to pass to the command */ ExternalCommand::ExternalCommand(Report& report, const QString& cmd, const QStringList& args, const QProcess::ProcessChannelMode processChannelMode) : m_Report(report.newChild()), m_Command(cmd), m_Args(args), m_ExitCode(-1), m_Output() { setup(processChannelMode); } void ExternalCommand::setup(const QProcess::ProcessChannelMode processChannelMode) { arguments.insert(QStringLiteral("environment"), QStringList() << QStringLiteral("LC_ALL=C") << QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1")); arguments.insert(QStringLiteral("processChannelMode"), processChannelMode); // connect(this, qOverload(&QProcess::finished), this, &ExternalCommand::onFinished); // connect(this, &ExternalCommand::readyReadStandardOutput, this, &ExternalCommand::onReadOutput); } /** Starts the external command. @param timeout timeout to wait for the process to start @return true on success */ bool ExternalCommand::start(int timeout) { // this->moveToThread(CoreBackendManager::self()->kauthThread()); // QTimer::singleShot(0, this, &ExternalCommand::execute); // QEventLoop loop; // connect(this, &ExternalCommand::finished, &loop, &QEventLoop::quit); // loop.exec(); // return true; + Q_UNUSED(timeout) execute(); return true; } /** Executes the external command in kauthThread() thread. */ void ExternalCommand::execute() { if (report()) { report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); } QString cmd = QStandardPaths::findExecutable(command()); if (cmd.isEmpty()) cmd = QStandardPaths::findExecutable(command(), { QStringLiteral("/sbin/"), QStringLiteral("/usr/sbin/"), QStringLiteral("/usr/local/sbin/") }); if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus system bus"; return; } - QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); + QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), + QStringLiteral("/Helper"), + QStringLiteral("org.kde.kpmcore.externalcommand"), + QDBusConnection::systemBus()); + iface.setTimeout(10 * 24 * 3600 * 1000); // 10 days + if (iface.isValid()) { - QDBusReply reply = iface.call(QStringLiteral("start"), CoreBackendManager::self()->Uuid(), cmd, args(), m_Input, QStringList()); - if (reply.isValid()) { - m_Output = reply.value()[QStringLiteral("output")].toByteArray(); - setExitCode(reply.value()[QStringLiteral("exitCode")].toInt()); - } - else { - qWarning() << reply.error().message(); - } - } + QDBusPendingCall pcall = iface.asyncCall(QStringLiteral("start"), + CoreBackendManager::self()->Uuid(), + cmd, + args(), + m_Input, + QStringList()); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); - emit finished(); + QEventLoop loop; + + auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { + loop.exit(); + + if (watcher->isError()) + qWarning() << watcher->error(); + else { + QDBusPendingReply reply = *watcher; + + m_Output = reply.value()[QStringLiteral("output")].toByteArray(); + setExitCode(reply.value()[QStringLiteral("exitCode")].toInt()); + } + + emit finished(); + }; + + connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); + loop.exec(); + } } bool ExternalCommand::write(const QByteArray& input) { m_Input = input; return true; } /** Waits for the external command to finish. @param timeout timeout to wait until the process finishes. @return true on success */ bool ExternalCommand::waitFor(int timeout) { // closeWriteChannel(); /* if (!waitForFinished(timeout)) { if (report()) report()->line() << xi18nc("@info:status", "(Command timeout while running)"); return false; }*/ // onReadOutput(); - + Q_UNUSED(timeout) return true; } /** Runs the command. @param timeout timeout to use for waiting when starting and when waiting for the process to finish @return true on success */ bool ExternalCommand::run(int timeout) { return start(timeout) && waitFor(timeout)/* && exitStatus() == 0*/; } void ExternalCommand::onReadOutput() { // const QByteArray s = readAllStandardOutput(); // // if(m_Output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems // if (report()) // report()->line() << xi18nc("@info:status", "(Command is printing too much output)"); // return; // } // // m_Output += s; // // if (report()) // *report() << QString::fromLocal8Bit(s); } void ExternalCommand::onFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) setExitCode(exitCode); } diff --git a/src/util/externalcommandhelper.cpp b/src/util/externalcommandhelper.cpp index 159af88..a4d9012 100644 --- a/src/util/externalcommandhelper.cpp +++ b/src/util/externalcommandhelper.cpp @@ -1,246 +1,251 @@ /************************************************************************* * Copyright (C) 2017 by Andrius Štikonas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* *************************************************************************/ #include "externalcommandhelper.h" #include #include #include #include #include #include #include #include /** Initialize ExternalCommandHelper Daemon and prepare DBus interface */ ActionReply ExternalCommandHelper::init(const QVariantMap& args) { ActionReply reply; if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus session bus"; reply.addData(QStringLiteral("success"), false); return reply; } m_callerUuid = args[QStringLiteral("callerUuid")].toString(); if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface"))) { qWarning() << QDBusConnection::systemBus().lastError().message(); reply.addData(QStringLiteral("success"), false); return reply; } QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots); HelperSupport::progressStep(QVariantMap()); m_loop.exec(); reply.addData(QStringLiteral("success"), true); return reply; } /** Reads the given number of bytes from the sourceDevice into the given buffer. @param sourceDevice device or file to read from @param buffer buffer to store the bytes read in @param offset offset where to begin reading @param size the number of bytes to read @return true on success */ bool ExternalCommandHelper::readData(const QString& sourceDevice, QByteArray& buffer, qint64 offset, qint64 size) { QFile device(sourceDevice); if (!device.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { qCritical() << xi18n("Could not open device %1 for reading.", sourceDevice); return false; } if (!device.seek(offset)) { qCritical() << xi18n("Could not seek position %1 on device %1.", sourceDevice); return false; } buffer = device.read(size); if (size != buffer.size()) { qCritical() << xi18n("Could not read from device %1.", sourceDevice); return false; } return true; } /** Writes the data from buffer to a given device or file. @param targetDevice device or file to write to @param buffer the data that we write @param offset offset where to begin writing @return true on success */ bool ExternalCommandHelper::writeData(const QString &targetDevice, const QByteArray& buffer, qint64 offset) { QFile device(targetDevice); if (!device.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered)) { qCritical() << xi18n("Could not open device %1 for writing.", targetDevice); return false; } if (!device.seek(offset)) { qCritical() << xi18n("Could not seek position %1 on device %1.", targetDevice); return false; } if (device.write(buffer) != buffer.size()) { qCritical() << xi18n("Could not write to device %1.", targetDevice); return false; } return true; } bool ExternalCommandHelper::copyblocks(const QString& Uuid, const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize) { isCallerAuthorized(Uuid); const qint64 blocksToCopy = sourceLength / blockSize; qint64 readOffset = sourceFirstByte; qint64 writeOffset = targetFirstByte; qint32 copyDirection = 1; if (targetFirstByte > sourceFirstByte) { readOffset = sourceFirstByte + sourceLength - blockSize; writeOffset = targetFirstByte + sourceLength - blockSize; copyDirection = -1; } const qint64 lastBlock = sourceLength % blockSize; qint64 bytesWritten = 0; qint64 blocksCopied = 0; QByteArray buffer; int percent = 0; QTime t; t.start(); QVariantMap report; report[QStringLiteral("report")] = xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy, sourceLength, readOffset, writeOffset, copyDirection == 1 ? i18nc("direction: left", "left") : i18nc("direction: right", "right")); HelperSupport::progressStep(report); bool rval = true; while (blocksCopied < blocksToCopy) { if (!(rval = readData(sourceDevice, buffer, readOffset + blockSize * blocksCopied * copyDirection, blockSize))) break; if (!(rval = writeData(targetDevice, buffer, writeOffset + blockSize * blocksCopied * copyDirection))) break; bytesWritten += buffer.size(); if (++blocksCopied * 100 / blocksToCopy != percent) { percent = blocksCopied * 100 / blocksToCopy; if (percent % 5 == 0 && t.elapsed() > 1000) { const qint64 mibsPerSec = (blocksCopied * blockSize / 1024 / 1024) / (t.elapsed() / 1000); const qint64 estSecsLeft = (100 - percent) * t.elapsed() / percent / 1000; report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString()); HelperSupport::progressStep(report); } HelperSupport::progressStep(percent); } } // copy the remainder if (rval && lastBlock > 0) { Q_ASSERT(lastBlock < blockSize); const qint64 lastBlockReadOffset = copyDirection > 0 ? readOffset + blockSize * blocksCopied : sourceFirstByte; const qint64 lastBlockWriteOffset = copyDirection > 0 ? writeOffset + blockSize * blocksCopied : targetFirstByte; report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset); HelperSupport::progressStep(report); rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock); if (rval) rval = writeData(targetDevice, buffer, lastBlockWriteOffset); if (rval) { HelperSupport::progressStep(100); bytesWritten += buffer.size(); } } report[QStringLiteral("report")] = xi18ncp("@info:progress argument 2 is a string such as 7 bytes (localized accordingly)", "Copying 1 block (%2) finished.", "Copying %1 blocks (%2) finished.", blocksCopied, i18np("1 byte", "%1 bytes", bytesWritten)); HelperSupport::progressStep(report); return rval; } QVariantMap ExternalCommandHelper::start(const QString& Uuid, const QString& command, const QStringList& arguments, const QByteArray& input, const QStringList& environment) { isCallerAuthorized(Uuid); QVariantMap reply; // connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput); m_cmd.setEnvironment(environment); m_cmd.start(command, arguments); m_cmd.write(input); m_cmd.closeWriteChannel(); m_cmd.waitForFinished(-1); QByteArray output = m_cmd.readAllStandardOutput(); reply[QStringLiteral("output")] = output; reply[QStringLiteral("exitCode")] = m_cmd.exitCode(); return reply; } bool ExternalCommandHelper::isCallerAuthorized(const QString& Uuid) { if (Uuid != m_callerUuid) { qWarning() << "Caller is not authorized"; return false; } return true; } void ExternalCommandHelper::exit(const QString& Uuid) { isCallerAuthorized(Uuid); m_loop.exit(); + + if (QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface"))) + qDebug() << "org.kde.kpmcore.helperinterface unregistered"; + + QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); } void ExternalCommandHelper::onReadOutput() { // const QByteArray s = cmd.readAllStandardOutput(); // if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems // if (report()) // report()->line() << xi18nc("@info:status", "(Command is printing too much output)"); // return; // } // output += s; // if (report()) // *report() << QString::fromLocal8Bit(s); } KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper)