// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qdbusconnectionmanager_p.h" #include #include #include #include #include "qdbuserror.h" #include "qdbuspendingcall_p.h" #include "qdbusmetatype_p.h" #ifndef QT_NO_DBUS QT_BEGIN_NAMESPACE #ifdef Q_OS_WIN static void preventDllUnload(); #endif Q_GLOBAL_STATIC(QDBusConnectionManager, _q_manager) QDBusConnectionPrivate *QDBusConnectionManager::busConnection(QDBusConnection::BusType type) { static_assert(int(QDBusConnection::SessionBus) + int(QDBusConnection::SystemBus) == 1); Q_ASSERT(type == QDBusConnection::SessionBus || type == QDBusConnection::SystemBus); if (!qdbus_loadLibDBus()) return nullptr; // we'll start in suspended delivery mode if we're in the main thread // (the event loop will resume delivery) bool suspendedDelivery = qApp && qApp->thread() == QThread::currentThread(); const auto locker = qt_scoped_lock(defaultBusMutex); if (defaultBuses[type]) return defaultBuses[type]; QString name = QStringLiteral("qt_default_session_bus"); if (type == QDBusConnection::SystemBus) name = QStringLiteral("qt_default_system_bus"); return defaultBuses[type] = connectToBus(type, name, suspendedDelivery); } QDBusConnectionPrivate *QDBusConnectionManager::connection(const QString &name) const { return connectionHash.value(name, nullptr); } QDBusConnectionPrivate *QDBusConnectionManager::existingConnection(const QString &name) const { const auto locker = qt_scoped_lock(mutex); auto *conn = connection(name); if (conn) conn->ref.ref(); return conn; } void QDBusConnectionManager::removeConnection(const QString &name) { QDBusConnectionPrivate *d = nullptr; d = connectionHash.take(name); if (d && !d->ref.deref()) d->deleteLater(); // Static objects may be keeping the connection open. // However, it is harmless to have outstanding references to a connection that is // closing as long as those references will be soon dropped without being used. // ### Output a warning if connections are being used after they have been removed. } void QDBusConnectionManager::removeConnections(const QStringList &names) { const auto locker = qt_scoped_lock(mutex); for (const auto &name : names) removeConnection(name); } void QDBusConnectionManager::disconnectFrom(const QString &name, QDBusConnectionPrivate::ConnectionMode mode) { const auto locker = qt_scoped_lock(mutex); QDBusConnectionPrivate *d = connection(name); if (d && d->mode != mode) return; removeConnection(name); } QDBusConnectionManager::QDBusConnectionManager() { // Ensure that the custom metatype registry is created before the instance // of this class. This will ensure that the registry is not destroyed before // the connection manager at application exit (see also QTBUG-58732). This // works with compilers that use mechanism similar to atexit() to call // destructurs for global statics. QDBusMetaTypeId::init(); moveToThread(this); // ugly, don't do this in other projects #ifdef Q_OS_WIN // prevent the library from being unloaded on Windows. See comments in the function. preventDllUnload(); #endif defaultBuses[0] = defaultBuses[1] = nullptr; start(); } QDBusConnectionManager::~QDBusConnectionManager() { quit(); wait(); } QDBusConnectionManager* QDBusConnectionManager::instance() { return _q_manager(); } Q_DBUS_EXPORT void qDBusBindToApplication(); void qDBusBindToApplication() { } void QDBusConnectionManager::setConnection(const QString &name, QDBusConnectionPrivate *c) { connectionHash[name] = c; c->name = name; } void QDBusConnectionManager::addConnection(const QString &name, QDBusConnectionPrivate *c) { const auto locker = qt_scoped_lock(mutex); setConnection(name, c); } void QDBusConnectionManager::run() { exec(); // cleanup: const auto locker = qt_scoped_lock(mutex); for (QDBusConnectionPrivate *d : std::as_const(connectionHash)) { if (!d->ref.deref()) { delete d; } else { d->closeConnection(); d->moveToThread(nullptr); // allow it to be deleted in another thread } } connectionHash.clear(); // allow deletion from any thread without warning moveToThread(nullptr); } QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(QDBusConnection::BusType type, const QString &name, bool suspendedDelivery) { QDBusConnectionPrivate *result = nullptr; QMetaObject::invokeMethod(this, &QDBusConnectionManager::doConnectToStandardBus, Qt::BlockingQueuedConnection, qReturnArg(result), type, name, suspendedDelivery); if (suspendedDelivery && result && result->connection) result->enableDispatchDelayed(qApp); // qApp was checked in the caller return result; } QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(const QString &address, const QString &name) { QDBusConnectionPrivate *result = nullptr; QMetaObject::invokeMethod(this, &QDBusConnectionManager::doConnectToBus, Qt::BlockingQueuedConnection, qReturnArg(result), address, name); return result; } QDBusConnectionPrivate *QDBusConnectionManager::connectToPeer(const QString &address, const QString &name) { QDBusConnectionPrivate *result = nullptr; QMetaObject::invokeMethod(this, &QDBusConnectionManager::doConnectToPeer, Qt::BlockingQueuedConnection, qReturnArg(result), address, name); return result; } QDBusConnectionPrivate * QDBusConnectionManager::doConnectToStandardBus(QDBusConnection::BusType type, const QString &name, bool suspendedDelivery) { const auto locker = qt_scoped_lock(mutex); // check if the connection exists by name QDBusConnectionPrivate *d = connection(name); if (d || name.isEmpty()) return d; d = new QDBusConnectionPrivate; DBusConnection *c = nullptr; QDBusErrorInternal error; switch (type) { case QDBusConnection::SystemBus: c = q_dbus_bus_get_private(DBUS_BUS_SYSTEM, error); break; case QDBusConnection::SessionBus: c = q_dbus_bus_get_private(DBUS_BUS_SESSION, error); break; case QDBusConnection::ActivationBus: c = q_dbus_bus_get_private(DBUS_BUS_STARTER, error); break; } setConnection(name, d); // create the bus service // will lock in QDBusConnectionPrivate::connectRelay() d->setConnection(c, error); d->createBusService(); if (c && suspendedDelivery) d->setDispatchEnabled(false); return d; } QDBusConnectionPrivate *QDBusConnectionManager::doConnectToBus(const QString &address, const QString &name) { const auto locker = qt_scoped_lock(mutex); // check if the connection exists by name QDBusConnectionPrivate *d = connection(name); if (d || name.isEmpty()) return d; d = new QDBusConnectionPrivate; QDBusErrorInternal error; DBusConnection *c = q_dbus_connection_open_private(address.toUtf8().constData(), error); if (c) { // register on the bus if (!q_dbus_bus_register(c, error)) { q_dbus_connection_close(c); q_dbus_connection_unref(c); c = nullptr; } } setConnection(name, d); // create the bus service // will lock in QDBusConnectionPrivate::connectRelay() d->setConnection(c, error); d->createBusService(); return d; } QDBusConnectionPrivate *QDBusConnectionManager::doConnectToPeer(const QString &address, const QString &name) { const auto locker = qt_scoped_lock(mutex); // check if the connection exists by name QDBusConnectionPrivate *d = connection(name); if (d || name.isEmpty()) return d; d = new QDBusConnectionPrivate; QDBusErrorInternal error; DBusConnection *c = q_dbus_connection_open_private(address.toUtf8().constData(), error); setConnection(name, d); d->setPeer(c, error); return d; } void QDBusConnectionManager::createServer(const QString &address, QDBusServer *server) { QMetaObject::invokeMethod( this, [&address, server] { QDBusErrorInternal error; QDBusConnectionPrivate *d = new QDBusConnectionPrivate; d->setServer(server, q_dbus_server_listen(address.toUtf8().constData(), error), error); }, Qt::BlockingQueuedConnection); } QT_END_NAMESPACE #include "moc_qdbusconnectionmanager_p.cpp" #ifdef Q_OS_WIN # include QT_BEGIN_NAMESPACE static void preventDllUnload() { // Thread termination is really wacky on Windows. For some reason we don't // understand, exiting from the thread may try to unload the DLL. Since the // QDBusConnectionManager thread runs until the DLL is unloaded, we've got // a deadlock: the main thread is waiting for the manager thread to exit, // but the manager thread is attempting to acquire a lock to unload the DLL. // // We work around the issue by preventing the unload from happening in the // first place. // // For this trick, see the blog post titled "What is the point of FreeLibraryAndExitThread?" // https://devblogs.microsoft.com/oldnewthing/20131105-00/?p=2733 static HMODULE self; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, reinterpret_cast(&self), // any address in this DLL &self); } QT_END_NAMESPACE #endif #endif // QT_NO_DBUS