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