ssu
Loading...
Searching...
No Matches
ssudeviceinfo.cpp
Go to the documentation of this file.
1
7
8
9#include <QTextStream>
10#include <QDir>
11
12#include <QDBusReply>
13#include <QDBusConnection>
14#include <QDBusArgument>
15
16#include <sys/utsname.h>
17
18#include "sandbox_p.h"
19#include "ssudeviceinfo.h"
20#include "ssucoreconfig_p.h"
21#include "ssulog_p.h"
22#include "ssuvariables_p.h"
23
24#include "../constants.h"
25
26SsuDeviceInfo::SsuDeviceInfo(const QString &model)
27 : QObject()
28{
30 if (!model.isEmpty())
31 cachedModel = model;
32}
33
34SsuDeviceInfo::~SsuDeviceInfo()
35{
36 delete boardMappings;
37}
38
40{
41 QStringList result;
42
43 QString model = deviceVariant(true);
44
45 if (boardMappings->contains(model + "/adaptation-repos"))
46 result = boardMappings->value(model + "/adaptation-repos").toStringList();
47
48 return result;
49}
50
51QString SsuDeviceInfo::adaptationVariables(const QString &adaptationName, QHash<QString, QString> *storageHash)
52{
53 SsuLog *ssuLog = SsuLog::instance();
54 QStringList adaptationRepoList = adaptationRepos();
55 QString model = deviceVariant(true);
56
57 // special handling for adaptation-repositories
58 // - check if repo is in right format (adaptation\d*)
59 // - check if the configuration has that many adaptation repos
60 // - export the entry in the adaptation list as %(adaptation)
61 // - look up variables for that adaptation, and export matching
62 // adaptation variable
63 QRegExp regex("adaptation\\d*", Qt::CaseSensitive, QRegExp::RegExp2);
64 if (regex.exactMatch(adaptationName)) {
65 regex.setPattern("\\d*");
66 regex.lastIndexIn(adaptationName);
67 int n = regex.cap().toInt();
68
69 if (!adaptationRepoList.isEmpty()) {
70 if (adaptationRepoList.size() <= n) {
71 ssuLog->print(LOG_INFO, "Note: repo index out of bounds, substituting 0" + adaptationName);
72 n = 0;
73 }
74
75 QString adaptationRepo = adaptationRepoList.at(n);
76 storageHash->insert("adaptation", adaptationRepo);
77 ssuLog->print(LOG_DEBUG, "Found first adaptation " + adaptationName);
78
79 QHash<QString, QString> h;
80
81 // add global variables for this model
82 if (boardMappings->contains(model + "/variables")) {
83 QStringList sections = boardMappings->value(model + "/variables").toStringList();
84 foreach (const QString &section, sections)
85 variableSection(section, &h);
86 }
87
88 // override with variables specific to this repository
89 variableSection(adaptationRepo, &h);
90
91 QHash<QString, QString>::const_iterator i = h.constBegin();
92 while (i != h.constEnd()) {
93 storageHash->insert(i.key(), i.value());
94 i++;
95 }
96 } else
97 ssuLog->print(LOG_INFO, "Note: adaptation repo for invalid repo requested " + adaptationName);
98
99 return "adaptation";
100 }
101
102 // If adaptation has defined repository-specific-variables and it matches to the
103 // repository name then also get those variables.
104 else if (boardMappings->contains(model + "/repository-specific-variables")) {
105 QStringList sections = boardMappings->value(model + "/repository-specific-variables").toStringList();
106 if (sections.contains(model + "-" + adaptationName)) {
107 QHash<QString, QString> h;
108
109 variableSection(model + "-" + adaptationName, &h);
110
111 QHash<QString, QString>::const_iterator i = h.constBegin();
112 while (i != h.constEnd()) {
113 storageHash->insert(i.key(), i.value());
114 i++;
115 }
116 }
117 }
118 return adaptationName;
119}
120
121void SsuDeviceInfo::clearCache()
122{
123 cachedFamily.clear();
124 cachedModel.clear();
125 cachedVariant.clear();
126}
127
128bool SsuDeviceInfo::contains(const QString &model)
129{
130 QString oldModel = deviceModel();
131 bool found = false;
132
133 if (!model.isEmpty()) {
134 clearCache();
135 setDeviceModel(model);
136 }
137
138 if (!deviceVariant(false).isEmpty())
139 found = true;
140 if (boardMappings->childGroups().contains(deviceModel()))
141 found = true;
142
143 if (!model.isEmpty()) {
144 clearCache();
145 setDeviceModel(oldModel);
146 }
147 return found;
148}
149
151{
152 if (!cachedFamily.isEmpty())
153 return cachedFamily;
154
155 QString model = deviceVariant(true);
156
157 if (boardMappings->contains(model + "/family")) {
158 cachedFamily = boardMappings->value(model + "/family").toString();
159 } else {
160 // In case family is not defined, lets use device variant, which
161 // falls back to device model. This way we can use the deviceFamily
162 // in common repository names where we want to have same feature package
163 // for multiple devices some of which have family and some which do not.
164 cachedFamily = model;
165 }
166
167 return cachedFamily;
168}
169
170QString SsuDeviceInfo::deviceVariant(bool fallback)
171{
172 if (!cachedVariant.isEmpty())
173 return cachedVariant;
174
175 if (boardMappings->contains("variants/" + deviceModel())) {
176 cachedVariant = boardMappings->value("variants/" + deviceModel()).toString();
177 }
178
179 if (cachedVariant.isEmpty() && fallback)
180 return deviceModel();
181
182 return cachedVariant;
183}
184
186{
187 if (!cachedModel.isEmpty())
188 return cachedModel;
189
190 boardMappings->beginGroup("file.exists");
191 QStringList keys = boardMappings->allKeys();
192
193 // check if the device can be identified by testing for a file
194 foreach (const QString &key, keys) {
195 QString value = boardMappings->value(key).toString();
196 QDir dir;
197 if (dir.exists(Sandbox::map(value))) {
198 cachedModel = key;
199 break;
200 }
201 }
202 boardMappings->endGroup();
203 if (!cachedModel.isEmpty()) return cachedModel;
204
205 // check if the device can be identified by a string in /proc/cpuinfo
206 QFile procCpuinfo;
207 procCpuinfo.setFileName(Sandbox::map("/proc/cpuinfo"));
208 procCpuinfo.open(QIODevice::ReadOnly | QIODevice::Text);
209 if (procCpuinfo.isOpen()) {
210 QTextStream in(&procCpuinfo);
211 QString cpuinfo = in.readAll();
212 boardMappings->beginGroup("cpuinfo.contains");
213 keys = boardMappings->allKeys();
214
215 foreach (const QString &key, keys) {
216 QString value = boardMappings->value(key).toString();
217 if (cpuinfo.contains(value)) {
218 cachedModel = key;
219 break;
220 }
221 }
222 boardMappings->endGroup();
223 }
224 if (!cachedModel.isEmpty()) return cachedModel;
225
226 // mer-hybris adaptations: /etc/hw-release MER_HA_DEVICE variable
227 QString hwReleaseDevice = hwRelease()["MER_HA_DEVICE"];
228 if (!hwReleaseDevice.isEmpty()) {
229 boardMappings->beginGroup("hwrelease.device");
230 keys = boardMappings->allKeys();
231
232 foreach (const QString &key, keys) {
233 QString value = boardMappings->value(key).toString();
234 if (hwReleaseDevice == value) {
235 cachedModel = key;
236 break;
237 }
238 }
239 boardMappings->endGroup();
240 }
241 if (!cachedModel.isEmpty()) return cachedModel;
242
243 // check if the device can be identified by the kernel version string
244 struct utsname buf;
245 if (!uname(&buf)) {
246 QString utsRelease(buf.release);
247 boardMappings->beginGroup("uname-release.contains");
248 keys = boardMappings->allKeys();
249
250 foreach (const QString &key, keys) {
251 QString value = boardMappings->value(key).toString();
252 if (utsRelease.contains(value)) {
253 cachedModel = key;
254 break;
255 }
256 }
257 boardMappings->endGroup();
258 }
259 if (!cachedModel.isEmpty()) return cachedModel;
260
261 // check if there's a match on arch of generic fallback. This probably
262 // only makes sense for x86
263 boardMappings->beginGroup("arch.equals");
264 keys = boardMappings->allKeys();
265
266 SsuCoreConfig *settings = SsuCoreConfig::instance();
267 foreach (const QString &key, keys) {
268 QString value = boardMappings->value(key).toString();
269 if (settings->value("arch").toString() == value) {
270 cachedModel = key;
271 break;
272 }
273 }
274 boardMappings->endGroup();
275 if (cachedModel.isEmpty()) cachedModel = "UNKNOWN";
276
277 return cachedModel;
278}
279
280static QStringList
281ofonoGetImeis()
282{
283 QStringList result;
284
285 QDBusReply<QStringList> reply = QDBusConnection::systemBus().call(
286 QDBusMessage::createMethodCall("org.ofono", "/",
287 "org.nemomobile.ofono.ModemManager", "GetIMEI"));
288
289 if (reply.isValid()) {
290 result = reply.value();
291 }
292
293 return result;
294}
295
296static QStringList
297getWlanMacs()
298{
299 // Based on QtSystems' qnetworkinfo_linux.cpp
300 QStringList result;
301
302 QStringList dirs = QDir(QLatin1String("/sys/class/net/"))
303 .entryList(QStringList() << QLatin1String("wlan*"));
304 foreach (const QString &dir, dirs) {
305 QFile carrier(QString("/sys/class/net/%1/address").arg(dir));
306 if (carrier.open(QIODevice::ReadOnly)) {
307 result.append(QString::fromLatin1(carrier.readAll().simplified().data()));
308 }
309 }
310 return result;
311}
312
313static QString
314normalizeUid(const QString &uid)
315{
316 // Normalize by stripping colons, dashes and making it lowercase
317 return uid.trimmed().replace(":", "").replace("-", "").toLower();
318}
319
321{
322 SsuLog *ssuLog = SsuLog::instance();
323 QStringList imeis = ofonoGetImeis();
324 if (imeis.size() > 0) {
325 return imeis[0];
326 }
327
328 QStringList wlanMacs = getWlanMacs();
329 if (wlanMacs.size() > 0) {
330 return normalizeUid(wlanMacs[0]);
331 }
332
333 ssuLog->print(LOG_WARNING, "Could not get IMEI(s) from ofono, nor WLAN mac, trying fallback");
334
335 // The fallback list is taken from QtSystems' qdeviceinfo_linux.cpp
336 QStringList fallbackFiles;
337 fallbackFiles << "/sys/devices/virtual/dmi/id/product_uuid";
338 fallbackFiles << "/etc/machine-id";
339 fallbackFiles << "/etc/unique-id";
340 fallbackFiles << "/var/lib/dbus/machine-id";
341
342 foreach (const QString &filename, fallbackFiles) {
343 QFile machineId(filename);
344 if (machineId.open(QFile::ReadOnly | QFile::Text) && machineId.size() > 0) {
345 QTextStream in(&machineId);
346 return normalizeUid(in.readAll());
347 }
348 }
349
350 ssuLog->print(LOG_CRIT, "Could not read fallback UID - returning empty string");
351 return QString();
352}
353
355{
356 QStringList result;
357
358 QString model = deviceVariant(true);
359
360 if (boardMappings->contains(model + "/disabled-repos"))
361 result = boardMappings->value(model + "/disabled-repos").toStringList();
362
363 return result;
364}
365
367{
368 QString model = deviceModel();
369 QString variant = deviceVariant(false);
370 QString value, key;
371
372 switch (type) {
374 key = "/deviceManufacturer";
375 break;
376 case Ssu::DeviceModel:
377 key = "/prettyModel";
378 break;
380 key = "/deviceDesignation";
381 break;
382 default:
383 return QString();
384 }
385
386 /*
387 * Go through different levels of fallbacks:
388 * 1. model specific setting
389 * 2. variant specific setting
390 * 3. global setting
391 * 4. return model name, or "UNKNOWN" in case query was for manufacturer
392 */
393
394 if (boardMappings->contains(model + key))
395 value = boardMappings->value(model + key).toString();
396 else if (!variant.isEmpty() && boardMappings->contains(variant + key))
397 value = boardMappings->value(variant + key).toString();
398 else if (boardMappings->contains(key))
399 value = boardMappings->value(key).toString();
400 else if (type != Ssu::DeviceManufacturer)
401 value = model;
402 else
403 value = "UNKNOWN";
404
405 return value;
406}
407
408// this half belongs into repo-manager, as it not only handles board-specific
409// repositories. Right now this one looks like the better place due to the
410// connection to board specific stuff, though
411QStringList SsuDeviceInfo::repos(bool rnd, int filter)
412{
413 int adaptationCount = adaptationRepos().size();
414 QStringList result;
415
416 if ((filter & Ssu::BoardFilter) == Ssu::BoardFilter) {
418 QString repoKey;
419 // for repo names we have adaptation0, adaptation1, ..., adaptationN
420 for (int i = 0; i < adaptationCount; i++)
421 result.append(QString("adaptation%1").arg(i));
422
423 if ((filter & Ssu::Available) == Ssu::Available) {
424 // Read *all* defined repos
425 QString group = (rnd ? "rnd" : "release");
426 repoSettings.beginGroup(group);
427 result.append(repoSettings.allKeys());
428 repoSettings.endGroup();
429 } else {
430 // Just take the default repos
431 repoKey = (rnd ? "default-repos/rnd" : "default-repos/release");
432 if (repoSettings.contains(repoKey))
433 result.append(repoSettings.value(repoKey).toStringList());
434 }
435
436 // TODO: add specific repos (developer, sdk, ..)
437
438 // add device configured repos
439 if (boardMappings->contains(deviceVariant(true) + "/repos"))
440 result.append(boardMappings->value(deviceVariant(true) + "/repos").toStringList());
441
442 // add device configured repos only valid for rnd and/or release
443 repoKey = (rnd ? "/repos-rnd" : "/repos-release");
444 if (boardMappings->contains(deviceVariant(true) + repoKey))
445 result.append(boardMappings->value(deviceVariant(true) + repoKey).toStringList());
446
447 if ((filter & Ssu::Available) != Ssu::Available) {
448 // read the disabled repositories for this device
449 // user can override repositories disabled here in the user configuration
450 foreach (const QString &key, disabledRepos())
451 result.removeAll(key);
452 }
453 }
454
455 result.removeDuplicates();
456 return result;
457}
458
459QVariant SsuDeviceInfo::variable(const QString &section, const QString &key)
460{
463 QString varSection(section);
464 if (!varSection.startsWith("var-"))
465 varSection = "var-" + varSection;
466
467 return SsuVariables::variable(boardMappings, varSection, key);
468}
469
470void SsuDeviceInfo::variableSection(const QString &section, QHash<QString, QString> *storageHash)
471{
472 QString varSection(section);
473 if (!varSection.startsWith("var-"))
474 varSection = "var-" + varSection;
475
476 SsuVariables::variableSection(boardMappings, varSection, storageHash);
477}
478
479void SsuDeviceInfo::setDeviceModel(const QString &model)
480{
481 if (model.isEmpty())
482 cachedModel.clear();
483 else
484 cachedModel = model;
485
486 cachedFamily.clear();
487 cachedVariant.clear();
488}
489
490QVariant SsuDeviceInfo::value(const QString &key, const QVariant &value)
491{
492 if (boardMappings->contains(deviceModel() + "/" + key)) {
493 return boardMappings->value(deviceModel() + "/" + key);
494 } else if (boardMappings->contains(deviceVariant() + "/" + key)) {
495 return boardMappings->value(deviceVariant() + "/" + key);
496 }
497
498 return value;
499}
500
501QMap<QString, QString> SsuDeviceInfo::hwRelease()
502{
503 QMap<QString, QString> result;
504
505 // Specification of the format, encoding is similar to /etc/os-release
506 // http://www.freedesktop.org/software/systemd/man/os-release.html
507
508 QFile hwRelease("/etc/hw-release");
509 if (hwRelease.open(QIODevice::ReadOnly | QIODevice::Text)) {
510 QTextStream in(&hwRelease);
511
512 // "All strings should be in UTF-8 format, and non-printable characters
513 // should not be used."
514 in.setCodec("UTF-8");
515
516 while (!in.atEnd()) {
517 QString line = in.readLine();
518
519 // "Lines beginning with "#" shall be ignored as comments."
520 if (line.startsWith('#')) {
521 continue;
522 }
523
524 QString key = line.section('=', 0, 0);
525 QString value = line.section('=', 1);
526
527 // Remove trailing whitespace in value
528 value = value.trimmed();
529
530 // POSIX.1-2001 says uppercase, digits and underscores.
531 //
532 // Bash uses "[a-zA-Z_]+[a-zA-Z0-9_]*", so we'll use that too,
533 // as we can safely assume that "shell-compatible variable
534 // assignments" means it should be compatible with bash.
535 //
536 // see http://stackoverflow.com/a/2821183
537 // and http://stackoverflow.com/a/2821201
538 if (!QRegExp("[a-zA-Z_]+[a-zA-Z0-9_]*").exactMatch(key)) {
539 qWarning("Invalid key in input line: '%s'", qPrintable(line));
540 continue;
541 }
542
543 // "Variable assignment values should be enclosed in double or
544 // single quotes if they include spaces, semicolons or other
545 // special characters outside of A-Z, a-z, 0-9."
546 if (((value.at(0) == '\'') || (value.at(0) == '"'))) {
547 if (value.at(0) != value.at(value.size() - 1)) {
548 qWarning("Quoting error in input line: '%s'", qPrintable(line));
549 continue;
550 }
551
552 // Remove the quotes
553 value = value.mid(1, value.size() - 2);
554 }
555
556 // "If double or single quotes or backslashes are to be used within
557 // variable assignments, they should be escaped with backslashes,
558 // following shell style."
559 value = value.replace("\\\"", "\"");
560 value = value.replace("\\'", "'");
561 value = value.replace("\\\\", "\\");
562
563 result[key] = value;
564 }
565
566 hwRelease.close();
567 }
568
569 return result;
570}
Q_INVOKABLE QString deviceUid()
Q_INVOKABLE QString deviceFamily()
QString adaptationVariables(const QString &adaptationName, QHash< QString, QString > *storageHash)
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
QVariant value(const QString &key, const QVariant &value=QVariant())
SsuDeviceInfo(const QString &model=QString())
QStringList disabledRepos()
QStringList repos(bool rnd=false, int filter=Ssu::NoFilter)
QVariant variable(const QString &section, const QString &key)
Q_INVOKABLE void setDeviceModel(const QString &model=QString())
QStringList adaptationRepos()
bool contains(const QString &model=QString())
Q_INVOKABLE QString deviceVariant(bool fallback=false)
Q_INVOKABLE QString displayName(int type)
Q_INVOKABLE QString deviceModel()
void print(int priority, const QString &message)
Definition ssulog.cpp:27
QVariant variable(const QString &section, const QString &key)
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
@ DeviceModel
Marketed device name, like Pogoblaster 3000. Board mappings key "prettyModel".
Definition ssu.h:84
@ DeviceDesignation
Type designation, like NCC-1701. Beard mappings key "deviceDesignation".
Definition ssu.h:85
@ DeviceManufacturer
Manufacturer, like ACME Corp. Board mappings key "deviceManufacturer".
Definition ssu.h:83
@ BoardFilter
Only global repositories.
Definition ssu.h:51
@ Available
Include all defined repos, including disabled.
Definition ssu.h:54
#define SSU_REPO_CONFIGURATION_DIR
Path to config.d for ssu domain configurations.
Definition constants.h:16
#define SSU_BOARD_MAPPING_CONFIGURATION
Path to board / device family mappings file.
Definition constants.h:18
#define SSU_BOARD_MAPPING_CONFIGURATION_DIR
Path to config.d for board mappings.
Definition constants.h:20
#define SSU_REPO_CONFIGURATION
Path to the ssu domain configuration file.
Definition constants.h:14