diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp index c6d0962..6e420cd 100644 --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -1,410 +1,421 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016-2018 by Andrius Štikonas * * Copyright (C) 2019 by Shubham * * * * 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/copytargetbytearray.h" #include "core/copysourcedevice.h" #include "core/copytargetdevice.h" #include "util/globallog.h" #include "util/externalcommand.h" #include "util/externalcommand_polkitbackend.h" #include "util/externalcommandhelper.h" #include "util/report.h" #include "externalcommandhelper_interface.h" #include #include #include #include #include #include #include #include #include #include #include struct ExternalCommandPrivate { Report *m_Report; QString m_Command; QStringList m_Args; int m_ExitCode; QByteArray m_Output; QByteArray m_Input; DBusThread *m_thread; QProcess::ProcessChannelMode processChannelMode; }; bool ExternalCommand::helperStarted = false; QWidget* ExternalCommand::parent; Auth::PolkitQt1Backend* ExternalCommand::m_authJob; /** 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) : d(std::make_unique()) { d->m_Report = nullptr; d->m_Command = cmd; d->m_Args = args; d->m_ExitCode = -1; d->m_Output = QByteArray(); if (!helperStarted) if(!startHelper()) Log(Log::Level::error) << xi18nc("@info:status", "Could not obtain administrator privileges."); d->processChannelMode = 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) : d(std::make_unique()) { d->m_Report = report.newChild(); d->m_Command = cmd; d->m_Args = args; d->m_ExitCode = -1; d->m_Output = QByteArray(); d->processChannelMode = processChannelMode; } ExternalCommand::~ExternalCommand() { } /* void ExternalCommand::setup() { connect(this, qOverload(&QProcess::finished), this, &ExternalCommand::onFinished); connect(this, &ExternalCommand::readyReadStandardOutput, this, &ExternalCommand::onReadOutput); } */ /** Executes the external command. @param timeout timeout to wait for the process to start @return true on success */ bool ExternalCommand::start(int timeout) { if (command().isEmpty()) return false; if (!QDBusConnection::systemBus().isConnected()) { qWarning() << QDBusConnection::systemBus().lastError().message(); QTimer::singleShot(timeout, this, &ExternalCommand::quit); return false; } if (report()) report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" )) qDebug() << 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/") }); auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days bool rval = false; QDBusPendingCall pcall = interface->start(cmd, args(), d->m_Input, d->processChannelMode); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QEventLoop loop; auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { loop.exit(); if (watcher->isError()) qWarning() << watcher->error(); else { QDBusPendingReply reply = *watcher; d->m_Output = reply.value()[QStringLiteral("output")].toByteArray(); setExitCode(reply.value()[QStringLiteral("exitCode")].toInt()); rval = reply.value()[QStringLiteral("success")].toBool(); } }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); QTimer::singleShot(timeout, this, &ExternalCommand::quit); return rval; } bool ExternalCommand::copyBlocks(const CopySource& source, CopyTarget& target) { bool rval = true; const qint64 blockSize = 10 * 1024 * 1024; // number of bytes per block to copy if (!QDBusConnection::systemBus().isConnected()) { qWarning() << QDBusConnection::systemBus().lastError().message(); return false; } auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), - - - - QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days QDBusPendingCall pcall = interface->copyblocks(source.path(), source.firstByte(), source.length(), target.path(), 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.value()[QStringLiteral("success")].toBool(); CopyTargetByteArray *byteArrayTarget = dynamic_cast(&target); if (byteArrayTarget) byteArrayTarget->m_Array = reply.value()[QStringLiteral("targetByteArray")].toByteArray(); } setExitCode(!rval); }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); return rval; } bool ExternalCommand::writeData(Report& commandReport, const QByteArray& buffer, const QString& deviceNode, const quint64 firstByte) { d->m_Report = commandReport.newChild(); if (report()) report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); bool rval = true; if (!QDBusConnection::systemBus().isConnected()) { qWarning() << QDBusConnection::systemBus().lastError().message(); return false; } auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days QDBusPendingCall pcall = interface->writeData(buffer, deviceNode, firstByte); 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>(); } setExitCode(!rval); }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); return rval; } bool ExternalCommand::write(const QByteArray& input) { if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" )) qDebug() << "Command input:" << QString::fromLocal8Bit(input); d->m_Input = input; 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) /* && 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::setCommand(const QString& cmd) { d->m_Command = cmd; } const QString& ExternalCommand::command() const { return d->m_Command; } const QStringList& ExternalCommand::args() const { return d->m_Args; } void ExternalCommand::addArg(const QString& s) { d->m_Args << s; } void ExternalCommand::setArgs(const QStringList& args) { d->m_Args = args; } int ExternalCommand::exitCode() const { return d->m_ExitCode; } const QString ExternalCommand::output() const { return QString::fromLocal8Bit(d->m_Output); } const QByteArray& ExternalCommand::rawOutput() const { return d->m_Output; } Report* ExternalCommand::report() { return d->m_Report; } void ExternalCommand::setExitCode(int i) { d->m_ExitCode = i; } // Dummy function for QTimer void ExternalCommand::quit() { } bool ExternalCommand::startHelper() { if (!QDBusConnection::systemBus().isConnected()) { qWarning() << QDBusConnection::systemBus().lastError().message(); return false; } QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); if (iface.isValid()) { exit(0); } d->m_thread = new DBusThread; d->m_thread->start(); ////////////////////////////////////// // Authorize using Polkit backend /// //////////////////////////////////// // initialize KDE Polkit daemon m_authJob->initPolkitAgent(QStringLiteral("org.kde.kpmcore.externalcommand.init"), parent); bool isActionAuthorized = m_authJob->authorizeAction(QStringLiteral("org.kde.kpmcore.externalcommand.init"), m_authJob->callerID()); auto authResult = m_authJob->actionStatus(QStringLiteral("org.kde.kpmcore.externalcommand.init"), m_authJob->callerID()); - - // Wait until ExternalCommand Helper is ready and sends signal + // Wait until ExternalCommand Helper is ready and sends signal(Connect to newData signal) QEventLoop loop; auto exitLoop = [&] () { loop.exit(); }; - + ExternalCommand cmd; + auto conn = QObject::connect(&cmd, &ExternalCommand::newData, exitLoop); + loop.exec(); + + QObject::disconnect(conn); if (!isActionAuthorized || authResult == PolkitQt1::Authority::No || authResult == PolkitQt1::Authority::Unknown) { qDebug() << "Unable to obtain Administrative privileges, the action can not be executed!!"; } helperStarted = true; return true; } void ExternalCommand::stopHelper() { auto interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus()); interface->exit(); } +void ExternalCommand::emitNewData(int percent) +{ + Q_UNUSED(percent) + emit newData(); +} + +void ExternalCommand::emitNewData(QString& message) +{ + Q_UNUSED(message) + emit newData(); +} + void DBusThread::run() { if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface")) || !QDBusConnection::systemBus().registerObject(QStringLiteral("/Application"), this, QDBusConnection::ExportAllSlots)) { qWarning() << QDBusConnection::systemBus().lastError().message(); return; } QEventLoop loop; loop.exec(); } diff --git a/src/util/externalcommand.h b/src/util/externalcommand.h index f087f96..f24b6df 100644 --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -1,143 +1,149 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016-2018 by Andrius Štikonas * * Copyright (C) 2019 by Shubham * * * * 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 .* *************************************************************************/ #ifndef KPMCORE_EXTERNALCOMMAND_H #define KPMCORE_EXTERNALCOMMAND_H #include "util/libpartitionmanagerexport.h" #include #include #include #include #include #include #include namespace Auth { class PolkitQt1Backend; } class CopySource; class CopyTarget; class Report; class KJob; class QDBusInterface; struct ExternalCommandPrivate; class DBusThread : public QThread { Q_OBJECT // We register on DBus so the helper can monitor us and terminate if we terminate. Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.applicationinterface") void run() override; }; /** An external command. Runs an external command as a child process. @author Volker Lanz @author Andrius Štikonas */ class LIBKPMCORE_EXPORT ExternalCommand : public QObject { Q_OBJECT Q_DISABLE_COPY(ExternalCommand) public: explicit ExternalCommand(const QString& cmd = QString(), const QStringList& args = QStringList(), const QProcess::ProcessChannelMode processChannelMode = QProcess::MergedChannels); explicit ExternalCommand(Report& report, const QString& cmd = QString(), const QStringList& args = QStringList(), const QProcess::ProcessChannelMode processChannelMode = QProcess::MergedChannels); ~ExternalCommand(); public: bool copyBlocks(const CopySource& source, CopyTarget& target); bool writeData(Report& commandReport, const QByteArray& buffer, const QString& deviceNode, const quint64 firstByte); // same as copyBlocks but from QByteArray /**< @param cmd the command to run */ void setCommand(const QString& cmd); /**< @return the command to run */ const QString& command() const; /**< @return the arguments */ const QStringList& args() const; /**< @param s the argument to add */ void addArg(const QString& s); /**< @param args the new arguments */ void setArgs(const QStringList& args); bool write(const QByteArray& input); /**< @param input the input for the program */ bool startCopyBlocks(); bool start(int timeout = 30000); bool run(int timeout = 30000); /**< @return the exit code */ int exitCode() const; /**< @return the command output */ const QString output() const; /**< @return the command output */ const QByteArray& rawOutput() const; /**< @return pointer to the Report or nullptr */ Report* report(); /**< Dummy function for QTimer */ void quit(); /**< start ExternalCommand Helper */ bool startHelper(); /**< stop ExternalCommand Helper */ static void stopHelper(); /**< Sets a parent widget for the authentication dialog. * @param p parent widget */ static void setParentWidget(QWidget *p) { parent = p; } Q_SIGNALS: void progress(int); void reportSignal(const QVariantMap&); - + + // remove above signals + void newData(); + public Q_SLOTS: void emitProgress(KJob*, unsigned long percent) { emit progress(percent); } void emitReport(const QVariantMap& report) { emit reportSignal(report); } + + void emitNewData(int percent); + void emitNewData(QString& message); private: void setExitCode(int i); // void onReadOutput(); private: std::unique_ptr d; // Use Polkit Authorization backend for authorizing actions static Auth::PolkitQt1Backend *m_authJob; static bool helperStarted; static QWidget *parent; }; #endif // KPMCORE_EXTERNALCOMMAND_H diff --git a/src/util/externalcommandhelper.cpp b/src/util/externalcommandhelper.cpp index 2951269..aef72a2 100644 --- a/src/util/externalcommandhelper.cpp +++ b/src/util/externalcommandhelper.cpp @@ -1,321 +1,380 @@ /************************************************************************* * Copyright (C) 2017-2018 by Andrius Štikonas * * Copyright (C) 2019 by Shubham * * * * 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 "externalcommand_whitelist.h" #include #include #include +#include #include #include -#include #include #include #define HELPER_MAIN() \ int main(int argc, char **argv) { ExternalCommandHelper helper; return helper.helperMain(argc, argv); } /** Initialize ExternalCommandHelper Daemon and prepare DBus interface * * Helper runs in the background until application exits. * To avoid forever running helper in case of application crash * ExternalCommand class opens a DBus service that we monitor for changes. * If helper is not busy then it exits when the client services gets * unregistered. Otherwise, * we wait for the current job to finish before exiting, so even in case * of main application crash, we do not leave partially moved data. * * This helper also starts another DBus interface where it listens to * command execution requests from the application that started the helper. * */ /** Reads the given number of bytes from the sourceDevice into the given buffer. @param argc argument count @param argv argument vector @return zero on success, non-zero on failure */ int ExternalCommandHelper::helperMain(int argc, char **argv) { QCoreApplication app(argc, argv); if (!QDBusConnection::systemBus().isConnected() || + !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots) || !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || - !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { + !QDBusConnection::systemBus().registerObject(QStringLiteral("/Application"), this, QDBusConnection::ExportAllSlots) || + !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface"))) { qDebug() << "Failed to initialize the Helper"; qWarning() << QDBusConnection::systemBus().lastError().message(); - // We have no reason to live when Main GUI app has expired - - qApp->quit(); return app.exec(); } m_loop = std::make_unique(); - emit reportProgress(QVariantMap()); + + // We send zero percent new data on initial start-up + sendProgress(0); // QDBus Service watcher which keeps an eye on the client (Main GUI app) // End the loop and return only once the client has unregistered over the QDBus. auto serviceWatcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kpmcore.applicationinterface"), QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForUnregistration, this); connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, [this]() { m_loop->exit(); }); m_loop->exec(); qApp->quit(); return app.exec(); } - /** 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, const qint64 offset, const 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 %2.", offset, 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, const 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 %2.", offset, targetDevice); return false; } if (device.write(buffer) != buffer.size()) { qCritical() << xi18n("Could not write to device %1.", targetDevice); return false; } return true; } +/** Sends progress to the main application in terms of percentage. + @param percent Percent of job completed. +*/ +void ExternalCommandHelper::sendProgress(int percent) +{ + QDBusInterface *iface = new QDBusInterface(QStringLiteral("org.kde.kpmcore.applicationinterface"), QStringLiteral("/Application"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); + + if (!iface->isValid()) { + return; + } + + iface->setTimeout(10 * 24 * 3600 * 1000); + + QDBusPendingCall pcall = iface->asyncCall(QLatin1String("emitNewData"), percent); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + QEventLoop loop; + + auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { + loop.exit(); + + if (watcher->isError()) + qWarning() << watcher->error(); + }; + + connect(watcher, &QDBusPendingCallWatcher::finished, [this, exitLoop, watcher](){ + watcher->deleteLater(); + } + ); + + loop.exec(); +} + +/** Sends progress to the main application in terms of string message. + @param message Message to send to the main application. +*/ +void ExternalCommandHelper::sendProgress(QString message) +{ + QDBusInterface *iface = new QDBusInterface(QStringLiteral("org.kde.kpmcore.applicationinterface"), QStringLiteral("/Application"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); + + if (!iface->isValid()) { + return; + } + + iface->setTimeout(10 * 24 * 3600 * 1000); + + QDBusPendingCall pcall = iface->asyncCall(QLatin1String("emitNewData"), message); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + QEventLoop loop; + + auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { + loop.exit(); + + if (watcher->isError()) + qWarning() << watcher->error(); + }; + + connect(watcher, &QDBusPendingCallWatcher::finished, [this, exitLoop, watcher](){ + watcher->deleteLater(); + } + ); + + loop.exec(); +} + // If targetDevice is empty then return QByteArray with data that was read from disk. QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize) { QVariantMap reply; reply[QStringLiteral("success")] = true; 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; - + + QElapsedTimer 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")); - - emit reportProgress(report); - + sendProgress(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"))); + bool rval = true; while (blocksCopied < blocksToCopy && !targetDevice.isEmpty()) { 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()); - emit reportProgress(report); + + sendProgress(xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString())); + } - emit progress(percent); + sendProgress(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); - - emit reportProgress(report); - + + sendProgress(xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset)); + rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock); if (rval) { if (targetDevice.isEmpty()) reply[QStringLiteral("targetByteArray")] = buffer; else rval = writeData(targetDevice, buffer, lastBlockWriteOffset); } if (rval) { - emit progress(100); + sendProgress(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)); - emit reportProgress(report); - + sendProgress(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))); + reply[QStringLiteral("success")] = rval; return reply; } bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte) { // Do not allow using this helper for writing to arbitrary location if ( targetDevice.left(5) != QStringLiteral("/dev/") && !targetDevice.contains(QStringLiteral("/etc/fstab"))) return false; return writeData(targetDevice, buffer, targetFirstByte); } QVariantMap ExternalCommandHelper::start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode) { QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); QVariantMap reply; reply[QStringLiteral("success")] = true; if (command.isEmpty()) { reply[QStringLiteral("success")] = false; return reply; } // Compare with command whitelist QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { qInfo() << command <<"Command is not one of the whitelisted command"; m_loop->exit(); reply[QStringLiteral("success")] = false; return reply; } // connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput); m_cmd.setEnvironment( { QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1") } ); m_cmd.setProcessChannelMode(static_cast(processChannelMode)); 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; } void ExternalCommandHelper::exit() { m_loop->exit(); QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface")); } /*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); }*/ HELPER_MAIN() diff --git a/src/util/externalcommandhelper.h b/src/util/externalcommandhelper.h index 1dccb5a..c623d93 100644 --- a/src/util/externalcommandhelper.h +++ b/src/util/externalcommandhelper.h @@ -1,58 +1,61 @@ /************************************************************************* * Copyright (C) 2017-2018 by Andrius Štikonas * * Copyright (C) 2019 by Shubham * * * * 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 .* *************************************************************************/ #ifndef KPMCORE_EXTERNALCOMMANDHELPER_H #define KPMCORE_EXTERNALCOMMANDHELPER_H #include #include #include #include #include #include class ExternalCommandHelper : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.externalcommand") Q_SIGNALS: void progress(int percent); void reportProgress(const QVariantMap& progress); + // remove above signals later on public: bool readData(const QString& sourceDevice, QByteArray& buffer, const qint64 offset, const qint64 size); bool writeData(const QString& targetDevice, const QByteArray& buffer, const qint64 offset); + void sendProgress(int percent); + void sendProgress(QString message); public Q_SLOTS: Q_SCRIPTABLE int helperMain(int argc, char **argv); Q_SCRIPTABLE QVariantMap start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode); Q_SCRIPTABLE QVariantMap copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize); Q_SCRIPTABLE bool writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte); Q_SCRIPTABLE void exit(); private: std::unique_ptr m_loop; QProcess m_cmd; // QByteArray output; // void onReadOutput(); }; #endif // KPMCORE_EXTERNALCOMMANDHELPER_H