/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtDBus module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qdbusintegrator_p.h" #include #include #include #include #include #include #include #include #include #include #include "qdbusargument.h" #include "qdbusconnection_p.h" #include "qdbusconnectionmanager_p.h" #include "qdbusinterface_p.h" #include "qdbusmessage.h" #include "qdbusmetatype.h" #include "qdbusmetatype_p.h" #include "qdbusabstractadaptor.h" #include "qdbusabstractadaptor_p.h" #include "qdbusserver.h" #include "qdbusutil_p.h" #include "qdbusvirtualobject.h" #include "qdbusmessage_p.h" #include "qdbuscontext_p.h" #include "qdbuspendingcall_p.h" #include "qdbusthreaddebug_p.h" #include #ifdef interface #undef interface #endif #ifndef QT_NO_DBUS QT_BEGIN_NAMESPACE // used with dbus_server_allocate_data_slot static dbus_int32_t server_slot = -1; static QBasicAtomicInt isDebugging = Q_BASIC_ATOMIC_INITIALIZER(-1); #define qDBusDebug if (::isDebugging == 0); else qDebug static inline QDebug operator<<(QDebug dbg, const QThread *th) { QDebugStateSaver saver(dbg); dbg.nospace() << "QThread(ptr=" << (const void*)th; if (th && !th->objectName().isEmpty()) dbg.nospace() << ", name=" << th->objectName(); else if (th) dbg.nospace() << ", name=" << th->metaObject()->className(); dbg.nospace() << ')'; return dbg; } #if QDBUS_THREAD_DEBUG static inline QDebug operator<<(QDebug dbg, const QDBusConnectionPrivate *conn) { QDebugStateSaver saver(dbg); dbg.nospace() << "QDBusConnection(" << "ptr=" << (const void*)conn << ", name=" << conn->name << ", baseService=" << conn->baseService << ')'; return dbg; } void qdbusDefaultThreadDebug(int action, int condition, QDBusConnectionPrivate *conn) { qDBusDebug() << QThread::currentThread() << "Qt D-Bus threading action" << action << (condition == QDBusLockerBase::BeforeLock ? "before lock" : condition == QDBusLockerBase::AfterLock ? "after lock" : condition == QDBusLockerBase::BeforeUnlock ? "before unlock" : condition == QDBusLockerBase::AfterUnlock ? "after unlock" : condition == QDBusLockerBase::BeforePost ? "before event posting" : condition == QDBusLockerBase::AfterPost ? "after event posting" : condition == QDBusLockerBase::BeforeDeliver ? "before event delivery" : condition == QDBusLockerBase::AfterDeliver ? "after event delivery" : condition == QDBusLockerBase::BeforeAcquire ? "before acquire" : condition == QDBusLockerBase::AfterAcquire ? "after acquire" : condition == QDBusLockerBase::BeforeRelease ? "before release" : condition == QDBusLockerBase::AfterRelease ? "after release" : "condition unknown") << "in connection" << conn; } qdbusThreadDebugFunc qdbusThreadDebug = nullptr; #endif typedef QVarLengthArray QDBusSpyHookList; Q_GLOBAL_STATIC(QDBusSpyHookList, qDBusSpyHookList) extern "C" { // libdbus-1 callbacks static dbus_bool_t qDBusAddTimeout(DBusTimeout *timeout, void *data) { Q_ASSERT(timeout); Q_ASSERT(data); // qDebug("addTimeout %d", q_dbus_timeout_get_interval(timeout)); QDBusConnectionPrivate *d = static_cast(data); Q_ASSERT(QThread::currentThread() == d->thread()); // we may get called from qDBusToggleTimeout if (Q_UNLIKELY(!q_dbus_timeout_get_enabled(timeout))) return false; Q_ASSERT(d->timeouts.key(timeout, 0) == 0); int timerId = d->startTimer(q_dbus_timeout_get_interval(timeout)); Q_ASSERT_X(timerId, "QDBusConnection", "Failed to start a timer"); if (!timerId) return false; d->timeouts[timerId] = timeout; return true; } static void qDBusRemoveTimeout(DBusTimeout *timeout, void *data) { Q_ASSERT(timeout); Q_ASSERT(data); // qDebug("removeTimeout"); QDBusConnectionPrivate *d = static_cast(data); Q_ASSERT(QThread::currentThread() == d->thread()); QDBusConnectionPrivate::TimeoutHash::iterator it = d->timeouts.begin(); while (it != d->timeouts.end()) { if (it.value() == timeout) { d->killTimer(it.key()); it = d->timeouts.erase(it); break; } else { ++it; } } } static void qDBusToggleTimeout(DBusTimeout *timeout, void *data) { Q_ASSERT(timeout); Q_ASSERT(data); //qDebug("ToggleTimeout"); qDBusRemoveTimeout(timeout, data); qDBusAddTimeout(timeout, data); } static dbus_bool_t qDBusAddWatch(DBusWatch *watch, void *data) { Q_ASSERT(watch); Q_ASSERT(data); QDBusConnectionPrivate *d = static_cast(data); Q_ASSERT(QThread::currentThread() == d->thread()); int flags = q_dbus_watch_get_flags(watch); int fd = q_dbus_watch_get_unix_fd(watch); QDBusConnectionPrivate::Watcher watcher; if (flags & DBUS_WATCH_READABLE) { //qDebug("addReadWatch %d", fd); watcher.watch = watch; watcher.read = new QSocketNotifier(fd, QSocketNotifier::Read, d); watcher.read->setEnabled(q_dbus_watch_get_enabled(watch)); d->connect(watcher.read, &QSocketNotifier::activated, d, &QDBusConnectionPrivate::socketRead); } if (flags & DBUS_WATCH_WRITABLE) { //qDebug("addWriteWatch %d", fd); watcher.watch = watch; watcher.write = new QSocketNotifier(fd, QSocketNotifier::Write, d); watcher.write->setEnabled(q_dbus_watch_get_enabled(watch)); d->connect(watcher.write, &QSocketNotifier::activated, d, &QDBusConnectionPrivate::socketWrite); } d->watchers.insert(fd, watcher); return true; } static void qDBusRemoveWatch(DBusWatch *watch, void *data) { Q_ASSERT(watch); Q_ASSERT(data); //qDebug("remove watch"); QDBusConnectionPrivate *d = static_cast(data); Q_ASSERT(QThread::currentThread() == d->thread()); int fd = q_dbus_watch_get_unix_fd(watch); QDBusConnectionPrivate::WatcherHash::iterator i = d->watchers.find(fd); while (i != d->watchers.end() && i.key() == fd) { if (i.value().watch == watch) { delete i.value().read; delete i.value().write; i = d->watchers.erase(i); } else { ++i; } } } static void qDBusToggleWatch(DBusWatch *watch, void *data) { Q_ASSERT(watch); Q_ASSERT(data); QDBusConnectionPrivate *d = static_cast(data); Q_ASSERT(QThread::currentThread() == d->thread()); int fd = q_dbus_watch_get_unix_fd(watch); QDBusConnectionPrivate::WatcherHash::iterator i = d->watchers.find(fd); while (i != d->watchers.end() && i.key() == fd) { if (i.value().watch == watch) { bool enabled = q_dbus_watch_get_enabled(watch); int flags = q_dbus_watch_get_flags(watch); //qDebug("toggle watch %d to %d (write: %d, read: %d)", q_dbus_watch_get_fd(watch), enabled, flags & DBUS_WATCH_WRITABLE, flags & DBUS_WATCH_READABLE); if (flags & DBUS_WATCH_READABLE && i.value().read) i.value().read->setEnabled(enabled); if (flags & DBUS_WATCH_WRITABLE && i.value().write) i.value().write->setEnabled(enabled); return; } ++i; } } static void qDBusUpdateDispatchStatus(DBusConnection *connection, DBusDispatchStatus new_status, void *data) { Q_ASSERT(connection); Q_UNUSED(connection); QDBusConnectionPrivate *d = static_cast(data); if (new_status == DBUS_DISPATCH_DATA_REMAINS) emit d->dispatchStatusChanged(); } static void qDBusNewConnection(DBusServer *server, DBusConnection *connection, void *data) { // ### We may want to separate the server from the QDBusConnectionPrivate Q_ASSERT(server); Q_UNUSED(server); Q_ASSERT(connection); Q_ASSERT(data); if (!QDBusConnectionManager::instance()) return; // keep the connection alive q_dbus_connection_ref(connection); QDBusConnectionPrivate *serverConnection = static_cast(data); // allow anonymous authentication if (serverConnection->anonymousAuthenticationAllowed) q_dbus_connection_set_allow_anonymous(connection, true); QDBusConnectionPrivate *newConnection = new QDBusConnectionPrivate(serverConnection->parent()); const auto locker = qt_scoped_lock(QDBusConnectionManager::instance()->mutex); QDBusConnectionManager::instance()->setConnection(QLatin1String("QDBusServer-") + QString::number(reinterpret_cast(newConnection), 16), newConnection); serverConnection->serverConnectionNames << newConnection->name; // setPeer does the error handling for us QDBusErrorInternal error; newConnection->setPeer(connection, error); newConnection->setDispatchEnabled(false); // this is a queued connection and will resume in the QDBusServer's thread emit serverConnection->newServerConnection(newConnection); // we've disabled dispatching of events, so now we post an event to the // QDBusServer's thread in order to enable it after the // QDBusServer::newConnection() signal has been received by the // application's code newConnection->ref.ref(); QReadLocker serverLock(&serverConnection->lock); QDBusConnectionDispatchEnabler *o = new QDBusConnectionDispatchEnabler(newConnection); QTimer::singleShot(0, o, SLOT(execute())); if (serverConnection->serverObject) o->moveToThread(serverConnection->serverObject->thread()); } void QDBusConnectionPrivate::_q_newConnection(QDBusConnectionPrivate *newConnection) { Q_ASSERT(mode == ServerMode); emit serverObject->newConnection(QDBusConnectionPrivate::q(newConnection)); } } // extern "C" static QByteArray buildMatchRule(const QString &service, const QString &objectPath, const QString &interface, const QString &member, const QDBusConnectionPrivate::ArgMatchRules &argMatch, const QString & /*signature*/) { QString result; result += QLatin1String("type='signal',"); const auto keyValue = QLatin1String("%1='%2',"); if (!service.isEmpty()) result += keyValue.arg(QLatin1String("sender"), service); if (!objectPath.isEmpty()) result += keyValue.arg(QLatin1String("path"), objectPath); if (!interface.isEmpty()) result += keyValue.arg(QLatin1String("interface"), interface); if (!member.isEmpty()) result += keyValue.arg(QLatin1String("member"), member); // add the argument string-matching now if (!argMatch.args.isEmpty()) { const QString keyValue = QLatin1String("arg%1='%2',"); for (int i = 0; i < argMatch.args.count(); ++i) if (!argMatch.args.at(i).isNull()) result += keyValue.arg(i).arg(argMatch.args.at(i)); } if (!argMatch.arg0namespace.isEmpty()) { result += QLatin1String("arg0namespace='%1',").arg(argMatch.arg0namespace); } result.chop(1); // remove ending comma return result.toLatin1(); } static bool findObject(const QDBusConnectionPrivate::ObjectTreeNode *root, const QString &fullpath, int &usedLength, QDBusConnectionPrivate::ObjectTreeNode &result) { if (!fullpath.compare(QLatin1String("/")) && root->obj) { usedLength = 1; result = *root; return root; } int start = 0; int length = fullpath.length(); if (fullpath.at(0) == QLatin1Char('/')) start = 1; // walk the object tree QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator node = root; while (start < length && node) { if (node->flags & QDBusConnection::ExportChildObjects) break; if ((node->flags & QDBusConnectionPrivate::VirtualObject) && (node->flags & QDBusConnection::SubPath)) break; int end = fullpath.indexOf(QLatin1Char('/'), start); end = (end == -1 ? length : end); QStringRef pathComponent(&fullpath, start, end - start); QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator it = std::lower_bound(node->children.constBegin(), node->children.constEnd(), pathComponent); if (it != node->children.constEnd() && it->name == pathComponent) // match node = it; else node = nullptr; start = end + 1; } // found our object usedLength = (start > length ? length : start); if (node) { if (node->obj || !node->children.isEmpty()) result = *node; else // there really is no object here // we're just looking at an unused space in the QVector node = nullptr; } return node; } static QObject *findChildObject(const QDBusConnectionPrivate::ObjectTreeNode *root, const QString &fullpath, int start) { int length = fullpath.length(); // any object in the tree can tell us to switch to its own object tree: const QDBusConnectionPrivate::ObjectTreeNode *node = root; if (node && node->flags & QDBusConnection::ExportChildObjects) { QObject *obj = node->obj; while (obj) { if (start >= length) // we're at the correct level return obj; int pos = fullpath.indexOf(QLatin1Char('/'), start); pos = (pos == -1 ? length : pos); QStringRef pathComponent(&fullpath, start, pos - start); const QObjectList children = obj->children(); // find a child with the proper name QObject *next = nullptr; QObjectList::ConstIterator it = children.constBegin(); QObjectList::ConstIterator end = children.constEnd(); for ( ; it != end; ++it) if ((*it)->objectName() == pathComponent) { next = *it; break; } if (!next) break; obj = next; start = pos + 1; } } // object not found return nullptr; } static QDBusConnectionPrivate::ArgMatchRules matchArgsForService(const QString &service, QDBusServiceWatcher::WatchMode mode) { QDBusConnectionPrivate::ArgMatchRules matchArgs; if (service.endsWith(QLatin1Char('*'))) { matchArgs.arg0namespace = service.chopped(1); matchArgs.args << QString(); } else matchArgs.args << service; switch (mode) { case QDBusServiceWatcher::WatchForOwnerChange: break; case QDBusServiceWatcher::WatchForRegistration: matchArgs.args << QString::fromLatin1("", 0); break; case QDBusServiceWatcher::WatchForUnregistration: matchArgs.args << QString() << QString::fromLatin1("", 0); break; } return matchArgs; } extern Q_DBUS_EXPORT void qDBusAddSpyHook(QDBusSpyCallEvent::Hook); void qDBusAddSpyHook(QDBusSpyCallEvent::Hook hook) { qDBusSpyHookList()->append(hook); } QDBusSpyCallEvent::~QDBusSpyCallEvent() { // Reinsert the message into the processing queue for the connection. // This is done in the destructor so the message is reinserted even if // QCoreApplication is destroyed. QDBusConnectionPrivate *d = static_cast(const_cast(sender())); qDBusDebug() << d << "message spies done for" << msg; emit d->spyHooksFinished(msg); } void QDBusSpyCallEvent::placeMetaCall(QObject *) { invokeSpyHooks(msg, hooks, hookCount); } inline void QDBusSpyCallEvent::invokeSpyHooks(const QDBusMessage &msg, const Hook *hooks, int hookCount) { // call the spy hook list for (int i = 0; i < hookCount; ++i) hooks[i](msg); } extern "C" { static DBusHandlerResult qDBusSignalFilter(DBusConnection *connection, DBusMessage *message, void *data) { Q_ASSERT(data); Q_UNUSED(connection); QDBusConnectionPrivate *d = static_cast(data); if (d->mode == QDBusConnectionPrivate::InvalidMode) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; QDBusMessage amsg = QDBusMessagePrivate::fromDBusMessage(message, d->capabilities); qDBusDebug() << d << "got message (signal):" << amsg; return d->handleMessage(amsg) ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } } bool QDBusConnectionPrivate::handleMessage(const QDBusMessage &amsg) { if (!ref.loadRelaxed()) return false; // local message are always delivered, regardless of filtering // or whether the dispatcher is enabled bool isLocal = QDBusMessagePrivate::isLocal(amsg); if (!dispatchEnabled && !isLocal) { // queue messages only, we'll handle them later qDBusDebug() << this << "delivery is suspended"; pendingMessages << amsg; return amsg.type() == QDBusMessage::MethodCallMessage; } switch (amsg.type()) { case QDBusMessage::SignalMessage: handleSignal(amsg); // if there are any other filters in this DBusConnection, // let them see the signal too return false; case QDBusMessage::MethodCallMessage: // run it through the spy filters (if any) before the regular processing: // a) if it's a local message, we're in the caller's thread, so invoke the filter directly // b) if it's an external message, post to the main thread if (Q_UNLIKELY(qDBusSpyHookList.exists()) && qApp) { const QDBusSpyHookList &list = *qDBusSpyHookList; if (isLocal) { Q_ASSERT(QThread::currentThread() != thread()); qDBusDebug() << this << "invoking message spies directly"; QDBusSpyCallEvent::invokeSpyHooks(amsg, list.constData(), list.size()); } else { qDBusDebug() << this << "invoking message spies via event"; QCoreApplication::postEvent(qApp, new QDBusSpyCallEvent(this, QDBusConnection(this), amsg, list.constData(), list.size())); // we'll be called back, so return return true; } } handleObjectCall(amsg); return true; case QDBusMessage::ReplyMessage: case QDBusMessage::ErrorMessage: case QDBusMessage::InvalidMessage: return false; // we don't handle those here } return false; } static void huntAndDestroy(QObject *needle, QDBusConnectionPrivate::ObjectTreeNode &haystack) { for (auto &node : haystack.children) huntAndDestroy(needle, node); auto isInactive = [](QDBusConnectionPrivate::ObjectTreeNode &node) { return !node.isActive(); }; haystack.children.erase(std::remove_if(haystack.children.begin(), haystack.children.end(), isInactive), haystack.children.end()); if (needle == haystack.obj) { haystack.obj = nullptr; haystack.flags = 0; } } static void huntAndUnregister(const QVector &pathComponents, int i, QDBusConnection::UnregisterMode mode, QDBusConnectionPrivate::ObjectTreeNode *node) { if (pathComponents.count() == i) { // found it node->obj = nullptr; node->flags = 0; if (mode == QDBusConnection::UnregisterTree) { // clear the sub-tree as well node->children.clear(); // can't disconnect the objects because we really don't know if they can // be found somewhere else in the path too } } else { // keep going QDBusConnectionPrivate::ObjectTreeNode::DataList::Iterator end = node->children.end(); QDBusConnectionPrivate::ObjectTreeNode::DataList::Iterator it = std::lower_bound(node->children.begin(), end, pathComponents.at(i)); if (it == end || it->name != pathComponents.at(i)) return; // node not found huntAndUnregister(pathComponents, i + 1, mode, it); if (!it->isActive()) node->children.erase(it); } } static void huntAndEmit(DBusConnection *connection, DBusMessage *msg, QObject *needle, const QDBusConnectionPrivate::ObjectTreeNode &haystack, bool isScriptable, bool isAdaptor, const QString &path = QString()) { QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator it = haystack.children.constBegin(); QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator end = haystack.children.constEnd(); for ( ; it != end; ++it) { if (it->isActive()) huntAndEmit(connection, msg, needle, *it, isScriptable, isAdaptor, path + QLatin1Char('/') + it->name); } if (needle == haystack.obj) { // is this a signal we should relay? if (isAdaptor && (haystack.flags & QDBusConnection::ExportAdaptors) == 0) return; // no: it comes from an adaptor and we're not exporting adaptors else if (!isAdaptor) { int mask = isScriptable ? QDBusConnection::ExportScriptableSignals : QDBusConnection::ExportNonScriptableSignals; if ((haystack.flags & mask) == 0) return; // signal was not exported } QByteArray p = path.toLatin1(); if (p.isEmpty()) p = "/"; qDBusDebug() << QThread::currentThread() << "emitting signal at" << p; DBusMessage *msg2 = q_dbus_message_copy(msg); q_dbus_message_set_path(msg2, p); q_dbus_connection_send(connection, msg2, nullptr); q_dbus_message_unref(msg2); } } static int findSlot(const QMetaObject *mo, const QByteArray &name, int flags, const QString &signature_, QVector &metaTypes) { QByteArray msgSignature = signature_.toLatin1(); for (int idx = mo->methodCount() - 1 ; idx >= QObject::staticMetaObject.methodCount(); --idx) { QMetaMethod mm = mo->method(idx); // check access: if (mm.access() != QMetaMethod::Public) continue; // check type: if (mm.methodType() != QMetaMethod::Slot && mm.methodType() != QMetaMethod::Method) continue; // check name: if (mm.name() != name) continue; int returnType = mm.returnType(); bool isAsync = qDBusCheckAsyncTag(mm.tag()); bool isScriptable = mm.attributes() & QMetaMethod::Scriptable; // consistency check: if (isAsync && returnType != QMetaType::Void) continue; QString errorMsg; int inputCount = qDBusParametersForMethod(mm, metaTypes, errorMsg); if (inputCount == -1) continue; // problem parsing metaTypes[0] = returnType; bool hasMessage = false; if (inputCount > 0 && metaTypes.at(inputCount) == QDBusMetaTypeId::message()) { // "no input parameters" is allowed as long as the message meta type is there hasMessage = true; --inputCount; } // try to match the parameters int i; QByteArray reconstructedSignature; for (i = 1; i <= inputCount; ++i) { const char *typeSignature = QDBusMetaType::typeToSignature( metaTypes.at(i) ); if (!typeSignature) break; // invalid reconstructedSignature += typeSignature; if (!msgSignature.startsWith(reconstructedSignature)) break; } if (reconstructedSignature != msgSignature) continue; // we didn't match them all if (hasMessage) ++i; // make sure that the output parameters have signatures too if (returnType != QMetaType::UnknownType && returnType != QMetaType::Void && QDBusMetaType::typeToSignature(returnType) == nullptr) continue; bool ok = true; for (int j = i; ok && j < metaTypes.count(); ++j) if (QDBusMetaType::typeToSignature(metaTypes.at(i)) == nullptr) ok = false; if (!ok) continue; // consistency check: if (isAsync && metaTypes.count() > i + 1) continue; if (mm.methodType() == QMetaMethod::Slot) { if (isScriptable && (flags & QDBusConnection::ExportScriptableSlots) == 0) continue; // scriptable slots not exported if (!isScriptable && (flags & QDBusConnection::ExportNonScriptableSlots) == 0) continue; // non-scriptable slots not exported } else { if (isScriptable && (flags & QDBusConnection::ExportScriptableInvokables) == 0) continue; // scriptable invokables not exported if (!isScriptable && (flags & QDBusConnection::ExportNonScriptableInvokables) == 0) continue; // non-scriptable invokables not exported } // if we got here, this slot matched return idx; } // no slot matched return -1; } /*! \internal Enables or disables the delivery of incoming method calls and signals. If \a enable is true, this will also cause any queued, pending messages to be delivered. */ void QDBusConnectionPrivate::setDispatchEnabled(bool enable) { checkThread(); dispatchEnabled = enable; if (enable) emit dispatchStatusChanged(); } static QDBusCallDeliveryEvent * const DIRECT_DELIVERY = (QDBusCallDeliveryEvent *)1; QDBusCallDeliveryEvent* QDBusConnectionPrivate::prepareReply(QDBusConnectionPrivate *target, QObject *object, int idx, const QVector &metaTypes, const QDBusMessage &msg) { Q_ASSERT(object); Q_UNUSED(object); int n = metaTypes.count() - 1; if (metaTypes[n] == QDBusMetaTypeId::message()) --n; if (msg.arguments().count() < n) return nullptr; // too few arguments // check that types match for (int i = 0; i < n; ++i) if (metaTypes.at(i + 1) != msg.arguments().at(i).userType() && msg.arguments().at(i).userType() != qMetaTypeId()) return nullptr; // no match // we can deliver // prepare for the call if (target == object) return DIRECT_DELIVERY; return new QDBusCallDeliveryEvent(QDBusConnection(target), idx, target, msg, metaTypes); } void QDBusConnectionPrivate::activateSignal(const QDBusConnectionPrivate::SignalHook& hook, const QDBusMessage &msg) { // This is called by QDBusConnectionPrivate::handleSignal to deliver a signal // that was received from D-Bus // // Signals are delivered to slots if the parameters match // Slots can have less parameters than there are on the message // Slots can optionally have one final parameter that is a QDBusMessage // Slots receive read-only copies of the message (i.e., pass by value or by const-ref) QDBusCallDeliveryEvent *call = prepareReply(this, hook.obj, hook.midx, hook.params, msg); if (call == DIRECT_DELIVERY) { // short-circuit delivery Q_ASSERT(this == hook.obj); deliverCall(this, 0, msg, hook.params, hook.midx); return; } if (call) postEventToThread(ActivateSignalAction, hook.obj, call); } bool QDBusConnectionPrivate::activateCall(QObject* object, int flags, const QDBusMessage &msg) { // This is called by QDBusConnectionPrivate::handleObjectCall to place a call // to a slot on the object. // // The call is delivered to the first slot that matches the following conditions: // - has the same name as the message's target member // - ALL of the message's types are found in slot's parameter list // - optionally has one more parameter of type QDBusMessage // If none match, then the slot of the same name as the message target and with // the first type of QDBusMessage is delivered. // // The D-Bus specification requires that all MethodCall messages be replied to, unless the // caller specifically waived this requirement. This means that we inspect if the user slot // generated a reply and, if it didn't, we will. Obviously, if the user slot doesn't take a // QDBusMessage parameter, it cannot generate a reply. // // When a return message is generated, the slot's return type, if any, will be placed // in the message's first position. If there are non-const reference parameters to the // slot, they must appear at the end and will be placed in the subsequent message // positions. static const char cachePropertyName[] = "_qdbus_slotCache"; if (!object) return false; Q_ASSERT_X(QThread::currentThread() == object->thread(), "QDBusConnection: internal threading error", "function called for an object that is in another thread!!"); QDBusSlotCache slotCache = qvariant_cast(object->property(cachePropertyName)); QString cacheKey = msg.member(), signature = msg.signature(); if (!signature.isEmpty()) { cacheKey.reserve(cacheKey.length() + 1 + signature.length()); cacheKey += QLatin1Char('.'); cacheKey += signature; } QDBusSlotCache::Hash::ConstIterator cacheIt = slotCache.hash.constFind(cacheKey); while (cacheIt != slotCache.hash.constEnd() && cacheIt->flags != flags && cacheIt.key() == cacheKey) ++cacheIt; if (cacheIt == slotCache.hash.constEnd() || cacheIt.key() != cacheKey) { // not cached, analyze the meta object const QMetaObject *mo = object->metaObject(); QByteArray memberName = msg.member().toUtf8(); // find a slot that matches according to the rules above QDBusSlotCache::Data slotData; slotData.flags = flags; slotData.slotIdx = ::findSlot(mo, memberName, flags, msg.signature(), slotData.metaTypes); if (slotData.slotIdx == -1) { // ### this is where we want to add the connection as an arg too // try with no parameters, but with a QDBusMessage slotData.slotIdx = ::findSlot(mo, memberName, flags, QString(), slotData.metaTypes); if (slotData.metaTypes.count() != 2 || slotData.metaTypes.at(1) != QDBusMetaTypeId::message()) { // not found // save the negative lookup slotData.slotIdx = -1; slotData.metaTypes.clear(); slotCache.hash.insert(cacheKey, slotData); object->setProperty(cachePropertyName, QVariant::fromValue(slotCache)); return false; } } // save to the cache slotCache.hash.insert(cacheKey, slotData); object->setProperty(cachePropertyName, QVariant::fromValue(slotCache)); // found the slot to be called deliverCall(object, flags, msg, slotData.metaTypes, slotData.slotIdx); return true; } else if (cacheIt->slotIdx == -1) { // negative cache return false; } else { // use the cache deliverCall(object, flags, msg, cacheIt->metaTypes, cacheIt->slotIdx); return true; } return false; } void QDBusConnectionPrivate::deliverCall(QObject *object, int /*flags*/, const QDBusMessage &msg, const QVector &metaTypes, int slotIdx) { Q_ASSERT_X(!object || QThread::currentThread() == object->thread(), "QDBusConnection: internal threading error", "function called for an object that is in another thread!!"); QVarLengthArray params; params.reserve(metaTypes.count()); QVariantList auxParameters; // let's create the parameter list // first one is the return type -- add it below params.append(0); // add the input parameters int i; int pCount = qMin(msg.arguments().count(), metaTypes.count() - 1); for (i = 1; i <= pCount; ++i) { int id = metaTypes[i]; if (id == QDBusMetaTypeId::message()) break; const QVariant &arg = msg.arguments().at(i - 1); if (arg.userType() == id) // no conversion needed params.append(const_cast(arg.constData())); else if (arg.userType() == qMetaTypeId()) { // convert to what the function expects void *null = nullptr; auxParameters.append(QVariant(id, null)); const QDBusArgument &in = *reinterpret_cast(arg.constData()); QVariant &out = auxParameters[auxParameters.count() - 1]; if (Q_UNLIKELY(!QDBusMetaType::demarshall(in, out.userType(), out.data()))) qFatal("Internal error: demarshalling function for type '%s' (%d) failed!", out.typeName(), out.userType()); params.append(const_cast(out.constData())); } else { qFatal("Internal error: got invalid meta type %d (%s) " "when trying to convert to meta type %d (%s)", arg.userType(), QMetaType::typeName(arg.userType()), id, QMetaType::typeName(id)); } } if (metaTypes.count() > i && metaTypes[i] == QDBusMetaTypeId::message()) { params.append(const_cast(static_cast(&msg))); ++i; } // output arguments const int numMetaTypes = metaTypes.count(); QVariantList outputArgs; void *null = nullptr; if (metaTypes[0] != QMetaType::Void && metaTypes[0] != QMetaType::UnknownType) { outputArgs.reserve(numMetaTypes - i + 1); QVariant arg(metaTypes[0], null); outputArgs.append( arg ); params[0] = const_cast(outputArgs.at( outputArgs.count() - 1 ).constData()); } else { outputArgs.reserve(numMetaTypes - i); } for ( ; i < numMetaTypes; ++i) { QVariant arg(metaTypes[i], null); outputArgs.append( arg ); params.append(const_cast(outputArgs.at( outputArgs.count() - 1 ).constData())); } // make call: bool fail; if (!object) { fail = true; } else { // FIXME: save the old sender! QDBusContextPrivate context(QDBusConnection(this), msg); QDBusContextPrivate *old = QDBusContextPrivate::set(object, &context); QPointer ptr = object; fail = object->qt_metacall(QMetaObject::InvokeMetaMethod, slotIdx, params.data()) >= 0; // the object might be deleted in the slot if (!ptr.isNull()) QDBusContextPrivate::set(object, old); } // do we create a reply? Only if the caller is waiting for a reply and one hasn't been sent // yet. if (msg.isReplyRequired() && !msg.isDelayedReply()) { if (!fail) { // normal reply qDBusDebug() << this << "Automatically sending reply:" << outputArgs; send(msg.createReply(outputArgs)); } else { // generate internal error qWarning("Internal error: Failed to deliver message"); send(msg.createErrorReply(QDBusError::InternalError, QLatin1String("Failed to deliver message"))); } } return; } extern bool qDBusInitThreads(); QDBusConnectionPrivate::QDBusConnectionPrivate(QObject *p) : QObject(p), ref(1), mode(InvalidMode), busService(nullptr), connection(nullptr), rootNode(QString(QLatin1Char('/'))), anonymousAuthenticationAllowed(false), dispatchEnabled(true) { static const bool threads = q_dbus_threads_init_default(); if (::isDebugging == -1) ::isDebugging = qEnvironmentVariableIntValue("QDBUS_DEBUG"); Q_UNUSED(threads) #ifdef QDBUS_THREAD_DEBUG if (::isDebugging > 1) qdbusThreadDebug = qdbusDefaultThreadDebug; #endif QDBusMetaTypeId::init(); connect(this, &QDBusConnectionPrivate::dispatchStatusChanged, this, &QDBusConnectionPrivate::doDispatch, Qt::QueuedConnection); connect(this, &QDBusConnectionPrivate::spyHooksFinished, this, &QDBusConnectionPrivate::handleObjectCall, Qt::QueuedConnection); connect(this, &QDBusConnectionPrivate::messageNeedsSending, this, &QDBusConnectionPrivate::sendInternal); connect(this, &QDBusConnectionPrivate::signalNeedsConnecting, this, &QDBusConnectionPrivate::addSignalHook, Qt::BlockingQueuedConnection); connect(this, &QDBusConnectionPrivate::signalNeedsDisconnecting, this, &QDBusConnectionPrivate::removeSignalHook, Qt::BlockingQueuedConnection); rootNode.flags = 0; // prepopulate watchedServices: // we know that the owner of org.freedesktop.DBus is itself watchedServices.insert(QDBusUtil::dbusService(), WatchedServiceData(QDBusUtil::dbusService(), 1)); // prepopulate matchRefCounts: // we know that org.freedesktop.DBus will never change owners matchRefCounts.insert("type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.freedesktop.DBus'", 1); } QDBusConnectionPrivate::~QDBusConnectionPrivate() { if (thread() && thread() != QThread::currentThread()) qWarning("QDBusConnection(name=\"%s\")'s last reference in not in its creation thread! " "Timer and socket errors will follow and the program will probably crash", qPrintable(name)); auto lastMode = mode; // reset on connection close closeConnection(); qDeleteAll(cachedMetaObjects); if (lastMode == ClientMode || lastMode == PeerMode) { // the bus service object holds a reference back to us; // we need to destroy it before we finish destroying ourselves Q_ASSERT(ref.loadRelaxed() == 0); QObject *obj = (QObject *)busService; if (obj) { disconnect(obj, nullptr, this, nullptr); delete obj; } if (connection) q_dbus_connection_unref(connection); connection = nullptr; } else if (lastMode == ServerMode) { if (server) q_dbus_server_unref(server); server = nullptr; } } void QDBusConnectionPrivate::collectAllObjects(QDBusConnectionPrivate::ObjectTreeNode &haystack, QSet &set) { QDBusConnectionPrivate::ObjectTreeNode::DataList::Iterator it = haystack.children.begin(); while (it != haystack.children.end()) { collectAllObjects(*it, set); it++; } if (haystack.obj) set.insert(haystack.obj); } void QDBusConnectionPrivate::closeConnection() { QDBusWriteLocker locker(CloseConnectionAction, this); qDBusDebug() << this << "Disconnected"; ConnectionMode oldMode = mode; mode = InvalidMode; // prevent reentrancy baseService.clear(); if (oldMode == ServerMode && server) { q_dbus_server_disconnect(server); q_dbus_server_free_data_slot(&server_slot); } if (oldMode == ClientMode || oldMode == PeerMode) { if (connection) { q_dbus_connection_close(connection); // send the "close" message while (q_dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) ; } } qDeleteAll(pendingCalls); // Disconnect all signals from signal hooks and from the object tree to // avoid QObject::destroyed being sent to dbus daemon thread which has // already quit. We need to make sure we disconnect exactly once per // object, because if we tried a second time, we might be hitting a // dangling pointer. QSet allObjects; collectAllObjects(rootNode, allObjects); SignalHookHash::const_iterator sit = signalHooks.constBegin(); while (sit != signalHooks.constEnd()) { allObjects.insert(sit.value().obj); ++sit; } // now disconnect ourselves QSet::const_iterator oit = allObjects.constBegin(); while (oit != allObjects.constEnd()) { (*oit)->disconnect(this); ++oit; } } void QDBusConnectionPrivate::handleDBusDisconnection() { while (!pendingCalls.isEmpty()) processFinishedCall(pendingCalls.first()); } void QDBusConnectionPrivate::checkThread() { Q_ASSERT(thread() == QDBusConnectionManager::instance()); Q_ASSERT(QThread::currentThread() == thread()); } bool QDBusConnectionPrivate::handleError(const QDBusErrorInternal &error) { if (!error) return false; // no error //lock.lockForWrite(); lastError = error; //lock.unlock(); return true; } void QDBusConnectionPrivate::timerEvent(QTimerEvent *e) { { DBusTimeout *timeout = timeouts.value(e->timerId(), 0); if (timeout) q_dbus_timeout_handle(timeout); } doDispatch(); } void QDBusConnectionPrivate::doDispatch() { if (mode == ClientMode || mode == PeerMode) { while (q_dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) ; if (dispatchEnabled && !pendingMessages.isEmpty()) { // dispatch previously queued messages PendingMessageList::Iterator it = pendingMessages.begin(); PendingMessageList::Iterator end = pendingMessages.end(); for ( ; it != end; ++it) { qDBusDebug() << this << "dequeueing message" << *it; handleMessage(std::move(*it)); } pendingMessages.clear(); } } } void QDBusConnectionPrivate::socketRead(int fd) { WatcherHash::ConstIterator it = watchers.constFind(fd); while (it != watchers.constEnd() && it.key() == fd) { if (it->watch && it->read && it->read->isEnabled()) { if (!q_dbus_watch_handle(it.value().watch, DBUS_WATCH_READABLE)) qDebug("OUT OF MEM"); break; } ++it; } doDispatch(); } void QDBusConnectionPrivate::socketWrite(int fd) { WatcherHash::ConstIterator it = watchers.constFind(fd); while (it != watchers.constEnd() && it.key() == fd) { if (it->watch && it->write && it->write->isEnabled()) { if (!q_dbus_watch_handle(it.value().watch, DBUS_WATCH_WRITABLE)) qDebug("OUT OF MEM"); break; } ++it; } } void QDBusConnectionPrivate::objectDestroyed(QObject *obj) { QDBusWriteLocker locker(ObjectDestroyedAction, this); huntAndDestroy(obj, rootNode); SignalHookHash::iterator sit = signalHooks.begin(); while (sit != signalHooks.end()) { if (static_cast(sit.value().obj) == obj) sit = removeSignalHookNoLock(sit); else ++sit; } obj->disconnect(this); } void QDBusConnectionPrivate::relaySignal(QObject *obj, const QMetaObject *mo, int signalId, const QVariantList &args) { QString interface = qDBusInterfaceFromMetaObject(mo); QMetaMethod mm = mo->method(signalId); QByteArray memberName = mm.name(); // check if it's scriptable bool isScriptable = mm.attributes() & QMetaMethod::Scriptable; bool isAdaptor = false; for ( ; mo; mo = mo->superClass()) if (mo == &QDBusAbstractAdaptor::staticMetaObject) { isAdaptor = true; break; } checkThread(); QDBusReadLocker locker(RelaySignalAction, this); QDBusMessage message = QDBusMessage::createSignal(QLatin1String("/"), interface, QLatin1String(memberName)); QDBusMessagePrivate::setParametersValidated(message, true); message.setArguments(args); QDBusError error; DBusMessage *msg = QDBusMessagePrivate::toDBusMessage(message, capabilities, &error); if (!msg) { qWarning("QDBusConnection: Could not emit signal %s.%s: %s", qPrintable(interface), memberName.constData(), qPrintable(error.message())); lastError = error; return; } //qDBusDebug() << "Emitting signal" << message; //qDBusDebug() << "for paths:"; q_dbus_message_set_no_reply(msg, true); // the reply would not be delivered to anything huntAndEmit(connection, msg, obj, rootNode, isScriptable, isAdaptor); q_dbus_message_unref(msg); } void QDBusConnectionPrivate::serviceOwnerChangedNoLock(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); // QDBusWriteLocker locker(UpdateSignalHookOwnerAction, this); WatchedServicesHash::Iterator it = watchedServices.find(name); if (it == watchedServices.end()) return; if (oldOwner != it->owner) qWarning("QDBusConnection: name '%s' had owner '%s' but we thought it was '%s'", qPrintable(name), qPrintable(oldOwner), qPrintable(it->owner)); qDBusDebug() << this << "Updating name" << name << "from" << oldOwner << "to" << newOwner; it->owner = newOwner; } int QDBusConnectionPrivate::findSlot(QObject* obj, const QByteArray &normalizedName, QVector ¶ms) { int midx = obj->metaObject()->indexOfMethod(normalizedName); if (midx == -1) return -1; QString errorMsg; int inputCount = qDBusParametersForMethod(obj->metaObject()->method(midx), params, errorMsg); if ( inputCount == -1 || inputCount + 1 != params.count() ) return -1; // failed to parse or invalid arguments or output arguments return midx; } bool QDBusConnectionPrivate::prepareHook(QDBusConnectionPrivate::SignalHook &hook, QString &key, const QString &service, const QString &path, const QString &interface, const QString &name, const ArgMatchRules &argMatch, QObject *receiver, const char *signal, int minMIdx, bool buildSignature) { QByteArray normalizedName = signal + 1; hook.midx = findSlot(receiver, signal + 1, hook.params); if (hook.midx == -1) { normalizedName = QMetaObject::normalizedSignature(signal + 1); hook.midx = findSlot(receiver, normalizedName, hook.params); } if (hook.midx < minMIdx) { return false; } hook.service = service; hook.path = path; hook.obj = receiver; hook.argumentMatch = argMatch; // build the D-Bus signal name and signature // This should not happen for QDBusConnection::connect, use buildSignature here, since // QDBusConnection::connect passes false and everything else uses true QString mname = name; if (buildSignature && mname.isNull()) { normalizedName.truncate(normalizedName.indexOf('(')); mname = QString::fromUtf8(normalizedName); } key = mname; key.reserve(interface.length() + 1 + mname.length()); key += QLatin1Char(':'); key += interface; if (buildSignature) { hook.signature.clear(); for (int i = 1; i < hook.params.count(); ++i) if (hook.params.at(i) != QDBusMetaTypeId::message()) hook.signature += QLatin1String( QDBusMetaType::typeToSignature( hook.params.at(i) ) ); } hook.matchRule = buildMatchRule(service, path, interface, mname, argMatch, hook.signature); return true; // connect to this signal } void QDBusConnectionPrivate::sendError(const QDBusMessage &msg, QDBusError::ErrorType code) { if (code == QDBusError::UnknownMethod) { QString interfaceMsg; if (msg.interface().isEmpty()) interfaceMsg = QLatin1String("any interface"); else interfaceMsg = QLatin1String("interface '%1'").arg(msg.interface()); send(msg.createErrorReply(code, QLatin1String("No such method '%1' in %2 at object path '%3' " "(signature '%4')") .arg(msg.member(), interfaceMsg, msg.path(), msg.signature()))); } else if (code == QDBusError::UnknownInterface) { send(msg.createErrorReply(QDBusError::UnknownInterface, QLatin1String("No such interface '%1' at object path '%2'") .arg(msg.interface(), msg.path()))); } else if (code == QDBusError::UnknownObject) { send(msg.createErrorReply(QDBusError::UnknownObject, QLatin1String("No such object path '%1'").arg(msg.path()))); } } bool QDBusConnectionPrivate::activateInternalFilters(const ObjectTreeNode &node, const QDBusMessage &msg) { // object may be null const QString interface = msg.interface(); if (interface.isEmpty() || interface == QDBusUtil::dbusInterfaceIntrospectable()) { if (msg.member() == QLatin1String("Introspect") && msg.signature().isEmpty()) { //qDebug() << "QDBusConnectionPrivate::activateInternalFilters introspect" << msg.d_ptr->msg; QDBusMessage reply = msg.createReply(qDBusIntrospectObject(node, msg.path())); send(reply); return true; } if (!interface.isEmpty()) { sendError(msg, QDBusError::UnknownMethod); return true; } } if (node.obj && (interface.isEmpty() || interface == QDBusUtil::dbusInterfaceProperties())) { //qDebug() << "QDBusConnectionPrivate::activateInternalFilters properties" << msg.d_ptr->msg; if (msg.member() == QLatin1String("Get") && msg.signature() == QLatin1String("ss")) { QDBusMessage reply = qDBusPropertyGet(node, msg); send(reply); return true; } else if (msg.member() == QLatin1String("Set") && msg.signature() == QLatin1String("ssv")) { QDBusMessage reply = qDBusPropertySet(node, msg); send(reply); return true; } else if (msg.member() == QLatin1String("GetAll") && msg.signature() == QLatin1String("s")) { QDBusMessage reply = qDBusPropertyGetAll(node, msg); send(reply); return true; } if (!interface.isEmpty()) { sendError(msg, QDBusError::UnknownMethod); return true; } } return false; } void QDBusConnectionPrivate::activateObject(ObjectTreeNode &node, const QDBusMessage &msg, int pathStartPos) { // This is called by QDBusConnectionPrivate::handleObjectCall to place a call to a slot // on the object. // // The call is routed through the adaptor sub-objects if we have any // object may be null if (node.flags & QDBusConnectionPrivate::VirtualObject) { if (node.treeNode->handleMessage(msg, q(this))) { return; } else { if (activateInternalFilters(node, msg)) return; } } if (pathStartPos != msg.path().length()) { node.flags &= ~QDBusConnection::ExportAllSignals; node.obj = findChildObject(&node, msg.path(), pathStartPos); if (!node.obj) { sendError(msg, QDBusError::UnknownObject); return; } } QDBusAdaptorConnector *connector; if (node.flags & QDBusConnection::ExportAdaptors && (connector = qDBusFindAdaptorConnector(node.obj))) { int newflags = node.flags | QDBusConnection::ExportAllSlots; if (msg.interface().isEmpty()) { // place the call in all interfaces // let the first one that handles it to work QDBusAdaptorConnector::AdaptorMap::ConstIterator it = connector->adaptors.constBegin(); QDBusAdaptorConnector::AdaptorMap::ConstIterator end = connector->adaptors.constEnd(); for ( ; it != end; ++it) if (activateCall(it->adaptor, newflags, msg)) return; } else { // check if we have an interface matching the name that was asked: QDBusAdaptorConnector::AdaptorMap::ConstIterator it; it = std::lower_bound(connector->adaptors.constBegin(), connector->adaptors.constEnd(), msg.interface()); if (it != connector->adaptors.constEnd() && msg.interface() == QLatin1String(it->interface)) { if (!activateCall(it->adaptor, newflags, msg)) sendError(msg, QDBusError::UnknownMethod); return; } } } // no adaptors matched or were exported // try our standard filters if (activateInternalFilters(node, msg)) return; // internal filters have already run or an error has been sent // try the object itself: if (node.flags & (QDBusConnection::ExportScriptableSlots|QDBusConnection::ExportNonScriptableSlots) || node.flags & (QDBusConnection::ExportScriptableInvokables|QDBusConnection::ExportNonScriptableInvokables)) { bool interfaceFound = true; if (!msg.interface().isEmpty()) { if (!node.interfaceName.isEmpty()) interfaceFound = msg.interface() == node.interfaceName; else interfaceFound = qDBusInterfaceInObject(node.obj, msg.interface()); } if (interfaceFound) { if (!activateCall(node.obj, node.flags, msg)) sendError(msg, QDBusError::UnknownMethod); return; } } // nothing matched, send an error code if (msg.interface().isEmpty()) sendError(msg, QDBusError::UnknownMethod); else sendError(msg, QDBusError::UnknownInterface); } void QDBusConnectionPrivate::handleObjectCall(const QDBusMessage &msg) { // if the msg is external, we were called from inside doDispatch // that means the dispatchLock mutex is locked // must not call out to user code in that case // // however, if the message is internal, handleMessage was called directly // (user's thread) and no lock is in place. We can therefore call out to // user code, if necessary. ObjectTreeNode result; int usedLength; QThread *objThread = nullptr; QSemaphore sem; bool semWait; { QDBusReadLocker locker(HandleObjectCallAction, this); if (!findObject(&rootNode, msg.path(), usedLength, result)) { // qDebug("Call failed: no object found at %s", qPrintable(msg.path())); sendError(msg, QDBusError::UnknownObject); return; } if (!result.obj) { // no object -> no threading issues // it's either going to be an error, or an internal filter activateObject(result, msg, usedLength); return; } objThread = result.obj->thread(); if (!objThread) { send(msg.createErrorReply(QDBusError::InternalError, QLatin1String("Object '%1' (at path '%2')" " has no thread. Cannot deliver message.") .arg(result.obj->objectName(), msg.path()))); return; } if (!QDBusMessagePrivate::isLocal(msg)) { // external incoming message // post it and forget postEventToThread(HandleObjectCallPostEventAction, result.obj, new QDBusActivateObjectEvent(QDBusConnection(this), this, result, usedLength, msg)); return; } else if (objThread != QThread::currentThread()) { // looped-back message, targeting another thread: // synchronize with it postEventToThread(HandleObjectCallPostEventAction, result.obj, new QDBusActivateObjectEvent(QDBusConnection(this), this, result, usedLength, msg, &sem)); semWait = true; } else { // looped-back message, targeting current thread semWait = false; } } // release the lock if (semWait) SEM_ACQUIRE(HandleObjectCallSemaphoreAction, sem); else activateObject(result, msg, usedLength); } QDBusActivateObjectEvent::~QDBusActivateObjectEvent() { if (!handled) { // we're being destroyed without delivering // it means the object was deleted between posting and delivering QDBusConnectionPrivate *that = QDBusConnectionPrivate::d(connection); that->sendError(message, QDBusError::UnknownObject); } // semaphore releasing happens in ~QMetaCallEvent } void QDBusActivateObjectEvent::placeMetaCall(QObject *) { QDBusConnectionPrivate *that = QDBusConnectionPrivate::d(connection); QDBusLockerBase::reportThreadAction(HandleObjectCallPostEventAction, QDBusLockerBase::BeforeDeliver, that); that->activateObject(node, message, pathStartPos); QDBusLockerBase::reportThreadAction(HandleObjectCallPostEventAction, QDBusLockerBase::AfterDeliver, that); handled = true; } void QDBusConnectionPrivate::handleSignal(const QString &key, const QDBusMessage& msg) { SignalHookHash::const_iterator it = signalHooks.constFind(key); SignalHookHash::const_iterator end = signalHooks.constEnd(); //qDebug("looking for: %s", path.toLocal8Bit().constData()); //qDBusDebug() << signalHooks.keys(); for ( ; it != end && it.key() == key; ++it) { const SignalHook &hook = it.value(); if (!hook.service.isEmpty()) { QString owner = watchedServices.value(hook.service, WatchedServiceData(hook.service)).owner; if (owner != msg.service()) continue; } if (!hook.path.isEmpty() && hook.path != msg.path()) continue; if (!hook.signature.isEmpty() && hook.signature != msg.signature()) continue; if (hook.signature.isEmpty() && !hook.signature.isNull() && !msg.signature().isEmpty()) continue; if (!hook.argumentMatch.args.isEmpty()) { const QVariantList arguments = msg.arguments(); if (hook.argumentMatch.args.size() > arguments.size()) continue; bool matched = true; for (int i = 0; i < hook.argumentMatch.args.size(); ++i) { const QString ¶m = hook.argumentMatch.args.at(i); if (param.isNull()) continue; // don't try to match against this if (param == arguments.at(i).toString()) continue; // matched matched = false; break; } if (!matched) continue; } if (!hook.argumentMatch.arg0namespace.isEmpty()) { const QVariantList arguments = msg.arguments(); if (arguments.size() < 1) continue; const QString param = arguments.at(0).toString(); if (param != hook.argumentMatch.arg0namespace && !param.startsWith(hook.argumentMatch.arg0namespace + QLatin1Char('.'))) continue; } activateSignal(hook, msg); } } void QDBusConnectionPrivate::handleSignal(const QDBusMessage& msg) { // We call handlesignal(QString, QDBusMessage) three times: // one with member:interface // one with member: // one with :interface // This allows us to match signals with wildcards on member or interface // (but not both) QString key = msg.member(); key.reserve(key.length() + 1 + msg.interface().length()); key += QLatin1Char(':'); key += msg.interface(); QDBusReadLocker locker(HandleSignalAction, this); handleSignal(key, msg); // one try key.truncate(msg.member().length() + 1); // keep the ':' handleSignal(key, msg); // second try key = QLatin1Char(':'); key += msg.interface(); handleSignal(key, msg); // third try } void QDBusConnectionPrivate::watchForDBusDisconnection() { SignalHook hook; // Initialize the hook for Disconnected signal hook.service.clear(); // org.freedesktop.DBus.Local.Disconnected uses empty service name hook.path = QDBusUtil::dbusPathLocal(); hook.obj = this; hook.params << QMetaType::Void; hook.midx = staticMetaObject.indexOfSlot("handleDBusDisconnection()"); Q_ASSERT(hook.midx != -1); signalHooks.insert(QLatin1String("Disconnected:" DBUS_INTERFACE_LOCAL), hook); } void QDBusConnectionPrivate::setServer(QDBusServer *object, DBusServer *s, const QDBusErrorInternal &error) { mode = ServerMode; serverObject = object; object->d = this; if (!s) { handleError(error); return; } server = s; dbus_bool_t data_allocated = q_dbus_server_allocate_data_slot(&server_slot); if (data_allocated && server_slot < 0) return; dbus_bool_t watch_functions_set = q_dbus_server_set_watch_functions(server, qDBusAddWatch, qDBusRemoveWatch, qDBusToggleWatch, this, nullptr); //qDebug() << "watch_functions_set" << watch_functions_set; Q_UNUSED(watch_functions_set); dbus_bool_t time_functions_set = q_dbus_server_set_timeout_functions(server, qDBusAddTimeout, qDBusRemoveTimeout, qDBusToggleTimeout, this, nullptr); //qDebug() << "time_functions_set" << time_functions_set; Q_UNUSED(time_functions_set); q_dbus_server_set_new_connection_function(server, qDBusNewConnection, this, nullptr); dbus_bool_t data_set = q_dbus_server_set_data(server, server_slot, this, nullptr); //qDebug() << "data_set" << data_set; Q_UNUSED(data_set); } void QDBusConnectionPrivate::setPeer(DBusConnection *c, const QDBusErrorInternal &error) { mode = PeerMode; if (!c) { handleError(error); return; } connection = c; q_dbus_connection_set_exit_on_disconnect(connection, false); q_dbus_connection_set_watch_functions(connection, qDBusAddWatch, qDBusRemoveWatch, qDBusToggleWatch, this, nullptr); q_dbus_connection_set_timeout_functions(connection, qDBusAddTimeout, qDBusRemoveTimeout, qDBusToggleTimeout, this, nullptr); q_dbus_connection_set_dispatch_status_function(connection, qDBusUpdateDispatchStatus, this, nullptr); q_dbus_connection_add_filter(connection, qDBusSignalFilter, this, nullptr); watchForDBusDisconnection(); QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection); } static QDBusConnection::ConnectionCapabilities connectionCapabilies(DBusConnection *connection) { QDBusConnection::ConnectionCapabilities result; typedef dbus_bool_t (*can_send_type_t)(DBusConnection *, int); static can_send_type_t can_send_type = nullptr; #if defined(QT_LINKED_LIBDBUS) # if DBUS_VERSION-0 >= 0x010400 can_send_type = dbus_connection_can_send_type; # endif #elif QT_CONFIG(library) // run-time check if the next functions are available can_send_type = (can_send_type_t)qdbus_resolve_conditionally("dbus_connection_can_send_type"); #endif #ifndef DBUS_TYPE_UNIX_FD # define DBUS_TYPE_UNIX_FD int('h') #endif if (can_send_type && can_send_type(connection, DBUS_TYPE_UNIX_FD)) result |= QDBusConnection::UnixFileDescriptorPassing; return result; } void QDBusConnectionPrivate::setConnection(DBusConnection *dbc, const QDBusErrorInternal &error) { mode = ClientMode; if (!dbc) { handleError(error); return; } connection = dbc; const char *service = q_dbus_bus_get_unique_name(connection); Q_ASSERT(service); baseService = QString::fromUtf8(service); capabilities = connectionCapabilies(connection); q_dbus_connection_set_exit_on_disconnect(connection, false); q_dbus_connection_set_watch_functions(connection, qDBusAddWatch, qDBusRemoveWatch, qDBusToggleWatch, this, nullptr); q_dbus_connection_set_timeout_functions(connection, qDBusAddTimeout, qDBusRemoveTimeout, qDBusToggleTimeout, this, nullptr); q_dbus_connection_set_dispatch_status_function(connection, qDBusUpdateDispatchStatus, this, nullptr); q_dbus_connection_add_filter(connection, qDBusSignalFilter, this, nullptr); // Initialize the hooks for the NameAcquired and NameLost signals // we don't use connectSignal here because we don't need the rules to be sent to the bus // the bus will always send us these two signals SignalHook hook; hook.service = QDBusUtil::dbusService(); hook.path.clear(); // no matching hook.obj = this; hook.params << QMetaType::Void << QMetaType::QString; // both functions take a QString as parameter and return void hook.midx = staticMetaObject.indexOfSlot("registerServiceNoLock(QString)"); Q_ASSERT(hook.midx != -1); signalHooks.insert(QLatin1String("NameAcquired:" DBUS_INTERFACE_DBUS), hook); hook.midx = staticMetaObject.indexOfSlot("unregisterServiceNoLock(QString)"); Q_ASSERT(hook.midx != -1); signalHooks.insert(QLatin1String("NameLost:" DBUS_INTERFACE_DBUS), hook); // And initialize the hook for the NameOwnerChanged signal; // we don't use connectSignal here because the rules are added by connectSignal on a per-need basis hook.params.clear(); hook.params.reserve(4); hook.params << QMetaType::Void << QMetaType::QString << QMetaType::QString << QMetaType::QString; hook.midx = staticMetaObject.indexOfSlot("serviceOwnerChangedNoLock(QString,QString,QString)"); Q_ASSERT(hook.midx != -1); signalHooks.insert(QLatin1String("NameOwnerChanged:" DBUS_INTERFACE_DBUS), hook); watchForDBusDisconnection(); qDBusDebug() << this << ": connected successfully"; // schedule a dispatch: QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection); } extern "C"{ static void qDBusResultReceived(DBusPendingCall *pending, void *user_data) { QDBusPendingCallPrivate *call = reinterpret_cast(user_data); Q_ASSERT(call->pending == pending); Q_UNUSED(pending); QDBusConnectionPrivate::processFinishedCall(call); } } void QDBusConnectionPrivate::processFinishedCall(QDBusPendingCallPrivate *call) { QDBusConnectionPrivate *connection = const_cast(call->connection); auto locker = qt_unique_lock(call->mutex); connection->pendingCalls.removeOne(call); QDBusMessage &msg = call->replyMessage; if (call->pending) { // when processFinishedCall is called and pending call is not completed, // it means we received disconnected signal from libdbus if (q_dbus_pending_call_get_completed(call->pending)) { // decode the message DBusMessage *reply = q_dbus_pending_call_steal_reply(call->pending); msg = QDBusMessagePrivate::fromDBusMessage(reply, connection->capabilities); q_dbus_message_unref(reply); } else { msg = QDBusMessage::createError(QDBusError::Disconnected, QDBusUtil::disconnectedErrorMessage()); } } qDBusDebug() << connection << "got message reply:" << msg; // Check if the reply has the expected signature call->checkReceivedSignature(); if (!call->receiver.isNull() && call->methodIdx != -1 && msg.type() == QDBusMessage::ReplyMessage) { // Deliver the return values of a remote function call. // // There is only one connection and it is specified by idx // The slot must have the same parameter types that the message does // The slot may have less parameters than the message // The slot may optionally have one final parameter that is QDBusMessage // The slot receives read-only copies of the message (i.e., pass by value or by const-ref) QDBusCallDeliveryEvent *e = prepareReply(connection, call->receiver, call->methodIdx, call->metaTypes, msg); if (e) connection->postEventToThread(MessageResultReceivedAction, call->receiver, e); else qDBusDebug("Deliver failed!"); } if (call->pending) { q_dbus_pending_call_unref(call->pending); call->pending = nullptr; } // Are there any watchers? if (call->watcherHelper) call->watcherHelper->emitSignals(msg, call->sentMessage); call->waitForFinishedCondition.wakeAll(); locker.unlock(); if (msg.type() == QDBusMessage::ErrorMessage) emit connection->callWithCallbackFailed(QDBusError(msg), call->sentMessage); if (!call->ref.deref()) delete call; } bool QDBusConnectionPrivate::send(const QDBusMessage& message) { if (QDBusMessagePrivate::isLocal(message)) return true; // don't send; the reply will be retrieved by the caller // through the d_ptr->localReply link QDBusError error; DBusMessage *msg = QDBusMessagePrivate::toDBusMessage(message, capabilities, &error); if (!msg) { if (message.type() == QDBusMessage::MethodCallMessage) qWarning("QDBusConnection: error: could not send message to service \"%s\" path \"%s\" interface \"%s\" member \"%s\": %s", qPrintable(message.service()), qPrintable(message.path()), qPrintable(message.interface()), qPrintable(message.member()), qPrintable(error.message())); else if (message.type() == QDBusMessage::SignalMessage) qWarning("QDBusConnection: error: could not send signal to service \"%s\" path \"%s\" interface \"%s\" member \"%s\": %s", qPrintable(message.service()), qPrintable(message.path()), qPrintable(message.interface()), qPrintable(message.member()), qPrintable(error.message())); else qWarning("QDBusConnection: error: could not send %s message to service \"%s\": %s", message.type() == QDBusMessage::ReplyMessage ? "reply" : message.type() == QDBusMessage::ErrorMessage ? "error" : "invalid", qPrintable(message.service()), qPrintable(error.message())); lastError = error; return false; } q_dbus_message_set_no_reply(msg, true); // the reply would not be delivered to anything qDBusDebug() << this << "sending message (no reply):" << message; emit messageNeedsSending(nullptr, msg); return true; } // small helper to note long running blocking dbus calls. // these are generally a sign of fragile software (too long a call can either // lead to bad user experience, if it's running on the GUI thread for instance) // or break completely under load (hitting the call timeout). // // as a result, this is something we want to watch for. class QDBusBlockingCallWatcher { public: QDBusBlockingCallWatcher(const QDBusMessage &message) : m_message(message), m_maxCallTimeoutMs(0) { #if defined(QT_NO_DEBUG) // when in a release build, we default these to off. // this means that we only affect code that explicitly enables the warning. static int mainThreadWarningAmount = -1; static int otherThreadWarningAmount = -1; #else static int mainThreadWarningAmount = 200; static int otherThreadWarningAmount = 500; #endif static bool initializedAmounts = false; static QBasicMutex initializeMutex; auto locker = qt_unique_lock(initializeMutex); if (!initializedAmounts) { int tmp = 0; QByteArray env; bool ok = true; env = qgetenv("Q_DBUS_BLOCKING_CALL_MAIN_THREAD_WARNING_MS"); if (!env.isEmpty()) { tmp = env.toInt(&ok); if (ok) mainThreadWarningAmount = tmp; else qWarning("QDBusBlockingCallWatcher: Q_DBUS_BLOCKING_CALL_MAIN_THREAD_WARNING_MS must be an integer; value ignored"); } env = qgetenv("Q_DBUS_BLOCKING_CALL_OTHER_THREAD_WARNING_MS"); if (!env.isEmpty()) { tmp = env.toInt(&ok); if (ok) otherThreadWarningAmount = tmp; else qWarning("QDBusBlockingCallWatcher: Q_DBUS_BLOCKING_CALL_OTHER_THREAD_WARNING_MS must be an integer; value ignored"); } initializedAmounts = true; } locker.unlock(); // if this call is running on the main thread, we have a much lower // tolerance for delay because any long-term delay will wreck user // interactivity. if (qApp && qApp->thread() == QThread::currentThread()) m_maxCallTimeoutMs = mainThreadWarningAmount; else m_maxCallTimeoutMs = otherThreadWarningAmount; m_callTimer.start(); } ~QDBusBlockingCallWatcher() { if (m_maxCallTimeoutMs < 0) return; // disabled if (m_callTimer.elapsed() >= m_maxCallTimeoutMs) { qWarning("QDBusConnection: warning: blocking call took a long time (%d ms, max for this thread is %d ms) to service \"%s\" path \"%s\" interface \"%s\" member \"%s\"", int(m_callTimer.elapsed()), m_maxCallTimeoutMs, qPrintable(m_message.service()), qPrintable(m_message.path()), qPrintable(m_message.interface()), qPrintable(m_message.member())); } } private: QDBusMessage m_message; int m_maxCallTimeoutMs; QElapsedTimer m_callTimer; }; QDBusMessage QDBusConnectionPrivate::sendWithReply(const QDBusMessage &message, int sendMode, int timeout) { QDBusBlockingCallWatcher watcher(message); QDBusPendingCallPrivate *pcall = sendWithReplyAsync(message, nullptr, nullptr, nullptr, timeout); Q_ASSERT(pcall); if (pcall->replyMessage.type() == QDBusMessage::InvalidMessage) { // need to wait for the reply if (sendMode == QDBus::BlockWithGui) { pcall->watcherHelper = new QDBusPendingCallWatcherHelper; QEventLoop loop; loop.connect(pcall->watcherHelper, &QDBusPendingCallWatcherHelper::reply, &loop, &QEventLoop::quit); loop.connect(pcall->watcherHelper, &QDBusPendingCallWatcherHelper::error, &loop, &QEventLoop::quit); // enter the event loop and wait for a reply loop.exec(QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); } else { pcall->waitForFinished(); } } QDBusMessage reply = pcall->replyMessage; lastError = QDBusError(reply); // set or clear error if (!pcall->ref.deref()) delete pcall; return reply; } QDBusMessage QDBusConnectionPrivate::sendWithReplyLocal(const QDBusMessage &message) { qDBusDebug() << this << "sending message via local-loop:" << message; QDBusMessage localCallMsg = QDBusMessagePrivate::makeLocal(*this, message); bool handled = handleMessage(localCallMsg); if (!handled) { QString interface = message.interface(); if (interface.isEmpty()) interface = QLatin1String(""); return QDBusMessage::createError(QDBusError::InternalError, QLatin1String("Internal error trying to call %1.%2 at %3 (signature '%4'") .arg(interface, message.member(), message.path(), message.signature())); } // if the message was handled, there might be a reply QDBusMessage localReplyMsg = QDBusMessagePrivate::makeLocalReply(*this, localCallMsg); if (localReplyMsg.type() == QDBusMessage::InvalidMessage) { qWarning("QDBusConnection: cannot call local method '%s' at object %s (with signature '%s') " "on blocking mode", qPrintable(message.member()), qPrintable(message.path()), qPrintable(message.signature())); return QDBusMessage::createError( QDBusError(QDBusError::InternalError, QLatin1String("local-loop message cannot have delayed replies"))); } // there is a reply qDBusDebug() << this << "got message via local-loop:" << localReplyMsg; return localReplyMsg; } QDBusPendingCallPrivate *QDBusConnectionPrivate::sendWithReplyAsync(const QDBusMessage &message, QObject *receiver, const char *returnMethod, const char *errorMethod, int timeout) { QDBusPendingCallPrivate *pcall = new QDBusPendingCallPrivate(message, this); bool isLoopback; if ((isLoopback = isServiceRegisteredByThread(message.service()))) { // special case for local calls pcall->replyMessage = sendWithReplyLocal(message); } if (receiver && returnMethod) pcall->setReplyCallback(receiver, returnMethod); if (errorMethod) { pcall->watcherHelper = new QDBusPendingCallWatcherHelper; connect(pcall->watcherHelper, SIGNAL(error(QDBusError,QDBusMessage)), receiver, errorMethod, Qt::QueuedConnection); pcall->watcherHelper->moveToThread(thread()); } if ((receiver && returnMethod) || errorMethod) { // no one waiting, will delete pcall in processFinishedCall() pcall->ref.storeRelaxed(1); } else { // set double ref to prevent race between processFinishedCall() and ref counting // by QDBusPendingCall::QExplicitlySharedDataPointer pcall->ref.storeRelaxed(2); } if (isLoopback) { // a loopback call processFinishedCall(pcall); return pcall; } QDBusError error; DBusMessage *msg = QDBusMessagePrivate::toDBusMessage(message, capabilities, &error); if (!msg) { qWarning("QDBusConnection: error: could not send message to service \"%s\" path \"%s\" interface \"%s\" member \"%s\": %s", qPrintable(message.service()), qPrintable(message.path()), qPrintable(message.interface()), qPrintable(message.member()), qPrintable(error.message())); pcall->replyMessage = QDBusMessage::createError(error); lastError = error; processFinishedCall(pcall); } else { qDBusDebug() << this << "sending message:" << message; emit messageNeedsSending(pcall, msg, timeout); } return pcall; } void QDBusConnectionPrivate::sendInternal(QDBusPendingCallPrivate *pcall, void *message, int timeout) { QDBusError error; DBusPendingCall *pending = nullptr; DBusMessage *msg = static_cast(message); bool isNoReply = !pcall; Q_ASSERT(isNoReply == !!q_dbus_message_get_no_reply(msg)); checkThread(); if (isNoReply && q_dbus_connection_send(connection, msg, nullptr)) { // success } else if (!isNoReply && q_dbus_connection_send_with_reply(connection, msg, &pending, timeout)) { if (pending) { q_dbus_message_unref(msg); pcall->pending = pending; q_dbus_pending_call_set_notify(pending, qDBusResultReceived, pcall, nullptr); // DBus won't notify us when a peer disconnects or server terminates so we need to track these ourselves if (mode == QDBusConnectionPrivate::PeerMode || mode == QDBusConnectionPrivate::ClientMode) pendingCalls.append(pcall); return; } else { // we're probably disconnected at this point lastError = error = QDBusError(QDBusError::Disconnected, QDBusUtil::disconnectedErrorMessage()); } } else { lastError = error = QDBusError(QDBusError::NoMemory, QStringLiteral("Out of memory")); } q_dbus_message_unref(msg); if (pcall) { pcall->replyMessage = QDBusMessage::createError(error); processFinishedCall(pcall); } } bool QDBusConnectionPrivate::connectSignal(const QString &service, const QString &path, const QString &interface, const QString &name, const QStringList &argumentMatch, const QString &signature, QObject *receiver, const char *slot) { ArgMatchRules rules; rules.args = argumentMatch; return connectSignal(service, path, interface, name, rules, signature, receiver, slot); } bool QDBusConnectionPrivate::connectSignal(const QString &service, const QString &path, const QString &interface, const QString &name, const ArgMatchRules &argumentMatch, const QString &signature, QObject *receiver, const char *slot) { // check the slot QDBusConnectionPrivate::SignalHook hook; QString key; hook.signature = signature; if (!prepareHook(hook, key, service, path, interface, name, argumentMatch, receiver, slot, 0, false)) return false; // don't connect Q_ASSERT(thread() != QThread::currentThread()); return emit signalNeedsConnecting(key, hook); } bool QDBusConnectionPrivate::addSignalHook(const QString &key, const SignalHook &hook) { QDBusWriteLocker locker(ConnectAction, this); // avoid duplicating: QDBusConnectionPrivate::SignalHookHash::ConstIterator it = signalHooks.constFind(key); QDBusConnectionPrivate::SignalHookHash::ConstIterator end = signalHooks.constEnd(); for ( ; it != end && it.key() == key; ++it) { const QDBusConnectionPrivate::SignalHook &entry = it.value(); if (entry.service == hook.service && entry.path == hook.path && entry.signature == hook.signature && entry.obj == hook.obj && entry.midx == hook.midx && entry.argumentMatch == hook.argumentMatch) { // no need to compare the parameters if it's the same slot return false; // already there } } signalHooks.insert(key, hook); connect(hook.obj, &QObject::destroyed, this, &QDBusConnectionPrivate::objectDestroyed, Qt::ConnectionType(Qt::BlockingQueuedConnection | Qt::UniqueConnection)); MatchRefCountHash::iterator mit = matchRefCounts.find(hook.matchRule); if (mit != matchRefCounts.end()) { // Match already present mit.value() = mit.value() + 1; return true; } matchRefCounts.insert(hook.matchRule, 1); if (connection) { if (mode != QDBusConnectionPrivate::PeerMode) { qDBusDebug() << this << "Adding rule:" << hook.matchRule; q_dbus_bus_add_match(connection, hook.matchRule, nullptr); // Successfully connected the signal // Do we need to watch for this name? if (shouldWatchService(hook.service)) { WatchedServicesHash::mapped_type &data = watchedServices[hook.service]; if (++data.refcount == 1) { // we need to watch for this service changing ArgMatchRules rules; rules.args << hook.service; q_dbus_bus_add_match(connection, buildMatchRule(QDBusUtil::dbusService(), QString(), QDBusUtil::dbusInterface(), QDBusUtil::nameOwnerChanged(), rules, QString()), nullptr); data.owner = getNameOwnerNoCache(hook.service); qDBusDebug() << this << "Watching service" << hook.service << "for owner changes (current owner:" << data.owner << ")"; } } } } return true; } bool QDBusConnectionPrivate::disconnectSignal(const QString &service, const QString &path, const QString &interface, const QString &name, const QStringList &argumentMatch, const QString &signature, QObject *receiver, const char *slot) { ArgMatchRules rules; rules.args = argumentMatch; return disconnectSignal(service, path, interface, name, rules, signature, receiver, slot); } bool QDBusConnectionPrivate::disconnectSignal(const QString &service, const QString &path, const QString &interface, const QString &name, const ArgMatchRules &argumentMatch, const QString &signature, QObject *receiver, const char *slot) { // check the slot QDBusConnectionPrivate::SignalHook hook; QString key; QString name2 = name; if (name2.isNull()) name2.detach(); hook.signature = signature; if (!prepareHook(hook, key, service, path, interface, name, argumentMatch, receiver, slot, 0, false)) return false; // don't disconnect Q_ASSERT(thread() != QThread::currentThread()); return emit signalNeedsDisconnecting(key, hook); } bool QDBusConnectionPrivate::removeSignalHook(const QString &key, const SignalHook &hook) { // remove it from our list: QDBusWriteLocker locker(ConnectAction, this); QDBusConnectionPrivate::SignalHookHash::Iterator it = signalHooks.find(key); QDBusConnectionPrivate::SignalHookHash::Iterator end = signalHooks.end(); for ( ; it != end && it.key() == key; ++it) { const QDBusConnectionPrivate::SignalHook &entry = it.value(); if (entry.service == hook.service && entry.path == hook.path && entry.signature == hook.signature && entry.obj == hook.obj && entry.midx == hook.midx && entry.argumentMatch.args == hook.argumentMatch.args) { // no need to compare the parameters if it's the same slot removeSignalHookNoLock(it); return true; // it was there } } // the slot was not found return false; } QDBusConnectionPrivate::SignalHookHash::Iterator QDBusConnectionPrivate::removeSignalHookNoLock(SignalHookHash::Iterator it) { const SignalHook &hook = it.value(); bool erase = false; MatchRefCountHash::iterator i = matchRefCounts.find(hook.matchRule); if (i == matchRefCounts.end()) { qWarning("QDBusConnectionPrivate::disconnectSignal: MatchRule not found in matchRefCounts!!"); } else { if (i.value() == 1) { erase = true; matchRefCounts.erase(i); } else { i.value() = i.value() - 1; } } // we don't care about errors here if (connection && erase) { if (mode != QDBusConnectionPrivate::PeerMode) { qDBusDebug() << this << "Removing rule:" << hook.matchRule; q_dbus_bus_remove_match(connection, hook.matchRule, nullptr); // Successfully disconnected the signal // Were we watching for this name? WatchedServicesHash::Iterator sit = watchedServices.find(hook.service); if (sit != watchedServices.end()) { if (--sit.value().refcount == 0) { watchedServices.erase(sit); ArgMatchRules rules; rules.args << hook.service; q_dbus_bus_remove_match(connection, buildMatchRule(QDBusUtil::dbusService(), QString(), QDBusUtil::dbusInterface(), QDBusUtil::nameOwnerChanged(), rules, QString()), nullptr); } } } } return signalHooks.erase(it); } void QDBusConnectionPrivate::registerObject(const ObjectTreeNode *node) { connect(node->obj, &QObject::destroyed, this, &QDBusConnectionPrivate::objectDestroyed, Qt::ConnectionType(Qt::BlockingQueuedConnection | Qt::UniqueConnection)); if (node->flags & (QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportNonScriptableSignals)) { QDBusAdaptorConnector *connector = qDBusCreateAdaptorConnector(node->obj); if (node->flags & (QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportNonScriptableSignals)) { connector->disconnectAllSignals(node->obj); connector->connectAllSignals(node->obj); } connect(connector, SIGNAL(relaySignal(QObject*,const QMetaObject*,int,QVariantList)), this, SLOT(relaySignal(QObject*,const QMetaObject*,int,QVariantList)), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection)); } } void QDBusConnectionPrivate::unregisterObject(const QString &path, QDBusConnection::UnregisterMode mode) { QDBusConnectionPrivate::ObjectTreeNode *node = &rootNode; QVector pathComponents; int i; if (path == QLatin1String("/")) { i = 0; } else { pathComponents = path.splitRef(QLatin1Char('/')); i = 1; } huntAndUnregister(pathComponents, i, mode, node); } void QDBusConnectionPrivate::connectRelay(const QString &service, const QString &path, const QString &interface, QDBusAbstractInterface *receiver, const QMetaMethod &signal) { // this function is called by QDBusAbstractInterface when one of its signals is connected // we set up a relay from D-Bus into it SignalHook hook; QString key; QByteArray sig; sig.append(QSIGNAL_CODE + '0'); sig.append(signal.methodSignature()); if (!prepareHook(hook, key, service, path, interface, QString(), ArgMatchRules(), receiver, sig, QDBusAbstractInterface::staticMetaObject.methodCount(), true)) return; // don't connect Q_ASSERT(thread() != QThread::currentThread()); emit signalNeedsConnecting(key, hook); } void QDBusConnectionPrivate::disconnectRelay(const QString &service, const QString &path, const QString &interface, QDBusAbstractInterface *receiver, const QMetaMethod &signal) { // this function is called by QDBusAbstractInterface when one of its signals is disconnected // we remove relay from D-Bus into it SignalHook hook; QString key; QByteArray sig; sig.append(QSIGNAL_CODE + '0'); sig.append(signal.methodSignature()); if (!prepareHook(hook, key, service, path, interface, QString(), ArgMatchRules(), receiver, sig, QDBusAbstractInterface::staticMetaObject.methodCount(), true)) return; // don't disconnect Q_ASSERT(thread() != QThread::currentThread()); emit signalNeedsDisconnecting(key, hook); } bool QDBusConnectionPrivate::shouldWatchService(const QString &service) { // we don't have to watch anything in peer mode if (mode != ClientMode) return false; // we don't have to watch wildcard services (empty strings) if (service.isEmpty()) return false; // we don't have to watch the bus driver if (service == QDBusUtil::dbusService()) return false; return true; } /*! Sets up a watch rule for service \a service for the change described by mode \a mode. When the change happens, slot \a member in object \a obj will be called. The caller should call QDBusConnectionPrivate::shouldWatchService() before calling this function to check whether the service needs to be watched at all. Failing to do so may add rules that are never activated. */ void QDBusConnectionPrivate::watchService(const QString &service, QDBusServiceWatcher::WatchMode mode, QObject *obj, const char *member) { ArgMatchRules matchArgs = matchArgsForService(service, mode); connectSignal(QDBusUtil::dbusService(), QString(), QDBusUtil::dbusInterface(), QDBusUtil::nameOwnerChanged(), matchArgs, QString(), obj, member); } /*! Removes a watch rule set up by QDBusConnectionPrivate::watchService(). The arguments to this function must be the same as the ones for that function. Sets up a watch rule for service \a service for the change described by mode \a mode. When the change happens, slot \a member in object \a obj will be called. */ void QDBusConnectionPrivate::unwatchService(const QString &service, QDBusServiceWatcher::WatchMode mode, QObject *obj, const char *member) { ArgMatchRules matchArgs = matchArgsForService(service, mode); disconnectSignal(QDBusUtil::dbusService(), QString(), QDBusUtil::dbusInterface(), QDBusUtil::nameOwnerChanged(), matchArgs, QString(), obj, member); } QString QDBusConnectionPrivate::getNameOwner(const QString& serviceName) { if (QDBusUtil::isValidUniqueConnectionName(serviceName)) return serviceName; if (!connection) return QString(); { // acquire a read lock for the cache QReadLocker locker(&lock); WatchedServicesHash::ConstIterator it = watchedServices.constFind(serviceName); if (it != watchedServices.constEnd()) return it->owner; } // not cached return getNameOwnerNoCache(serviceName); } QString QDBusConnectionPrivate::getNameOwnerNoCache(const QString &serviceName) { QDBusMessage msg = QDBusMessage::createMethodCall(QDBusUtil::dbusService(), QDBusUtil::dbusPath(), QDBusUtil::dbusInterface(), QStringLiteral("GetNameOwner")); QDBusMessagePrivate::setParametersValidated(msg, true); msg << serviceName; QDBusPendingCallPrivate *pcall = sendWithReplyAsync(msg, nullptr, nullptr, nullptr); if (thread() == QThread::currentThread()) { // this function may be called in our own thread and // QDBusPendingCallPrivate::waitForFinished() would deadlock there q_dbus_pending_call_block(pcall->pending); } pcall->waitForFinished(); msg = pcall->replyMessage; if (!pcall->ref.deref()) delete pcall; if (msg.type() == QDBusMessage::ReplyMessage) return msg.arguments().at(0).toString(); return QString(); } QDBusMetaObject * QDBusConnectionPrivate::findMetaObject(const QString &service, const QString &path, const QString &interface, QDBusError &error) { // service must be a unique connection name if (!interface.isEmpty()) { QDBusReadLocker locker(FindMetaObject1Action, this); QDBusMetaObject *mo = cachedMetaObjects.value(interface, 0); if (mo) return mo; } // introspect the target object QDBusMessage msg = QDBusMessage::createMethodCall(service, path, QDBusUtil::dbusInterfaceIntrospectable(), QStringLiteral("Introspect")); QDBusMessagePrivate::setParametersValidated(msg, true); QDBusMessage reply = sendWithReply(msg, QDBus::Block); // it doesn't exist yet, we have to create it QDBusWriteLocker locker(FindMetaObject2Action, this); QDBusMetaObject *mo = nullptr; if (!interface.isEmpty()) mo = cachedMetaObjects.value(interface, 0); if (mo) // maybe it got created when we switched from read to write lock return mo; QString xml; if (reply.type() == QDBusMessage::ReplyMessage) { if (reply.signature() == QLatin1String("s")) // fetch the XML description xml = reply.arguments().at(0).toString(); } else { error = QDBusError(reply); lastError = error; if (reply.type() != QDBusMessage::ErrorMessage || error.type() != QDBusError::UnknownMethod) return nullptr; // error } // release the lock and return QDBusMetaObject *result = QDBusMetaObject::createMetaObject(interface, xml, cachedMetaObjects, error); lastError = error; return result; } void QDBusConnectionPrivate::registerService(const QString &serviceName) { QDBusWriteLocker locker(RegisterServiceAction, this); registerServiceNoLock(serviceName); } void QDBusConnectionPrivate::registerServiceNoLock(const QString &serviceName) { serviceNames.append(serviceName); } void QDBusConnectionPrivate::unregisterService(const QString &serviceName) { QDBusWriteLocker locker(UnregisterServiceAction, this); unregisterServiceNoLock(serviceName); } void QDBusConnectionPrivate::unregisterServiceNoLock(const QString &serviceName) { serviceNames.removeAll(serviceName); } bool QDBusConnectionPrivate::isServiceRegisteredByThread(const QString &serviceName) { if (!serviceName.isEmpty() && serviceName == baseService) return true; if (serviceName == QDBusUtil::dbusService()) return false; QDBusReadLocker locker(UnregisterServiceAction, this); return serviceNames.contains(serviceName); } void QDBusConnectionPrivate::postEventToThread(int action, QObject *object, QEvent *ev) { QDBusLockerBase::reportThreadAction(action, QDBusLockerBase::BeforePost, this); QCoreApplication::postEvent(object, ev); QDBusLockerBase::reportThreadAction(action, QDBusLockerBase::AfterPost, this); } QT_END_NAMESPACE #endif // QT_NO_DBUS