From 5aee36b74eb1d7613ea0108971e8a22f8dca8101 Mon Sep 17 00:00:00 2001 From: Katja Marttila Date: Fri, 22 Jan 2021 14:50:16 +0200 Subject: Try rerunning execute operation Execute operation can have hard coded paths to program which is executed. In case the program is relocated, UNDO operation will fail as it will not find the program. Implemented new XXXX_OLD value which can be used for overwriting the hardcoded value. In case the program execution fails, program is tried to launch again with the replaced value. Task-number: QTIFW-2125 Change-Id: I446a4c423e53cc4ffc6e5e25617d2400945ac3d9 Reviewed-by: Arttu Tarkiainen --- src/libs/installer/elevatedexecuteoperation.cpp | 85 +++++++++++++++++++------ src/libs/installer/elevatedexecuteoperation.h | 8 ++- src/libs/installer/extractarchiveoperation.cpp | 4 +- src/libs/installer/packagemanagercore.cpp | 15 ++++- src/libs/installer/packagemanagercore.h | 1 + src/libs/installer/packagemanagercore_p.cpp | 28 ++++---- src/libs/installer/packagemanagercore_p.h | 12 +--- src/libs/installer/packagemanagercoredata.cpp | 7 +- src/libs/installer/packagemanagercoredata.h | 3 +- src/libs/kdtools/updateoperation.h | 6 ++ 10 files changed, 118 insertions(+), 51 deletions(-) (limited to 'src/libs') diff --git a/src/libs/installer/elevatedexecuteoperation.cpp b/src/libs/installer/elevatedexecuteoperation.cpp index 39be98e6f..f81377ac4 100644 --- a/src/libs/installer/elevatedexecuteoperation.cpp +++ b/src/libs/installer/elevatedexecuteoperation.cpp @@ -61,7 +61,11 @@ private: public: void readProcessOutput(); - bool run(const QStringList &arguments); + int run(QStringList &arguments, const OperationType type); + +private: + bool needsRerunWithReplacedVariables(QStringList &arguments, const OperationType type); + QProcessWrapper *process; bool showStandardError; @@ -100,10 +104,10 @@ bool ElevatedExecuteOperation::performOperation() PackageManagerCore *const core = packageManager(); args = core->replaceVariables(args); } - return d->run(args); + return d->run(args, Operation::Perform) ? false : true; } -bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) +int ElevatedExecuteOperation::Private::run(QStringList &arguments, const OperationType type) { QStringList args = arguments; QString workingDirectory; @@ -192,13 +196,17 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) success = process->waitForFinished(-1); } - bool returnValue = true; + int returnValue = NoError; if (!success) { q->setError(UserDefinedError); //TODO: pass errorString() through the wrapper */ q->setErrorString(tr("Cannot start: \"%1\": %2").arg(callstr, process->errorString())); - returnValue = false; + if (!needsRerunWithReplacedVariables(arguments, type)) { + returnValue = Error; + } else { + returnValue = NeedsRerun; + } } if (QThread::currentThread() == qApp->thread()) { @@ -213,26 +221,29 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) if (process->exitStatus() == QProcessWrapper::CrashExit) { q->setError(UserDefinedError); q->setErrorString(tr("Program crashed: \"%1\"").arg(callstr)); - returnValue = false; + returnValue = Error; } - if (!allowedExitCodes.contains(process->exitCode())) { - q->setError(UserDefinedError); - if (customErrorMessage.isEmpty()) { - q->setErrorString(tr("Execution failed (Unexpected exit code: %1): \"%2\"") - .arg(QString::number(process->exitCode()), callstr)); + if (!allowedExitCodes.contains(process->exitCode()) && returnValue != NeedsRerun) { + if (!needsRerunWithReplacedVariables(arguments, type)) { + q->setError(UserDefinedError); + if (customErrorMessage.isEmpty()) { + q->setErrorString(tr("Execution failed (Unexpected exit code: %1): \"%2\"") + .arg(QString::number(process->exitCode()), callstr)); + } else { + q->setErrorString(customErrorMessage); + } + + QByteArray standardErrorOutput = process->readAllStandardError(); + // in error case it would be useful to see something in verbose output + if (!standardErrorOutput.isEmpty()) + qCWarning(QInstaller::lcInstallerInstallLog).noquote() << standardErrorOutput; + + returnValue = Error; } else { - q->setErrorString(customErrorMessage); + returnValue = NeedsRerun; } - - QByteArray standardErrorOutput = process->readAllStandardError(); - // in error case it would be useful to see something in verbose output - if (!standardErrorOutput.isEmpty()) - qCWarning(QInstaller::lcInstallerInstallLog).noquote() << standardErrorOutput; - - returnValue = false; } - Q_ASSERT(process); Q_ASSERT(process->state() == QProcessWrapper::NotRunning); delete process; @@ -241,6 +252,28 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) return returnValue; } +bool ElevatedExecuteOperation::Private::needsRerunWithReplacedVariables(QStringList &arguments, const OperationType type) +{ + if (type != Operation::Undo) + return false; + bool rerun = false; + PackageManagerCore *const core = q->packageManager(); + for (int i = 0; i < arguments.count(); i++) { + QString key = core->key(arguments.at(i)); + if (!key.isEmpty() && key.endsWith(QLatin1String("_OLD"))) { + key.remove(key.length() - 4, 4); + if (core->containsValue(key)) { + key.prepend(QLatin1String("@")); + key.append(QLatin1String("@")); + QString value = core->replaceVariables(key); + arguments.replace(i, value); + rerun = true; + } + } + } + return rerun; +} + /*! Cancels the ElevatedExecuteOperation. This methods tries to terminate the process gracefully by calling QProcessWrapper::terminate. After 10 seconds, the process gets killed. @@ -285,7 +318,17 @@ bool ElevatedExecuteOperation::undoOperation() PackageManagerCore *const core = packageManager(); args = core->replaceVariables(args); } - return d->run(args); + + int returnValue = d->run(args, Operation::Undo); + if (returnValue == NeedsRerun) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote() << QString::fromLatin1("Failed to run " + "undo operation \"%1\" for component %2. Trying again with arguments %3").arg(name(), + value(QLatin1String("component")).toString(), args.join(QLatin1String(", "))); + setError(NoError); + setErrorString(QString()); + returnValue = d->run(args, Operation::Undo); + } + return returnValue ? false : true; } bool ElevatedExecuteOperation::testOperation() diff --git a/src/libs/installer/elevatedexecuteoperation.h b/src/libs/installer/elevatedexecuteoperation.h index 5585e77a8..470d3e506 100644 --- a/src/libs/installer/elevatedexecuteoperation.h +++ b/src/libs/installer/elevatedexecuteoperation.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -38,6 +38,12 @@ class INSTALLER_EXPORT ElevatedExecuteOperation : public QObject, public Operati Q_OBJECT public: + enum Error { + NoError = 0, + Error, + NeedsRerun + }; + explicit ElevatedExecuteOperation(PackageManagerCore *core); ~ElevatedExecuteOperation(); diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp index 5b13b1f25..c2d541929 100644 --- a/src/libs/installer/extractarchiveoperation.cpp +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -163,7 +163,7 @@ bool ExtractArchiveOperation::performOperation() // TODO: Use backups for rollback, too? Doesn't work for uninstallation though. // delete all backups we can delete right now, remember the rest - foreach (const Backup &i, callback.backupFiles()) + foreach (const QInstaller::Backup &i, callback.backupFiles()) deleteFileNowOrLater(i.second); if (!receiver.success()) { diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index ed513fb69..7eaae7b33 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -875,7 +875,7 @@ void PackageManagerCore::rollBackInstallation() operation->setValue(QLatin1String("forceremoval"), false); } - PackageManagerCorePrivate::performOperationThreaded(operation, PackageManagerCorePrivate::Undo); + PackageManagerCorePrivate::performOperationThreaded(operation, Operation::Undo); const QString componentName = operation->value(QLatin1String("component")).toString(); if (!componentName.isEmpty()) { @@ -2963,7 +2963,7 @@ bool PackageManagerCore::performOperation(const QString &name, const QStringList op->setArguments(replaceVariables(arguments)); op->backup(); if (!PackageManagerCorePrivate::performOperationThreaded(op.data())) { - PackageManagerCorePrivate::performOperationThreaded(op.data(), PackageManagerCorePrivate::Undo); + PackageManagerCorePrivate::performOperationThreaded(op.data(), Operation::Undo); return false; } return true; @@ -3120,6 +3120,17 @@ QStringList PackageManagerCore::values(const QString &key, const QStringList &de return d->m_data.value(key, defaultValue).toStringList(); } +/*! + Returns the installer key for \a value. If \a value is not known, empty string is + returned. + + \sa {installer::key}{installer.key} +*/ +QString PackageManagerCore::key(const QString &value) const +{ + return d->m_data.key(value); +} + /*! Sets the installer value for \a key to \a value. diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index b83d8f05a..5c480e1a9 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -169,6 +169,7 @@ public: Q_INVOKABLE void setValue(const QString &key, const QString &value); Q_INVOKABLE QString value(const QString &key, const QString &defaultValue = QString()) const; Q_INVOKABLE QStringList values(const QString &key, const QStringList &defaultValue = QStringList()) const; + Q_INVOKABLE QString key(const QString &value) const; QString replaceVariables(const QString &str) const; QByteArray replaceVariables(const QByteArray &str) const; diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 274ea017b..94e08018a 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -117,18 +117,18 @@ private: Operation *m_operation; }; -static bool runOperation(Operation *operation, PackageManagerCorePrivate::OperationType type) +static bool runOperation(Operation *operation, Operation::OperationType type) { OperationTracer tracer(operation); switch (type) { - case PackageManagerCorePrivate::Backup: + case Operation::Backup: tracer.trace(QLatin1String("backup")); operation->backup(); return true; - case PackageManagerCorePrivate::Perform: + case Operation::Perform: tracer.trace(QLatin1String("perform")); return operation->performOperation(); - case PackageManagerCorePrivate::Undo: + case Operation::Undo: tracer.trace(QLatin1String("undo")); return operation->undoOperation(); default: @@ -368,7 +368,7 @@ bool PackageManagerCorePrivate::isProcessRunning(const QString &name, } /* static */ -bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, OperationType type) +bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, Operation::OperationType type) { QFutureWatcher futureWatcher; const QFuture future = QtConcurrent::run(runOperation, operation, type); @@ -1254,7 +1254,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper // create the directory containing the maintenance tool (like a bundle structure on macOS...) Operation *op = createOwnedOperation(QLatin1String("Mkdir")); op->setArguments(QStringList() << targetAppDirPath); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); performedOperations.append(takeOwnedOperation(op)); } @@ -1266,14 +1266,14 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper Operation *op = createOwnedOperation(QLatin1String("Copy")); op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../PkgInfo")) << (targetAppDirPath + QLatin1String("/../PkgInfo"))); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // copy Info.plist to target directory op = createOwnedOperation(QLatin1String("Copy")); op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Info.plist")) << (targetAppDirPath + QLatin1String("/../Info.plist"))); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // patch the Info.plist after copying it @@ -1306,7 +1306,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper op = createOwnedOperation(QLatin1String("Mkdir")); op->setArguments(QStringList() << (QFileInfo(targetAppDirPath).path() + QLatin1String("/Resources"))); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // copy application icons if it exists. @@ -1315,7 +1315,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper op = createOwnedOperation(QLatin1String("Copy")); op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Resources/") + icon) << (targetAppDirPath + QLatin1String("/../Resources/") + icon)); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // finally, copy everything within Frameworks and plugins @@ -1617,7 +1617,7 @@ bool PackageManagerCorePrivate::runInstaller() mkdirOp->setValue(QLatin1String("forceremoval"), true); mkdirOp->setValue(QLatin1String("uninstall-only"), true); - performOperationThreaded(mkdirOp, Backup); + performOperationThreaded(mkdirOp, Operation::Backup); if (!performOperationThreaded(mkdirOp)) { // if we cannot create the target dir, we try to activate the admin rights adminRightsGained = m_core->gainAdminRights(); @@ -2177,7 +2177,7 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr connectOperationCallMethodRequest(operation); // allow the operation to backup stuff before performing the operation - performOperationThreaded(operation, PackageManagerCorePrivate::Backup); + performOperationThreaded(operation, Operation::Backup); bool ignoreError = false; bool ok = performOperationThreaded(operation); @@ -2412,7 +2412,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera qCDebug(QInstaller::lcInstallerInstallLog) << "undo operation=" << undoOperation->name(); bool ignoreError = false; - bool ok = performOperationThreaded(undoOperation, PackageManagerCorePrivate::Undo); + bool ok = performOperationThreaded(undoOperation, Operation::Undo); const QString componentName = undoOperation->value(QLatin1String("component")).toString(); @@ -2425,7 +2425,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera QMessageBox::Retry | QMessageBox::Ignore, QMessageBox::Ignore); if (button == QMessageBox::Retry) - ok = performOperationThreaded(undoOperation, Undo); + ok = performOperationThreaded(undoOperation, Operation::Undo); else if (button == QMessageBox::Ignore) ignoreError = true; } diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h index 8983a95c2..858baf9eb 100644 --- a/src/libs/installer/packagemanagercore_p.h +++ b/src/libs/installer/packagemanagercore_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -67,12 +67,6 @@ class PackageManagerCorePrivate : public QObject Q_DISABLE_COPY(PackageManagerCorePrivate) public: - enum OperationType { - Backup, - Perform, - Undo - }; - explicit PackageManagerCorePrivate(PackageManagerCore *core); explicit PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker, const QList &performedOperations); @@ -80,8 +74,8 @@ public: static bool isProcessRunning(const QString &name, const QList &processes); - static bool performOperationThreaded(Operation *op, PackageManagerCorePrivate::OperationType type - = PackageManagerCorePrivate::Perform); + static bool performOperationThreaded(Operation *op, UpdateOperation::OperationType type + = UpdateOperation::Perform); void initialize(const QHash ¶ms); bool isOfflineOnly() const; diff --git a/src/libs/installer/packagemanagercoredata.cpp b/src/libs/installer/packagemanagercoredata.cpp index d488731df..2f3dd9204 100644 --- a/src/libs/installer/packagemanagercoredata.cpp +++ b/src/libs/installer/packagemanagercoredata.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -244,6 +244,11 @@ QVariant PackageManagerCoreData::value(const QString &key, const QVariant &_defa return m_settings.value(key, _default); } +QString PackageManagerCoreData::key(const QString &value) const +{ + return m_variables.key(value, QString()); +} + QString PackageManagerCoreData::replaceVariables(const QString &str) const { static const QChar at = QLatin1Char('@'); diff --git a/src/libs/installer/packagemanagercoredata.h b/src/libs/installer/packagemanagercoredata.h index e112fea1c..4c954d943 100644 --- a/src/libs/installer/packagemanagercoredata.h +++ b/src/libs/installer/packagemanagercoredata.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -52,6 +52,7 @@ public: bool contains(const QString &key) const; bool setValue(const QString &key, const QString &normalizedValue); QVariant value(const QString &key, const QVariant &_default = QVariant()) const; + QString key(const QString &value) const; QString replaceVariables(const QString &str) const; QByteArray replaceVariables(const QByteArray &ba) const; diff --git a/src/libs/kdtools/updateoperation.h b/src/libs/kdtools/updateoperation.h index e05fc34d2..a8110791c 100644 --- a/src/libs/kdtools/updateoperation.h +++ b/src/libs/kdtools/updateoperation.h @@ -53,6 +53,12 @@ public: UserDefinedError = 128 }; + enum OperationType { + Backup, + Perform, + Undo + }; + explicit UpdateOperation(QInstaller::PackageManagerCore *core); virtual ~UpdateOperation(); -- cgit v1.2.3