summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Griebl <robert.griebl@qt.io>2022-06-29 16:26:21 +0200
committerRobert Griebl <robert.griebl@qt.io>2022-06-30 14:27:17 +0000
commit426cc90cdc6f9444025e6089474031141e808d74 (patch)
tree69da9756d6644cc1033114baa90933000c6bff43
parentd4153704a38e81d4ceccd4d2ca5b35c4601ec3fd (diff)
downloadqtapplicationmanager-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.qdoc8
-rw-r--r--doc/controller.qdoc4
-rw-r--r--src/application-lib/packageinfo.cpp28
-rw-r--r--src/common-lib/utilities.cpp29
-rw-r--r--src/common-lib/utilities.h3
-rw-r--r--src/main-lib/configuration.cpp33
-rw-r--r--src/main-lib/configuration.h1
-rw-r--r--src/main-lib/configuration_p.h1
-rw-r--r--src/main-lib/main.cpp19
-rw-r--r--src/main-lib/main.h5
-rw-r--r--src/tools/controller/controller.cpp49
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();
+}