1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtSystems module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #include <stdio.h> |
35 | |
36 | #include <QtCore> |
37 | #include <QTextStream> |
38 | #include <qservicemanager.h> |
39 | #include <servicemetadata_p.h> |
40 | #include <QString> |
41 | #include <QDir> |
42 | |
43 | static const char * const errorTable[] = { |
44 | "No error" , //0 |
45 | "Storage read error" , |
46 | "Invalid service location" , |
47 | "Invalid service xml" , |
48 | "Invalid service interface descriptor" , |
49 | "Service already exists" , |
50 | "Interface implementation already exists" , |
51 | "Loading of plug-in failed" , |
52 | "Service or interface not found" , |
53 | "Insufficient capabilities to access service" , |
54 | "Unknown error" , |
55 | }; |
56 | |
57 | class CommandProcessor : public QObject |
58 | { |
59 | Q_OBJECT |
60 | |
61 | public: |
62 | CommandProcessor(QObject *parent = 0); |
63 | ~CommandProcessor(); |
64 | |
65 | void execute(const QStringList &options, const QString &cmd, const QStringList &args); |
66 | void showUsage(); |
67 | static void showUsage(QTextStream *stream); |
68 | int errorCode(); |
69 | |
70 | public slots: |
71 | void browse(const QStringList &args); |
72 | void search(const QStringList &args); |
73 | void add(const QStringList &args); |
74 | void remove(const QStringList &args); |
75 | void dbusservice(const QStringList &args); |
76 | void setdefault(const QStringList &args); |
77 | |
78 | private: |
79 | bool setOptions(const QStringList &options); |
80 | void setErrorCode(int error); |
81 | void showAllEntries(); |
82 | void showInterfaceInfo(const QServiceFilter &filter); |
83 | void showInterfaceInfo(QList<QServiceInterfaceDescriptor> descriptors); |
84 | void showServiceInfo(const QString &service); |
85 | |
86 | QServiceManager *serviceManager; |
87 | QTextStream *stdoutStream; |
88 | int m_error; |
89 | }; |
90 | |
91 | CommandProcessor::CommandProcessor(QObject *parent) |
92 | : QObject(parent), |
93 | serviceManager(0), |
94 | stdoutStream(new QTextStream(stdout)), |
95 | m_error(0) |
96 | { |
97 | } |
98 | |
99 | CommandProcessor::~CommandProcessor() |
100 | { |
101 | delete stdoutStream; |
102 | } |
103 | |
104 | void CommandProcessor::execute(const QStringList &options, const QString &cmd, const QStringList &args) |
105 | { |
106 | if (cmd.isEmpty()) { |
107 | *stdoutStream << "Error: no command given\n\n" ; |
108 | showUsage(); |
109 | return; |
110 | } |
111 | |
112 | if (!setOptions(options)) |
113 | return; |
114 | |
115 | int methodIndex = metaObject()->indexOfMethod(method: cmd.toLatin1() + "(QStringList)" ); |
116 | if (methodIndex < 0) { |
117 | *stdoutStream << "Bad command: " << cmd << "\n\n" ; |
118 | showUsage(); |
119 | return; |
120 | } |
121 | |
122 | if (!metaObject()->method(index: methodIndex).invoke(object: this, Q_ARG(QStringList, args))) |
123 | *stdoutStream << "Cannot invoke method for command:" << cmd << '\n'; |
124 | } |
125 | |
126 | void CommandProcessor::showUsage() |
127 | { |
128 | showUsage(stream: stdoutStream); |
129 | } |
130 | |
131 | void CommandProcessor::showUsage(QTextStream *stream) |
132 | { |
133 | *stream << "Usage: servicefw [options] <command> [command parameters]\n\n" |
134 | "Commands:\n" |
135 | "\tbrowse List all registered services\n" |
136 | "\tsearch Search for a service or interface\n" |
137 | "\tadd Register a service\n" |
138 | "\tremove Unregister a service\n" |
139 | "\tdbusservice Generates a .service file for D-Bus service autostart\n" |
140 | "\n" |
141 | "Options:\n" |
142 | "\t--system Use the system-wide services database instead of the\n" |
143 | "\t user-specific database\n" |
144 | "\t--user Use the user-specific services database for add/remove.\n" |
145 | "\t This is the default\n" |
146 | "\n" ; |
147 | } |
148 | |
149 | void CommandProcessor::browse(const QStringList &args) |
150 | { |
151 | Q_UNUSED(args); |
152 | showAllEntries(); |
153 | } |
154 | |
155 | void CommandProcessor::setdefault(const QStringList &args) |
156 | { |
157 | if (args.isEmpty() || args.count() < 2) { |
158 | *stdoutStream << "Usage:\n\tsetdefault <interface> <service>\n\n" |
159 | "Examples:\n" |
160 | "\tsetdefault com.nokia.SomeInterface foo" ; |
161 | return; |
162 | } |
163 | |
164 | |
165 | const QString &serviceInterface = args[0]; |
166 | const QString &service = args[1]; |
167 | bool res = serviceManager->setInterfaceDefault(service, interfaceName: serviceInterface); |
168 | if(!res) { |
169 | *stdoutStream << "Failed to set interface default" |
170 | << errorTable[serviceManager->error()]; |
171 | } |
172 | } |
173 | |
174 | void CommandProcessor::search(const QStringList &args) |
175 | { |
176 | if (args.isEmpty()) { |
177 | *stdoutStream << "Usage:\n\tsearch <service|interface [version]>\n\n" |
178 | "Examples:\n" |
179 | "\tsearch SomeService\n" |
180 | "\tsearch com.nokia.SomeInterface\n" |
181 | "\tsearch com.nokia.SomeInterface 3.5\n" |
182 | "\tsearch com.nokia.SomeInterface 3.5+\n" ; |
183 | return; |
184 | } |
185 | |
186 | const QString &name = args[0]; |
187 | if (name.contains(c: '.')) { |
188 | QServiceFilter filter; |
189 | if (args.count() > 1) { |
190 | const QString &version = args[1]; |
191 | bool minimumMatch = version.endsWith(c: '+'); |
192 | filter.setInterface(interfaceName: name, |
193 | version: minimumMatch ? (version.mid(position: 0, n: version.length()-1)) : version, |
194 | rule: minimumMatch ? QServiceFilter::MinimumVersionMatch : QServiceFilter::ExactVersionMatch); |
195 | if (filter.interfaceName().isEmpty()) { // setInterface() failed |
196 | *stdoutStream << "Error: invalid interface version: " << version << '\n'; |
197 | return; |
198 | } |
199 | } else { |
200 | filter.setInterface(interfaceName: name); |
201 | } |
202 | showInterfaceInfo(filter); |
203 | } else { |
204 | showServiceInfo(service: name); |
205 | } |
206 | } |
207 | |
208 | |
209 | void CommandProcessor::add(const QStringList &args) |
210 | { |
211 | if (args.isEmpty()) { |
212 | *stdoutStream << "Usage:\n\tadd <service-xml-file>\n" ; |
213 | return; |
214 | } |
215 | |
216 | const QString &xmlPath = args[0]; |
217 | if (!QFile::exists(fileName: xmlPath)) { |
218 | *stdoutStream << "Error: cannot find file " << xmlPath << '\n'; |
219 | setErrorCode(11); |
220 | return; |
221 | } |
222 | |
223 | if (serviceManager->addService(xmlFilePath: xmlPath)) { |
224 | *stdoutStream << "Registered service at " << xmlPath << '\n'; |
225 | } else { |
226 | int error = serviceManager->error(); |
227 | if (error > 10) //map anything larger than 10 to 10 |
228 | error = 10; |
229 | *stdoutStream << "Error: cannot register service at " << xmlPath |
230 | << " (" << errorTable[error] << ")" << '\n'; |
231 | |
232 | setErrorCode(error); |
233 | } |
234 | } |
235 | |
236 | void CommandProcessor::remove(const QStringList &args) |
237 | { |
238 | if (args.isEmpty()) { |
239 | *stdoutStream << "Usage:\n\tremove <service-name>\n" ; |
240 | return; |
241 | } |
242 | |
243 | const QString &service = args[0]; |
244 | if (serviceManager->removeService(serviceName: service)) |
245 | *stdoutStream << "Unregistered service " << service << '\n'; |
246 | else |
247 | *stdoutStream << "Error: cannot unregister service " << service << '\n'; |
248 | } |
249 | |
250 | void CommandProcessor::dbusservice(const QStringList &args) |
251 | { |
252 | if (args.isEmpty() || args.size() == 1) { |
253 | *stdoutStream << "Usage:\n\tdbusservice <service-xml-file> <service-executable>\n" ; |
254 | return; |
255 | } |
256 | |
257 | #if defined(QT_NO_DBUS) |
258 | *stdoutStream << "Error: no D-Bus module found in Qt\n" ; |
259 | return; |
260 | #endif |
261 | |
262 | const QString &xmlPath = args[0]; |
263 | if (!QFile::exists(fileName: xmlPath)) { |
264 | *stdoutStream << "Error: cannot find xml file at " << xmlPath << '\n'; |
265 | return; |
266 | } |
267 | |
268 | const QString &servicePath = args[1]; |
269 | if (!QFile::exists(fileName: servicePath)) { |
270 | *stdoutStream << "Error: cannot find service file " << servicePath << '\n'; |
271 | return; |
272 | } |
273 | |
274 | QFile *f = new QFile(xmlPath); |
275 | ServiceMetaData parser(f); |
276 | if (!parser.extractMetadata()) { |
277 | *stdoutStream << "Error: invalid service xml at " << xmlPath << '\n'; |
278 | return; |
279 | } |
280 | |
281 | const ServiceMetaDataResults results = parser.parseResults(); |
282 | if (results.type != QService::InterProcess) { |
283 | *stdoutStream << "Error: not an inter-process service xml at " << xmlPath << '\n'; |
284 | return; |
285 | } |
286 | |
287 | QString path; |
288 | if (serviceManager->scope() == QService::UserScope) { |
289 | // the d-bus xdg environment variable for the local service paths |
290 | const QByteArray xdgPath = qgetenv(varName: "XDG_DATA_HOME" ); |
291 | if (xdgPath.isEmpty()) { |
292 | // if not supplied generate in default |
293 | QDir dir(QDir::homePath()); |
294 | dir.mkpath(dirPath: ".local/share/dbus-1/services/" ); |
295 | path = QDir::homePath() + "/.local/share/dbus-1/services/" ; |
296 | } else { |
297 | path = QString::fromLocal8Bit(str: xdgPath); |
298 | } |
299 | } else { |
300 | path = "/usr/share/dbus-1/services/" ; |
301 | } |
302 | |
303 | const QString &name = "com.nokia.qtmobility.sfw." + results.name; |
304 | const QString &exec = QFileInfo(args[1]).absoluteFilePath(); |
305 | |
306 | QStringList names; |
307 | foreach (const QServiceInterfaceDescriptor &serviceInterface, results.interfaces) { |
308 | names << serviceInterface.interfaceName(); |
309 | } |
310 | names.removeDuplicates(); |
311 | |
312 | for (int i = 0; i < names.size(); i++) { |
313 | const QString &file = path + names.at(i) + "." + results.name + ".service" ; |
314 | QFile data(file); |
315 | if (data.open(flags: QFile::WriteOnly)) { |
316 | QTextStream out(&data); |
317 | out << "[D-BUS Service]\n" |
318 | << "Name=" << name << '\n' |
319 | << "Exec=" << exec; |
320 | data.close(); |
321 | } else { |
322 | *stdoutStream << "Error: cannot write to " << file << " (insufficient permissions)" << '\n'; |
323 | return; |
324 | } |
325 | |
326 | *stdoutStream << "Generated D-Bus autostart file " << file << '\n'; |
327 | } |
328 | } |
329 | |
330 | bool CommandProcessor::setOptions(const QStringList &options) |
331 | { |
332 | if (serviceManager) |
333 | delete serviceManager; |
334 | |
335 | QService::Scope scope = QService::UserScope; |
336 | |
337 | QStringList opts = options; |
338 | QMutableListIterator<QString> i(opts); |
339 | while (i.hasNext()) { |
340 | QString option = i.next(); |
341 | if (option == "--system" ) { |
342 | scope = QService::SystemScope; |
343 | i.remove(); |
344 | } else if (option == "--user" ) { |
345 | scope = QService::UserScope; |
346 | i.remove(); |
347 | } |
348 | } |
349 | |
350 | if (!opts.isEmpty()) { |
351 | *stdoutStream << "Bad options: " << opts.join(sep: QLatin1Char(' ')) << "\n\n" ; |
352 | showUsage(); |
353 | return false; |
354 | } |
355 | |
356 | serviceManager = new QServiceManager(scope, this); |
357 | |
358 | return true; |
359 | } |
360 | |
361 | void CommandProcessor::showAllEntries() |
362 | { |
363 | QStringList services = serviceManager->findServices(); |
364 | if (services.isEmpty()) |
365 | *stdoutStream << "No services found.\n" ; |
366 | else if (services.count() == 1) |
367 | *stdoutStream << "Found 1 service.\n\n" ; |
368 | else |
369 | *stdoutStream << "Found " << services.count() << " services.\n\n" ; |
370 | |
371 | foreach (const QString &service, services) |
372 | showServiceInfo(service); |
373 | } |
374 | |
375 | void CommandProcessor::showInterfaceInfo(const QServiceFilter &filter) |
376 | { |
377 | QString serviceInterface = filter.interfaceName(); |
378 | if (filter.majorVersion() >= 0 && filter.minorVersion() >= 0) { |
379 | serviceInterface += QString(" %1.%2" ).arg(a: filter.majorVersion()).arg(a: filter.minorVersion()); |
380 | if (filter.versionMatchRule() == QServiceFilter::MinimumVersionMatch) |
381 | serviceInterface += '+'; |
382 | } |
383 | |
384 | QList<QServiceInterfaceDescriptor> descriptors = serviceManager->findInterfaces(filter); |
385 | if (descriptors.isEmpty()) { |
386 | *stdoutStream << "Interface " << serviceInterface << " not found.\n" ; |
387 | return; |
388 | } |
389 | |
390 | *stdoutStream << serviceInterface << " is implemented by:\n" ; |
391 | foreach (const QServiceInterfaceDescriptor &desc, descriptors) |
392 | *stdoutStream << '\t' << desc.serviceName() << '\n'; |
393 | } |
394 | |
395 | void CommandProcessor::showServiceInfo(const QString &service) |
396 | { |
397 | QList<QServiceInterfaceDescriptor> descriptors = serviceManager->findInterfaces(serviceName: service); |
398 | if (descriptors.isEmpty()) { |
399 | *stdoutStream << "Service " << service << " not found.\n" ; |
400 | return; |
401 | } |
402 | |
403 | QList<QServiceInterfaceDescriptor> pluginDescs; |
404 | QList<QServiceInterfaceDescriptor> ipcDescs; |
405 | foreach (const QServiceInterfaceDescriptor &desc, descriptors) { |
406 | int serviceType = desc.attribute(which: QServiceInterfaceDescriptor::ServiceType).toInt(); |
407 | if (serviceType == QService::Plugin) |
408 | pluginDescs.append(t: desc); |
409 | else |
410 | ipcDescs.append(t: desc); |
411 | } |
412 | |
413 | if (pluginDescs.size() > 0) { |
414 | *stdoutStream << service; |
415 | if (ipcDescs.size() > 0) |
416 | *stdoutStream << " (Plugin):\n" ; |
417 | else |
418 | *stdoutStream << ":\n" ; |
419 | |
420 | QString description = pluginDescs[0].attribute( |
421 | which: QServiceInterfaceDescriptor::ServiceDescription).toString(); |
422 | if (!description.isEmpty()) |
423 | *stdoutStream << '\t' << description << '\n'; |
424 | |
425 | *stdoutStream << "\tPlugin Library: " ; |
426 | showInterfaceInfo(descriptors: pluginDescs); |
427 | } |
428 | |
429 | if (ipcDescs.size() > 0) { |
430 | *stdoutStream << service; |
431 | if (pluginDescs.size() > 0) |
432 | *stdoutStream << " (IPC):\n" ; |
433 | else |
434 | *stdoutStream << ":\n" ; |
435 | |
436 | QString description = ipcDescs[0].attribute( |
437 | which: QServiceInterfaceDescriptor::ServiceDescription).toString(); |
438 | if (!description.isEmpty()) |
439 | *stdoutStream << '\t' << description << '\n'; |
440 | |
441 | *stdoutStream << "\tIPC Address: " ; |
442 | showInterfaceInfo(descriptors: ipcDescs); |
443 | } |
444 | } |
445 | |
446 | void CommandProcessor::showInterfaceInfo(QList<QServiceInterfaceDescriptor> descriptors) |
447 | { |
448 | QList<QServiceInterfaceDescriptor> systemList; |
449 | QList<QServiceInterfaceDescriptor> userList; |
450 | |
451 | foreach (const QServiceInterfaceDescriptor &desc, descriptors) { |
452 | if (desc.scope() == QService::SystemScope) |
453 | systemList.append(t: desc); |
454 | else |
455 | userList.append(t: desc); |
456 | } |
457 | |
458 | if (userList.size() > 0) { |
459 | *stdoutStream << userList[0].attribute(which: QServiceInterfaceDescriptor::Location).toString() << '\n' |
460 | << "\tUser Implements:\n" ; |
461 | |
462 | foreach (const QServiceInterfaceDescriptor &desc, userList) { |
463 | QStringList capabilities = desc.attribute( |
464 | which: QServiceInterfaceDescriptor::Capabilities).toStringList(); |
465 | |
466 | *stdoutStream << "\t " << desc.interfaceName() << ' ' |
467 | << desc.majorVersion() << '.' << desc.minorVersion() |
468 | << (capabilities.isEmpty() ? "" : "\t{" + capabilities.join(sep: ", " ) + "}" ) << "\n" ; |
469 | } |
470 | } |
471 | |
472 | if (systemList.size() > 0) { |
473 | if (userList.size() < 1) |
474 | *stdoutStream << systemList[0].attribute(which: QServiceInterfaceDescriptor::Location).toString() << '\n'; |
475 | |
476 | *stdoutStream << "\tSystem Implements:\n" ; |
477 | |
478 | foreach (const QServiceInterfaceDescriptor &desc, systemList) { |
479 | QStringList capabilities = desc.attribute( |
480 | which: QServiceInterfaceDescriptor::Capabilities).toStringList(); |
481 | |
482 | *stdoutStream << "\t " << desc.interfaceName() << ' ' |
483 | << desc.majorVersion() << '.' << desc.minorVersion() |
484 | << (capabilities.isEmpty() ? "" : "\t{" + capabilities.join(sep: ", " ) + "}" ) << "\n" ; |
485 | } |
486 | } |
487 | } |
488 | |
489 | int CommandProcessor::errorCode() |
490 | { |
491 | return m_error; |
492 | } |
493 | |
494 | void CommandProcessor::setErrorCode(int error) |
495 | { |
496 | m_error = error; |
497 | } |
498 | |
499 | int main(int argc, char *argv[]) |
500 | { |
501 | QCoreApplication app(argc, argv); |
502 | QStringList args = QCoreApplication::arguments(); |
503 | |
504 | // check for at least one non-option argument |
505 | bool exec = false; |
506 | |
507 | QStringList options; |
508 | for (int i=1; i<args.count(); i++) { |
509 | if (args[i].startsWith(s: "--" )) |
510 | options += args[i]; |
511 | else |
512 | exec = true; |
513 | } |
514 | |
515 | if (!exec || args.count() == 1 || args.value(i: 1) == "--help" || args.value(i: 1) == "-h" ) { |
516 | QTextStream stream(stdout); |
517 | CommandProcessor::showUsage(stream: &stream); |
518 | return 0; |
519 | } |
520 | |
521 | CommandProcessor processor; |
522 | processor.execute(options, cmd: args.value(i: options.count() + 1), args: args.mid(pos: options.count() + 2)); |
523 | return processor.errorCode(); |
524 | } |
525 | |
526 | #include "servicefw.moc" |
527 | |