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"