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 | |
22 | QT_BEGIN_NAMESPACE |
23 | Q_DBUS_EXPORT extern bool qt_dbus_metaobject_skip_annotations; |
24 | QT_END_NAMESPACE |
25 | |
26 | static QDBusConnection connection(QLatin1String("" )); |
27 | static bool printArgumentsLiterally = false; |
28 | |
29 | static 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 | |
48 | static 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 | |
86 | static 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 | |
125 | static 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 | |
179 | static 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 | |
214 | static 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 | |
228 | static 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 | |
381 | static 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 | |
396 | static 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 | |
417 | int 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 | |