diff options
author | Robert Griebl <robert.griebl@qt.io> | 2022-06-29 16:26:21 +0200 |
---|---|---|
committer | Robert Griebl <robert.griebl@qt.io> | 2022-06-30 14:27:17 +0000 |
commit | 426cc90cdc6f9444025e6089474031141e808d74 (patch) | |
tree | 69da9756d6644cc1033114baa90933000c6bff43 | |
parent | d4153704a38e81d4ceccd4d2ca5b35c4601ec3fd (diff) | |
download | qtapplicationmanager-426cc90cdc6f9444025e6089474031141e808d74.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)
-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 | 33 | ||||
-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, 140 insertions, 40 deletions
diff --git a/doc/configuration.qdoc b/doc/configuration.qdoc index 86b71262..d968652e 100644 --- a/doc/configuration.qdoc +++ b/doc/configuration.qdoc @@ -148,6 +148,14 @@ or across multiple config files, the final value is resolved based on these rule 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 61070891..e64cf2ce 100644 --- a/doc/controller.qdoc +++ b/doc/controller.qdoc @@ -40,6 +40,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 05946096..b3cf20db 100644 --- a/src/application-lib/packageinfo.cpp +++ b/src/application-lib/packageinfo.cpp @@ -47,6 +47,7 @@ #include "applicationinfo.h" #include "intentinfo.h" #include "exception.h" +#include "utilities.h" #include "installationreport.h" #include "yamlpackagescanner.h" @@ -259,33 +260,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 ccc8ae56..78d22ad0 100644 --- a/src/common-lib/utilities.cpp +++ b/src/common-lib/utilities.cpp @@ -301,4 +301,33 @@ void recursiveMergeVariantMap(QVariantMap &into, const QVariantMap &from) recursiveMergeMap(&into, from); } +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 fa99f90f..09a59d2f 100644 --- a/src/common-lib/utilities.h +++ b/src/common-lib/utilities.h @@ -154,4 +154,7 @@ inline bool loadResource(const QString &resource) : (QResource::registerResource(resource) || QLibrary(QDir().absoluteFilePath(resource)).load()); } +// 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 3fb6988b..63fa4f6b 100644 --- a/src/main-lib/configuration.cpp +++ b/src/main-lib/configuration.cpp @@ -207,6 +207,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("Enables the touch emulation, converting mouse to touch events.") }); + m_clp.addOption({ qSL("instance-id"), qSL("Use this id to distinguish between multiple instances."), qSL("id") }); } QVariant Configuration::buildConfig() const @@ -313,6 +314,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(); @@ -401,7 +412,7 @@ void Configuration::parseWithArguments(const QStringList &arguments) } -const quint32 ConfigurationData::DataStreamVersion = 7; +const quint32 ConfigurationData::DataStreamVersion = 9; ConfigurationData *ConfigurationData::loadFromCache(QDataStream &ds) @@ -464,7 +475,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; } @@ -528,7 +540,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) @@ -630,6 +643,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) @@ -688,6 +702,15 @@ ConfigurationData *ConfigurationData::loadFromSource(QIODevice *source, const QS QScopedPointer<ConfigurationData> cd(new 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) { @@ -960,6 +983,10 @@ static QString replaceEnvVars(QString string) return string; } +QString Configuration::instanceId() const +{ + return value<QString>("instance-id", m_data->instanceId); +} QString Configuration::mainQmlFile() const { diff --git a/src/main-lib/configuration.h b/src/main-lib/configuration.h index 3bdc1b48..5b622292 100644 --- a/src/main-lib/configuration.h +++ b/src/main-lib/configuration.h @@ -67,6 +67,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 08295589..e35bbe54 100644 --- a/src/main-lib/configuration_p.h +++ b/src/main-lib/configuration_p.h @@ -67,6 +67,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 500cc3f3..481bfb2c 100644 --- a/src/main-lib/main.cpp +++ b/src/main-lib/main.cpp @@ -289,7 +289,8 @@ void Main::setup(const Configuration *cfg) Q_DECL_NOEXCEPT_EXPR(false) setupSSDPService(); 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 @@ -1011,7 +1012,9 @@ void Main::setupSSDPService() Q_DECL_NOEXCEPT_EXPR(false) #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())); @@ -1069,7 +1072,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)); @@ -1085,7 +1092,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(); @@ -1163,7 +1171,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 e2d9f798..197f1bb9 100644 --- a/src/main-lib/main.h +++ b/src/main-lib/main.h @@ -112,7 +112,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, @@ -147,7 +147,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 cc330013..70cfe723 100644 --- a/src/tools/controller/controller.cpp +++ b/src/tools/controller/controller.cpp @@ -38,6 +38,7 @@ #include <QDBusPendingReply> #include <QDBusError> #include <QMetaObject> +#include <QStringBuilder> #include <functional> @@ -63,6 +64,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) @@ -86,7 +94,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()); @@ -124,6 +132,7 @@ public: private: IoQtPackageManagerInterface *m_packager = nullptr; IoQtApplicationManagerInterface *m_manager = nullptr; + QString m_instanceId; }; static class DBus dbus; @@ -144,7 +153,8 @@ enum Command { ListInstallationTasks, CancelInstallationTask, ListInstallationLocations, - ShowInstallationLocation + ShowInstallationLocation, + ListInstances, }; // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below! @@ -166,7 +176,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) @@ -200,6 +211,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 { @@ -256,10 +268,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(); @@ -274,6 +287,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)) { @@ -474,6 +489,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(); @@ -895,3 +915,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(); +} |