summaryrefslogtreecommitdiffstats
path: root/src/libs/installer/elevatedexecuteoperation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/installer/elevatedexecuteoperation.cpp')
-rw-r--r--src/libs/installer/elevatedexecuteoperation.cpp276
1 files changed, 276 insertions, 0 deletions
diff --git a/src/libs/installer/elevatedexecuteoperation.cpp b/src/libs/installer/elevatedexecuteoperation.cpp
new file mode 100644
index 000000000..1e1539222
--- /dev/null
+++ b/src/libs/installer/elevatedexecuteoperation.cpp
@@ -0,0 +1,276 @@
+/**************************************************************************
+**
+** This file is part of Installer Framework
+**
+** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "elevatedexecuteoperation.h"
+
+#include "environment.h"
+#include "qprocesswrapper.h"
+
+#include <QtCore/QThread>
+#include <QtCore/QProcessEnvironment>
+#include <QtCore/QDebug>
+
+using namespace QInstaller;
+
+class ElevatedExecuteOperation::Private
+{
+public:
+ explicit Private(ElevatedExecuteOperation *qq)
+ : q(qq), process(0), showStandardError(false)
+ {
+ }
+
+private:
+ ElevatedExecuteOperation *const q;
+
+public:
+ void readProcessOutput();
+ bool run(const QStringList &arguments);
+
+ QProcessWrapper *process;
+ bool showStandardError;
+};
+
+ElevatedExecuteOperation::ElevatedExecuteOperation()
+ : d(new Private(this))
+{
+ // this operation has to "overwrite" the Execute operation from KDUpdater
+ setName(QLatin1String("Execute"));
+}
+
+ElevatedExecuteOperation::~ElevatedExecuteOperation()
+{
+ delete d;
+}
+
+bool ElevatedExecuteOperation::performOperation()
+{
+ // This operation receives only one argument. It is the complete
+ // command line of the external program to execute.
+ if (arguments().isEmpty()) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments in %1: %2 arguments given, at least 1 expected.").arg(name(),
+ QString::number(arguments().count())));
+ return false;
+ }
+ QStringList args;
+ foreach (const QString &argument, arguments()) {
+ if (argument!=QLatin1String("UNDOEXECUTE"))
+ args.append(argument);
+ else
+ break; //we don't need the UNDOEXECUTE args here
+ }
+
+ return d->run(args);
+}
+
+bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
+{
+ QStringList args = arguments;
+ QString workingDirectory;
+ QStringList filteredWorkingDirectoryArgs = args.filter(QLatin1String("workingdirectory="),
+ Qt::CaseInsensitive);
+ if (!filteredWorkingDirectoryArgs.isEmpty()) {
+ QString workingDirectoryArgument = filteredWorkingDirectoryArgs.at(0);
+ workingDirectory = workingDirectoryArgument;
+ workingDirectory.replace(QLatin1String("workingdirectory="), QString(), Qt::CaseInsensitive);
+ args.removeAll(workingDirectoryArgument);
+ }
+
+
+ if (args.last().endsWith(QLatin1String("showStandardError"))) {
+ showStandardError = true;
+ args.pop_back();
+ }
+
+ QList< int > allowedExitCodes;
+
+ QRegExp re(QLatin1String("^\\{((-?\\d+,)*-?\\d+)\\}$"));
+ if (re.exactMatch(args.first())) {
+ const QStringList numbers = re.cap(1).split(QLatin1Char(','));
+ for(QStringList::const_iterator it = numbers.constBegin(); it != numbers.constEnd(); ++it)
+ allowedExitCodes.push_back(it->toInt());
+ args.pop_front();
+ } else {
+ allowedExitCodes.push_back(0);
+ }
+
+ const QString callstr = args.join(QLatin1String(" "));
+
+ // unix style: when there's an ampersand after the command, it's started detached
+ if (args.count() >= 2 && args.last() == QLatin1String("&")) {
+ args.pop_back();
+ const bool success = QProcessWrapper::startDetached(args.front(), args.mid(1));
+ if (!success) {
+ q->setError(UserDefinedError);
+ q->setErrorString(tr("Execution failed: Could not start detached: \"%1\"").arg(callstr));
+ }
+ return success;
+ }
+
+ process = new QProcessWrapper();
+ if (!workingDirectory.isEmpty()) {
+ process->setWorkingDirectory(workingDirectory);
+ qDebug() << "ElevatedExecuteOperation setWorkingDirectory:" << workingDirectory;
+ }
+
+ QProcessEnvironment penv;
+ // there is no way to serialize a QProcessEnvironment properly other than per mangled QStringList:
+ // (i.e. no other way to list all keys)
+ process->setEnvironment(KDUpdater::Environment::instance().applyTo(penv).toStringList());
+
+ if (showStandardError)
+ process->setProcessChannelMode(QProcessWrapper::MergedChannels);
+
+ connect(q, SIGNAL(cancelProcess()), process, SLOT(cancel()));
+
+ //we still like the none blocking possibility to perform this operation without threads
+ QEventLoop loop;
+ if (QThread::currentThread() == qApp->thread()) {
+ QObject::connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
+ }
+ //readProcessOutput should only called from this current Thread -> Qt::DirectConnection
+ QObject::connect(process, SIGNAL(readyRead()), q, SLOT(readProcessOutput()), Qt::DirectConnection);
+#ifdef Q_OS_WIN
+ if (args.count() == 1) {
+ process->setNativeArguments(args.front());
+ qDebug() << "ElevatedExecuteOperation setNativeArguments to start:" << args.front();
+ process->start(QString(), QStringList());
+ } else
+#endif
+ {
+ process->start(args.front(), args.mid(1));
+ }
+ qDebug() << args.front() << "started, arguments:" << QStringList(args.mid(1)).join(QLatin1String(" "));
+
+ bool success = false;
+ //we still like the none blocking possibility to perform this operation without threads
+ if (QThread::currentThread() == qApp->thread()) {
+ success = process->waitForStarted();
+ } else {
+ success = process->waitForFinished(-1);
+ }
+
+ bool returnValue = true;
+ if (!success) {
+ q->setError(UserDefinedError);
+ //TODO: pass errorString() through the wrapper */
+ q->setErrorString(tr("Execution failed: Could not start: \"%1\"").arg(callstr));
+ returnValue = false;
+ }
+
+ if (QThread::currentThread() == qApp->thread()) {
+ if (process->state() != QProcessWrapper::NotRunning) {
+ loop.exec();
+ }
+ readProcessOutput();
+ }
+
+ q->setValue(QLatin1String("ExitCode"), process->exitCode());
+
+ if (process->exitStatus() == QProcessWrapper::CrashExit) {
+ q->setError(UserDefinedError);
+ q->setErrorString(tr("Execution failed(Crash): \"%1\"").arg(callstr));
+ returnValue = false;
+ }
+
+ if (!allowedExitCodes.contains(process->exitCode())) {
+ q->setError(UserDefinedError);
+ q->setErrorString(tr("Execution failed(Unexpected exit code: %1): \"%2\"")
+ .arg(QString::number(process->exitCode()), callstr));
+ returnValue = false;
+ }
+
+ Q_ASSERT(process);
+ process->deleteLater();
+ process = 0;
+
+ return returnValue;
+}
+
+/*!
+ Cancels the ElevatedExecuteOperation. This methods tries to terminate the process
+ gracefully by calling QProcessWrapper::terminate. After 10 seconds, the process gets killed.
+ */
+void ElevatedExecuteOperation::cancelOperation()
+{
+ emit cancelProcess();
+}
+
+void ElevatedExecuteOperation::Private::readProcessOutput()
+{
+ Q_ASSERT(process);
+ Q_ASSERT(QThread::currentThread() == process->thread());
+ if (QThread::currentThread() != process->thread()) {
+ qDebug() << Q_FUNC_INFO << "can only be called from the same thread as the process is.";
+ }
+ const QByteArray output = process->readAll();
+ if (!output.isEmpty()) {
+ qDebug() << output;
+ emit q->outputTextChanged(QString::fromLocal8Bit(output));
+ }
+}
+
+
+bool ElevatedExecuteOperation::undoOperation()
+{
+ QStringList args;
+ bool found = false;
+ foreach (const QString &argument, arguments()) {
+ if (found)
+ args.append(argument);
+ else
+ found = argument == QLatin1String("UNDOEXECUTE");
+ }
+ if (args.isEmpty())
+ return true;
+
+ return d->run(args);
+}
+
+bool ElevatedExecuteOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+Operation *ElevatedExecuteOperation::clone() const
+{
+ return new ElevatedExecuteOperation;
+}
+
+void ElevatedExecuteOperation::backup()
+{
+}
+
+
+#include "moc_elevatedexecuteoperation.cpp"