File indexing completed on 2024-04-21 05:46:11

0001 /*
0002     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2013-2020 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
0005     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0006     SPDX-FileCopyrightText: 2018 Huzaifa Faruqui <huzaifafaruqui@gmail.com>
0007     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0008     SPDX-FileCopyrightText: 2019 Shubham Jangra <aryan100jangid@gmail.com>
0009     SPDX-FileCopyrightText: 2019 Yuri Chornoivan <yurchor@ukr.net>
0010     SPDX-FileCopyrightText: 2020 David Edmundson <kde@davidedmundson.co.uk>
0011 
0012     SPDX-License-Identifier: GPL-3.0-or-later
0013 */
0014 
0015 #include <unordered_set>
0016 
0017 #include "util/externalcommand.h"
0018 #include "backend/corebackendmanager.h"
0019 #include "core/device.h"
0020 #include "core/copysource.h"
0021 #include "core/copytarget.h"
0022 #include "core/copytargetbytearray.h"
0023 #include "core/copysourcedevice.h"
0024 #include "core/copytargetdevice.h"
0025 #include "util/externalcommand_trustedprefixes.h"
0026 #include "util/globallog.h"
0027 #include "util/report.h"
0028 
0029 #include "externalcommandhelper_interface.h"
0030 
0031 #include <QCryptographicHash>
0032 #include <QDBusConnection>
0033 #include <QDBusInterface>
0034 #include <QDBusReply>
0035 #include <QEventLoop>
0036 #include <QtGlobal>
0037 #include <QStandardPaths>
0038 #include <QString>
0039 #include <QStringList>
0040 #include <QTimer>
0041 #include <QThread>
0042 #include <QVariant>
0043 #include <KJob>
0044 #include <KLocalizedString>
0045 
0046 struct ExternalCommandPrivate
0047 {
0048     Report *m_Report;
0049     QString m_Command;
0050     QStringList m_Args;
0051     int m_ExitCode;
0052     QByteArray m_Output;
0053     QByteArray m_Input;
0054     QProcess::ProcessChannelMode processChannelMode;
0055 };
0056 
0057 /** Creates a new ExternalCommand instance without Report.
0058     @param cmd the command to run
0059     @param args the arguments to pass to the command
0060 */
0061 ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args, const QProcess::ProcessChannelMode processChannelMode) :
0062     d(std::make_unique<ExternalCommandPrivate>())
0063 {
0064     d->m_Report = nullptr;
0065     d->m_Command = cmd;
0066     d->m_Args = args;
0067     d->m_ExitCode = -1;
0068     d->m_Output = QByteArray();
0069     d->processChannelMode = processChannelMode;
0070 }
0071 
0072 /** Creates a new ExternalCommand instance with Report.
0073     @param report the Report to write output to.
0074     @param cmd the command to run
0075     @param args the arguments to pass to the command
0076  */
0077 ExternalCommand::ExternalCommand(Report& report, const QString& cmd, const QStringList& args, const QProcess::ProcessChannelMode processChannelMode) :
0078     d(std::make_unique<ExternalCommandPrivate>())
0079 {
0080     d->m_Report = report.newChild();
0081     d->m_Command = cmd;
0082     d->m_Args = args;
0083     d->m_ExitCode = -1;
0084     d->m_Output = QByteArray();
0085 
0086     d->processChannelMode = processChannelMode;
0087 }
0088 
0089 ExternalCommand::~ExternalCommand()
0090 {
0091     
0092 }
0093 
0094 /*
0095 void ExternalCommand::setup()
0096 {
0097      connect(this, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, &ExternalCommand::onFinished);
0098      connect(this, &ExternalCommand::readyReadStandardOutput, this, &ExternalCommand::onReadOutput);
0099 }
0100 */
0101 
0102 /** Executes the external command.
0103     @param timeout timeout to wait for the process to start
0104     @return true on success
0105 */
0106 bool ExternalCommand::start(int timeout)
0107 {
0108     Q_UNUSED(timeout)
0109 
0110     if (command().isEmpty())
0111         return false;
0112 
0113     if (report())
0114         report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" "))));
0115 
0116     if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" ))
0117         qDebug() << xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")));
0118 
0119     QString cmd = findTrustedCommand(command());
0120 
0121     auto interface = helperInterface();
0122     if (!interface)
0123         return false;
0124 
0125     bool rval = false;
0126 
0127     QDBusPendingCall pcall = interface->RunCommand(cmd, args(), d->m_Input, d->processChannelMode);
0128 
0129     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
0130     QEventLoop loop;
0131 
0132     auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) {
0133         loop.exit();
0134 
0135         if (watcher->isError())
0136             qWarning() << watcher->error();
0137         else {
0138             QDBusPendingReply<QVariantMap> reply = *watcher;
0139 
0140             d->m_Output = reply.value()[QStringLiteral("output")].toByteArray();
0141             setExitCode(reply.value()[QStringLiteral("exitCode")].toInt());
0142             rval = reply.value()[QStringLiteral("success")].toBool();
0143         }
0144     };
0145 
0146     connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop);
0147     loop.exec();
0148 
0149     return rval;
0150 }
0151 
0152 bool ExternalCommand::copyBlocks(const CopySource& source, CopyTarget& target)
0153 {
0154     bool rval = true;
0155     const qint64 blockSize = 10 * 1024 * 1024; // number of bytes per block to copy
0156 
0157     auto interface = helperInterface();
0158     if (!interface)
0159         return false;
0160 
0161     connect(interface, &OrgKdeKpmcoreExternalcommandInterface::progress, this, &ExternalCommand::progress);
0162     connect(interface, &OrgKdeKpmcoreExternalcommandInterface::report, this, &ExternalCommand::reportSignal);
0163 
0164     QDBusPendingCall pcall = interface->CopyFileData(source.path(), source.firstByte(), source.length(),
0165                                                    target.path(), target.firstByte(), blockSize);
0166 
0167     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
0168     QEventLoop loop;
0169 
0170     auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) {
0171         loop.exit();
0172         if (watcher->isError())
0173             qWarning() << watcher->error();
0174         else {
0175             QDBusPendingReply<QVariantMap> reply = *watcher;
0176             rval = reply.value()[QStringLiteral("success")].toBool();
0177 
0178             CopyTargetByteArray *byteArrayTarget = dynamic_cast<CopyTargetByteArray*>(&target);
0179             if (byteArrayTarget)
0180                 byteArrayTarget->m_Array = reply.value()[QStringLiteral("targetByteArray")].toByteArray();
0181         }
0182         setExitCode(!rval);
0183     };
0184 
0185     connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop);
0186     loop.exec();
0187 
0188     return rval;
0189 }
0190 
0191 QByteArray ExternalCommand::readData(const CopySourceDevice& source)
0192 {
0193     auto interface = helperInterface();
0194     if (!interface)
0195         return {};
0196 
0197     // Helper is restricted not to resolve symlinks
0198     QFileInfo sourceInfo(source.path());
0199     QDBusPendingCall pcall = interface->ReadData(sourceInfo.canonicalFilePath(), source.firstByte(), source.length());
0200 
0201     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
0202 
0203     QEventLoop loop;
0204     QByteArray target;
0205     auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) {
0206         loop.exit();
0207 
0208         if (watcher->isError())
0209             qWarning() << watcher->error();
0210         else {
0211             QDBusPendingReply<QByteArray> reply = *watcher;
0212 
0213             target = reply.value();
0214         }
0215     };
0216 
0217     connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop);
0218     loop.exec();
0219 
0220     return target;
0221 }
0222 
0223 bool ExternalCommand::writeData(Report& commandReport, const QByteArray& buffer, const QString& deviceNode, const quint64 firstByte)
0224 {
0225     d->m_Report = commandReport.newChild();
0226     if (report())
0227         report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" "))));
0228 
0229     auto interface = helperInterface();
0230     if (!interface)
0231         return false;
0232 
0233     QDBusPendingCall pcall = interface->WriteData(buffer, deviceNode, firstByte);
0234     return waitForDbusReply(pcall);
0235 }
0236 
0237 bool ExternalCommand::writeFstab(const QByteArray& fileContents)
0238 {
0239     auto interface = helperInterface();
0240     if (!interface)
0241         return false;
0242 
0243     QDBusPendingCall pcall = interface->WriteFstab(fileContents);
0244     return waitForDbusReply(pcall);
0245 }
0246 
0247 OrgKdeKpmcoreExternalcommandInterface* ExternalCommand::helperInterface()
0248 {
0249     if (!QDBusConnection::systemBus().isConnected()) {
0250         qWarning() << QDBusConnection::systemBus().lastError().message();
0251         return nullptr;
0252     }
0253 
0254     auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.helperinterface"),
0255                 QStringLiteral("/Helper"), QDBusConnection::systemBus(), this);
0256     interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days
0257     return interface;
0258 }
0259 
0260 bool ExternalCommand::waitForDbusReply(QDBusPendingCall &pcall)
0261 {
0262     bool rval = true;
0263     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
0264     QEventLoop loop;
0265 
0266     auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) {
0267         loop.exit();
0268         if (watcher->isError())
0269             qWarning() << watcher->error();
0270         else {
0271             QDBusPendingReply<bool> reply = *watcher;
0272             rval = reply.argumentAt<0>();
0273         }
0274         setExitCode(!rval);
0275     };
0276 
0277     connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop);
0278     loop.exec();
0279 
0280     return rval;
0281 }
0282 
0283 bool ExternalCommand::write(const QByteArray& input)
0284 {
0285     if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" ))
0286         qDebug() << "Command input:" << QString::fromLocal8Bit(input);
0287     d->m_Input = input;
0288     return true;
0289 }
0290 
0291 /** Runs the command.
0292     @param timeout timeout to use for waiting when starting and when waiting for the process to finish
0293     @return true on success
0294 */
0295 bool ExternalCommand::run(int timeout)
0296 {
0297     return start(timeout) /* && exitStatus() == 0*/;
0298 }
0299 
0300 void ExternalCommand::onReadOutput()
0301 {
0302 //     const QByteArray s = readAllStandardOutput();
0303 //
0304 //     if(m_Output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems
0305 //         if (report())
0306 //             report()->line() << xi18nc("@info:status", "(Command is printing too much output)");
0307 //         return;
0308 //     }
0309 //
0310 //     m_Output += s;
0311 //
0312 //     if (report())
0313 //         *report() << QString::fromLocal8Bit(s);
0314 }
0315 
0316 void ExternalCommand::setCommand(const QString& cmd)
0317 {
0318     d->m_Command = cmd;
0319 }
0320 
0321 const QString& ExternalCommand::command() const
0322 {
0323     return d->m_Command;
0324 }
0325 
0326 const QStringList& ExternalCommand::args() const
0327 {
0328     return d->m_Args;
0329 }
0330 
0331 void ExternalCommand::addArg(const QString& s)
0332 {
0333     d->m_Args << s;
0334 }
0335 
0336 void ExternalCommand::setArgs(const QStringList& args)
0337 {
0338     d->m_Args = args;
0339 }
0340 
0341 int ExternalCommand::exitCode() const
0342 {
0343     return d->m_ExitCode;
0344 }
0345 
0346 const QString ExternalCommand::output() const
0347 {
0348     return QString::fromLocal8Bit(d->m_Output);
0349 }
0350 
0351 const QByteArray& ExternalCommand::rawOutput() const
0352 {
0353     return d->m_Output;
0354 }
0355 
0356 Report* ExternalCommand::report()
0357 {
0358     return d->m_Report;
0359 }
0360 
0361 void ExternalCommand::setExitCode(int i)
0362 {
0363     d->m_ExitCode = i;
0364 }
0365 
0366 #include "moc_externalcommand.cpp"