Changeset View
Changeset View
Standalone View
Standalone View
src/util/externalcommandhelper.cpp
1 | /************************************************************************* | 1 | /************************************************************************* | ||
---|---|---|---|---|---|
2 | * Copyright (C) 2017-2018 by Andrius Štikonas <andrius@stikonas.eu> * | 2 | * Copyright (C) 2017-2018 by Andrius Štikonas <andrius@stikonas.eu> * | ||
3 | * Copyright (C) 2019 by Shubham <aryan100jangid@gmail.com> * | ||||
3 | * * | 4 | * * | ||
4 | * This program is free software; you can redistribute it and/or * | 5 | * This program is free software; you can redistribute it and/or * | ||
5 | * modify it under the terms of the GNU General Public License as * | 6 | * modify it under the terms of the GNU General Public License as * | ||
6 | * published by the Free Software Foundation; either version 3 of * | 7 | * published by the Free Software Foundation; either version 3 of * | ||
7 | * the License, or (at your option) any later version. * | 8 | * the License, or (at your option) any later version. * | ||
8 | * * | 9 | * * | ||
9 | * This program is distributed in the hope that it will be useful, * | 10 | * This program is distributed in the hope that it will be useful, * | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | ||
12 | * GNU General Public License for more details. * | 13 | * GNU General Public License for more details. * | ||
13 | * * | 14 | * * | ||
14 | * You should have received a copy of the GNU General Public License * | 15 | * You should have received a copy of the GNU General Public License * | ||
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>.* | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>.* | ||
16 | *************************************************************************/ | 17 | *************************************************************************/ | ||
17 | 18 | | |||
18 | #include "externalcommandhelper.h" | 19 | #include "externalcommandhelper.h" | ||
19 | #include "externalcommand_whitelist.h" | 20 | #include "externalcommand_whitelist.h" | ||
20 | 21 | | |||
21 | #include <QtDBus> | | |||
22 | #include <QCoreApplication> | 22 | #include <QCoreApplication> | ||
23 | #include <QtDBus> | ||||
23 | #include <QDebug> | 24 | #include <QDebug> | ||
24 | #include <QFile> | 25 | #include <QFile> | ||
25 | #include <QString> | 26 | #include <QString> | ||
26 | #include <QTime> | 27 | #include <QTime> | ||
27 | #include <QVariant> | 28 | #include <QVariant> | ||
28 | 29 | | |||
29 | #include <KLocalizedString> | 30 | #include <KLocalizedString> | ||
30 | 31 | | |||
32 | #define HELPER_MAIN() \ | ||||
33 | int main(int argc, char **argv) { ExternalCommandHelper helper; return helper.helperMain(argc, argv); } | ||||
34 | | ||||
31 | /** Initialize ExternalCommandHelper Daemon and prepare DBus interface | 35 | /** Initialize ExternalCommandHelper Daemon and prepare DBus interface | ||
32 | * | 36 | * | ||
33 | * KAuth helper runs in the background until application exits. | 37 | * Helper runs in the background until application exits. | ||
34 | * To avoid forever running helper in case of application crash | 38 | * To avoid forever running helper in case of application crash | ||
35 | * ExternalCommand class opens a DBus service that we monitor for changes. | 39 | * ExternalCommand class opens a DBus service that we monitor for changes. | ||
36 | * If helper is not busy then it exits when the client services gets | 40 | * If helper is not busy then it exits when the client services gets | ||
37 | * unregistered. Otherwise, | 41 | * unregistered. Otherwise, | ||
38 | * we wait for the current job to finish before exiting, so even in case | 42 | * we wait for the current job to finish before exiting, so even in case | ||
39 | * of main application crash, we do not leave partially moved data. | 43 | * of main application crash, we do not leave partially moved data. | ||
40 | * | 44 | * | ||
41 | * This helper also starts another DBus interface where it listens to | 45 | * This helper also starts another DBus interface where it listens to | ||
42 | * command execution requests from the application that started the helper. | 46 | * command execution requests from the application that started the helper. | ||
43 | * | 47 | * | ||
44 | */ | 48 | */ | ||
45 | ActionReply ExternalCommandHelper::init(const QVariantMap& args) | | |||
46 | { | | |||
47 | Q_UNUSED(args) | | |||
48 | 49 | | |||
49 | ActionReply reply; | 50 | /** Reads the given number of bytes from the sourceDevice into the given buffer. | ||
51 | @param argc argument count | ||||
52 | @param argv argument vector | ||||
53 | @return zero on success, non-zero on failure | ||||
54 | */ | ||||
55 | int ExternalCommandHelper::helperMain(int argc, char **argv) | ||||
56 | { | ||||
57 | QCoreApplication app(argc, argv); | ||||
50 | 58 | | |||
51 | if (!QDBusConnection::systemBus().isConnected() || !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || | 59 | if (!QDBusConnection::systemBus().isConnected() || | ||
60 | !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || | ||||
52 | !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { | 61 | !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { | ||
62 | qDebug() << "Failed to initialize the Helper"; | ||||
53 | qWarning() << QDBusConnection::systemBus().lastError().message(); | 63 | qWarning() << QDBusConnection::systemBus().lastError().message(); | ||
54 | reply.addData(QStringLiteral("success"), false); | | |||
55 | 64 | | |||
56 | // Also end the application loop started by KAuth's main() code. Our loop | 65 | // We have no reason to live when Main GUI app has expired | ||
57 | // exits when our client disappears. Without client we have no reason to | | |||
58 | // live. | | |||
59 | qApp->quit(); | 66 | qApp->quit(); | ||
60 | 67 | | |||
61 | return reply; | 68 | return app.exec(); | ||
62 | } | 69 | } | ||
63 | 70 | | |||
64 | m_loop = std::make_unique<QEventLoop>(); | 71 | m_loop = std::make_unique<QEventLoop>(); | ||
65 | HelperSupport::progressStep(QVariantMap()); | 72 | emit reportProgress(QVariantMap()); | ||
66 | 73 | | |||
67 | // End the loop and return only once the client is done using us. | 74 | // QDBus Service watcher which keeps an eye on the client (Main GUI app) | ||
68 | auto serviceWatcher = | 75 | // End the loop and return only once the client has unregistered over the QDBus. | ||
69 | new QDBusServiceWatcher(QStringLiteral("org.kde.kpmcore.applicationinterface"), | 76 | auto serviceWatcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kpmcore.applicationinterface"), | ||
70 | QDBusConnection::systemBus(), | 77 | QDBusConnection::systemBus(), | ||
71 | QDBusServiceWatcher::WatchForUnregistration, | 78 | QDBusServiceWatcher::WatchForUnregistration, | ||
72 | this); | 79 | this); | ||
80 | | ||||
73 | connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, | 81 | connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, | ||
74 | [this]() { | 82 | [this]() { | ||
75 | m_loop->exit(); | 83 | m_loop->exit(); | ||
76 | }); | 84 | }); | ||
77 | 85 | | |||
78 | m_loop->exec(); | 86 | m_loop->exec(); | ||
79 | reply.addData(QStringLiteral("success"), true); | | |||
80 | 87 | | |||
81 | // Also end the application loop started by KAuth's main() code. Our loop | | |||
82 | // exits when our client disappears. Without client we have no reason to | | |||
83 | // live. | | |||
84 | qApp->quit(); | 88 | qApp->quit(); | ||
85 | 89 | | |||
86 | return reply; | 90 | return app.exec(); | ||
87 | } | 91 | } | ||
88 | 92 | | |||
89 | | ||||
90 | /** Reads the given number of bytes from the sourceDevice into the given buffer. | 93 | /** Reads the given number of bytes from the sourceDevice into the given buffer. | ||
91 | @param sourceDevice device or file to read from | 94 | @param sourceDevice device or file to read from | ||
92 | @param buffer buffer to store the bytes read in | 95 | @param buffer buffer to store the bytes read in | ||
93 | @param offset offset where to begin reading | 96 | @param offset offset where to begin reading | ||
94 | @param size the number of bytes to read | 97 | @param size the number of bytes to read | ||
95 | @return true on success | 98 | @return true on success | ||
96 | */ | 99 | */ | ||
97 | bool ExternalCommandHelper::readData(const QString& sourceDevice, QByteArray& buffer, const qint64 offset, const qint64 size) | 100 | bool ExternalCommandHelper::readData(const QString& sourceDevice, QByteArray& buffer, const qint64 offset, const qint64 size) | ||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Line(s) | 154 | { | |||
175 | t.start(); | 178 | t.start(); | ||
176 | 179 | | |||
177 | QVariantMap report; | 180 | QVariantMap report; | ||
178 | 181 | | |||
179 | report[QStringLiteral("report")] = xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy, | 182 | report[QStringLiteral("report")] = xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy, | ||
180 | sourceLength, readOffset, writeOffset, copyDirection == 1 ? i18nc("direction: left", "left") | 183 | sourceLength, readOffset, writeOffset, copyDirection == 1 ? i18nc("direction: left", "left") | ||
181 | : i18nc("direction: right", "right")); | 184 | : i18nc("direction: right", "right")); | ||
182 | 185 | | |||
183 | HelperSupport::progressStep(report); | 186 | emit reportProgress(report); | ||
184 | 187 | | |||
185 | bool rval = true; | 188 | bool rval = true; | ||
186 | 189 | | |||
187 | while (blocksCopied < blocksToCopy && !targetDevice.isEmpty()) { | 190 | while (blocksCopied < blocksToCopy && !targetDevice.isEmpty()) { | ||
188 | if (!(rval = readData(sourceDevice, buffer, readOffset + blockSize * blocksCopied * copyDirection, blockSize))) | 191 | if (!(rval = readData(sourceDevice, buffer, readOffset + blockSize * blocksCopied * copyDirection, blockSize))) | ||
189 | break; | 192 | break; | ||
190 | 193 | | |||
191 | if (!(rval = writeData(targetDevice, buffer, writeOffset + blockSize * blocksCopied * copyDirection))) | 194 | if (!(rval = writeData(targetDevice, buffer, writeOffset + blockSize * blocksCopied * copyDirection))) | ||
192 | break; | 195 | break; | ||
193 | 196 | | |||
194 | bytesWritten += buffer.size(); | 197 | bytesWritten += buffer.size(); | ||
195 | 198 | | |||
196 | if (++blocksCopied * 100 / blocksToCopy != percent) { | 199 | if (++blocksCopied * 100 / blocksToCopy != percent) { | ||
197 | percent = blocksCopied * 100 / blocksToCopy; | 200 | percent = blocksCopied * 100 / blocksToCopy; | ||
198 | 201 | | |||
199 | if (percent % 5 == 0 && t.elapsed() > 1000) { | 202 | if (percent % 5 == 0 && t.elapsed() > 1000) { | ||
200 | const qint64 mibsPerSec = (blocksCopied * blockSize / 1024 / 1024) / (t.elapsed() / 1000); | 203 | const qint64 mibsPerSec = (blocksCopied * blockSize / 1024 / 1024) / (t.elapsed() / 1000); | ||
201 | const qint64 estSecsLeft = (100 - percent) * t.elapsed() / percent / 1000; | 204 | const qint64 estSecsLeft = (100 - percent) * t.elapsed() / percent / 1000; | ||
202 | report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString()); | 205 | report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString()); | ||
203 | HelperSupport::progressStep(report); | 206 | emit reportProgress(report); | ||
204 | } | 207 | } | ||
205 | HelperSupport::progressStep(percent); | 208 | emit progress(percent); | ||
206 | } | 209 | } | ||
207 | } | 210 | } | ||
208 | 211 | | |||
209 | // copy the remainder | 212 | // copy the remainder | ||
210 | if (rval && lastBlock > 0) { | 213 | if (rval && lastBlock > 0) { | ||
211 | Q_ASSERT(lastBlock < blockSize); | 214 | Q_ASSERT(lastBlock < blockSize); | ||
212 | 215 | | |||
213 | const qint64 lastBlockReadOffset = copyDirection > 0 ? readOffset + blockSize * blocksCopied : sourceFirstByte; | 216 | const qint64 lastBlockReadOffset = copyDirection > 0 ? readOffset + blockSize * blocksCopied : sourceFirstByte; | ||
214 | const qint64 lastBlockWriteOffset = copyDirection > 0 ? writeOffset + blockSize * blocksCopied : targetFirstByte; | 217 | const qint64 lastBlockWriteOffset = copyDirection > 0 ? writeOffset + blockSize * blocksCopied : targetFirstByte; | ||
218 | | ||||
215 | report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset); | 219 | report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset); | ||
216 | HelperSupport::progressStep(report); | 220 | | ||
221 | emit reportProgress(report); | ||||
222 | | ||||
217 | rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock); | 223 | rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock); | ||
218 | 224 | | |||
219 | if (rval) { | 225 | if (rval) { | ||
220 | if (targetDevice.isEmpty()) | 226 | if (targetDevice.isEmpty()) | ||
221 | reply[QStringLiteral("targetByteArray")] = buffer; | 227 | reply[QStringLiteral("targetByteArray")] = buffer; | ||
222 | else | 228 | else | ||
223 | rval = writeData(targetDevice, buffer, lastBlockWriteOffset); | 229 | rval = writeData(targetDevice, buffer, lastBlockWriteOffset); | ||
224 | } | 230 | } | ||
225 | 231 | | |||
226 | if (rval) { | 232 | if (rval) { | ||
227 | HelperSupport::progressStep(100); | 233 | emit progress(100); | ||
228 | bytesWritten += buffer.size(); | 234 | bytesWritten += buffer.size(); | ||
229 | } | 235 | } | ||
230 | } | 236 | } | ||
231 | 237 | | |||
232 | 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)); | 238 | 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)); | ||
233 | HelperSupport::progressStep(report); | 239 | | ||
240 | emit reportProgress(report); | ||||
234 | 241 | | |||
235 | reply[QStringLiteral("success")] = rval; | 242 | reply[QStringLiteral("success")] = rval; | ||
236 | return reply; | 243 | return reply; | ||
237 | } | 244 | } | ||
238 | 245 | | |||
239 | bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte) | 246 | bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte) | ||
240 | { | 247 | { | ||
241 | // Do not allow using this helper for writing to arbitrary location | 248 | // Do not allow using this helper for writing to arbitrary location | ||
Show All 13 Lines | 257 | { | |||
255 | if (command.isEmpty()) { | 262 | if (command.isEmpty()) { | ||
256 | reply[QStringLiteral("success")] = false; | 263 | reply[QStringLiteral("success")] = false; | ||
257 | return reply; | 264 | return reply; | ||
258 | } | 265 | } | ||
259 | 266 | | |||
260 | // Compare with command whitelist | 267 | // Compare with command whitelist | ||
261 | QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); | 268 | QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); | ||
262 | if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { | 269 | if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { | ||
263 | qInfo() << command <<" command is not one of the whitelisted command"; | 270 | qInfo() << command <<"Command is not one of the whitelisted command"; | ||
264 | m_loop->exit(); | 271 | m_loop->exit(); | ||
265 | reply[QStringLiteral("success")] = false; | 272 | reply[QStringLiteral("success")] = false; | ||
266 | return reply; | 273 | return reply; | ||
267 | } | 274 | } | ||
268 | 275 | | |||
269 | // connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput); | 276 | // connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput); | ||
270 | 277 | | |||
271 | m_cmd.setEnvironment( { QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1") } ); | 278 | m_cmd.setEnvironment( { QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1") } ); | ||
272 | m_cmd.setProcessChannelMode(static_cast<QProcess::ProcessChannelMode>(processChannelMode)); | 279 | m_cmd.setProcessChannelMode(static_cast<QProcess::ProcessChannelMode>(processChannelMode)); | ||
273 | m_cmd.start(command, arguments); | 280 | m_cmd.start(command, arguments); | ||
274 | m_cmd.write(input); | 281 | m_cmd.write(input); | ||
275 | m_cmd.closeWriteChannel(); | 282 | m_cmd.closeWriteChannel(); | ||
276 | m_cmd.waitForFinished(-1); | 283 | m_cmd.waitForFinished(-1); | ||
284 | | ||||
277 | QByteArray output = m_cmd.readAllStandardOutput(); | 285 | QByteArray output = m_cmd.readAllStandardOutput(); | ||
286 | | ||||
278 | reply[QStringLiteral("output")] = output; | 287 | reply[QStringLiteral("output")] = output; | ||
279 | reply[QStringLiteral("exitCode")] = m_cmd.exitCode(); | 288 | reply[QStringLiteral("exitCode")] = m_cmd.exitCode(); | ||
280 | 289 | | |||
281 | return reply; | 290 | return reply; | ||
282 | } | 291 | } | ||
283 | 292 | | |||
284 | void ExternalCommandHelper::exit() | 293 | void ExternalCommandHelper::exit() | ||
285 | { | 294 | { | ||
286 | m_loop->exit(); | 295 | m_loop->exit(); | ||
287 | 296 | | |||
288 | QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); | 297 | QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); | ||
289 | QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface")); | 298 | QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface")); | ||
290 | } | 299 | } | ||
291 | 300 | | |||
292 | void ExternalCommandHelper::onReadOutput() | 301 | /*void ExternalCommandHelper::onReadOutput() | ||
293 | { | 302 | { | ||
294 | /* const QByteArray s = cmd.readAllStandardOutput(); | 303 | const QByteArray s = cmd.readAllStandardOutput(); | ||
295 | 304 | | |||
296 | if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems | 305 | if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems | ||
297 | if (report()) | 306 | if (report()) | ||
298 | report()->line() << xi18nc("@info:status", "(Command is printing too much output)"); | 307 | report()->line() << xi18nc("@info:status", "(Command is printing too much output)"); | ||
299 | return; | 308 | return; | ||
300 | } | 309 | } | ||
301 | 310 | | |||
302 | output += s; | 311 | output += s; | ||
303 | 312 | | |||
304 | if (report()) | 313 | if (report()) | ||
305 | *report() << QString::fromLocal8Bit(s);*/ | 314 | *report() << QString::fromLocal8Bit(s); | ||
306 | } | 315 | }*/ | ||
307 | 316 | | |||
308 | KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper) | 317 | HELPER_MAIN() |