1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include <stdio.h>
5#include <stdlib.h>
6
7#include <QtCore/QCoreApplication>
8#include <QtCore/QRegularExpression>
9#include <QtCore/QStringList>
10#include <QtCore/qmetaobject.h>
11#include <QtXml/QDomDocument>
12#include <QtXml/QDomElement>
13#include <QtDBus/QDBusConnection>
14#include <QtDBus/QDBusInterface>
15#include <QtDBus/QDBusConnectionInterface>
16#include <QtDBus/QDBusVariant>
17#include <QtDBus/QDBusArgument>
18#include <QtDBus/QDBusMessage>
19#include <QtDBus/QDBusReply>
20#include <private/qdbusutil_p.h>
21
22QT_BEGIN_NAMESPACE
23Q_DBUS_EXPORT extern bool qt_dbus_metaobject_skip_annotations;
24QT_END_NAMESPACE
25
26static QDBusConnection connection(QLatin1String(""));
27static bool printArgumentsLiterally = false;
28
29static void showUsage()
30{
31 printf(format: "Usage: qdbus [--system] [--bus busaddress] [--literal] [servicename] [path] [method] [args]\n"
32 "\n"
33 " servicename the service to connect to (e.g., org.freedesktop.DBus)\n"
34 " path the path to the object (e.g., /)\n"
35 " method the method to call, with or without the interface\n"
36 " args arguments to pass to the call\n"
37 "With 0 arguments, qdbus will list the services available on the bus\n"
38 "With just the servicename, qdbus will list the object paths available on the service\n"
39 "With service name and object path, qdbus will list the methods, signals and properties available on the object\n"
40 "\n"
41 "Options:\n"
42 " --system connect to the system bus\n"
43 " --bus busaddress connect to a custom bus\n"
44 " --literal print replies literally\n"
45 );
46}
47
48static void printArg(const QVariant &v)
49{
50 if (printArgumentsLiterally) {
51 printf(format: "%s\n", qPrintable(QDBusUtil::argumentToString(v)));
52 return;
53 }
54
55 if (v.metaType() == QMetaType::fromType<QStringList>()) {
56 const QStringList sl = v.toStringList();
57 for (const QString &s : sl)
58 printf(format: "%s\n", qPrintable(s));
59 } else if (v.metaType() == QMetaType::fromType<QVariantList>()) {
60 const QVariantList vl = v.toList();
61 for (const QVariant &var : vl)
62 printArg(v: var);
63 } else if (v.metaType() == QMetaType::fromType<QVariantMap>()) {
64 const QVariantMap map = v.toMap();
65 QVariantMap::ConstIterator it = map.constBegin();
66 for ( ; it != map.constEnd(); ++it) {
67 printf(format: "%s: ", qPrintable(it.key()));
68 printArg(v: it.value());
69 }
70 } else if (v.metaType() == QMetaType::fromType<QDBusVariant>()) {
71 printArg(v: qvariant_cast<QDBusVariant>(v).variant());
72 } else if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
73 QDBusArgument arg = qvariant_cast<QDBusArgument>(v);
74 if (arg.currentSignature() == QLatin1String("av"))
75 printArg(v: qdbus_cast<QVariantList>(arg));
76 else if (arg.currentSignature() == QLatin1String("a{sv}"))
77 printArg(v: qdbus_cast<QVariantMap>(arg));
78 else
79 printf(format: "qdbus: I don't know how to display an argument of type '%s', run with --literal.\n",
80 qPrintable(arg.currentSignature()));
81 } else if (v.metaType().isValid()) {
82 printf(format: "%s\n", qPrintable(v.toString()));
83 }
84}
85
86static void listObjects(const QString &service, const QString &path)
87{
88 // make a low-level call, to avoid introspecting the Introspectable interface
89 QDBusMessage call = QDBusMessage::createMethodCall(destination: service, path: path.isEmpty() ? QLatin1String("/") : path,
90 interface: QLatin1String("org.freedesktop.DBus.Introspectable"),
91 method: QLatin1String("Introspect"));
92 QDBusReply<QString> xml = connection.call(message: call);
93
94 if (path.isEmpty()) {
95 // top-level
96 if (xml.isValid()) {
97 printf(format: "/\n");
98 } else {
99 QDBusError err = xml.error();
100 if (err.type() == QDBusError::ServiceUnknown)
101 fprintf(stderr, format: "Service '%s' does not exist.\n", qPrintable(service));
102 else
103 printf(format: "Error: %s\n%s\n", qPrintable(err.name()), qPrintable(err.message()));
104 exit(status: 2);
105 }
106 } else if (!xml.isValid()) {
107 // this is not the first object, just fail silently
108 return;
109 }
110
111 QDomDocument doc;
112 doc.setContent(data: xml.value());
113 QDomElement node = doc.documentElement();
114 QDomElement child = node.firstChildElement();
115 while (!child.isNull()) {
116 if (child.tagName() == QLatin1String("node")) {
117 QString sub = path + QLatin1Char('/') + child.attribute(name: QLatin1String("name"));
118 printf(format: "%s\n", qPrintable(sub));
119 listObjects(service, path: sub);
120 }
121 child = child.nextSiblingElement();
122 }
123}
124
125static void listInterface(const QString &service, const QString &path, const QString &interface)
126{
127 QDBusInterface iface(service, path, interface, connection);
128 if (!iface.isValid()) {
129 QDBusError err(iface.lastError());
130 fprintf(stderr, format: "Interface '%s' not available in object %s at %s:\n%s (%s)\n",
131 qPrintable(interface), qPrintable(path), qPrintable(service),
132 qPrintable(err.name()), qPrintable(err.message()));
133 exit(status: 1);
134 }
135 const QMetaObject *mo = iface.metaObject();
136
137 // properties
138 for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
139 QMetaProperty mp = mo->property(index: i);
140 printf(format: "property ");
141
142 if (mp.isReadable() && mp.isWritable())
143 printf(format: "readwrite");
144 else if (mp.isReadable())
145 printf(format: "read");
146 else
147 printf(format: "write");
148
149 printf(format: " %s %s.%s\n", mp.typeName(), qPrintable(interface), mp.name());
150 }
151
152 // methods (signals and slots)
153 for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) {
154 QMetaMethod mm = mo->method(index: i);
155
156 QByteArray signature = mm.methodSignature();
157 signature.truncate(pos: signature.indexOf(c: '('));
158 printf(format: "%s %s%s%s %s.%s(",
159 mm.methodType() == QMetaMethod::Signal ? "signal" : "method",
160 mm.tag(), *mm.tag() ? " " : "",
161 *mm.typeName() ? mm.typeName() : "void",
162 qPrintable(interface), signature.constData());
163
164 QList<QByteArray> types = mm.parameterTypes();
165 QList<QByteArray> names = mm.parameterNames();
166 bool first = true;
167 for (int i = 0; i < types.size(); ++i) {
168 printf(format: "%s%s",
169 first ? "" : ", ",
170 types.at(i).constData());
171 if (!names.at(i).isEmpty())
172 printf(format: " %s", names.at(i).constData());
173 first = false;
174 }
175 printf(format: ")\n");
176 }
177}
178
179static void listAllInterfaces(const QString &service, const QString &path)
180{
181 // make a low-level call, to avoid introspecting the Introspectable interface
182 QDBusMessage call = QDBusMessage::createMethodCall(destination: service, path: path.isEmpty() ? QLatin1String("/") : path,
183 interface: QLatin1String("org.freedesktop.DBus.Introspectable"),
184 method: QLatin1String("Introspect"));
185 QDBusReply<QString> xml = connection.call(message: call);
186
187 if (!xml.isValid()) {
188 QDBusError err = xml.error();
189 if (err.type() == QDBusError::ServiceUnknown)
190 fprintf(stderr, format: "Service '%s' does not exist.\n", qPrintable(service));
191 else
192 printf(format: "Error: %s\n%s\n", qPrintable(err.name()), qPrintable(err.message()));
193 exit(status: 2);
194 }
195
196 QDomDocument doc;
197 doc.setContent(data: xml.value());
198 QDomElement node = doc.documentElement();
199 QDomElement child = node.firstChildElement();
200 while (!child.isNull()) {
201 if (child.tagName() == QLatin1String("interface")) {
202 QString ifaceName = child.attribute(name: QLatin1String("name"));
203 if (QDBusUtil::isValidInterfaceName(ifaceName))
204 listInterface(service, path, interface: ifaceName);
205 else {
206 qWarning(msg: "Invalid D-BUS interface name '%s' found while parsing introspection",
207 qPrintable(ifaceName));
208 }
209 }
210 child = child.nextSiblingElement();
211 }
212}
213
214static QStringList readList(QStringList &args)
215{
216 args.takeFirst();
217
218 QStringList retval;
219 while (!args.isEmpty() && args.at(i: 0) != QLatin1String(")"))
220 retval += args.takeFirst();
221
222 if (args.value(i: 0) == QLatin1String(")"))
223 args.takeFirst();
224
225 return retval;
226}
227
228static int placeCall(const QString &service, const QString &path, const QString &interface,
229 const QString &member, const QStringList& arguments, bool try_prop=true)
230{
231 QDBusInterface iface(service, path, interface, connection);
232
233 // Don't check whether the interface is valid to allow DBus try to
234 // activate the service if possible.
235
236 QList<int> knownIds;
237 bool matchFound = false;
238 QStringList args = arguments;
239 QVariantList params;
240 if (!args.isEmpty()) {
241 const QMetaObject *mo = iface.metaObject();
242 QByteArray match = member.toLatin1();
243 match += '(';
244
245 for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) {
246 QMetaMethod mm = mo->method(index: i);
247 QByteArray signature = mm.methodSignature();
248 if (signature.startsWith(bv: match))
249 knownIds += i;
250 }
251
252
253 while (!matchFound) {
254 args = arguments; // reset
255 params.clear();
256 if (knownIds.isEmpty()) {
257 // Failed to set property after falling back?
258 // Bail out without displaying an error
259 if (!try_prop)
260 return 1;
261 if (try_prop && args.size() == 1) {
262 QStringList proparg;
263 proparg += interface;
264 proparg += member;
265 proparg += args.first();
266 if (!placeCall(service, path, interface: "org.freedesktop.DBus.Properties", member: "Set", arguments: proparg, try_prop: false))
267 return 0;
268 }
269 fprintf(stderr, format: "Cannot find '%s.%s' in object %s at %s\n",
270 qPrintable(interface), qPrintable(member), qPrintable(path),
271 qPrintable(service));
272 return 1;
273 }
274
275 QMetaMethod mm = mo->method(index: knownIds.takeFirst());
276 QList<QByteArray> types = mm.parameterTypes();
277 for (int i = 0; i < types.size(); ++i) {
278 if (types.at(i).endsWith(c: '&')) {
279 // reference (and not a reference to const): output argument
280 // we're done with the inputs
281 while (types.size() > i)
282 types.removeLast();
283 break;
284 }
285 }
286
287 for (int i = 0; !args.isEmpty() && i < types.size(); ++i) {
288 const QMetaType metaType = QMetaType::fromName(name: types.at(i));
289 if (!metaType.isValid()) {
290 fprintf(stderr, format: "Cannot call method '%s' because type '%s' is unknown to this tool\n",
291 qPrintable(member), types.at(i).constData());
292 return 1;
293 }
294 const int id = metaType.id();
295
296 QVariant p;
297 QString argument;
298 if ((id == QMetaType::QVariantList || id == QMetaType::QStringList)
299 && args.at(i: 0) == QLatin1String("("))
300 p = readList(args);
301 else
302 p = argument = args.takeFirst();
303
304 if (id == QMetaType::UChar) {
305 // special case: QVariant::convert doesn't convert to/from
306 // UChar because it can't decide if it's a character or a number
307 p = QVariant::fromValue<uchar>(value: p.toUInt());
308 } else if (id < QMetaType::User && id != QMetaType::QVariantMap) {
309 p.convert(type: metaType);
310 if (!p.isValid()) {
311 fprintf(stderr, format: "Could not convert '%s' to type '%s'.\n",
312 qPrintable(argument), types.at(i).constData());
313 return 1 ;
314 }
315 } else if (id == qMetaTypeId<QDBusVariant>()) {
316 QDBusVariant tmp(p);
317 p = QVariant::fromValue(value: tmp);
318 } else if (id == qMetaTypeId<QDBusObjectPath>()) {
319 QDBusObjectPath path(argument);
320 if (path.path().isNull()) {
321 fprintf(stderr, format: "Cannot pass argument '%s' because it is not a valid object path.\n",
322 qPrintable(argument));
323 return 1;
324 }
325 p = QVariant::fromValue(value: path);
326 } else if (id == qMetaTypeId<QDBusSignature>()) {
327 QDBusSignature sig(argument);
328 if (sig.signature().isNull()) {
329 fprintf(stderr, format: "Cannot pass argument '%s' because it is not a valid signature.\n",
330 qPrintable(argument));
331 return 1;
332 }
333 p = QVariant::fromValue(value: sig);
334 } else {
335 fprintf(stderr, format: "Sorry, can't pass arg of type '%s'.\n",
336 types.at(i).constData());
337 return 1;
338 }
339 params += p;
340 }
341 if (params.size() == types.size() && args.isEmpty())
342 matchFound = true;
343 else if (knownIds.isEmpty()) {
344 fprintf(stderr, format: "Invalid number of parameters\n");
345 return 1;
346 }
347 } // while (!matchFound)
348 } // if (!args.isEmpty()
349
350 QDBusMessage reply = iface.callWithArgumentList(mode: QDBus::Block, method: member, args: params);
351 if (reply.type() == QDBusMessage::ErrorMessage) {
352 QDBusError err = reply;
353 // Failed to retrieve property after falling back?
354 // Bail out without displaying an error
355 if (!try_prop)
356 return 1;
357 if (err.type() == QDBusError::UnknownMethod && try_prop) {
358 QStringList proparg;
359 proparg += interface;
360 proparg += member;
361 if (!placeCall(service, path, interface: "org.freedesktop.DBus.Properties", member: "Get", arguments: proparg, try_prop: false))
362 return 0;
363 }
364 if (err.type() == QDBusError::ServiceUnknown)
365 fprintf(stderr, format: "Service '%s' does not exist.\n", qPrintable(service));
366 else
367 printf(format: "Error: %s\n%s\n", qPrintable(err.name()), qPrintable(err.message()));
368 return 2;
369 } else if (reply.type() != QDBusMessage::ReplyMessage) {
370 fprintf(stderr, format: "Invalid reply type %d\n", int(reply.type()));
371 return 1;
372 }
373
374 const QVariantList replyArguments = reply.arguments();
375 for (const QVariant &v : replyArguments)
376 printArg(v);
377
378 return 0;
379}
380
381static bool globServices(QDBusConnectionInterface *bus, const QString &glob)
382{
383 QRegularExpression pattern(QRegularExpression::wildcardToRegularExpression(str: glob));
384 if (!pattern.isValid())
385 return false;
386
387 QStringList names = bus->registeredServiceNames();
388 names.sort();
389 for (const QString &name : std::as_const(t&: names))
390 if (pattern.match(subject: name).hasMatch())
391 printf(format: "%s\n", qPrintable(name));
392
393 return true;
394}
395
396static void printAllServices(QDBusConnectionInterface *bus)
397{
398 const QStringList services = bus->registeredServiceNames();
399 QMap<QString, QStringList> servicesWithAliases;
400
401 for (const QString &serviceName : services) {
402 QDBusReply<QString> reply = bus->serviceOwner(name: serviceName);
403 QString owner = reply;
404 if (owner.isEmpty())
405 owner = serviceName;
406 servicesWithAliases[owner].append(t: serviceName);
407 }
408
409 for (QMap<QString,QStringList>::const_iterator it = servicesWithAliases.constBegin();
410 it != servicesWithAliases.constEnd(); ++it) {
411 QStringList names = it.value();
412 names.sort();
413 printf(format: "%s\n", qPrintable(names.join(QLatin1String("\n "))));
414 }
415}
416
417int main(int argc, char **argv)
418{
419 QT_PREPEND_NAMESPACE(qt_dbus_metaobject_skip_annotations) = true;
420 QCoreApplication app(argc, argv);
421 QStringList args = app.arguments();
422 args.takeFirst();
423
424 bool connectionOpened = false;
425 while (!args.isEmpty() && args.at(i: 0).startsWith(c: QLatin1Char('-'))) {
426 QString arg = args.takeFirst();
427 if (arg == QLatin1String("--system")) {
428 connection = QDBusConnection::systemBus();
429 connectionOpened = true;
430 } else if (arg == QLatin1String("--bus")) {
431 if (!args.isEmpty()) {
432 connection = QDBusConnection::connectToBus(address: args.takeFirst(), name: "QDBus");
433 connectionOpened = true;
434 }
435 } else if (arg == QLatin1String("--literal")) {
436 printArgumentsLiterally = true;
437 } else if (arg == QLatin1String("--help")) {
438 showUsage();
439 return 0;
440 }
441 }
442
443 if (!connectionOpened)
444 connection = QDBusConnection::sessionBus();
445
446 if (!connection.isConnected()) {
447 const QDBusError lastError = connection.lastError();
448 if (lastError.isValid()) {
449 fprintf(stderr, format: "Could not connect to D-Bus server: %s: %s\n",
450 qPrintable(lastError.name()),
451 qPrintable(lastError.message()));
452 } else {
453 // an invalid last error means that we were not able to even load the D-Bus library
454 fprintf(stderr, format: "Could not connect to D-Bus server: Unable to load dbus libraries\n");
455 }
456 return 1;
457 }
458
459 QDBusConnectionInterface *bus = connection.interface();
460 if (args.isEmpty()) {
461 printAllServices(bus);
462 return 0;
463 }
464
465 QString service = args.takeFirst();
466 if (!QDBusUtil::isValidBusName(busName: service)) {
467 if (service.contains(c: QLatin1Char('*'))) {
468 if (globServices(bus, glob: service))
469 return 0;
470 }
471 fprintf(stderr, format: "Service '%s' is not a valid name.\n", qPrintable(service));
472 return 1;
473 }
474
475 if (args.isEmpty()) {
476 listObjects(service, path: QString());
477 return 0;
478 }
479
480 QString path = args.takeFirst();
481 if (!QDBusUtil::isValidObjectPath(path)) {
482 fprintf(stderr, format: "Path '%s' is not a valid path name.\n", qPrintable(path));
483 return 1;
484 }
485 if (args.isEmpty()) {
486 listAllInterfaces(service, path);
487 return 0;
488 }
489
490 QString interface = args.takeFirst();
491 QString member;
492 int pos = interface.lastIndexOf(c: QLatin1Char('.'));
493 if (pos == -1) {
494 member = interface;
495 interface.clear();
496 } else {
497 member = interface.mid(position: pos + 1);
498 interface.truncate(pos);
499 }
500 if (!interface.isEmpty() && !QDBusUtil::isValidInterfaceName(ifaceName: interface)) {
501 fprintf(stderr, format: "Interface '%s' is not a valid interface name.\n", qPrintable(interface));
502 exit(status: 1);
503 }
504 if (!QDBusUtil::isValidMemberName(memberName: member)) {
505 fprintf(stderr, format: "Method name '%s' is not a valid member name.\n", qPrintable(member));
506 return 1;
507 }
508
509 int ret = placeCall(service, path, interface, member, arguments: args);
510 return ret;
511}
512
513

source code of qttools/src/qdbus/qdbus/qdbus.cpp