summaryrefslogtreecommitdiffstats
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:26:17 +0200
commit8f4fbe0665f7e83c89364e44711f01c4408ff59f (patch)
tree2eee02b3964f69de3f0114db0aaadc5eb2e5b5aa
parentb6eca2f33af9b3b1721d367c0fdea60f18cb33d7 (diff)
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 Pick-to: 6.4 5.15 Fixes: AUTOSUITE-1678 Reviewed-by: Dominik Holland <dominik.holland@qt.io>
-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.cpp34
-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, 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 acba9a4b..648d2179 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 e1d420bc..cc23b263 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 2680e018..e597fe09 100644
--- a/src/main-lib/configuration.h
+++ b/src/main-lib/configuration.h
@@ -31,6 +31,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 895d574a..f09e18fb 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();
+}