summaryrefslogtreecommitdiff
path: root/src/manager-lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/manager-lib')
-rw-r--r--src/manager-lib/application.cpp34
-rw-r--r--src/manager-lib/application.h8
-rw-r--r--src/manager-lib/applicationmanager.cpp29
-rw-r--r--src/manager-lib/applicationmanager.h3
-rw-r--r--src/manager-lib/asynchronoustask.cpp133
-rw-r--r--src/manager-lib/asynchronoustask.h111
-rw-r--r--src/manager-lib/deinstallationtask.cpp155
-rw-r--r--src/manager-lib/deinstallationtask.h75
-rw-r--r--src/manager-lib/installationtask.cpp484
-rw-r--r--src/manager-lib/installationtask.h111
-rw-r--r--src/manager-lib/manager-lib.pro28
-rw-r--r--src/manager-lib/package.cpp237
-rw-r--r--src/manager-lib/package.h151
-rw-r--r--src/manager-lib/packagemanager.cpp1192
-rw-r--r--src/manager-lib/packagemanager.h210
-rw-r--r--src/manager-lib/packagemanager_p.h97
-rw-r--r--src/manager-lib/scopeutilities.cpp215
-rw-r--r--src/manager-lib/scopeutilities.h105
-rw-r--r--src/manager-lib/sudo.cpp495
-rw-r--r--src/manager-lib/sudo.h154
20 files changed, 3979 insertions, 48 deletions
diff --git a/src/manager-lib/application.cpp b/src/manager-lib/application.cpp
index 2e489e7f..722ad753 100644
--- a/src/manager-lib/application.cpp
+++ b/src/manager-lib/application.cpp
@@ -284,6 +284,20 @@ Application::Application(ApplicationInfo *info, Package *package)
{
Q_ASSERT(info);
Q_ASSERT(package);
+
+ // handle package blocking: all apps have to be stopped and the stop state has to be reported
+ // back to the package
+ connect(package, &Package::blockedChanged, this, [this](bool blocked) {
+ emit blockedChanged(blocked);
+ if (blocked && (runState() == Am::NotRunning))
+ this->package()->applicationStoppedDueToBlock(id());
+ else if (blocked)
+ stop(true);
+ });
+ connect(this, &Application::runStateChanged, this, [this](Am::RunState runState) {
+ if (isBlocked() && (runState == Am::NotRunning))
+ this->package()->applicationStoppedDueToBlock(id());
+ });
}
bool Application::start(const QString &documentUrl)
@@ -373,6 +387,11 @@ QString Application::name(const QString &language) const
return package()->names().value(language).toString();
}
+bool Application::isBlocked() const
+{
+ return package()->isBlocked();
+}
+
QVariantMap Application::applicationProperties() const
{
return info()->applicationProperties();
@@ -412,21 +431,6 @@ qreal Application::progress() const
return package()->progress();
}
-bool Application::isBlocked() const
-{
- return m_blocked.load() == 1;
-}
-
-bool Application::block()
-{
- return m_blocked.testAndSetOrdered(0, 1);
-}
-
-bool Application::unblock()
-{
- return m_blocked.testAndSetOrdered(1, 0);
-}
-
void Application::setRunState(Am::RunState runState)
{
if (runState != m_runState) {
diff --git a/src/manager-lib/application.h b/src/manager-lib/application.h
index 9e3bcc16..0909ce00 100644
--- a/src/manager-lib/application.h
+++ b/src/manager-lib/application.h
@@ -92,6 +92,7 @@ class Application : public QObject
Q_PROPERTY(QString codeDir READ codeDir NOTIFY bulkChange)
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(QT_PREPEND_NAMESPACE_AM(Am::RunState) runState READ runState NOTIFY runStateChanged)
+ Q_PROPERTY(bool blocked READ isBlocked NOTIFY blockedChanged)
public:
enum State { // kept for compatibility ... in reality moved to class Package
@@ -122,6 +123,7 @@ public:
QStringList supportedMimeTypes() const;
QString name() const;
Q_INVOKABLE QString name(const QString &language) const;
+ bool isBlocked() const;
// Properties that mainly forward content from ApplicationInfo
QString id() const;
@@ -140,9 +142,6 @@ public:
State state() const;
qreal progress() const;
Am::RunState runState() const { return m_runState; }
- bool isBlocked() const;
- bool block();
- bool unblock();
int lastExitCode() const { return m_lastExitCode; }
Am::ExitStatus lastExitStatus() const { return m_lastExitStatus; }
@@ -159,6 +158,7 @@ signals:
void activated();
void stateChanged(State state);
void runStateChanged(Am::RunState state);
+ void blockedChanged(bool blocked);
private:
void setLastExitCodeAndStatus(int exitCode, Am::ExitStatus exitStatus);
@@ -166,8 +166,6 @@ private:
QScopedPointer<ApplicationInfo> m_info;
Package *m_package = nullptr;
AbstractRuntime *m_runtime = nullptr;
- QAtomicInt m_blocked;
- QAtomicInt m_mounted;
Am::RunState m_runState = Am::NotRunning;
diff --git a/src/manager-lib/applicationmanager.cpp b/src/manager-lib/applicationmanager.cpp
index eaa8e205..0bcc9585 100644
--- a/src/manager-lib/applicationmanager.cpp
+++ b/src/manager-lib/applicationmanager.cpp
@@ -1126,30 +1126,6 @@ QString ApplicationManager::identifyApplication(qint64 pid) const
return app ? app->id() : QString();
}
-bool ApplicationManager::blockApplication(const QString &id)
-{
- Application *app = fromId(id);
- if (!app)
- return false;
- if (!app->block())
- return false;
- emitDataChanged(app, QVector<int> { IsBlocked });
- stopApplicationInternal(app, true);
- emitDataChanged(app, QVector<int> { IsRunning });
- return true;
-}
-
-bool ApplicationManager::unblockApplication(const QString &id)
-{
- Application *app = fromId(id);
- if (!app)
- return false;
- if (!app->unblock())
- return false;
- emitDataChanged(app, QVector<int> { IsBlocked });
- return true;
-}
-
void ApplicationManager::shutDown()
{
d->shuttingDown = true;
@@ -1416,6 +1392,11 @@ void ApplicationManager::addApplication(Application *app)
stopApplication(app->id(), forceKill);
};
+ connect(app, &Application::blockedChanged,
+ this, [this, app]() {
+ emitDataChanged(app, QVector<int> { IsBlocked });
+ });
+
d->apps << app;
}
diff --git a/src/manager-lib/applicationmanager.h b/src/manager-lib/applicationmanager.h
index 3e98038c..f221a91b 100644
--- a/src/manager-lib/applicationmanager.h
+++ b/src/manager-lib/applicationmanager.h
@@ -184,9 +184,6 @@ private slots:
void openUrlRelay(const QUrl &url);
void addApplication(Application *app);
- bool blockApplication(const QString &id);
- bool unblockApplication(const QString &id);
-
private:
void emitDataChanged(Application *app, const QVector<int> &roles = QVector<int>());
void emitActivated(Application *app);
diff --git a/src/manager-lib/asynchronoustask.cpp b/src/manager-lib/asynchronoustask.cpp
new file mode 100644
index 00000000..168dc72f
--- /dev/null
+++ b/src/manager-lib/asynchronoustask.cpp
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include <QUuid>
+
+#include "global.h"
+#include "asynchronoustask.h"
+
+QT_BEGIN_NAMESPACE_AM
+
+AsynchronousTask::AsynchronousTask(QObject *parent)
+ : QThread(parent)
+ , m_id(QUuid::createUuid().toString())
+{
+ static int once = qRegisterMetaType<AsynchronousTask::TaskState>();
+ Q_UNUSED(once)
+}
+
+QString AsynchronousTask::id() const
+{
+ return m_id;
+}
+
+AsynchronousTask::TaskState AsynchronousTask::state() const
+{
+ return m_state;
+}
+
+void AsynchronousTask::setState(AsynchronousTask::TaskState state)
+{
+ if (m_state != state) {
+ m_state = state;
+ emit stateChanged(m_state);
+ }
+}
+
+bool AsynchronousTask::hasFailed() const
+{
+ return (m_state == Failed);
+}
+
+Error AsynchronousTask::errorCode() const
+{
+ return m_errorCode;
+}
+
+QString AsynchronousTask::errorString() const
+{
+ return m_errorString;
+}
+
+
+bool AsynchronousTask::cancel()
+{
+ return false;
+}
+
+bool AsynchronousTask::forceCancel()
+{
+ if (m_state == Queued) {
+ setError(Error::Canceled, qSL("canceled"));
+ return true;
+ }
+ return cancel();
+}
+
+QString AsynchronousTask::packageId() const
+{
+ return m_packageId;
+}
+
+bool AsynchronousTask::preExecute()
+{
+ return true;
+}
+
+bool AsynchronousTask::postExecute()
+{
+ return true;
+}
+
+void AsynchronousTask::setError(Error errorCode, const QString &errorString)
+{
+ m_errorCode = errorCode;
+ m_errorString = errorString;
+ setState(Failed);
+}
+
+void AsynchronousTask::run()
+{
+ execute();
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/asynchronoustask.h b/src/manager-lib/asynchronoustask.h
new file mode 100644
index 00000000..977140df
--- /dev/null
+++ b/src/manager-lib/asynchronoustask.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QThread>
+#include <QMutex>
+
+#include <QtAppManCommon/error.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+class AsynchronousTask : public QThread
+{
+ Q_OBJECT
+
+public:
+ enum TaskState
+ {
+ Invalid,
+ Queued,
+ Executing,
+ Failed,
+ Finished,
+
+ // installation task only
+ AwaitingAcknowledge,
+ Installing,
+ CleaningUp
+ };
+ Q_ENUM(TaskState)
+
+ AsynchronousTask(QObject *parent = nullptr);
+
+ QString id() const;
+
+ TaskState state() const;
+ void setState(TaskState state);
+
+ bool hasFailed() const;
+ Error errorCode() const;
+ QString errorString() const;
+
+ virtual bool cancel();
+ bool forceCancel(); // will always work in Queued state
+
+ QString packageId() const; // convenience
+
+ virtual bool preExecute();
+ virtual bool postExecute();
+
+signals:
+ void stateChanged(QT_PREPEND_NAMESPACE_AM(AsynchronousTask::TaskState) newState);
+ void progress(qreal p);
+
+protected:
+ void setError(Error errorCode, const QString &errorString);
+ virtual void execute() = 0;
+ void run() override final;
+
+protected:
+ QMutex m_mutex;
+
+ QString m_id;
+ QString m_packageId;
+ TaskState m_state = Queued;
+ Error m_errorCode = Error::None;
+ QString m_errorString;
+};
+
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/deinstallationtask.cpp b/src/manager-lib/deinstallationtask.cpp
new file mode 100644
index 00000000..09483022
--- /dev/null
+++ b/src/manager-lib/deinstallationtask.cpp
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include "logging.h"
+#include "packagemanager.h"
+#include "packagemanager_p.h"
+#include "installationreport.h"
+#include "package.h"
+#include "exception.h"
+#include "scopeutilities.h"
+#include "deinstallationtask.h"
+
+QT_BEGIN_NAMESPACE_AM
+
+DeinstallationTask::DeinstallationTask(Package *package, const QString &installationPath,
+ const QString &documentPath, bool forceDeinstallation,
+ bool keepDocuments, QObject *parent)
+ : AsynchronousTask(parent)
+ , m_package(package)
+ , m_installationPath(installationPath)
+ , m_documentPath(documentPath)
+ , m_forceDeinstallation(forceDeinstallation)
+ , m_keepDocuments(keepDocuments)
+{
+ m_packageId = m_package->id(); // in base class
+}
+
+bool DeinstallationTask::cancel()
+{
+ if (m_canBeCanceled)
+ m_canceled = true;
+ return m_canceled;
+}
+
+void DeinstallationTask::execute()
+{
+ // these have been checked in PackageManager::removePackage() already
+ Q_ASSERT(m_package);
+ Q_ASSERT(m_package->info());
+ Q_ASSERT(m_package->info()->installationReport());
+
+ bool managerApproval = false;
+
+ try {
+ // we need to call those PackageManager methods in the correct thread
+ // this will also exclusively lock the package for us
+ QMetaObject::invokeMethod(PackageManager::instance(), [this, &managerApproval]()
+ { managerApproval = PackageManager::instance()->startingPackageRemoval(m_package->id()); },
+ Qt::BlockingQueuedConnection);
+
+ if (!managerApproval)
+ throw Exception("PackageManager rejected the removal of package %1").arg(m_package->id());
+
+ // if any of the apps in the package were running before, we now need to wait until all of
+ // them have actually stopped
+ while (!m_canceled && !m_package->areAllApplicationsStoppedDueToBlock())
+ QThread::msleep(30);
+
+ // there's a small race condition here, but not doing a planned cancellation isn't harmful
+ m_canBeCanceled = false;
+ if (m_canceled)
+ throw Exception(Error::Canceled, "canceled");
+
+ ScopedRenamer docDirRename;
+ ScopedRenamer appDirRename;
+
+ if (!m_keepDocuments) {
+ if (!docDirRename.rename(QDir(m_documentPath).absoluteFilePath(m_package->id()),
+ ScopedRenamer::NameToNameMinus)) {
+ throw Exception(Error::IO, "could not rename %1 to %1-").arg(docDirRename.baseName());
+ }
+ }
+
+ if (!appDirRename.rename(QDir(m_installationPath).absoluteFilePath(m_package->id()),
+ ScopedRenamer::NameToNameMinus)) {
+ throw Exception(Error::IO, "could not rename %1 to %1-").arg(appDirRename.baseName());
+ }
+
+ docDirRename.take();
+ appDirRename.take();
+
+ // point of no return
+
+ for (ScopedRenamer *toDelete : { &docDirRename, &appDirRename }) {
+ if (toDelete->isRenamed()) {
+ if (!removeRecursiveHelper(toDelete->baseName() + qL1C('-')))
+ qCCritical(LogInstaller) << "ERROR: could not remove" << (toDelete->baseName() + qL1C('-'));
+ }
+ }
+
+ // we need to call those PackageManager methods in the correct thread
+ bool finishOk = false;
+ QMetaObject::invokeMethod(PackageManager::instance(), [this, &finishOk]()
+ { finishOk = PackageManager::instance()->finishedPackageInstall(m_package->id()); },
+ Qt::BlockingQueuedConnection);
+
+ if (!finishOk)
+ qCWarning(LogInstaller) << "PackageManager did not approve deinstallation of " << m_packageId;
+
+ } catch (const Exception &e) {
+ // we need to call those ApplicationManager methods in the correct thread
+ if (managerApproval) {
+ bool cancelOk = false;
+ QMetaObject::invokeMethod(PackageManager::instance(), [this, &cancelOk]()
+ { cancelOk = PackageManager::instance()->canceledPackageInstall(m_package->id()); },
+ Qt::BlockingQueuedConnection);
+
+ if (!cancelOk)
+ qCWarning(LogInstaller) << "PackageManager could not re-enable package" << m_packageId << "after a failed removal";
+ }
+
+ setError(e.errorCode(), e.errorString());
+ }
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/deinstallationtask.h b/src/manager-lib/deinstallationtask.h
new file mode 100644
index 00000000..9161d1ba
--- /dev/null
+++ b/src/manager-lib/deinstallationtask.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtAppManManager/asynchronoustask.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+class Package;
+class InstallationLocation;
+
+class DeinstallationTask : public AsynchronousTask
+{
+ Q_OBJECT
+
+public:
+ DeinstallationTask(Package *package, const QString &installationPath, const QString &documentPath,
+ bool forceDeinstallation, bool keepDocuments, QObject *parent = nullptr);
+
+ bool cancel() override;
+
+protected:
+ void execute() override;
+
+private:
+ Package *m_package;
+ QString m_installationPath;
+ QString m_documentPath;
+ bool m_forceDeinstallation;
+ bool m_keepDocuments;
+ bool m_canBeCanceled = true;
+ bool m_canceled = false;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/installationtask.cpp b/src/manager-lib/installationtask.cpp
new file mode 100644
index 00000000..ad017286
--- /dev/null
+++ b/src/manager-lib/installationtask.cpp
@@ -0,0 +1,484 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include <QTemporaryDir>
+#include <QMessageAuthenticationCode>
+
+#include "logging.h"
+#include "packagemanager_p.h"
+#include "packageinfo.h"
+#include "packageextractor.h"
+#include "yamlpackagescanner.h"
+#include "exception.h"
+#include "packagemanager.h"
+#include "sudo.h"
+#include "utilities.h"
+#include "signature.h"
+#include "sudo.h"
+#include "installationtask.h"
+
+/*
+ Overview of what happens on an installation of an app with <id> to <location>:
+
+ Step 1 -- startInstallation()
+ =============================
+
+ delete <location>/<id>+
+
+ create dir <location>/<id>+
+ set <extractiondir> to <location>/<id>+
+
+
+ Step 2 -- unpack files
+ ======================
+
+ PackageExtractor does its job
+
+
+ Step 3 -- finishInstallation()
+ ================================
+
+ if (exists <location>/<id>)
+ set <isupdate> to <true>
+
+ create installation report at <extractiondir>/.installation-report.yaml
+
+ if (not <isupdate>)
+ create document directory
+
+ if (optional uid separation)
+ chown/chmod recursively in <extractiondir> and document directory
+
+
+ Step 3.1 -- final rename in finishInstallation()
+ ==================================================
+
+ if (<isupdate>)
+ rename <location>/<id> to <location>/<id>-
+ rename <location>/<id>+ to <location>/<id>
+*/
+
+QT_BEGIN_NAMESPACE_AM
+
+
+
+// The standard QTemporaryDir destructor cannot cope with read-only sub-directories.
+class TemporaryDir : public QTemporaryDir
+{
+public:
+ TemporaryDir()
+ : QTemporaryDir()
+ { }
+ explicit TemporaryDir(const QString &templateName)
+ : QTemporaryDir(templateName)
+ { }
+ ~TemporaryDir()
+ {
+ recursiveOperation(path(), safeRemove);
+ }
+private:
+ Q_DISABLE_COPY(TemporaryDir)
+};
+
+
+QMutex InstallationTask::s_serializeFinishInstallation { };
+
+InstallationTask::InstallationTask(const QString &installationPath, const QString &documentPath,
+ const QUrl &sourceUrl, QObject *parent)
+ : AsynchronousTask(parent)
+ , m_pm(PackageManager::instance())
+ , m_installationPath(installationPath)
+ , m_documentPath(documentPath)
+ , m_sourceUrl(sourceUrl)
+{ }
+
+InstallationTask::~InstallationTask()
+{ }
+
+bool InstallationTask::cancel()
+{
+ QMutexLocker locker(&m_mutex);
+
+ // we cannot cancel anymore after finishInstallation() has been called
+ if (m_installationAcknowledged)
+ return false;
+
+ m_canceled = true;
+ if (m_extractor)
+ m_extractor->cancel();
+ m_installationAcknowledgeWaitCondition.wakeAll();
+ return true;
+}
+
+void InstallationTask::acknowledge()
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (m_canceled)
+ return;
+
+ m_installationAcknowledged = true;
+ m_installationAcknowledgeWaitCondition.wakeAll();
+}
+
+void InstallationTask::execute()
+{
+ try {
+ if (m_installationPath.isEmpty())
+ throw Exception("no installation location was configured");
+
+ TemporaryDir extractionDir;
+ if (!extractionDir.isValid())
+ throw Exception("could not create a temporary extraction directory");
+
+ // protect m_canceled and changes to m_extractor
+ QMutexLocker locker(&m_mutex);
+ if (m_canceled)
+ throw Exception(Error::Canceled, "canceled");
+
+ m_extractor = new PackageExtractor(m_sourceUrl, QDir(extractionDir.path()));
+ locker.unlock();
+
+ connect(m_extractor, &PackageExtractor::progress, this, &AsynchronousTask::progress);
+
+ m_extractor->setFileExtractedCallback(std::bind(&InstallationTask::checkExtractedFile,
+ this, std::placeholders::_1));
+
+ if (!m_extractor->extract())
+ throw Exception(m_extractor->errorCode(), m_extractor->errorString());
+
+ if (!m_foundInfo || !m_foundIcon)
+ throw Exception(Error::Package, "package did not contain a valid info.json and icon file");
+
+ QList<QByteArray> chainOfTrust = m_pm->caCertificates();
+
+ if (!m_pm->allowInstallationOfUnsignedPackages()) {
+ if (!m_extractor->installationReport().storeSignature().isEmpty()) {
+ // normal package from the store
+ QByteArray sigDigest = m_extractor->installationReport().digest();
+ bool sigOk = false;
+
+ if (Signature(sigDigest).verify(m_extractor->installationReport().storeSignature(), chainOfTrust)) {
+ sigOk = true;
+ } else if (!m_pm->hardwareId().isEmpty()) {
+ // did not verify - if we have a hardware-id, try to verify with it
+ sigDigest = QMessageAuthenticationCode::hash(sigDigest, m_pm->hardwareId().toUtf8(), QCryptographicHash::Sha256);
+ if (Signature(sigDigest).verify(m_extractor->installationReport().storeSignature(), chainOfTrust))
+ sigOk = true;
+ }
+ if (!sigOk)
+ throw Exception(Error::Package, "could not verify the package's store signature");
+ } else if (!m_extractor->installationReport().developerSignature().isEmpty()) {
+ // developer package - needs a device in dev mode
+ if (!m_pm->developmentMode())
+ throw Exception(Error::Package, "cannot install development packages on consumer devices");
+
+ if (!Signature(m_extractor->installationReport().digest()).verify(m_extractor->installationReport().developerSignature(), chainOfTrust))
+ throw Exception(Error::Package, "could not verify the package's developer signature");
+
+ } else {
+ throw Exception(Error::Package, "cannot install unsigned packages");
+ }
+ }
+
+ emit finishedPackageExtraction();
+ setState(AwaitingAcknowledge);
+
+ // now wait in a wait-condition until we get an acknowledge or we get canceled
+ locker.relock();
+ while (!m_canceled && !m_installationAcknowledged)
+ m_installationAcknowledgeWaitCondition.wait(&m_mutex);
+
+ // this is the last cancellation point
+ if (m_canceled)
+ throw Exception(Error::Canceled, "canceled");
+ locker.unlock();
+
+ setState(Installing);
+
+ // However many downloads are allowed to happen in parallel: we need to serialize those
+ // tasks here for the finishInstallation() step
+ QMutexLocker finishLocker(&s_serializeFinishInstallation);
+
+ finishInstallation();
+
+ // At this point, the installation is done, so we cannot throw anymore.
+
+ // we need to call those PackageManager methods in the correct thread
+ bool finishOk = false;
+ QMetaObject::invokeMethod(PackageManager::instance(), [this, &finishOk]()
+ { finishOk = PackageManager::instance()->finishedPackageInstall(m_packageId); },
+ Qt::BlockingQueuedConnection);
+
+ if (!finishOk)
+ qCWarning(LogInstaller) << "PackageManager rejected the installation of " << m_packageId;
+
+ } catch (const Exception &e) {
+ setError(e.errorCode(), e.errorString());
+
+ if (m_managerApproval) {
+ // we need to call those ApplicationManager methods in the correct thread
+ bool cancelOk = false;
+ QMetaObject::invokeMethod(PackageManager::instance(), [this, &cancelOk]()
+ { cancelOk = PackageManager::instance()->canceledPackageInstall(m_packageId); },
+ Qt::BlockingQueuedConnection);
+
+ if (!cancelOk)
+ qCWarning(LogInstaller) << "PackageManager could not remove package" << m_packageId << "after a failed installation";
+ }
+ }
+
+
+ {
+ QMutexLocker locker(&m_mutex);
+ delete m_extractor;
+ m_extractor = nullptr;
+ }
+}
+
+
+void InstallationTask::checkExtractedFile(const QString &file) Q_DECL_NOEXCEPT_EXPR(false)
+{
+ ++m_extractedFileCount;
+
+ if (m_extractedFileCount == 1) {
+ if (file != qL1S("info.yaml"))
+ throw Exception(Error::Package, "info.yaml must be the first file in the package. Got %1")
+ .arg(file);
+
+ YamlPackageScanner yps;
+ m_package.reset(yps.scan(m_extractor->destinationDirectory().absoluteFilePath(file)));
+ if (m_package->id() != m_extractor->installationReport().packageId())
+ throw Exception(Error::Package, "the package identifiers in --PACKAGE-HEADER--' and info.yaml do not match");
+
+ m_iconFileName = m_package->icon(); // store it separately as we will give away ApplicationInfo later on
+
+ if (m_iconFileName.isEmpty())
+ throw Exception(Error::Package, "the 'icon' field in info.yaml cannot be empty or absent.");
+
+ m_packageId = m_package->id();
+
+ m_foundInfo = true;
+ } else if (m_extractedFileCount == 2) {
+ // the second file must be the icon
+
+ Q_ASSERT(m_foundInfo);
+ Q_ASSERT(!m_foundIcon);
+
+ if (file != m_iconFileName)
+ throw Exception(Error::Package,
+ "The package icon (as stated in info.yaml) must be the second file in the package."
+ " Expected '%1', got '%2'").arg(m_iconFileName, file);
+
+ QFile icon(m_extractor->destinationDirectory().absoluteFilePath(file));
+
+ if (icon.size() > 256*1024)
+ throw Exception(Error::Package, "the size of %1 is too large (max. 256KB)").arg(file);
+
+ m_foundIcon = true;
+ } else {
+ throw Exception(Error::Package, "Could not find info.yaml and the icon file at the beginning of the package.");
+ }
+
+ if (m_foundIcon && m_foundInfo) {
+ qCDebug(LogInstaller) << "emit taskRequestingInstallationAcknowledge" << id() << "for package" << m_package->id();
+
+ QVariantMap nameMap;
+ auto names = m_package->names();
+ for (auto it = names.constBegin(); it != names.constEnd(); ++it)
+ nameMap.insert(it.key(), it.value());
+
+ QVariantMap applicationData {
+ { qSL("id"), m_package->id() },
+ { qSL("version"), m_package->version() },
+ { qSL("icon"), m_package->icon() },
+ { qSL("displayIcon"), m_package->icon() }, // legacy
+ { qSL("name"), nameMap },
+ { qSL("displayName"), nameMap }, // legacy
+ { qSL("baseDir"), m_package->baseDir().absolutePath() },
+ { qSL("codeDir"), m_package->baseDir().absolutePath() }, // 5.12 backward compatibility
+ { qSL("manifestDir"), m_package->baseDir().absolutePath() }, // 5.12 backward compatibility
+ { qSL("installationLocationId"), qSL("internal-0") } // 5.13 backward compatibility
+ };
+ emit m_pm->taskRequestingInstallationAcknowledge(id(), applicationData,
+ m_extractor->installationReport().extraMetaData(),
+ m_extractor->installationReport().extraSignedMetaData());
+
+ QDir oldDestinationDirectory = m_extractor->destinationDirectory();
+
+ startInstallation();
+
+ QFile::copy(oldDestinationDirectory.filePath(qSL("info.yaml")), m_extractionDir.filePath(qSL("info.yaml")));
+ QFile::copy(oldDestinationDirectory.filePath(m_iconFileName), m_extractionDir.filePath(m_iconFileName));
+
+ {
+ QMutexLocker locker(&m_mutex);
+ m_extractor->setDestinationDirectory(m_extractionDir);
+
+ QString path = m_extractionDir.absolutePath();
+ path.chop(1); // remove the '+'
+ m_package->setBaseDir(QDir(path));
+ }
+ // we need to find a free uid before we call startingApplicationInstallation
+ m_package->m_uid = m_pm->findUnusedUserId();
+ m_applicationUid = m_package->m_uid;
+
+ // we need to call those ApplicationManager methods in the correct thread
+ // this will also exclusively lock the application for us
+ // m_package ownership is transferred to the ApplicationManager
+ QString packageId = m_package->id(); // m_package is gone after the invoke
+ QMetaObject::invokeMethod(PackageManager::instance(), [this]()
+ { m_managerApproval = PackageManager::instance()->startingPackageInstallation(m_package.take()); },
+ Qt::BlockingQueuedConnection);
+
+ if (!m_managerApproval)
+ throw Exception("PackageManager declined the installation of %1").arg(packageId);
+
+ // we're not interested in any other files from here on...
+ m_extractor->setFileExtractedCallback(nullptr);
+ }
+}
+
+void InstallationTask::startInstallation() Q_DECL_NOEXCEPT_EXPR(false)
+{
+ // 2. delete old, partial installation
+
+ QDir installationDir = QString(m_installationPath + qL1C('/'));
+ QString installationTarget = m_packageId + qL1C('+');
+ if (installationDir.exists(installationTarget)) {
+ if (!removeRecursiveHelper(installationDir.absoluteFilePath(installationTarget)))
+ throw Exception("could not remove old, partial installation %1/%2").arg(installationDir).arg(installationTarget);
+ }
+
+ // 4. create new installation
+ if (!m_installationDirCreator.create(installationDir.absoluteFilePath(installationTarget)))
+ throw Exception("could not create installation directory %1/%2").arg(installationDir).arg(installationTarget);
+ m_extractionDir = installationDir;
+ if (!m_extractionDir.cd(installationTarget))
+ throw Exception("could not cd into installation directory %1/%2").arg(installationDir).arg(installationTarget);
+ m_applicationDir.setPath(installationDir.absoluteFilePath(m_packageId));
+}
+
+void InstallationTask::finishInstallation() Q_DECL_NOEXCEPT_EXPR(false)
+{
+ QDir documentDirectory(m_documentPath);
+ ScopedDirectoryCreator documentDirCreator;
+
+ enum { Installation, Update } mode = Installation;
+
+ if (m_applicationDir.exists())
+ mode = Update;
+
+ // create the installation report
+ InstallationReport report = m_extractor->installationReport();
+
+ QFile reportFile(m_extractionDir.absoluteFilePath(qSL(".installation-report.yaml")));
+ if (!reportFile.open(QFile::WriteOnly) || !report.serialize(&reportFile))
+ throw Exception(reportFile, "could not write the installation report");
+ reportFile.close();
+
+ // create the document directories when installing (not needed on updates)
+ if (mode == Installation) {
+ // this package may have been installed earlier and the document directory may not have been removed
+ if (!documentDirectory.cd(m_packageId)) {
+ if (!documentDirCreator.create(documentDirectory.absoluteFilePath(m_packageId)))
+ throw Exception(Error::IO, "could not create the document directory %1").arg(documentDirectory.filePath(m_packageId));
+ }
+ }
+#ifdef Q_OS_UNIX
+ // update the owner, group and permission bits on both the installation and document directories
+ SudoClient *root = SudoClient::instance();
+
+ if (m_pm->isApplicationUserIdSeparationEnabled() && root) {
+ uid_t uid = m_applicationUid;
+ gid_t gid = m_pm->commonApplicationGroupId();
+
+ if (!root->setOwnerAndPermissionsRecursive(documentDirectory.filePath(m_packageId), uid, gid, 02700)) {
+ throw Exception(Error::IO, "could not recursively change the owner to %1:%2 and the permission bits to %3 in %4")
+ .arg(uid).arg(gid).arg(02700, 0, 8).arg(documentDirectory.filePath(m_packageId));
+ }
+
+ if (!root->setOwnerAndPermissionsRecursive(m_extractionDir.path(), uid, gid, 0440)) {
+ throw Exception(Error::IO, "could not recursively change the owner to %1:%2 and the permission bits to %3 in %4")
+ .arg(uid).arg(gid).arg(0440, 0, 8).arg(m_extractionDir.absolutePath());
+ }
+ }
+#endif
+
+ // final rename
+
+ // POSIX cannot atomically rename directories, if the destination directory exists
+ // and is non-empty. We need to do a double-rename in this case, which might fail!
+ // The image is a file, so this limitation does not apply!
+
+ ScopedRenamer renameApplication;
+
+ if (mode == Update) {
+ if (!renameApplication.rename(m_applicationDir, ScopedRenamer::NamePlusToName | ScopedRenamer::NameToNameMinus))
+ throw Exception(Error::IO, "could not rename application directory %1+ to %1 (including a backup to %1-)").arg(m_applicationDir);
+ } else {
+ if (!renameApplication.rename(m_applicationDir, ScopedRenamer::NamePlusToName))
+ throw Exception(Error::IO, "could not rename application directory %1+ to %1").arg(m_applicationDir);
+ }
+
+ // from this point onwards, we are not allowed to throw anymore, since the installation is "done"
+
+ setState(CleaningUp);
+
+ renameApplication.take();
+ documentDirCreator.take();
+
+ m_installationDirCreator.take();
+
+ // this should not be necessary, but it also won't hurt
+ if (mode == Update)
+ removeRecursiveHelper(m_applicationDir.absolutePath() + qL1C('-'));
+
+#ifdef Q_OS_UNIX
+ // write files to the filesystem
+ sync();
+#endif
+
+ m_errorString.clear();
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/installationtask.h b/src/manager-lib/installationtask.h
new file mode 100644
index 00000000..5ab947fe
--- /dev/null
+++ b/src/manager-lib/installationtask.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QUrl>
+#include <QStringList>
+#include <QWaitCondition>
+#include <QMutex>
+
+#include <QtAppManApplication/installationreport.h>
+#include <QtAppManManager/asynchronoustask.h>
+#include <QtAppManManager/scopeutilities.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+class PackageInfo;
+class PackageManager;
+class PackageExtractor;
+
+
+class InstallationTask : public AsynchronousTask
+{
+ Q_OBJECT
+public:
+ InstallationTask(const QString &installationPath, const QString &documentPath,
+ const QUrl &sourceUrl, QObject *parent = nullptr);
+ ~InstallationTask() override;
+
+ void acknowledge();
+ bool cancel() override;
+
+signals:
+ void finishedPackageExtraction();
+
+protected:
+ void execute() override;
+
+private:
+ void startInstallation() Q_DECL_NOEXCEPT_EXPR(false);
+ void finishInstallation() Q_DECL_NOEXCEPT_EXPR(false);
+ void checkExtractedFile(const QString &file) Q_DECL_NOEXCEPT_EXPR(false);
+
+private:
+ PackageManager *m_pm;
+ QString m_installationPath;
+ QString m_documentPath;
+ QUrl m_sourceUrl;
+ bool m_foundInfo = false;
+ bool m_foundIcon = false;
+ QString m_iconFileName;
+ bool m_locked = false;
+ uint m_extractedFileCount = 0;
+ bool m_managerApproval = false;
+ QScopedPointer<PackageInfo> m_package;
+ uint m_applicationUid = uint(-1);
+
+ // changes to these 4 member variables are protected by m_mutex
+ PackageExtractor *m_extractor = nullptr;
+ bool m_canceled = false;
+ bool m_installationAcknowledged = false;
+ QWaitCondition m_installationAcknowledgeWaitCondition;
+
+ static QMutex s_serializeFinishInstallation;
+
+ QDir m_applicationDir;
+ QDir m_extractionDir;
+
+ ScopedDirectoryCreator m_installationDirCreator;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/manager-lib.pro b/src/manager-lib/manager-lib.pro
index 9ae1ee90..71fad983 100644
--- a/src/manager-lib/manager-lib.pro
+++ b/src/manager-lib/manager-lib.pro
@@ -13,7 +13,6 @@ QT_FOR_PRIVATE *= \
appman_plugininterfaces-private \
appman_intent_server-private \
appman_intent_client-private \
- appman_installer-private \
appman_monitor-private \
CONFIG *= static internal_module
@@ -57,6 +56,9 @@ HEADERS += \
amnamespace.h \
intentaminterface.h \
processstatus.h \
+ package.h \
+ packagemanager.h \
+ packagemanager_p.h \
!headless:HEADERS += \
qmlinprocessapplicationmanagerwindow.h \
@@ -82,6 +84,8 @@ SOURCES += \
debugwrapper.cpp \
intentaminterface.cpp \
processstatus.cpp \
+ packagemanager.cpp \
+ package.cpp \
!headless:SOURCES += \
qmlinprocessapplicationmanagerwindow.cpp \
@@ -95,4 +99,26 @@ qtHaveModule(qml):SOURCES += \
# compile the moc-data into the exporting binary (appman itself)
HEADERS += ../plugin-interfaces/containerinterface.h
+
+!disable-installer {
+
+ QT_FOR_PRIVATE *= \
+ appman_package-private \
+ appman_crypto-private \
+
+ HEADERS += \
+ asynchronoustask.h \
+ deinstallationtask.h \
+ installationtask.h \
+ scopeutilities.h \
+ sudo.h \
+
+ SOURCES += \
+ asynchronoustask.cpp \
+ installationtask.cpp \
+ deinstallationtask.cpp \
+ scopeutilities.cpp \
+ sudo.cpp \
+}
+
load(qt_module)
diff --git a/src/manager-lib/package.cpp b/src/manager-lib/package.cpp
new file mode 100644
index 00000000..5f414488
--- /dev/null
+++ b/src/manager-lib/package.cpp
@@ -0,0 +1,237 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include <QLocale>
+
+#include "package.h"
+#include "packageinfo.h"
+#include "applicationinfo.h"
+
+QT_BEGIN_NAMESPACE_AM
+
+Package::Package(PackageInfo *packageInfo, State initialState)
+ : m_info(packageInfo)
+ , m_state(initialState)
+{ }
+
+QString Package::id() const
+{
+ return info()->id();
+}
+
+bool Package::isBuiltIn() const
+{
+ return info()->isBuiltIn();
+}
+
+QString Package::version() const
+{
+ return info()->version();
+}
+
+QString Package::name() const
+{
+ QString name;
+ if (!info()->names().isEmpty()) {
+ name = info()->name(QLocale::system().name()); //TODO: language changes
+ if (name.isEmpty())
+ name = info()->name(qSL("en"));
+ if (name.isEmpty())
+ name = info()->name(qSL("en_US"));
+ if (name.isEmpty())
+ name = *info()->names().constBegin();
+ } else {
+ name = id();
+ }
+ return name;
+}
+
+QVariantMap Package::names() const
+{
+ QVariantMap names;
+ for (auto it = info()->names().cbegin(); it != info()->names().cend(); ++it)
+ names.insert(it.key(), it.value());
+ return names;
+}
+
+QString Package::description() const
+{
+ QString description;
+ if (!info()->descriptions().isEmpty()) {
+ description = info()->description(QLocale::system().name()); //TODO: language changes
+ if (description.isEmpty())
+ description = info()->description(qSL("en"));
+ if (description.isEmpty())
+ description = info()->description(qSL("en_US"));
+ if (description.isEmpty())
+ description = *info()->descriptions().constBegin();
+ }
+ return description;
+}
+
+QVariantMap Package::descriptions() const
+{
+ QVariantMap descriptions;
+ for (auto it = info()->descriptions().cbegin(); it != info()->descriptions().cend(); ++it)
+ descriptions.insert(it.key(), it.value());
+ return descriptions;
+}
+
+QStringList Package::categories() const
+{
+ return info()->categories();
+}
+
+QUrl Package::icon() const
+{
+ if (info()->icon().isEmpty())
+ return QUrl();
+
+ QDir dir;
+ switch (state()) {
+ default:
+ case Installed:
+ dir = info()->baseDir();
+ break;
+ case BeingInstalled:
+ case BeingUpdated:
+ dir = QDir(info()->baseDir().absolutePath() + QLatin1Char('+'));
+ break;
+ case BeingRemoved:
+ dir = QDir(info()->baseDir().absolutePath() + QLatin1Char('-'));
+ break;
+ }
+ return QUrl::fromLocalFile(dir.absoluteFilePath(info()->icon()));
+}
+
+void Package::setState(State state)
+{
+ if (m_state != state) {
+ m_state = state;
+ emit stateChanged(m_state);
+ }
+}
+
+void Package::setProgress(qreal progress)
+{
+ m_progress = progress;
+}
+
+
+void Package::setBaseInfo(PackageInfo *info)
+{
+ m_info.reset(info);
+ emit bulkChange();
+}
+
+void Package::setUpdatedInfo(PackageInfo *info)
+{
+ Q_ASSERT(!info || (m_info && info->id() == m_info->id()));
+
+ m_updatedInfo.reset(info);
+ emit bulkChange();
+}
+
+PackageInfo *Package::info() const
+{
+ return m_updatedInfo ? m_updatedInfo.data() : m_info.data();
+}
+
+PackageInfo *Package::updatedInfo() const
+{
+ return m_updatedInfo.data();
+}
+
+PackageInfo *Package::takeBaseInfo()
+{
+ return m_info.take();
+}
+
+bool Package::canBeRevertedToBuiltIn() const
+{
+ return m_info && m_updatedInfo;
+}
+
+bool Package::isBlocked() const
+{
+ return m_blocked > 0;
+}
+
+bool Package::block()
+{
+ bool blockedNow = (m_blocked.fetchAndAddOrdered(1) == 0);
+ if (blockedNow) {
+ emit blockedChanged(true);
+ m_blockedApps = info()->applications();
+ }
+ return blockedNow;
+}
+
+bool Package::unblock()
+{
+ bool unblockedNow = (m_blocked.fetchAndSubOrdered(1) == 1);
+ if (unblockedNow) {
+ m_blockedApps.clear();
+ emit blockedChanged(false);
+ }
+ return unblockedNow;
+
+}
+
+void Package::applicationStoppedDueToBlock(const QString &appId)
+{
+ if (!isBlocked())
+ return;
+
+ auto it = std::find_if(m_blockedApps.cbegin(), m_blockedApps.cend(), [appId](const ApplicationInfo *appInfo) {
+ return appInfo->id() == appId;
+ });
+ if (it != m_blockedApps.cend())
+ m_blockedApps.removeOne(*it);
+}
+
+bool Package::areAllApplicationsStoppedDueToBlock() const
+{
+ return isBlocked() && m_blockedApps.isEmpty();
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/package.h b/src/manager-lib/package.h
new file mode 100644
index 00000000..7932f426
--- /dev/null
+++ b/src/manager-lib/package.h
@@ -0,0 +1,151 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtAppManCommon/global.h>
+#include <QtAppManApplication/packageinfo.h>
+#include <QUrl>
+#include <QString>
+#include <QAtomicInt>
+#include <QObject>
+
+QT_BEGIN_NAMESPACE_AM
+
+
+class Package : public QObject
+{
+ Q_OBJECT
+ Q_CLASSINFO("AM-QmlType", "QtApplicationManager.SystemUI/PackageObject 2.0 UNCREATABLE")
+ Q_PROPERTY(QString id READ id CONSTANT)
+ Q_PROPERTY(bool builtIn READ isBuiltIn NOTIFY bulkChange)
+ Q_PROPERTY(QUrl icon READ icon NOTIFY bulkChange)
+ Q_PROPERTY(QString version READ version NOTIFY bulkChange)
+ Q_PROPERTY(QString name READ name NOTIFY bulkChange)
+ Q_PROPERTY(QVariantMap names READ names NOTIFY bulkChange)
+ Q_PROPERTY(QString description READ version NOTIFY bulkChange)
+ Q_PROPERTY(QVariantMap descriptions READ descriptions NOTIFY bulkChange)
+ Q_PROPERTY(QStringList categories READ categories NOTIFY bulkChange)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+ Q_PROPERTY(bool blocked READ isBlocked NOTIFY blockedChanged)
+
+public:
+ enum State {
+ Installed,
+ BeingInstalled,
+ BeingUpdated,
+ BeingDowngraded,
+ BeingRemoved
+ };
+ Q_ENUM(State)
+
+ Package(PackageInfo *packageInfo, State initialState = Installed);
+
+ QString id() const;
+ bool isBuiltIn() const;
+ QUrl icon() const;
+ QString version() const;
+ QString name() const;
+ QVariantMap names() const;
+ QString description() const;
+ QVariantMap descriptions() const;
+ QStringList categories() const;
+
+ State state() const { return m_state; }
+ qreal progress() const { return m_progress; }
+
+ void setState(State state);
+ void setProgress(qreal progress);
+
+ // Creates a list of Applications from a list of ApplicationInfo objects.
+ // Ownership of the given ApplicationInfo objects is passed to the returned Applications.
+ //static QVector<Application *> fromApplicationInfoVector(QVector<ApplicationInfo *> &);
+
+ /*
+ All packages have a base info.
+
+ Built-in packages, when updated, also get an updated info.
+ The updated info then overlays the base one. Subsequent updates
+ just replace the updated info. When requested to be removed, a
+ built-in packages only loses its updated info, returning to
+ expose the base one.
+
+ Regular packages (ie, non-built-in) only have a base info. When
+ updated, their base info gets replaced and thus there's no way to go
+ back to a previous version. Regular packages get completely
+ removed when requested.
+ */
+ void setBaseInfo(PackageInfo *info);
+ void setUpdatedInfo(PackageInfo *info);
+
+ // Returns the updated info, if there's one. Otherwise returns the base info.
+ PackageInfo *info() const;
+ PackageInfo *updatedInfo() const;
+ PackageInfo *takeBaseInfo();
+
+ bool canBeRevertedToBuiltIn() const;
+
+ bool isBlocked() const;
+ bool block();
+ bool unblock();
+
+ // function for Application to report it has stopped after getting a block request
+ void applicationStoppedDueToBlock(const QString &appId);
+ // query function for the installer to verify that it is safe to manipulate binaries
+ bool areAllApplicationsStoppedDueToBlock() const;
+
+signals:
+ void bulkChange();
+ void stateChanged(State state);
+ void blockedChanged(bool blocked);
+
+private:
+ QScopedPointer<PackageInfo> m_info;
+ QScopedPointer<PackageInfo> m_updatedInfo;
+
+ State m_state = Installed;
+ qreal m_progress = 0;
+ QAtomicInt m_blocked;
+ QVector<ApplicationInfo *> m_blockedApps;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/packagemanager.cpp b/src/manager-lib/packagemanager.cpp
new file mode 100644
index 00000000..36cd5d07
--- /dev/null
+++ b/src/manager-lib/packagemanager.cpp
@@ -0,0 +1,1192 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include <QMetaMethod>
+#include <QQmlEngine>
+#include <QVersionNumber>
+#include "packagemanager.h"
+#include "packagedatabase.h"
+#include "packagemanager_p.h"
+#include "package.h"
+#include "logging.h"
+#include "installationreport.h"
+#include "exception.h"
+#include "sudo.h"
+#include "utilities.h"
+
+#if defined(Q_OS_WIN)
+# include <Windows.h>
+#else
+# include <sys/stat.h>
+# include <errno.h>
+# if defined(Q_OS_ANDROID)
+# include <sys/vfs.h>
+# define statvfs statfs
+# else
+# include <sys/statvfs.h>
+# endif
+#endif
+
+
+QT_BEGIN_NAMESPACE_AM
+
+enum Roles
+{
+ Id = Qt::UserRole,
+ Name,
+ Description,
+ Icon,
+
+ IsBlocked,
+ IsUpdating,
+ IsRemovable,
+
+ UpdateProgress,
+
+ Version,
+ PackageItem,
+};
+
+PackageManager *PackageManager::s_instance = nullptr;
+QHash<int, QByteArray> PackageManager::s_roleNames;
+
+PackageManager *PackageManager::createInstance(PackageDatabase *packageDatabase,
+ const QString &documentPath)
+{
+ if (Q_UNLIKELY(s_instance))
+ qFatal("PackageManager::createInstance() was called a second time.");
+
+ Q_ASSERT(packageDatabase);
+
+ QScopedPointer<PackageManager> pm(new PackageManager(packageDatabase, documentPath));
+ registerQmlTypes();
+
+ // map all the built-in packages first
+ const auto builtinPackages = packageDatabase->builtInPackages();
+ for (auto packageInfo : builtinPackages) {
+ auto *package = new Package(packageInfo);
+ QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership);
+ pm->d->packages << package;
+ }
+
+ // next, map all the installed packages, making sure to detect updates to built-in ones
+ const auto installedPackages = packageDatabase->installedPackages();
+ for (auto packageInfo : installedPackages) {
+ Package *builtInPackage = pm->fromId(packageInfo->id());
+
+ if (builtInPackage) { // update
+ if (builtInPackage->updatedInfo()) { // but there already is an update applied!?
+ throw Exception(Error::Package, "Found more than one update for the built-in package '%1'")
+ .arg(builtInPackage->id());
+ //TODO: can we get the paths to both info.yaml here?
+ }
+ builtInPackage->setUpdatedInfo(packageInfo);
+ } else {
+ auto *package = new Package(packageInfo);
+ QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership);
+ pm->d->packages << package;
+ }
+ }
+
+ return s_instance = pm.take();
+}
+
+PackageManager *PackageManager::instance()
+{
+ if (!s_instance)
+ qFatal("PackageManager::instance() was called before createInstance().");
+ return s_instance;
+}
+
+QObject *PackageManager::instanceForQml(QQmlEngine *, QJSEngine *)
+{
+ QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership);
+ return instance();
+}
+
+QVector<Package *> PackageManager::packages() const
+{
+ return d->packages;
+}
+
+void PackageManager::registerQmlTypes()
+{
+ qmlRegisterSingletonType<PackageManager>("QtApplicationManager.SystemUI", 2, 0, "PackageManager",
+ &PackageManager::instanceForQml);
+ qmlRegisterUncreatableType<Package>("QtApplicationManager.SystemUI", 2, 0, "PackageObject",
+ qSL("Cannot create objects of type PackageObject"));
+ qRegisterMetaType<Package *>("Package*");
+
+ s_roleNames.insert(Id, "packageId");
+ s_roleNames.insert(Name, "name");
+ s_roleNames.insert(Description, "description");
+ s_roleNames.insert(Icon, "icon");
+ s_roleNames.insert(IsBlocked, "isBlocked");
+ s_roleNames.insert(IsUpdating, "isUpdating");
+ s_roleNames.insert(IsRemovable, "isRemovable");
+ s_roleNames.insert(UpdateProgress, "updateProgress");
+ s_roleNames.insert(Version, "version");
+ s_roleNames.insert(PackageItem, "package");
+}
+
+PackageManager::PackageManager(PackageDatabase *packageDatabase,
+ const QString &documentPath)
+ : QAbstractListModel()
+ , d(new PackageManagerPrivate())
+{
+ d->database = packageDatabase;
+ d->installationPath = packageDatabase->installedPackagesDir();
+ d->documentPath = documentPath;
+}
+
+PackageManager::~PackageManager()
+{
+ delete d->database;
+ delete d;
+ s_instance = nullptr;
+}
+
+Package *PackageManager::fromId(const QString &id) const
+{
+ for (auto package : d->packages) {
+ if (package->id() == id)
+ return package;
+ }
+ return nullptr;
+}
+
+void PackageManager::emitDataChanged(Package *package, const QVector<int> &roles)
+{
+ int row = d->packages.indexOf(package);
+ if (row >= 0) {
+ emit dataChanged(index(row), index(row), roles);
+
+ static const auto pkgChanged = QMetaMethod::fromSignal(&PackageManager::packageChanged);
+ if (isSignalConnected(pkgChanged)) {
+ QStringList stringRoles;
+ for (auto role : roles)
+ stringRoles << qL1S(s_roleNames[role]);
+ emit packageChanged(package->id(), stringRoles);
+ }
+ }
+}
+
+// item model part
+
+int PackageManager::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return d->packages.count();
+}
+
+QVariant PackageManager::data(const QModelIndex &index, int role) const
+{
+ if (index.parent().isValid() || !index.isValid())
+ return QVariant();
+
+ Package *package = d->packages.at(index.row());
+
+ switch (role) {
+ case Id:
+ return package->id();
+ case Name:
+ return package->name();
+ case Description:
+ return package->description();
+ case Icon:
+ return package->icon();
+ case IsBlocked:
+ return package->isBlocked();
+ case IsUpdating:
+ return package->state() != Package::Installed;
+ case UpdateProgress:
+ return package->progress();
+ case IsRemovable:
+ return !package->isBuiltIn();
+ case Version:
+ return package->version();
+ case PackageItem:
+ return QVariant::fromValue(package);
+ }
+ return QVariant();
+}
+
+QHash<int, QByteArray> PackageManager::roleNames() const
+{
+ return s_roleNames;
+}
+
+int PackageManager::count() const
+{
+ return rowCount();
+}
+
+/*!
+ \qmlmethod object PackageManager::get(int index)
+
+ Retrieves the model data at \a index as a JavaScript object. See the
+ \l {PackageManager Roles}{role names} for the expected object fields.
+
+ Returns an empty object if the specified \a index is invalid.
+
+ \note This is very inefficient if you only want to access a single property from QML; use
+ package() instead to access the Package object's properties directly.
+*/
+QVariantMap PackageManager::get(int index) const
+{
+ if (index < 0 || index >= count()) {
+ qCWarning(LogSystem) << "PackageManager::get(index): invalid index:" << index;
+ return QVariantMap();
+ }
+
+ QVariantMap map;
+ QHash<int, QByteArray> roles = roleNames();
+ for (auto it = roles.begin(); it != roles.end(); ++it)
+ map.insert(qL1S(it.value()), data(this->index(index), it.key()));
+ return map;
+}
+
+/*!
+ \qmlmethod PackageObject PackageManager::package(int index)
+
+ Returns the \l{PackageObject}{package} corresponding to the given \a index in the
+ model, or \c null if the index is invalid.
+
+ \note The object ownership of the returned Package object stays with the application-manager.
+ If you want to store this pointer, you can use the PackageManager's QAbstractListModel
+ signals or the packageAboutToBeRemoved signal to get notified if the object is about
+ to be deleted on the C++ side.
+*/
+Package *PackageManager::package(int index) const
+{
+ if (index < 0 || index >= count()) {
+ qCWarning(LogSystem) << "PackageManager::application(index): invalid index:" << index;
+ return nullptr;
+ }
+ return d->packages.at(index);
+}
+
+/*!
+ \qmlmethod PackageObject PackageManager::package(string id)
+
+ Returns the \l{PackageObject}{package} corresponding to the given package \a id,
+ or \c null if the id does not exist.
+
+ \note The object ownership of the returned Package object stays with the application-manager.
+ If you want to store this pointer, you can use the PackageManager's QAbstractListModel
+ signals or the packageAboutToBeRemoved signal to get notified if the object is about
+ to be deleted on the C++ side.
+*/
+Package *PackageManager::package(const QString &id) const
+{
+ auto index = indexOfPackage(id);
+ return (index < 0) ? nullptr : package(index);
+}
+
+/*!
+ \qmlmethod int PackageManager::indexOfPackage(string id)
+
+ Maps the package \a id to its position within the model.
+
+ Returns \c -1 if the specified \a id is invalid.
+*/
+int PackageManager::indexOfPackage(const QString &id) const
+{
+ for (int i = 0; i < d->packages.size(); ++i) {
+ if (d->packages.at(i)->id() == id)
+ return i;
+ }
+ return -1;
+}
+
+bool PackageManager::developmentMode() const
+{
+ return d->developmentMode;
+}
+
+void PackageManager::setDevelopmentMode(bool enable)
+{
+ d->developmentMode = enable;
+}
+
+bool PackageManager::allowInstallationOfUnsignedPackages() const
+{
+ return d->allowInstallationOfUnsignedPackages;
+}
+
+void PackageManager::setAllowInstallationOfUnsignedPackages(bool enable)
+{
+ d->allowInstallationOfUnsignedPackages = enable;
+}
+
+QString PackageManager::hardwareId() const
+{
+ return d->hardwareId;
+}
+
+void PackageManager::setHardwareId(const QString &hwId)
+{
+ d->hardwareId = hwId;
+}
+
+bool PackageManager::isApplicationUserIdSeparationEnabled() const
+{
+ return d->userIdSeparation;
+}
+
+uint PackageManager::commonApplicationGroupId() const
+{
+ return d->commonGroupId;
+}
+
+bool PackageManager::enableApplicationUserIdSeparation(uint minUserId, uint maxUserId, uint commonGroupId)
+{
+ if (minUserId >= maxUserId || minUserId == uint(-1) || maxUserId == uint(-1))
+ return false;
+ d->userIdSeparation = true;
+ d->minUserId = minUserId;
+ d->maxUserId = maxUserId;
+ d->commonGroupId = commonGroupId;
+ return true;
+}
+
+uint PackageManager::findUnusedUserId() const Q_DECL_NOEXCEPT_EXPR(false)
+{
+ if (!isApplicationUserIdSeparationEnabled())
+ return uint(-1);
+
+ for (uint uid = d->minUserId; uid <= d->maxUserId; ++uid) {
+ bool match = false;
+ for (Package *package : d->packages) {
+ if (package->info()->uid() == uid) {
+ match = true;
+ break;
+ }
+ }
+ if (!match)
+ return uid;
+ }
+ throw Exception("could not find a free user-id for application separation in the range %1 to %2")
+ .arg(d->minUserId).arg(d->maxUserId);
+}
+
+QList<QByteArray> PackageManager::caCertificates() const
+{
+ return d->chainOfTrust;
+}
+
+void PackageManager::setCACertificates(const QList<QByteArray> &chainOfTrust)
+{
+ d->chainOfTrust = chainOfTrust;
+}
+
+static QVariantMap locationMap(const QString &path)
+{
+ QString cpath = QFileInfo(path).canonicalPath();
+ quint64 bytesTotal = 0;
+ quint64 bytesFree = 0;
+
+#if defined(Q_OS_WIN)
+ GetDiskFreeSpaceExW((LPCWSTR) cpath.utf16(), (ULARGE_INTEGER *) &bytesFree,
+ (ULARGE_INTEGER *) &bytesTotal, nullptr);
+
+#else // Q_OS_UNIX
+ int result;
+ struct ::statvfs svfs;
+
+ do {
+ result = ::statvfs(cpath.toLocal8Bit(), &svfs);
+ if (result == -1 && errno == EINTR)
+ continue;
+ } while (false);
+
+ if (result == 0) {
+ bytesTotal = quint64(svfs.f_frsize) * svfs.f_blocks;
+ bytesFree = quint64(svfs.f_frsize) * svfs.f_bavail;
+ }
+#endif // Q_OS_WIN
+
+
+ return QVariantMap {
+ { qSL("path"), path },
+ { qSL("deviceSize"), bytesTotal },
+ { qSL("deviceFree"), bytesFree }
+ };
+}
+
+/*!
+ \qmlproperty object PackageManager::installationLocation
+
+ Returns an object describing the location under which applications are installed in detail.
+
+ The returned object has the following members:
+
+ \table
+ \header
+ \li \c Name
+ \li \c Type
+ \li Description
+ \row
+ \li \c path
+ \li \c string
+ \li The absolute file-system path to the base directory.
+ \row
+ \li \c deviceSize
+ \li \c int
+ \li The size of the device holding \c path in bytes.
+ \row
+ \li \c deviceFree
+ \li \c int
+ \li The amount of bytes available on the device holding \c path.
+ \endtable
+
+ Returns an empty object in case the installer component is disabled.
+*/
+QVariantMap PackageManager::installationLocation() const
+{
+ return locationMap(d->installationPath);
+}
+
+/*!
+ \qmlproperty object PackageManager::documentLocation
+
+ Returns an object describing the location under which per-user document
+ directories are created in detail.
+
+ The returned object has the same members as described in PackageManager::installationLocation.
+*/
+QVariantMap PackageManager::documentLocation() const
+{
+ return locationMap(d->documentPath);
+}
+
+void PackageManager::cleanupBrokenInstallations() Q_DECL_NOEXCEPT_EXPR(false)
+{
+ // Check that everything in the app-db is available
+ // -> if not, remove from app-db
+
+ // key: baseDirPath, value: subDirName/ or fileName
+ QMultiMap<QString, QString> validPaths;
+ if (!d->documentPath.isEmpty())
+ validPaths.insert(d->documentPath, QString());
+ if (!d->installationPath.isEmpty())
+ validPaths.insert(d->installationPath, QString());
+
+ for (Package *pkg : d->packages) { // we want to detach here!
+ const InstallationReport *ir = pkg->info()->installationReport();
+ if (ir) {
+ bool valid = true;
+
+ QString pkgDir = d->installationPath + pkg->id();
+ QStringList checkDirs;
+ QStringList checkFiles;
+
+ checkFiles << pkgDir + qSL("/info.yaml");
+ checkFiles << pkgDir + qSL("/.installation-report.yaml");
+ checkDirs << pkgDir;
+ checkDirs << d->installationPath + pkg->id();
+
+ for (const QString &checkFile : qAsConst(checkFiles)) {
+ QFileInfo fi(checkFile);
+ if (!fi.exists() || !fi.isFile() || !fi.isReadable()) {
+ valid = false;
+ qCDebug(LogInstaller) << "cleanup: uninstalling" << pkg->id() << "- file missing:" << checkFile;
+ break;
+ }
+ }
+ for (const QString &checkDir : checkDirs) {
+ QFileInfo fi(checkDir);
+ if (!fi.exists() || !fi.isDir() || !fi.isReadable()) {
+ valid = false;
+ qCDebug(LogInstaller) << "cleanup: uninstalling" << pkg->id() << "- directory missing:" << checkDir;
+ break;
+ }
+ }
+
+ if (valid) {
+ validPaths.insertMulti(d->installationPath, pkg->id() + qL1C('/'));
+ validPaths.insertMulti(d->documentPath, pkg->id() + qL1C('/'));
+ } else {
+ if (startingPackageRemoval(pkg->id())) {
+ if (finishedPackageInstall(pkg->id()))
+ continue;
+ }
+ throw Exception(Error::Package, "could not remove broken installation of package %1 from database").arg(pkg->id());
+ }
+ }
+ }
+
+ // Remove everything that is not referenced from the app-db
+
+ for (auto it = validPaths.cbegin(); it != validPaths.cend(); ) {
+ const QString currentDir = it.key();
+
+ // collect all values for the unique key currentDir
+ QVector<QString> validNames;
+ for ( ; it != validPaths.cend() && it.key() == currentDir; ++it)
+ validNames << it.value();
+
+ const QFileInfoList &dirEntries = QDir(currentDir).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
+
+ // check if there is anything in the filesystem that is NOT listed in the validNames
+ for (const QFileInfo &fi : dirEntries) {
+ QString name = fi.fileName();
+ if (fi.isDir())
+ name.append(qL1C('/'));
+
+ if ((!fi.isDir() && !fi.isFile()) || !validNames.contains(name)) {
+ qCDebug(LogInstaller) << "cleanup: removing unreferenced inode" << name;
+
+ if (SudoClient::instance()) {
+ if (!SudoClient::instance()->removeRecursive(fi.absoluteFilePath())) {
+ throw Exception(Error::IO, "could not remove broken installation leftover %1: %2")
+ .arg(fi.absoluteFilePath()).arg(SudoClient::instance()->lastError());
+ }
+ } else {
+ if (!recursiveOperation(fi.absoluteFilePath(), safeRemove)) {
+ throw Exception(Error::IO, "could not remove broken installation leftover %1 (maybe due to missing root privileges)")
+ .arg(fi.absoluteFilePath());
+ }
+ }
+ }
+ }
+ }
+}
+
+/*!
+ \qmlmethod list<string> PackageManager::packageIds()
+
+ Returns a list of all available package ids. This can be used to further query for specific
+ information via get().
+*/
+QStringList PackageManager::packageIds() const
+{
+ QStringList ids;
+ ids.reserve(d->packages.size());
+ for (int i = 0; i < d->packages.size(); ++i)
+ ids << d->packages.at(i)->id();
+ return ids;
+}
+
+/*!
+ \qmlmethod object PackageManager::get(string id)
+
+ Retrieves the model data for the package identified by \a id as a JavaScript object.
+ See the \l {PackageManager Roles}{role names} for the expected object fields.
+
+ Returns an empty object if the specified \a id is invalid.
+*/
+QVariantMap PackageManager::get(const QString &id) const
+{
+ int index = indexOfPackage(id);
+ return (index < 0) ? QVariantMap{} : get(index);
+}
+
+/*!
+ \qmlmethod int PackageManager::installedPackageSize(string packageId)
+
+ Returns the size in bytes that the package identified by \a packageId is occupying on the storage
+ device.
+
+ Returns \c -1 in case the package \a packageId is not valid, or the package is not installed.
+*/
+qint64 PackageManager::installedPackageSize(const QString &packageId) const
+{
+ if (Package *package = fromId(packageId)) {
+ if (const InstallationReport *report = package->info()->installationReport())
+ return static_cast<qint64>(report->diskSpaceUsed());
+ }
+ return -1;
+}
+
+/*!
+ \qmlmethod var PackageManager::installedPackageExtraMetaData(string packageId)
+
+ Returns a map of all extra metadata in the package header of the package identified by \a packageId.
+
+ Returns an empty map in case the package \a packageId is not valid, or the package is not installed.
+*/
+QVariantMap PackageManager::installedPackageExtraMetaData(const QString &packageId) const
+{
+ if (Package *package = fromId(packageId)) {
+ if (const InstallationReport *report = package->info()->installationReport())
+ return report->extraMetaData();
+ }
+ return QVariantMap();
+}
+
+/*!
+ \qmlmethod var PackageManager::installedPackageExtraSignedMetaData(string packageId)
+
+ Returns a map of all signed extra metadata in the package header of the package identified
+ by \a packageId.
+
+ Returns an empty map in case the package \a packageId is not valid, or the package is not installed.
+*/
+QVariantMap PackageManager::installedPackageExtraSignedMetaData(const QString &packageId) const
+{
+ if (Package *package = fromId(packageId)) {
+ if (const InstallationReport *report = package->info()->installationReport())
+ return report->extraSignedMetaData();
+ }
+ return QVariantMap();
+}
+
+/*! \internal
+ Type safe convenience function, since DBus does not like QUrl
+*/
+QString PackageManager::startPackageInstallation(const QUrl &sourceUrl)
+{
+ AM_TRACE(LogInstaller, sourceUrl);
+
+ return enqueueTask(new InstallationTask(d->installationPath, d->documentPath, sourceUrl));
+}
+
+/*!
+ \qmlmethod string PackageManager::startPackageInstallation(string sourceUrl)
+
+ Downloads an application package from \a sourceUrl and installs it.
+
+ The actual download and installation will happen asynchronously in the background. The
+ PackageManager emits the signals \l taskStarted, \l taskProgressChanged, \l
+ taskRequestingInstallationAcknowledge, \l taskFinished, \l taskFailed, and \l taskStateChanged
+ for the returned taskId when applicable.
+
+ \note Simply calling this function is not enough to complete a package installation: The
+ taskRequestingInstallationAcknowledge() signal needs to be connected to a slot where the
+ supplied package meta-data can be validated (either programmatically or by asking the user).
+ If the validation is successful, the installation can be completed by calling
+ acknowledgePackageInstallation() or, if the validation was unsuccessful, the installation should
+ be canceled by calling cancelTask().
+ Failing to do one or the other will leave an unfinished "zombie" installation.
+
+ Returns a unique \c taskId. This can also be an empty string, if the task could not be
+ created (in this case, no signals will be emitted).
+*/
+QString PackageManager::startPackageInstallation(const QString &sourceUrl)
+{
+ QUrl url(sourceUrl);
+ if (url.scheme().isEmpty())
+ url = QUrl::fromLocalFile(sourceUrl);
+ return startPackageInstallation(url);
+}
+
+/*!
+ \qmlmethod void PackageManager::acknowledgePackageInstallation(string taskId)
+
+ Calling this function enables the installer to complete the installation task identified by \a
+ taskId. Normally, this function is called after receiving the taskRequestingInstallationAcknowledge()
+ signal, and the user and/or the program logic decided to proceed with the installation.
+
+ \sa startPackageInstallation()
+ */
+void PackageManager::acknowledgePackageInstallation(const QString &taskId)
+{
+ AM_TRACE(LogInstaller, taskId)
+
+ const auto allTasks = d->allTasks();
+
+ for (AsynchronousTask *task : allTasks) {
+ if (qobject_cast<InstallationTask *>(task) && (task->id() == taskId)) {
+ static_cast<InstallationTask *>(task)->acknowledge();
+ break;
+ }
+ }
+}
+
+/*!
+ \qmlmethod string PackageManager::removePackage(string packageId, bool keepDocuments, bool force)
+
+ Uninstalls the package identified by \a id. Normally, the documents directory of the
+ package is deleted on removal, but this can be prevented by setting \a keepDocuments to \c true.
+
+ The actual removal will happen asynchronously in the background. The PackageManager will
+ emit the signals \l taskStarted, \l taskProgressChanged, \l taskFinished, \l taskFailed and \l
+ taskStateChanged for the returned \c taskId when applicable.
+
+ Normally, \a force should only be set to \c true if a previous call to removePackage() failed.
+ This may be necessary if the installation process was interrupted, or or has file-system issues.
+
+ Returns a unique \c taskId. This can also be an empty string, if the task could not be created
+ (in this case, no signals will be emitted).
+*/
+QString PackageManager::removePackage(const QString &packageId, bool keepDocuments, bool force)
+{
+ AM_TRACE(LogInstaller, packageId, keepDocuments)
+
+ if (Package *package = fromId(packageId)) {
+ if (package->info()->installationReport()) {
+ return enqueueTask(new DeinstallationTask(package, d->installationPath,
+ d->documentPath, force, keepDocuments));
+ }
+ }
+ return QString();
+}
+
+
+/*!
+ \qmlmethod enumeration PackageManager::taskState(string taskId)
+
+ Returns the current state of the installation task identified by \a taskId.
+ \l {TaskStates}{See here} for a list of valid task states.
+
+ Returns \c PackageManager.Invalid if the \a taskId is invalid.
+*/
+AsynchronousTask::TaskState PackageManager::taskState(const QString &taskId) const
+{
+ const auto allTasks = d->allTasks();
+
+ for (const AsynchronousTask *task : allTasks) {
+ if (task && (task->id() == taskId))
+ return task->state();
+ }
+ return AsynchronousTask::Invalid;
+}
+
+/*!
+ \qmlmethod string PackageManager::taskPackageId(string taskId)
+
+ Returns the package id associated with the task identified by \a taskId. The task may not
+ have a valid package id at all times though and in this case the function will return an
+ empty string (this will be the case for installations before the taskRequestingInstallationAcknowledge
+ signal has been emitted).
+
+ Returns an empty string if the \a taskId is invalid.
+*/
+QString PackageManager::taskPackageId(const QString &taskId) const
+{
+ const auto allTasks = d->allTasks();
+
+ for (const AsynchronousTask *task : allTasks) {
+ if (task && (task->id() == taskId))
+ return task->packageId();
+ }
+ return QString();
+}
+
+/*!
+ \qmlmethod list<string> PackageManager::activeTaskIds()
+
+ Retuns a list of all currently active (as in not yet finished or failed) installation task ids.
+*/
+QStringList PackageManager::activeTaskIds() const
+{
+ const auto allTasks = d->allTasks();
+
+ QStringList result;
+ for (const AsynchronousTask *task : allTasks)
+ result << task->id();
+ return result;
+}
+
+/*!
+ \qmlmethod bool PackageManager::cancelTask(string taskId)
+
+ Tries to cancel the installation task identified by \a taskId.
+
+ Returns \c true if the task was canceled, \c false otherwise.
+*/
+bool PackageManager::cancelTask(const QString &taskId)
+{
+ AM_TRACE(LogInstaller, taskId)
+
+ // incoming tasks can be forcefully cancelled right away
+ for (AsynchronousTask *task : qAsConst(d->incomingTaskList)) {
+ if (task->id() == taskId) {
+ task->forceCancel();
+ task->deleteLater();
+
+ handleFailure(task);
+
+ d->incomingTaskList.removeOne(task);
+ triggerExecuteNextTask();
+ return true;
+ }
+ }
+
+ // the active task and async tasks might be in a state where cancellation is not possible,
+ // so we have to ask them nicely
+ if (d->activeTask && d->activeTask->id() == taskId)
+ return d->activeTask->cancel();
+
+ for (AsynchronousTask *task : qAsConst(d->installationTaskList)) {
+ if (task->id() == taskId)
+ return task->cancel();
+ }
+ return false;
+}
+
+/*!
+ \qmlmethod int PackageManager::compareVersions(string version1, string version2)
+
+ Convenience method for app-store implementations or taskRequestingInstallationAcknowledge()
+ callbacks for comparing version numbers, as the actual version comparison algorithm is not
+ trivial.
+
+ Returns \c -1, \c 0 or \c 1 if \a version1 is smaller than, equal to, or greater than \a
+ version2 (similar to how \c strcmp() works).
+*/
+int PackageManager::compareVersions(const QString &version1, const QString &version2)
+{
+ int vn1Suffix = -1;
+ int vn2Suffix = -1;
+ QVersionNumber vn1 = QVersionNumber::fromString(version1, &vn1Suffix);
+ QVersionNumber vn2 = QVersionNumber::fromString(version2, &vn2Suffix);
+
+ int d = QVersionNumber::compare(vn1, vn2);
+ return d < 0 ? -1 : (d > 0 ? 1 : version1.mid(vn1Suffix).compare(version2.mid(vn2Suffix)));
+}
+
+/*!
+ \qmlmethod int PackageManager::validateDnsName(string name, int minimalPartCount)
+
+ Convenience method for app-store implementations or taskRequestingInstallationAcknowledge()
+ callbacks for checking if the given \a name is a valid DNS (or reverse-DNS) name according to
+ RFC 1035/1123. If the optional parameter \a minimalPartCount is specified, this function will
+ also check if \a name contains at least this amount of parts/sub-domains.
+
+ Returns \c true if the name is a valid DNS name or \c false otherwise.
+*/
+bool PackageManager::validateDnsName(const QString &name, int minimalPartCount)
+{
+ try {
+ // check if we have enough parts: e.g. "tld.company.app" would have 3 parts
+ QStringList parts = name.split('.');
+ if (parts.size() < minimalPartCount) {
+ throw Exception(Error::Parse, "the minimum amount of parts (subdomains) is %1 (found %2)")
+ .arg(minimalPartCount).arg(parts.size());
+ }
+
+ // standard RFC compliance tests (RFC 1035/1123)
+
+ auto partCheck = [](const QString &part) {
+ int len = part.length();
+
+ if (len < 1 || len > 63)
+ throw Exception(Error::Parse, "domain parts must consist of at least 1 and at most 63 characters (found %2 characters)").arg(len);
+
+ for (int pos = 0; pos < len; ++pos) {
+ ushort ch = part.at(pos).unicode();
+ bool isFirst = (pos == 0);
+ bool isLast = (pos == (len - 1));
+ bool isDash = (ch == '-');
+ bool isDigit = (ch >= '0' && ch <= '9');
+ bool isLower = (ch >= 'a' && ch <= 'z');
+
+ if ((isFirst || isLast || !isDash) && !isDigit && !isLower)
+ throw Exception(Error::Parse, "domain parts must consist of only the characters '0-9', 'a-z', and '-' (which cannot be the first or last character)");
+ }
+ };
+
+ for (const QString &part : parts)
+ partCheck(part);
+
+ return true;
+ } catch (const Exception &e) {
+ qCDebug(LogInstaller).noquote() << "validateDnsName failed:" << e.errorString();
+ return false;
+ }
+}
+
+QString PackageManager::enqueueTask(AsynchronousTask *task)
+{
+ d->incomingTaskList.append(task);
+ triggerExecuteNextTask();
+ return task->id();
+}
+
+void PackageManager::triggerExecuteNextTask()
+{
+ if (!QMetaObject::invokeMethod(this, "executeNextTask", Qt::QueuedConnection))
+ qCCritical(LogSystem) << "ERROR: failed to invoke method checkQueue";
+}
+
+void PackageManager::executeNextTask()
+{
+ if (d->activeTask || d->incomingTaskList.isEmpty())
+ return;
+
+ AsynchronousTask *task = d->incomingTaskList.takeFirst();
+
+ if (task->hasFailed()) {
+ task->setState(AsynchronousTask::Failed);
+
+ handleFailure(task);
+
+ task->deleteLater();
+ triggerExecuteNextTask();
+ return;
+ }
+
+ connect(task, &AsynchronousTask::started, this, [this, task]() {
+ emit taskStarted(task->id());
+ });
+
+ connect(task, &AsynchronousTask::stateChanged, this, [this, task](AsynchronousTask::TaskState newState) {
+ emit taskStateChanged(task->id(), newState);
+ });
+
+ connect(task, &AsynchronousTask::progress, this, [this, task](qreal p) {
+ emit taskProgressChanged(task->id(), p);
+
+ Package *package = fromId(task->packageId());
+ if (package && (package->state() != Package::Installed)) {
+ package->setProgress(p);
+ // Icon will be in a "+" suffixed directory during installation. So notify about a change on its
+ // location as well.
+ emitDataChanged(package, QVector<int> { Icon, UpdateProgress });
+ }
+ });
+
+ connect(task, &AsynchronousTask::finished, this, [this, task]() {
+ task->setState(task->hasFailed() ? AsynchronousTask::Failed : AsynchronousTask::Finished);
+
+ if (task->hasFailed()) {
+ handleFailure(task);
+ } else {
+ qCDebug(LogInstaller) << "emit finished" << task->id();
+ emit taskFinished(task->id());
+ }
+
+ if (d->activeTask == task)
+ d->activeTask = nullptr;
+ d->installationTaskList.removeOne(task);
+
+ delete task;
+ triggerExecuteNextTask();
+ });
+
+ if (qobject_cast<InstallationTask *>(task)) {
+ connect(static_cast<InstallationTask *>(task), &InstallationTask::finishedPackageExtraction, this, [this, task]() {
+ qCDebug(LogInstaller) << "emit blockingUntilInstallationAcknowledge" << task->id();
+ emit taskBlockingUntilInstallationAcknowledge(task->id());
+
+ // we can now start the next download in parallel - the InstallationTask will take care
+ // of serializing the final installation steps on its own as soon as it gets the
+ // required acknowledge (or cancel).
+ if (d->activeTask == task)
+ d->activeTask = nullptr;
+ d->installationTaskList.append(task);
+ triggerExecuteNextTask();
+ });
+ }
+
+
+ d->activeTask = task;
+ task->setState(AsynchronousTask::Executing);
+ task->start();
+}
+
+void PackageManager::handleFailure(AsynchronousTask *task)
+{
+ qCDebug(LogInstaller) << "emit failed" << task->id() << task->errorCode() << task->errorString();
+ emit taskFailed(task->id(), int(task->errorCode()), task->errorString());
+}
+
+bool PackageManager::startingPackageInstallation(PackageInfo *info)
+{
+ // ownership of info is transferred to PackageManager
+ QScopedPointer<PackageInfo> newInfo(info);
+
+ if (!newInfo || newInfo->id().isEmpty())
+ return false;
+ Package *package = fromId(newInfo->id());
+// if (!RuntimeFactory::instance()->manager(newInfo->runtimeName()))
+// return false;
+
+ if (package) { // update
+ if (!package->block())
+ return false;
+
+ if (package->isBuiltIn()) {
+ // overlay the existing base info
+ // we will rollback to the base one if this update is removed.
+ package->setUpdatedInfo(newInfo.take());
+ } else {
+ // overwrite the existing base info
+ // we're not keeping track of the original. so removing the updated base version removes the
+ // application entirely.
+ package->setBaseInfo(newInfo.take());
+ }
+ package->setState(Package::BeingUpdated);
+ package->setProgress(0);
+ emitDataChanged(package);
+ } else { // installation
+ package = new Package(newInfo.take(), Package::BeingInstalled);
+
+ Q_ASSERT(package->block());
+
+ beginInsertRows(QModelIndex(), d->packages.count(), d->packages.count());
+
+ QQmlEngine::setObjectOwnership(package, QQmlEngine::CppOwnership);
+ d->packages << package;
+
+ endInsertRows();
+
+ emitDataChanged(package);
+
+ emit packageAdded(package->id());
+ }
+ return true;
+}
+
+bool PackageManager::startingPackageRemoval(const QString &id)
+{
+ Package *package = fromId(id);
+ if (!package)
+ return false;
+
+ if (package->isBlocked() || (package->state() != Package::Installed))
+ return false;
+
+ if (package->isBuiltIn() && !package->canBeRevertedToBuiltIn())
+ return false;
+
+ if (!package->block()) // this will implicitly stop all apps in this package (asynchronously)
+ return false;
+
+ package->setState(package->canBeRevertedToBuiltIn() ? Package::BeingDowngraded
+ : Package::BeingRemoved);
+
+ package->setProgress(0);
+ emitDataChanged(package, QVector<int> { IsUpdating });
+ return true;
+}
+
+bool PackageManager::finishedPackageInstall(const QString &id)
+{
+ Package *package = fromId(id);
+ if (!package)
+ return false;
+
+ switch (package->state()) {
+ case Package::Installed:
+ return false;
+
+ case Package::BeingInstalled:
+ case Package::BeingUpdated: {
+ // The Package object has been updated right at the start of the installation/update.
+ // Now's the time to update the InstallationReport that was written by the installer.
+ QFile irfile(QDir(package->info()->baseDir()).absoluteFilePath(qSL(".installation-report.yaml")));
+ QScopedPointer<InstallationReport> ir(new InstallationReport(package->id()));
+ if (!irfile.open(QFile::ReadOnly) || !ir->deserialize(&irfile)) {
+ qCCritical(LogInstaller) << "Could not read the new installation-report for package"
+ << package->id() << "at" << irfile.fileName();
+ return false;
+ }
+ package->info()->setInstallationReport(ir.take());
+ package->setState(Package::Installed);
+ package->setProgress(0);
+
+ emitDataChanged(package);
+
+ package->unblock();
+ emit package->bulkChange(); // not ideal, but icon and codeDir have changed
+ break;
+ }
+ case Package::BeingDowngraded:
+ package->setUpdatedInfo(nullptr);
+ package->setState(Package::Installed);
+ break;
+
+ case Package::BeingRemoved: {
+ int row = d->packages.indexOf(package);
+ if (row >= 0) {
+ emit packageAboutToBeRemoved(package->id());
+ beginRemoveRows(QModelIndex(), row, row);
+ d->packages.removeAt(row);
+ endRemoveRows();
+ }
+ delete package;
+ break;
+ }
+ }
+
+ //emit internalSignals.applicationsChanged();
+
+ return true;
+}
+
+bool PackageManager::canceledPackageInstall(const QString &id)
+{
+ Package *package = fromId(id);
+ if (!package)
+ return false;
+
+ switch (package->state()) {
+ case Package::Installed:
+ return false;
+
+ case Package::BeingInstalled: {
+ int row = d->packages.indexOf(package);
+ if (row >= 0) {
+ emit packageAboutToBeRemoved(package->id());
+ beginRemoveRows(QModelIndex(), row, row);
+ d->packages.removeAt(row);
+ endRemoveRows();
+ }
+ delete package;
+ break;
+ }
+ case Package::BeingUpdated:
+ case Package::BeingDowngraded:
+ case Package::BeingRemoved:
+ package->setState(Package::Installed);
+ package->setProgress(0);
+ emitDataChanged(package, QVector<int> { IsUpdating });
+
+ package->unblock();
+ break;
+ }
+ return true;
+}
+
+bool removeRecursiveHelper(const QString &path)
+{
+ if (PackageManager::instance()->isApplicationUserIdSeparationEnabled() && SudoClient::instance())
+ return SudoClient::instance()->removeRecursive(path);
+ else
+ return recursiveOperation(path, safeRemove);
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/packagemanager.h b/src/manager-lib/packagemanager.h
new file mode 100644
index 00000000..9d4419de
--- /dev/null
+++ b/src/manager-lib/packagemanager.h
@@ -0,0 +1,210 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QAbstractListModel>
+#include <QtAppManCommon/global.h>
+#include <QtAppManApplication/packageinfo.h>
+#include <QtAppManManager/asynchronoustask.h>
+#include <QtAppManManager/installationtask.h>
+#include <QtAppManManager/deinstallationtask.h>
+
+
+QT_FORWARD_DECLARE_CLASS(QQmlEngine)
+QT_FORWARD_DECLARE_CLASS(QJSEngine)
+
+QT_BEGIN_NAMESPACE_AM
+
+class PackageDatabase;
+class Package;
+class PackageManagerPrivate;
+
+class PackageManager : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_CLASSINFO("D-Bus Interface", "io.qt.PackageManager")
+ Q_CLASSINFO("AM-QmlType", "QtApplicationManager.SystemUI/PackageManager 2.0 SINGLETON")
+
+ // these are const on purpose - these should never change in a running system
+ Q_PROPERTY(bool allowInstallationOfUnsignedPackages READ allowInstallationOfUnsignedPackages CONSTANT)
+ Q_PROPERTY(bool developmentMode READ developmentMode CONSTANT)
+ Q_PROPERTY(QString hardwareId READ hardwareId CONSTANT)
+
+ Q_PROPERTY(QVariantMap installationLocation READ installationLocation CONSTANT)
+ Q_PROPERTY(QVariantMap documentLocation READ documentLocation CONSTANT)
+
+ Q_PROPERTY(bool applicationUserIdSeparation READ isApplicationUserIdSeparationEnabled)
+ Q_PROPERTY(uint commonApplicationGroupId READ commonApplicationGroupId)
+
+public:
+ enum CacheMode {
+ NoCache,
+ UseCache,
+ RecreateCache
+ };
+
+ ~PackageManager() override;
+ static PackageManager *createInstance(PackageDatabase *packageDatabase,
+ const QString &documentPath);
+ static PackageManager *instance();
+ static QObject *instanceForQml(QQmlEngine *qmlEngine, QJSEngine *);
+
+ QVector<Package *> packages() const;
+
+ Package *fromId(const QString &id) const;
+
+ // the item model part
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ int count() const;
+ Q_INVOKABLE QVariantMap get(int index) const;
+ Q_INVOKABLE Package *package(int index) const;
+ Q_INVOKABLE Package *package(const QString &id) const;
+ Q_INVOKABLE int indexOfPackage(const QString &id) const;
+
+ bool developmentMode() const;
+ void setDevelopmentMode(bool enable);
+ bool allowInstallationOfUnsignedPackages() const;
+ void setAllowInstallationOfUnsignedPackages(bool enable);
+ QString hardwareId() const;
+ void setHardwareId(const QString &hwId);
+// bool securityChecksEnabled() const;
+// void setSecurityChecksEnabled(bool enabled);
+
+ bool isApplicationUserIdSeparationEnabled() const;
+ uint commonApplicationGroupId() const;
+
+ bool enableApplicationUserIdSeparation(uint minUserId, uint maxUserId, uint commonGroupId);
+
+ void setCACertificates(const QList<QByteArray> &chainOfTrust);
+
+ void cleanupBrokenInstallations() Q_DECL_NOEXCEPT_EXPR(false);
+
+ QVariantMap installationLocation() const;
+ QVariantMap documentLocation() const;
+
+ // Q_SCRIPTABLEs are available via both QML and D-Bus
+ Q_SCRIPTABLE QStringList packageIds() const;
+ Q_SCRIPTABLE QVariantMap get(const QString &id) const;
+
+ Q_SCRIPTABLE qint64 installedPackageSize(const QString &packageId) const;
+ Q_SCRIPTABLE QVariantMap installedPackageExtraMetaData(const QString &packageId) const;
+ Q_SCRIPTABLE QVariantMap installedPackageExtraSignedMetaData(const QString &packageId) const;
+
+ // all QString return values are task-ids
+ QString startPackageInstallation(const QUrl &sourceUrl);
+ Q_SCRIPTABLE QString startPackageInstallation(const QString &sourceUrl);
+ Q_SCRIPTABLE void acknowledgePackageInstallation(const QString &taskId);
+ Q_SCRIPTABLE QString removePackage(const QString &id, bool keepDocuments, bool force = false);
+
+ Q_SCRIPTABLE AsynchronousTask::TaskState taskState(const QString &taskId) const;
+ Q_SCRIPTABLE QString taskPackageId(const QString &taskId) const;
+ Q_SCRIPTABLE QStringList activeTaskIds() const;
+ Q_SCRIPTABLE bool cancelTask(const QString &taskId);
+
+ // convenience function for app-store implementations
+ Q_SCRIPTABLE int compareVersions(const QString &version1, const QString &version2);
+ Q_SCRIPTABLE bool validateDnsName(const QString &name, int minimumParts = 1);
+
+
+signals:
+ Q_SCRIPTABLE void countChanged();
+
+ Q_SCRIPTABLE void packageAdded(const QString &id);
+ Q_SCRIPTABLE void packageAboutToBeRemoved(const QString &id);
+ Q_SCRIPTABLE void packageChanged(const QString &id, const QStringList &changedRoles);
+
+ Q_SCRIPTABLE void taskStarted(const QString &taskId);
+ Q_SCRIPTABLE void taskProgressChanged(const QString &taskId, qreal progress);
+ Q_SCRIPTABLE void taskFinished(const QString &taskId);
+ Q_SCRIPTABLE void taskFailed(const QString &taskId, int errorCode, const QString &errorString);
+ Q_SCRIPTABLE void taskStateChanged(const QString &taskId,
+ QT_PREPEND_NAMESPACE_AM(AsynchronousTask::TaskState) newState);
+
+ // installation only
+ Q_SCRIPTABLE void taskRequestingInstallationAcknowledge(const QString &taskId,
+ const QVariantMap &packageAsVariantMap,
+ const QVariantMap &packageExtraMetaData,
+ const QVariantMap &packageExtraSignedMetaData);
+ Q_SCRIPTABLE void taskBlockingUntilInstallationAcknowledge(const QString &taskId);
+
+private slots:
+ void executeNextTask();
+
+protected:
+ bool startingPackageInstallation(PackageInfo *info);
+ bool startingPackageRemoval(const QString &id);
+ bool finishedPackageInstall(const QString &id);
+ bool canceledPackageInstall(const QString &id);
+
+private:
+ void emitDataChanged(Package *package, const QVector<int> &roles = QVector<int>());
+ static void registerQmlTypes();
+
+ void triggerExecuteNextTask();
+ QString enqueueTask(AsynchronousTask *task);
+ void handleFailure(AsynchronousTask *task);
+
+ QList<QByteArray> caCertificates() const;
+
+private:
+ uint findUnusedUserId() const Q_DECL_NOEXCEPT_EXPR(false);
+
+ explicit PackageManager(PackageDatabase *packageDatabase,
+ const QString &documentPath);
+ PackageManager(const PackageManager &);
+ PackageManager &operator=(const PackageManager &);
+ static PackageManager *s_instance;
+ static QHash<int, QByteArray> s_roleNames;
+
+ PackageManagerPrivate *d;
+
+ friend class InstallationTask;
+ friend class DeinstallationTask;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/packagemanager_p.h b/src/manager-lib/packagemanager_p.h
new file mode 100644
index 00000000..8c916db2
--- /dev/null
+++ b/src/manager-lib/packagemanager_p.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QMutex>
+#include <QList>
+#include <QSet>
+#include <QScopedPointer>
+#include <QThread>
+
+#include <QtAppManManager/packagemanager.h>
+#include <QtAppManApplication/packagedatabase.h>
+#include <QtAppManManager/asynchronoustask.h>
+#include <QtAppManCommon/global.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+bool removeRecursiveHelper(const QString &path);
+
+class PackageManagerPrivate
+{
+public:
+ PackageDatabase *database = nullptr;
+ QVector<Package *> packages;
+
+ bool developmentMode = false;
+ bool allowInstallationOfUnsignedPackages = false;
+ bool userIdSeparation = false;
+ uint minUserId = uint(-1);
+ uint maxUserId = uint(-1);
+ uint commonGroupId = uint(-1);
+
+ QString installationPath;
+ QString documentPath;
+
+ QString error;
+
+ QString hardwareId;
+ QList<QByteArray> chainOfTrust;
+
+ QList<AsynchronousTask *> incomingTaskList; // incoming queue
+ QList<AsynchronousTask *> installationTaskList; // installation jobs in state >= AwaitingAcknowledge
+ AsynchronousTask *activeTask = nullptr; // currently active
+
+ QList<AsynchronousTask *> allTasks() const
+ {
+ QList<AsynchronousTask *> all = incomingTaskList;
+ if (!installationTaskList.isEmpty())
+ all += installationTaskList;
+ if (activeTask)
+ all += activeTask;
+ return all;
+ }
+};
+
+QT_END_NAMESPACE_AM
+// We mean it. Dummy comment since syncqt needs this also for completely private Qt modules.
diff --git a/src/manager-lib/scopeutilities.cpp b/src/manager-lib/scopeutilities.cpp
new file mode 100644
index 00000000..eff1d1bc
--- /dev/null
+++ b/src/manager-lib/scopeutilities.cpp
@@ -0,0 +1,215 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#include "logging.h"
+#include "scopeutilities.h"
+#include "packagemanager_p.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE_AM
+
+ScopedDirectoryCreator::ScopedDirectoryCreator()
+{ }
+
+bool ScopedDirectoryCreator::create(const QString &path, bool removeExisting)
+{
+ m_path = path;
+ QFileInfo fi(m_path);
+
+ if (fi.exists() && fi.isDir()) {
+ if (!removeExisting)
+ return m_created = true;
+ else if (!removeRecursiveHelper(m_path))
+ return false;
+ }
+ //qWarning() << "CREATE" << path << fi.absolutePath() << fi.fileName();
+ return m_created = QDir::root().mkpath(path);
+}
+
+bool ScopedDirectoryCreator::take()
+{
+ if (m_created && !m_taken)
+ m_taken = true;
+ return m_taken;
+}
+
+bool ScopedDirectoryCreator::destroy()
+{
+ if (!m_taken) {
+ if (m_created )
+ removeRecursiveHelper(m_path);
+ m_taken = true;
+ }
+ return m_taken;
+}
+
+ScopedDirectoryCreator::~ScopedDirectoryCreator()
+{
+ destroy();
+}
+
+QDir ScopedDirectoryCreator::dir()
+{
+ return QDir(m_path);
+}
+
+ScopedRenamer::ScopedRenamer()
+{ }
+
+bool ScopedRenamer::internalRename(const QDir &dir, const QString &from, const QString &to)
+{
+ QFileInfo fromInfo(dir.absoluteFilePath(from));
+ QFileInfo toInfo(dir.absoluteFilePath(to));
+
+ // POSIX cannot atomically rename directories, if the destination directory exists
+ // and is non-empty. We need to delete it first.
+ // Windows on the other hand cannot do anything atomically.
+#ifdef Q_OS_UNIX
+ if (fromInfo.isDir()) {
+#else
+ Q_UNUSED(fromInfo)
+
+ if (true) {
+#endif
+ if (toInfo.exists() && !recursiveOperation(toInfo.absoluteFilePath(), safeRemove))
+ return false;
+ }
+#ifdef Q_OS_UNIX
+ return (::rename(fromInfo.absoluteFilePath().toLocal8Bit(), toInfo.absoluteFilePath().toLocal8Bit()) == 0);
+#else
+ return QDir(dir).rename(from, to);
+#endif
+}
+
+bool ScopedRenamer::rename(const QString &baseName, ScopedRenamer::Modes modes)
+{
+ QFileInfo fi(baseName);
+ m_basePath.setPath(fi.absolutePath());
+ m_name = fi.fileName();
+ m_requested = modes;
+
+ // convenience
+ bool backupRequired = (modes & NameToNameMinus);
+ bool backupDone = false;
+
+ if (m_requested & NameToNameMinus) {
+ backupDone = internalRename(m_basePath, m_name, m_name + qL1C('-'));
+ if (backupDone)
+ m_done |= NameToNameMinus;
+ }
+ if (m_requested & NamePlusToName) {
+ // only try if no backup required, or it worked
+ if (!backupRequired || backupDone) {
+ if (internalRename(m_basePath, m_name + qL1C('+'), m_name)) {
+ m_done |= NamePlusToName;
+ }
+ else if (backupDone && !undoRename()) {
+ qCCritical(LogSystem) << QString::fromLatin1("failed to rename '%1+' to '%1', but also failed to rename backup '%1-' back to '%1' (in directory %2)")
+ .arg(m_name, m_basePath.absolutePath());
+ }
+ }
+ }
+ return m_requested == m_done;
+}
+
+bool ScopedRenamer::rename(const QDir &baseName, ScopedRenamer::Modes modes)
+{
+ return rename(baseName.absolutePath(), modes);
+}
+
+
+bool ScopedRenamer::take()
+{
+ if (!m_taken)
+ m_taken = true;
+ return m_taken;
+}
+
+bool ScopedRenamer::undoRename()
+{
+ if (!m_taken) {
+ if (interalUndoRename()) {
+ m_taken = true;
+ } else {
+ if (m_done & NamePlusToName) {
+ qCCritical(LogSystem) << QString::fromLatin1("failed to undo rename from '%1+' to '%1' (in directory %2)")
+ .arg(m_name, m_basePath.absolutePath());
+ }
+ if (m_done & NameToNameMinus) {
+ qCCritical(LogSystem) << QString::fromLatin1("failed to undo rename from '%1' to '%1-' (in directory %2)")
+ .arg(m_name, m_basePath.absolutePath());
+ }
+ }
+ }
+ return m_taken;
+}
+
+bool ScopedRenamer::interalUndoRename()
+{
+ if (m_done & NamePlusToName) {
+ if (internalRename(m_basePath, m_name, m_name + qL1C('+')))
+ m_done &= ~NamePlusToName;
+ }
+ if (m_done & NameToNameMinus) {
+ if (internalRename(m_basePath, m_name + qL1C('-'), m_name))
+ m_done &= ~NameToNameMinus;
+ }
+
+ return (m_done == 0);
+}
+
+ScopedRenamer::~ScopedRenamer()
+{
+ undoRename();
+}
+
+bool ScopedRenamer::isRenamed() const
+{
+ return m_requested && (m_requested == m_done);
+}
+
+QString ScopedRenamer::baseName() const
+{
+ return m_basePath.absoluteFilePath(m_name);
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/scopeutilities.h b/src/manager-lib/scopeutilities.h
new file mode 100644
index 00000000..f3d30424
--- /dev/null
+++ b/src/manager-lib/scopeutilities.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QString>
+#include <QDir>
+#include <QFile>
+
+#include <QtAppManCommon/global.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+class ScopedDirectoryCreator
+{
+public:
+ ScopedDirectoryCreator();
+ bool create(const QString &path, bool removeExisting = true);
+ bool take();
+ bool destroy();
+ ~ScopedDirectoryCreator();
+
+ QDir dir();
+
+private:
+ Q_DISABLE_COPY(ScopedDirectoryCreator)
+
+ QString m_path;
+ bool m_created = false;
+ bool m_taken = false;
+};
+
+class ScopedRenamer
+{
+public:
+ enum Mode {
+ NameToNameMinus = 1, // create backup : foo -> foo-
+ NamePlusToName = 2 // replace with new: foo+ -> foo
+ };
+
+ Q_DECLARE_FLAGS(Modes, Mode)
+
+ ScopedRenamer();
+ bool rename(const QString &baseName, ScopedRenamer::Modes modes);
+ bool rename(const QDir &baseName, ScopedRenamer::Modes modes);
+ bool take();
+ bool undoRename();
+ ~ScopedRenamer();
+
+ bool isRenamed() const;
+ QString baseName() const;
+
+private:
+ bool interalUndoRename();
+ static bool internalRename(const QDir &dir, const QString &from, const QString &to);
+ Q_DISABLE_COPY(ScopedRenamer)
+ QDir m_basePath;
+ QString m_name;
+ Modes m_requested;
+ Modes m_done;
+ bool m_taken = false;
+};
+
+QT_END_NAMESPACE_AM
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QT_PREPEND_NAMESPACE_AM(ScopedRenamer::Modes))
diff --git a/src/manager-lib/sudo.cpp b/src/manager-lib/sudo.cpp
new file mode 100644
index 00000000..a9d661fe
--- /dev/null
+++ b/src/manager-lib/sudo.cpp
@@ -0,0 +1,495 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+
+#include <QProcess>
+#include <QDir>
+#include <QFile>
+#include <QtEndian>
+#include <QDataStream>
+#include <qplatformdefs.h>
+#include <QDataStream>
+
+#include "logging.h"
+#include "sudo.h"
+#include "utilities.h"
+#include "exception.h"
+#include "global.h"
+
+#include <errno.h>
+
+#if defined(Q_OS_LINUX)
+# include "processtitle.h"
+
+# include <fcntl.h>
+# include <unistd.h>
+# include <sys/socket.h>
+# include <sys/errno.h>
+# include <sys/ioctl.h>
+# include <sys/stat.h>
+# include <sys/prctl.h>
+# include <linux/capability.h>
+
+// These two functions are implemented in glibc, but the header file is
+// in the separate libcap-dev package. Since we want to avoid unnecessary
+// dependencies, we just declare them here
+extern "C" int capset(cap_user_header_t header, cap_user_data_t data);
+extern "C" int capget(cap_user_header_t header, const cap_user_data_t data);
+
+// Support for old/broken C libraries
+# if defined(_LINUX_CAPABILITY_VERSION) && !defined(_LINUX_CAPABILITY_VERSION_1)
+# define _LINUX_CAPABILITY_VERSION_1 _LINUX_CAPABILITY_VERSION
+# define _LINUX_CAPABILITY_U32S_1 1
+# if !defined(CAP_TO_INDEX)
+# define CAP_TO_INDEX(x) ((x) >> 5)
+# endif
+# if !defined(CAP_TO_MASK)
+# define CAP_TO_MASK(x) (1 << ((x) & 31))
+# endif
+# endif
+# if defined(_LINUX_CAPABILITY_VERSION_3) // use 64-bit support, if available
+# define AM_CAP_VERSION _LINUX_CAPABILITY_VERSION_3
+# define AM_CAP_SIZE _LINUX_CAPABILITY_U32S_3
+# else // fallback to 32-bit support
+# define AM_CAP_VERSION _LINUX_CAPABILITY_VERSION_1
+# define AM_CAP_SIZE _LINUX_CAPABILITY_U32S_1
+# endif
+
+// Convenient way to ignore EINTR on any system call
+# define EINTR_LOOP(cmd) __extension__ ({__typeof__(cmd) res = 0; do { res = cmd; } while (res == -1 && errno == EINTR); res; })
+
+
+// Declared as weak symbol here, so we can check at runtime if we were compiled against libgcov
+extern "C" void __gcov_init() __attribute__((weak));
+
+
+QT_BEGIN_NAMESPACE_AM
+
+static void sigHupHandler(int sig)
+{
+ if (sig == SIGHUP)
+ _exit(0);
+}
+
+QT_END_NAMESPACE_AM
+
+#endif // Q_OS_LINUX
+
+
+QT_BEGIN_NAMESPACE_AM
+
+void Sudo::forkServer(DropPrivileges dropPrivileges, QStringList *warnings)
+{
+ bool canSudo = false;
+
+#if defined(Q_OS_LINUX)
+ uid_t realUid = getuid();
+ uid_t effectiveUid = geteuid();
+ canSudo = (realUid == 0) || (effectiveUid == 0);
+#else
+ Q_UNUSED(warnings)
+ Q_UNUSED(dropPrivileges)
+#endif
+
+ if (!canSudo) {
+ SudoServer::createInstance(-1);
+ SudoClient::createInstance(-1, SudoServer::instance());
+ if (warnings) {
+ *warnings << qSL("For the installer to work correctly, the executable needs to be run either as root via sudo or SUID (preferred)");
+ *warnings << qSL("(using fallback implementation - you might experience permission errors on installer operations)");
+ }
+ return;
+ }
+
+#if defined(Q_OS_LINUX)
+ gid_t realGid = getgid();
+ uid_t sudoUid = static_cast<uid_t>(qEnvironmentVariableIntValue("SUDO_UID"));
+
+ // run as normal user (e.g. 1000): uid == 1000 euid == 1000
+ // run with binary suid-root: uid == 1000 euid == 0
+ // run with sudo (no suid-root): uid == 0 euid == 0 $SUDO_UID == 1000
+
+ // treat sudo as special variant of a SUID executable
+ if (realUid == 0 && effectiveUid == 0 && sudoUid != 0) {
+ realUid = sudoUid;
+ realGid = static_cast<gid_t>(qEnvironmentVariableIntValue("SUDO_GID"));
+
+ if (setresgid(realGid, 0, 0) || setresuid(realUid, 0, 0))
+ throw Exception(errno, "Could not set real user or group ID");
+ }
+
+ int socketFds[2];
+ if (EINTR_LOOP(socketpair(AF_UNIX, SOCK_DGRAM, 0, socketFds)) != 0)
+ throw Exception(errno, "Could not create a pair of sockets");
+
+ // We need to make the gcda files generated by the root process writable by the normal user.
+ // There is no way to detect a compilation with -ftest-coverage, but we can check for gcov
+ // symbols at runtime. GCov will open all gcda files at fork() time, so we can get away with
+ // switching umasks around the fork() call.
+
+ mode_t realUmask = 0;
+ if (__gcov_init)
+ realUmask = umask(0);
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ throw Exception(errno, "Could not fork process");
+ } else if (pid == 0) {
+ // child
+ close(0);
+ setsid();
+
+ // reset umask
+ if (realUmask)
+ umask(realUmask);
+
+ // This call is Linux only, but it makes it so easy to detect a dying parent process.
+ // We would have a big problem otherwise, since the main process drops its privileges,
+ // which prevents it from sending SIGHUP to the child process, which still runs with
+ // root privileges.
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+ signal(SIGHUP, sigHupHandler);
+
+ // Drop as many capabilities as possible, just to be on the safe side
+ static const quint32 neededCapabilities[] = {
+ CAP_SYS_ADMIN,
+ CAP_CHOWN,
+ CAP_FOWNER,
+ CAP_DAC_OVERRIDE
+ };
+
+ bool capSetOk = false;
+ __user_cap_header_struct capHeader { AM_CAP_VERSION, getpid() };
+ __user_cap_data_struct capData[AM_CAP_SIZE];
+ if (capget(&capHeader, capData) == 0) {
+ quint32 capNeeded[AM_CAP_SIZE];
+ memset(&capNeeded, 0, sizeof(capNeeded));
+ for (quint32 cap : neededCapabilities) {
+ int idx = CAP_TO_INDEX(cap);
+ Q_ASSERT(idx < AM_CAP_SIZE);
+ capNeeded[idx] |= CAP_TO_MASK(cap);
+ }
+ for (int i = 0; i < AM_CAP_SIZE; ++i)
+ capData[i].effective = capData[i].permitted = capData[i].inheritable = capNeeded[i];
+ if (capset(&capHeader, capData) == 0)
+ capSetOk = true;
+ }
+ if (!capSetOk)
+ qCCritical(LogSystem) << "could not drop privileges in the SudoServer process -- continuing with full root privileges";
+
+ SudoServer::createInstance(socketFds[0]);
+ ProcessTitle::setTitle("%s", "sudo helper");
+ SudoServer::instance()->run();
+ }
+ // parent
+
+ // reset umask
+ if (realUmask)
+ umask(realUmask);
+
+ SudoClient::createInstance(socketFds[1]);
+
+ if (realUid != effectiveUid) {
+ // drop all root privileges
+ if (dropPrivileges == DropPrivilegesPermanently) {
+ if (setresgid(realGid, realGid, realGid) || setresuid(realUid, realUid, realUid)) {
+ kill(pid, SIGKILL);
+ throw Exception(errno, "Could not set real user or group ID");
+ }
+ } else {
+ qCCritical(LogSystem) << "\nSudo was instructed to NOT drop root privileges permanently.\nThis is dangerous and should only be used in auto-tests!\n";
+ if (setresgid(realGid, realGid, 0) || setresuid(realUid, realUid, 0)) {
+ kill(pid, 9);
+ throw Exception(errno, "Could not set real user or group ID");
+ }
+ }
+ }
+ ::atexit([]() { SudoClient::instance()->stopServer(); });
+#endif
+}
+
+SudoInterface::SudoInterface()
+{ }
+
+#ifdef Q_OS_LINUX
+bool SudoInterface::sendMessage(int socket, const QByteArray &msg, MessageType type, const QString &errorString)
+{
+ QByteArray packet;
+ QDataStream ds(&packet, QIODevice::WriteOnly);
+ ds << errorString << msg;
+ packet.prepend((type == Request) ? "RQST" : "RPLY");
+
+ auto bytesWritten = EINTR_LOOP(write(socket, packet.constData(), static_cast<size_t>(packet.size())));
+ return bytesWritten == packet.size();
+}
+
+
+QByteArray SudoInterface::receiveMessage(int socket, MessageType type, QString *errorString)
+{
+ const int headerSize = 4;
+ char recvBuffer[8*1024];
+ auto bytesReceived = EINTR_LOOP(recv(socket, recvBuffer, sizeof(recvBuffer), 0));
+
+ if ((bytesReceived < headerSize) || qstrncmp(recvBuffer, (type == Request ? "RQST" : "RPLY"), 4)) {
+ *errorString = qL1S("failed to receive command from the SudoClient process");
+ //qCCritical(LogSystem) << *errorString;
+ return QByteArray();
+ }
+
+ QByteArray packet(recvBuffer + headerSize, int(bytesReceived) - headerSize);
+
+ QDataStream ds(&packet, QIODevice::ReadOnly);
+ QByteArray msg;
+ ds >> *errorString >> msg;
+ return msg;
+}
+#endif // Q_OS_LINUX
+
+
+SudoClient *SudoClient::s_instance = nullptr;
+
+SudoClient *SudoClient::instance()
+{
+ return s_instance;
+}
+
+bool SudoClient::isFallbackImplementation() const
+{
+ return m_socket < 0;
+}
+
+SudoClient::SudoClient(int socketFd)
+ : m_socket(socketFd)
+{ }
+
+SudoClient *SudoClient::createInstance(int socketFd, SudoServer *shortCircuit)
+{
+ if (!s_instance) {
+ s_instance = new SudoClient(socketFd);
+ s_instance->m_shortCircuit = shortCircuit;
+ }
+ return s_instance;
+}
+
+
+// this is not nice, but it prevents a lot of copy/paste errors. (the C++ variadic template version
+// would be equally ugly, since it needs a friend declaration in the public header)
+template <typename R, typename C, typename ...Ps> R returnType(R (C::*)(Ps...));
+
+#define CALL(FUNC_NAME, PARAM) \
+ QByteArray msg; \
+ QDataStream(&msg, QIODevice::WriteOnly) << #FUNC_NAME << PARAM; \
+ QByteArray reply = call(msg); \
+ QDataStream result(&reply, QIODevice::ReadOnly); \
+ decltype(returnType(&SudoClient::FUNC_NAME)) r; \
+ result >> r; \
+ return r
+
+bool SudoClient::removeRecursive(const QString &fileOrDir)
+{
+ CALL(removeRecursive, fileOrDir);
+}
+
+bool SudoClient::setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions)
+{
+ CALL(setOwnerAndPermissionsRecursive, fileOrDir << user << group << permissions);
+}
+
+void SudoClient::stopServer()
+{
+#ifdef Q_OS_LINUX
+ if (!m_shortCircuit && m_socket >= 0) {
+ QByteArray msg;
+ QDataStream(&msg, QIODevice::WriteOnly) << "stopServer";
+ sendMessage(m_socket, msg, Request);
+ }
+#endif
+}
+
+QByteArray SudoClient::call(const QByteArray &msg)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (m_shortCircuit)
+ return m_shortCircuit->receive(msg);
+
+#ifdef Q_OS_LINUX
+ if (m_socket >= 0) {
+ if (sendMessage(m_socket, msg, Request))
+ return receiveMessage(m_socket, Reply, &m_errorString);
+ }
+#else
+ Q_UNUSED(m_socket)
+#endif
+
+ //qCCritical(LogSystem) << "failed to send command to the SudoServer process";
+ m_errorString = qL1S("failed to send command to the SudoServer process");
+ return QByteArray();
+}
+
+
+
+SudoServer *SudoServer::s_instance = nullptr;
+
+SudoServer *SudoServer::instance()
+{
+ return s_instance;
+}
+
+SudoServer::SudoServer(int socketFd)
+ : m_socket(socketFd)
+{ }
+
+SudoServer *SudoServer::createInstance(int socketFd)
+{
+ if (!s_instance)
+ s_instance = new SudoServer(socketFd);
+ return s_instance;
+}
+
+void SudoServer::run()
+{
+#ifdef Q_OS_LINUX
+ QString dummy;
+
+ forever {
+ QByteArray msg = receiveMessage(m_socket, Request, &dummy);
+ QByteArray reply = receive(msg);
+
+ if (m_stop)
+ exit(0);
+
+ sendMessage(m_socket, reply, Reply, m_errorString);
+ }
+#else
+ Q_UNUSED(m_socket)
+ Q_ASSERT(false);
+ exit(0);
+#endif
+}
+
+QByteArray SudoServer::receive(const QByteArray &msg)
+{
+ QDataStream params(msg);
+ char *functionArray;
+ params >> functionArray;
+ QByteArray function(functionArray);
+ delete [] functionArray;
+ QByteArray reply;
+ QDataStream result(&reply, QIODevice::WriteOnly);
+ m_errorString.clear();
+
+ if (function == "removeRecursive") {
+ QString fileOrDir;
+ params >> fileOrDir;
+ result << removeRecursive(fileOrDir);
+ } else if (function == "setOwnerAndPermissionsRecursive") {
+ QString fileOrDir;
+ uid_t user;
+ gid_t group;
+ mode_t permissions;
+ params >> fileOrDir >> user >> group >> permissions;
+ result << setOwnerAndPermissionsRecursive(fileOrDir, user, group, permissions);
+ } else if (function == "stopServer") {
+ m_stop = true;
+ } else {
+ reply.truncate(0);
+ m_errorString = QString::fromLatin1("unknown function '%1' called in SudoServer").arg(qL1S(function));
+ }
+ return reply;
+}
+
+bool SudoServer::removeRecursive(const QString &fileOrDir)
+{
+ try {
+ if (!recursiveOperation(fileOrDir, safeRemove))
+ throw Exception(errno, "could not recursively remove %1").arg(fileOrDir);
+ return true;
+ } catch (const Exception &e) {
+ m_errorString = e.errorString();
+ return false;
+ }
+}
+
+bool SudoServer::setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions)
+{
+#if defined(Q_OS_LINUX)
+ static auto setOwnerAndPermissions =
+ [user, group, permissions](const QString &path, RecursiveOperationType type) -> bool {
+ if (type == RecursiveOperationType::EnterDirectory)
+ return true;
+
+ const QByteArray localPath = path.toLocal8Bit();
+ mode_t mode = permissions;
+
+ if (type == RecursiveOperationType::LeaveDirectory) {
+ // set the x bit for directories, but only where it makes sense
+ if (mode & 06)
+ mode |= 01;
+ if (mode & 060)
+ mode |= 010;
+ if (mode & 0600)
+ mode |= 0100;
+ }
+
+ return ((chmod(localPath, mode) == 0) && (chown(localPath, user, group) == 0));
+ };
+
+ try {
+ if (!recursiveOperation(fileOrDir, setOwnerAndPermissions)) {
+ throw Exception(errno, "could not recursively set owner and permission on %1 to %2:%3 / %4")
+ .arg(fileOrDir).arg(user).arg(group).arg(permissions, 4, 8, QLatin1Char('0'));
+ }
+ } catch (const Exception &e) {
+ m_errorString = e.errorString();
+ return false;
+ }
+#else
+ Q_UNUSED(fileOrDir)
+ Q_UNUSED(user)
+ Q_UNUSED(group)
+ Q_UNUSED(permissions)
+#endif // Q_OS_LINUX
+ return false;
+}
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager-lib/sudo.h b/src/manager-lib/sudo.h
new file mode 100644
index 00000000..357261e6
--- /dev/null
+++ b/src/manager-lib/sudo.h
@@ -0,0 +1,154 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Luxoft Sweden AB
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Luxoft Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QString>
+#include <QByteArray>
+#include <QMutex>
+#include <qplatformdefs.h>
+
+#ifdef Q_OS_UNIX
+# include <sys/types.h>
+#else
+typedef uint uid_t;
+typedef uint gid_t;
+//typedef uint mode_t; // already typedef'ed in qplatformdefs.h
+#endif
+
+#include <QtAppManCommon/global.h>
+
+QT_BEGIN_NAMESPACE_AM
+
+class Sudo
+{
+public:
+ enum DropPrivileges {
+ DropPrivilegesPermanently,
+ DropPrivilegesRegainable, // only use this for auto-tests
+ };
+
+ static void forkServer(DropPrivileges dropPrivileges, QStringList *warnings = nullptr) Q_DECL_NOEXCEPT_EXPR(false);
+};
+
+class SudoInterface
+{
+public:
+ virtual ~SudoInterface() = default;
+
+ virtual bool removeRecursive(const QString &fileOrDir) = 0;
+ virtual bool setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions) = 0;
+
+protected:
+ enum MessageType { Request, Reply };
+
+#ifdef Q_OS_LINUX
+ QByteArray receiveMessage(int socket, MessageType type, QString *errorString);
+ bool sendMessage(int socket, const QByteArray &msg, MessageType type, const QString &errorString = QString());
+#endif
+ QByteArray receive(const QByteArray &packet);
+
+protected:
+ SudoInterface();
+private:
+ Q_DISABLE_COPY(SudoInterface)
+};
+
+class SudoServer;
+
+class SudoClient : public SudoInterface
+{
+public:
+ static SudoClient *createInstance(int socketFd, SudoServer *shortCircuit = 0);
+
+ static SudoClient *instance();
+
+ bool isFallbackImplementation() const;
+
+ bool removeRecursive(const QString &fileOrDir) override;
+ bool setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions) override;
+
+ void stopServer();
+
+ QString lastError() const { return m_errorString; }
+
+private:
+ SudoClient(int socketFd);
+
+ QByteArray call(const QByteArray &msg);
+
+ int m_socket;
+ QString m_errorString;
+ QMutex m_mutex;
+ SudoServer *m_shortCircuit;
+
+ static SudoClient *s_instance;
+};
+
+class SudoServer : public SudoInterface
+{
+public:
+ static SudoServer *createInstance(int socketFd);
+
+ static SudoServer *instance();
+
+ bool removeRecursive(const QString &fileOrDir) override;
+ bool setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions) override;
+
+ QString lastError() const { return m_errorString; }
+
+ Q_NORETURN void run();
+
+private:
+ SudoServer(int socketFd);
+
+ QByteArray receive(const QByteArray &msg);
+ friend class SudoClient;
+
+ int m_socket;
+ QString m_errorString;
+ bool m_stop = false;
+
+ static SudoServer *s_instance;
+};
+
+QT_END_NAMESPACE_AM