diff options
author | Robert Griebl <robert.griebl@qt.io> | 2022-06-29 16:26:21 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-06-30 20:52:55 +0000 |
commit | c344046075fa3359dd4c70e92253f2edc5368579 (patch) | |
tree | 03dc30a86803edfcb895268fbb6f2da74cd3d0c5 | |
parent | 9a41317657da25661c96da1a65d3bff34200a07f (diff) | |
download | qtapplicationmanager-c344046075fa3359dd4c70e92253f2edc5368579.tar.gz |
Add support for managing multiple instances from appman-controller
Added an optional instance-id, which can be set via command line option
or via am-config.yaml in the appman process.
appman-controller also gained a new option --instance-id to address
the given instance, instead of the default, unnamed one.
Change-Id: I582d0ea69ed0697ee9ac7353725f93c50df05e34
Fixes: AUTOSUITE-1678
Reviewed-by: Dominik Holland <dominik.holland@qt.io>
(cherry picked from commit 8f4fbe0665f7e83c89364e44711f01c4408ff59f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | doc/configuration.qdoc | 8 | ||||
-rw-r--r-- | doc/controller.qdoc | 4 | ||||
-rw-r--r-- | src/application-lib/packageinfo.cpp | 28 | ||||
-rw-r--r-- | src/common-lib/utilities.cpp | 29 | ||||
-rw-r--r-- | src/common-lib/utilities.h | 3 | ||||
-rw-r--r-- | src/main-lib/configuration.cpp | 34 | ||||
-rw-r--r-- | src/main-lib/configuration.h | 1 | ||||
-rw-r--r-- | src/main-lib/configuration_p.h | 1 | ||||
-rw-r--r-- | src/main-lib/main.cpp | 19 | ||||
-rw-r--r-- | src/main-lib/main.h | 5 | ||||
-rw-r--r-- | src/tools/controller/controller.cpp | 49 |
11 files changed, 141 insertions, 40 deletions
diff --git a/doc/configuration.qdoc b/doc/configuration.qdoc index 8d165314..a58aa960 100644 --- a/doc/configuration.qdoc +++ b/doc/configuration.qdoc @@ -136,6 +136,14 @@ ui: configuration files, but before more specific, direct options such as \c --fullscreen (which can be rewritten as \c{ -o 'ui: { fullscreen: no }'}). \row + \target instance-id + \li \b --instance-id + \br [\c instanceId] + \li string + \li Assign an unique name to this application manager instance. Only useful if you are + running multiple instances at the same time and you need to address them via the + \l{Controller}{appman-controller tool}. + \row \li \b --database \br [\c applications/database] \li string diff --git a/doc/controller.qdoc b/doc/controller.qdoc index 326d8bca..7986e1dc 100644 --- a/doc/controller.qdoc +++ b/doc/controller.qdoc @@ -16,6 +16,10 @@ communicating directly with its D-Bus interface. \note In order to use this tool, the application manager has to be connected to either a session- or system-bus; don't run it with \c{--dbus none}. +If you are running multiple application manager instances in the same system, you need to first +\l{instance-id}{assign unique instance-ids} to each of them and then you can address them +individually from the \c appman-controller tool by using the \c instance-id command line option. + The following commands are available: \table diff --git a/src/application-lib/packageinfo.cpp b/src/application-lib/packageinfo.cpp index c318273c..7a113ddc 100644 --- a/src/application-lib/packageinfo.cpp +++ b/src/application-lib/packageinfo.cpp @@ -9,6 +9,7 @@ #include "applicationinfo.h" #include "intentinfo.h" #include "exception.h" +#include "utilities.h" #include "installationreport.h" #include "yamlpackagescanner.h" @@ -212,33 +213,8 @@ PackageInfo *PackageInfo::readFromDataStream(QDataStream &ds) bool PackageInfo::isValidApplicationId(const QString &appId, QString *errorString) { - // we need to make sure that we can use the name as directory in a filesystem and inode names - // are limited to 255 characters in Linux. We need to subtract a safety margin for prefixes - // or suffixes though: - static const int maxLength = 150; - try { - if (appId.isEmpty()) - throw Exception(Error::Parse, "must not be empty"); - - if (appId.length() > maxLength) - throw Exception(Error::Parse, "the maximum length is %1 characters (found %2 characters)").arg(maxLength, appId.length()); - - // all characters need to be ASCII minus any filesystem special characters: - bool spaceOnly = true; - static const char forbiddenChars[] = "<>:\"/\\|?*"; - for (int pos = 0; pos < appId.length(); ++pos) { - ushort ch = appId.at(pos).unicode(); - if ((ch < 0x20) || (ch > 0x7f) || strchr(forbiddenChars, ch & 0xff)) { - throw Exception(Error::Parse, "must consist of printable ASCII characters only, except any of \'%1'") - .arg(QString::fromLatin1(forbiddenChars)); - } - if (spaceOnly) - spaceOnly = QChar(ch).isSpace(); - } - if (spaceOnly) - throw Exception(Error::Parse, "must not consist of only white-space characters"); - + validateIdForFilesystemUsage(appId); return true; } catch (const Exception &e) { if (errorString) diff --git a/src/common-lib/utilities.cpp b/src/common-lib/utilities.cpp index 75b03487..6501a45a 100644 --- a/src/common-lib/utilities.cpp +++ b/src/common-lib/utilities.cpp @@ -318,4 +318,33 @@ void closeAndClearFileDescriptors(QVector<int> &fdList) fdList.clear(); } +void validateIdForFilesystemUsage(const QString &id) Q_DECL_NOEXCEPT_EXPR(false) +{ + // we need to make sure that we can use the name as directory in a filesystem and inode names + // are limited to 255 characters in Linux. We need to subtract a safety margin for prefixes + // or suffixes though: + static const int maxLength = 150; + + if (id.isEmpty()) + throw Exception(Error::Parse, "must not be empty"); + + if (id.length() > maxLength) + throw Exception(Error::Parse, "the maximum length is %1 characters (found %2 characters)").arg(maxLength, id.length()); + + // all characters need to be ASCII minus any filesystem special characters: + bool spaceOnly = true; + static const char forbiddenChars[] = "<>:\"/\\|?*"; + for (int pos = 0; pos < id.length(); ++pos) { + ushort ch = id.at(pos).unicode(); + if ((ch < 0x20) || (ch > 0x7f) || strchr(forbiddenChars, ch & 0xff)) { + throw Exception(Error::Parse, "must consist of printable ASCII characters only, except any of \'%1'") + .arg(QString::fromLatin1(forbiddenChars)); + } + if (spaceOnly) + spaceOnly = QChar(ch).isSpace(); + } + if (spaceOnly) + throw Exception(Error::Parse, "must not consist of only white-space characters"); +} + QT_END_NAMESPACE_AM diff --git a/src/common-lib/utilities.h b/src/common-lib/utilities.h index 18960653..c871f863 100644 --- a/src/common-lib/utilities.h +++ b/src/common-lib/utilities.h @@ -123,4 +123,7 @@ template <typename T> T *qt6_v_cast(QVariant::Private *vp) // close all valid file descriptors and then clear the (non-const) vector void closeAndClearFileDescriptors(QVector<int> &fdList); +// make sure that the given id can be used as a filename +void validateIdForFilesystemUsage(const QString &id) Q_DECL_NOEXCEPT_EXPR(false); + QT_END_NAMESPACE_AM diff --git a/src/main-lib/configuration.cpp b/src/main-lib/configuration.cpp index 8ec0b761..4ea2553c 100644 --- a/src/main-lib/configuration.cpp +++ b/src/main-lib/configuration.cpp @@ -177,6 +177,7 @@ Configuration::Configuration(const QStringList &defaultConfigFilePaths, m_clp.addOption({ qSL("logging-rule"), qSL("Adds a standard Qt logging rule."), qSL("rule") }); m_clp.addOption({ qSL("qml-debug"), qSL("Enables QML debugging and profiling.") }); m_clp.addOption({ qSL("enable-touch-emulation"), qSL("Deprecated (ignored).") }); + m_clp.addOption({ qSL("instance-id"), qSL("Use this id to distinguish between multiple instances."), qSL("id") }); } QVariant Configuration::buildConfig() const @@ -283,6 +284,16 @@ void Configuration::parseWithArguments(const QStringList &arguments) } } + if (m_clp.isSet(qSL("instance-id"))) { + auto id = m_clp.value(qSL("instance-id")); + try { + validateIdForFilesystemUsage(id); + } catch (const Exception &e) { + showParserMessage(qSL("Invalid instance-id (%1): %2\n").arg(id, e.errorString()), ErrorMessage); + exit(1); + } + } + #if defined(AM_TIME_CONFIG_PARSING) QElapsedTimer timer; timer.start(); @@ -371,7 +382,7 @@ void Configuration::parseWithArguments(const QStringList &arguments) } -const quint32 ConfigurationData::DataStreamVersion = 8; +const quint32 ConfigurationData::DataStreamVersion = 9; ConfigurationData *ConfigurationData::loadFromCache(QDataStream &ds) @@ -433,7 +444,8 @@ ConfigurationData *ConfigurationData::loadFromCache(QDataStream &ds) >> cd->wayland.socketName >> cd->wayland.extraSockets >> cd->flags.allowUnsignedPackages - >> cd->flags.allowUnknownUiClients; + >> cd->flags.allowUnknownUiClients + >> cd->instanceId; return cd; } @@ -496,7 +508,8 @@ void ConfigurationData::saveToCache(QDataStream &ds) const << wayland.socketName << wayland.extraSockets << flags.allowUnsignedPackages - << flags.allowUnknownUiClients; + << flags.allowUnknownUiClients + << instanceId; } template <typename T> void mergeField(T &into, const T &from, const T &def) @@ -597,6 +610,7 @@ void ConfigurationData::mergeFrom(const ConfigurationData *from) MERGE_FIELD(wayland.extraSockets); MERGE_FIELD(flags.allowUnsignedPackages); MERGE_FIELD(flags.allowUnknownUiClients); + MERGE_FIELD(instanceId); } QByteArray ConfigurationData::substituteVars(const QByteArray &sourceContent, const QString &fileName) @@ -655,6 +669,15 @@ ConfigurationData *ConfigurationData::loadFromSource(QIODevice *source, const QS auto cd = std::make_unique<ConfigurationData>(); YamlParser::Fields fields = { + { "instanceId", false, YamlParser::Scalar, [&cd](YamlParser *p) { + auto id = p->parseString(); + try { + validateIdForFilesystemUsage(id); + cd->instanceId = id; + } catch (const Exception &e) { + throw YamlParserException(p, "invalid instanaceId: %1").arg(e.errorString()); + } + } }, { "runtimes", false, YamlParser::Map, [&cd](YamlParser *p) { cd->runtimes.configurations = p->parseMap(); } }, { "containers", false, YamlParser::Map, [&cd](YamlParser *p) { @@ -888,6 +911,11 @@ ConfigurationData *ConfigurationData::loadFromSource(QIODevice *source, const QS } } +QString Configuration::instanceId() const +{ + return value<QString>("instance-id", m_data->instanceId); +} + QString Configuration::mainQmlFile() const { if (!m_clp.positionalArguments().isEmpty()) diff --git a/src/main-lib/configuration.h b/src/main-lib/configuration.h index 12594c8e..1acd9b6c 100644 --- a/src/main-lib/configuration.h +++ b/src/main-lib/configuration.h @@ -29,6 +29,7 @@ public: virtual void parseWithArguments(const QStringList &arguments); QVariant buildConfig() const; + QString instanceId() const; QString mainQmlFile() const; bool noCache() const; diff --git a/src/main-lib/configuration_p.h b/src/main-lib/configuration_p.h index 77dd19a3..6e2de47f 100644 --- a/src/main-lib/configuration_p.h +++ b/src/main-lib/configuration_p.h @@ -29,6 +29,7 @@ struct ConfigurationData void saveToCache(QDataStream &ds) const; void mergeFrom(const ConfigurationData *from); + QString instanceId; struct Runtimes { QVariantMap configurations; diff --git a/src/main-lib/main.cpp b/src/main-lib/main.cpp index baacbd8d..437753b6 100644 --- a/src/main-lib/main.cpp +++ b/src/main-lib/main.cpp @@ -221,7 +221,8 @@ void Main::setup(const Configuration *cfg) Q_DECL_NOEXCEPT_EXPR(false) cfg->noUiWatchdog(), cfg->allowUnknownUiClients()); setupDBus(std::bind(&Configuration::dbusRegistration, cfg, std::placeholders::_1), - std::bind(&Configuration::dbusPolicy, cfg, std::placeholders::_1)); + std::bind(&Configuration::dbusPolicy, cfg, std::placeholders::_1), + cfg->instanceId()); } bool Main::isSingleProcessMode() const @@ -820,7 +821,9 @@ void Main::showWindow(bool showFullscreen) #if defined(QT_DBUS_LIB) && !defined(AM_DISABLE_EXTERNAL_DBUS_INTERFACES) -void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, QString dbusName, const char *serviceName, const char *interfaceName, const char *path) Q_DECL_NOEXCEPT_EXPR(false) +void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, QString dbusName, const char *serviceName, + const char *interfaceName, const char *path, + const QString &instanceId) Q_DECL_NOEXCEPT_EXPR(false) { QString dbusAddress; QDBusConnection conn((QString())); @@ -878,7 +881,11 @@ void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, QString dbusName, c // Write the bus address of the interface to a file in /tmp. This is needed for the // controller tool, which does not even have a session bus, when started via ssh. - QFile f(QDir::temp().absoluteFilePath(qL1S(interfaceName) + qSL(".dbus"))); + QString fileName = qL1S(interfaceName) % qSL(".dbus"); + if (!instanceId.isEmpty()) + fileName = instanceId % u'-' % fileName; + + QFile f(QDir::temp().absoluteFilePath(fileName)); QByteArray dbusUtf8 = dbusAddress.isEmpty() ? dbusName.toUtf8() : dbusAddress.toUtf8(); if (!f.open(QFile::WriteOnly | QFile::Truncate) || (f.write(dbusUtf8) != dbusUtf8.size())) throw Exception(f, "Could not write D-Bus address of interface %1").arg(qL1S(interfaceName)); @@ -894,7 +901,8 @@ void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, QString dbusName, c void Main::setupDBus(const std::function<QString(const char *)> &busForInterface, - const std::function<QVariantMap(const char *)> &policyForInterface) + const std::function<QVariantMap(const char *)> &policyForInterface, + const QString &instanceId) { #if defined(QT_DBUS_LIB) && !defined(AM_DISABLE_EXTERNAL_DBUS_INTERFACES) registerDBusTypes(); @@ -970,7 +978,8 @@ void Main::setupDBus(const std::function<QString(const char *)> &busForInterface continue; registerDBusObject(dbusAdaptor->generatedAdaptor(), dbusName, - std::get<2>(iface),interfaceName, std::get<3>(iface)); + std::get<2>(iface),interfaceName, std::get<3>(iface), + instanceId); if (!DBusPolicy::add(dbusAdaptor->generatedAdaptor(), policyForInterface(interfaceName))) throw Exception(Error::DBus, "could not set DBus policy for %1").arg(qL1S(interfaceName)); } diff --git a/src/main-lib/main.h b/src/main-lib/main.h index d054bc35..b6f1459e 100644 --- a/src/main-lib/main.h +++ b/src/main-lib/main.h @@ -72,7 +72,7 @@ protected: void loadStartupPlugins(const QStringList &startupPluginPaths) Q_DECL_NOEXCEPT_EXPR(false); void parseSystemProperties(const QVariantMap &rawSystemProperties); void setupDBus(const std::function<QString(const char *)> &busForInterface, - const std::function<QVariantMap(const char *)> &policyForInterface); + const std::function<QVariantMap(const char *)> &policyForInterface, const QString &instanceId); void setMainQmlFile(const QString &mainQml) Q_DECL_NOEXCEPT_EXPR(false); void setupSingleOrMultiProcess(bool forceSingleProcess, bool forceMultiProcess) Q_DECL_NOEXCEPT_EXPR(false); void setupRuntimesAndContainers(const QVariantMap &runtimeConfigurations, const QVariantMap &openGLConfiguration, @@ -103,7 +103,8 @@ protected: private: #if defined(QT_DBUS_LIB) && !defined(AM_DISABLE_EXTERNAL_DBUS_INTERFACES) void registerDBusObject(QDBusAbstractAdaptor *adaptor, QString dbusName, const char *serviceName, - const char *interfaceName, const char *path) Q_DECL_NOEXCEPT_EXPR(false); + const char *interfaceName, const char *path, + const QString &instanceId) Q_DECL_NOEXCEPT_EXPR(false); #endif static int &preConstructor(int &argc); diff --git a/src/tools/controller/controller.cpp b/src/tools/controller/controller.cpp index 2716df3e..2fade30d 100644 --- a/src/tools/controller/controller.cpp +++ b/src/tools/controller/controller.cpp @@ -14,6 +14,7 @@ #include <QDBusPendingReply> #include <QDBusError> #include <QMetaObject> +#include <QStringBuilder> #include <functional> @@ -39,6 +40,13 @@ public: registerDBusTypes(); } + void setInstanceId(const QString &instanceId) + { + m_instanceId = instanceId; + if (!m_instanceId.isEmpty()) + m_instanceId.append(u'-'); + } + void connectToManager() Q_DECL_NOEXCEPT_EXPR(false) { if (m_manager) @@ -62,7 +70,7 @@ private: { QDBusConnection conn(iface); - QFile f(QDir::temp().absoluteFilePath(QString(qSL("%1.dbus")).arg(iface))); + QFile f(QDir::temp().absoluteFilePath(m_instanceId % QString(qSL("%1.dbus")).arg(iface))); QString dbus; if (f.open(QFile::ReadOnly)) { dbus = QString::fromUtf8(f.readAll()); @@ -100,6 +108,7 @@ public: private: IoQtPackageManagerInterface *m_packager = nullptr; IoQtApplicationManagerInterface *m_manager = nullptr; + QString m_instanceId; }; static class DBus dbus; @@ -120,7 +129,8 @@ enum Command { ListInstallationTasks, CancelInstallationTask, ListInstallationLocations, - ShowInstallationLocation + ShowInstallationLocation, + ListInstances, }; // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below! @@ -142,7 +152,8 @@ static struct { { ListInstallationTasks, "list-installation-tasks", "List all active installation tasks." }, { CancelInstallationTask, "cancel-installation-task", "Cancel an active installation task." }, { ListInstallationLocations, "list-installation-locations", "List all installaton locations." }, - { ShowInstallationLocation, "show-installation-location", "Show details for installation location." } + { ShowInstallationLocation, "show-installation-location", "Show details for installation location." }, + { ListInstances, "list-instances", "List all named application manager instances." }, }; static Command command(QCommandLineParser &clp) @@ -176,6 +187,7 @@ static void listInstallationTasks() Q_DECL_NOEXCEPT_EXPR(false); static void cancelInstallationTask(bool all, const QString &taskId) Q_DECL_NOEXCEPT_EXPR(false); static void listInstallationLocations() Q_DECL_NOEXCEPT_EXPR(false); static void showInstallationLocation(bool asJson = false) Q_DECL_NOEXCEPT_EXPR(false); +static void listInstances() Q_DECL_NOEXCEPT_EXPR(false); class ThrowingApplication : public QCoreApplication // clazy:exclude=missing-qobject-macro { @@ -232,10 +244,11 @@ int main(int argc, char *argv[]) } desc += "\nMore information about each command can be obtained by running\n" \ - " appman-controller <command> --help"; + " appman-controller <command> --help"; QCommandLineParser clp; + clp.addOption({ { qSL("instance-id") }, qSL("Connect to the named instance."), qSL("instance-id") }); clp.addHelpOption(); clp.addVersionOption(); @@ -250,6 +263,8 @@ int main(int argc, char *argv[]) clp.parse(QCoreApplication::arguments()); clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); + dbus.setInstanceId(clp.value(qSL("instance-id"))); + // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below! try { switch (command(clp)) { @@ -450,6 +465,11 @@ int main(int argc, char *argv[]) a.runLater(std::bind(showInstallationLocation, clp.isSet(qSL("json")))); break; + + case ListInstances: + clp.process(a); + a.runLater(listInstances); + break; } int result = a.exec(); @@ -871,3 +891,24 @@ void showInstallationLocation(bool asJson) Q_DECL_NOEXCEPT_EXPR(false) : QtYaml::yamlFromVariantDocuments({ installationLocation }).constData()); qApp->quit(); } + +void listInstances() +{ + QString dir = QDir::temp().absolutePath() % u"/"; + QString suffix = qSL("io.qt.ApplicationManager.dbus"); + + QDirIterator dit(dir, { u"*" % suffix }); + while (dit.hasNext()) { + QByteArray name = dit.next().toLocal8Bit(); + name.chop(suffix.length()); + name = name.mid(dir.length()); + if (name.isEmpty()) { + name = "(no instance id)"; + } else { + name.chop(1); // remove the '-' separator + name = '"' % name % '"'; + } + fprintf(stdout, "%s\n", name.constData()); + } + qApp->quit(); +} |