ssu
ssudeviceinfo.cpp
Go to the documentation of this file.
1 
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 
26 SsuDeviceInfo::SsuDeviceInfo(const QString &model)
27  : QObject()
28 {
30  if (!model.isEmpty())
31  cachedModel = model;
32 }
33 
34 SsuDeviceInfo::~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 
51 QString 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 
121 void SsuDeviceInfo::clearCache()
122 {
123  cachedFamily.clear();
124  cachedModel.clear();
125  cachedVariant.clear();
126 }
127 
128 bool 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 
170 QString 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 
280 static QStringList
281 ofonoGetImeis()
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 
296 static QStringList
297 getWlanMacs()
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 
313 static QString
314 normalizeUid(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 
366 QString SsuDeviceInfo::displayName(int type)
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
411 QStringList SsuDeviceInfo::repos(bool rnd, int filter)
412 {
413  int adaptationCount = adaptationRepos().size();
414  QStringList result;
415 
416 
421  if (filter == Ssu::NoFilter ||
422  filter == Ssu::BoardFilter ||
423  filter == Ssu::BoardFilterUserBlacklist) {
424  // for repo names we have adaptation0, adaptation1, ..., adaptationN
425  for (int i = 0; i < adaptationCount; i++)
426  result.append(QString("adaptation%1").arg(i));
427 
428  // now read the release/rnd repos
430  QString repoKey = (rnd ? "default-repos/rnd" : "default-repos/release");
431  if (repoSettings.contains(repoKey))
432  result.append(repoSettings.value(repoKey).toStringList());
433 
434  // TODO: add specific repos (developer, sdk, ..)
435 
436  // add device configured repos
437  if (boardMappings->contains(deviceVariant(true) + "/repos"))
438  result.append(boardMappings->value(deviceVariant(true) + "/repos").toStringList());
439 
440  // add device configured repos only valid for rnd and/or release
441  repoKey = (rnd ? "/repos-rnd" : "/repos-release");
442  if (boardMappings->contains(deviceVariant(true) + repoKey))
443  result.append(boardMappings->value(deviceVariant(true) + repoKey).toStringList());
444 
445  // read the disabled repositories for this device
446  // user can override repositories disabled here in the user configuration
447  foreach (const QString &key, disabledRepos())
448  result.removeAll(key);
449  }
450 
451  result.removeDuplicates();
452  return result;
453 }
454 
455 QVariant SsuDeviceInfo::variable(const QString &section, const QString &key)
456 {
459  QString varSection(section);
460  if (!varSection.startsWith("var-"))
461  varSection = "var-" + varSection;
462 
463  return SsuVariables::variable(boardMappings, varSection, key);
464 }
465 
466 void SsuDeviceInfo::variableSection(const QString &section, QHash<QString, QString> *storageHash)
467 {
468  QString varSection(section);
469  if (!varSection.startsWith("var-"))
470  varSection = "var-" + varSection;
471 
472  SsuVariables::variableSection(boardMappings, varSection, storageHash);
473 }
474 
475 void SsuDeviceInfo::setDeviceModel(const QString &model)
476 {
477  if (model.isEmpty())
478  cachedModel.clear();
479  else
480  cachedModel = model;
481 
482  cachedFamily.clear();
483  cachedVariant.clear();
484 }
485 
486 QVariant SsuDeviceInfo::value(const QString &key, const QVariant &value)
487 {
488  if (boardMappings->contains(deviceModel() + "/" + key)) {
489  return boardMappings->value(deviceModel() + "/" + key);
490  } else if (boardMappings->contains(deviceVariant() + "/" + key)) {
491  return boardMappings->value(deviceVariant() + "/" + key);
492  }
493 
494  return value;
495 }
496 
497 QMap<QString, QString> SsuDeviceInfo::hwRelease()
498 {
499  QMap<QString, QString> result;
500 
501  // Specification of the format, encoding is similar to /etc/os-release
502  // http://www.freedesktop.org/software/systemd/man/os-release.html
503 
504  QFile hwRelease("/etc/hw-release");
505  if (hwRelease.open(QIODevice::ReadOnly | QIODevice::Text)) {
506  QTextStream in(&hwRelease);
507 
508  // "All strings should be in UTF-8 format, and non-printable characters
509  // should not be used."
510  in.setCodec("UTF-8");
511 
512  while (!in.atEnd()) {
513  QString line = in.readLine();
514 
515  // "Lines beginning with "#" shall be ignored as comments."
516  if (line.startsWith('#')) {
517  continue;
518  }
519 
520  QString key = line.section('=', 0, 0);
521  QString value = line.section('=', 1);
522 
523  // Remove trailing whitespace in value
524  value = value.trimmed();
525 
526  // POSIX.1-2001 says uppercase, digits and underscores.
527  //
528  // Bash uses "[a-zA-Z_]+[a-zA-Z0-9_]*", so we'll use that too,
529  // as we can safely assume that "shell-compatible variable
530  // assignments" means it should be compatible with bash.
531  //
532  // see http://stackoverflow.com/a/2821183
533  // and http://stackoverflow.com/a/2821201
534  if (!QRegExp("[a-zA-Z_]+[a-zA-Z0-9_]*").exactMatch(key)) {
535  qWarning("Invalid key in input line: '%s'", qPrintable(line));
536  continue;
537  }
538 
539  // "Variable assignment values should be enclosed in double or
540  // single quotes if they include spaces, semicolons or other
541  // special characters outside of A-Z, a-z, 0-9."
542  if (((value.at(0) == '\'') || (value.at(0) == '"'))) {
543  if (value.at(0) != value.at(value.size() - 1)) {
544  qWarning("Quoting error in input line: '%s'", qPrintable(line));
545  continue;
546  }
547 
548  // Remove the quotes
549  value = value.mid(1, value.size() - 2);
550  }
551 
552  // "If double or single quotes or backslashes are to be used within
553  // variable assignments, they should be escaped with backslashes,
554  // following shell style."
555  value = value.replace("\\\"", "\"");
556  value = value.replace("\\'", "'");
557  value = value.replace("\\\\", "\\");
558 
559  result[key] = value;
560  }
561 
562  hwRelease.close();
563  }
564 
565  return result;
566 }
SsuVariables::variableSection
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
Definition: ssuvariables.cpp:173
SsuSettings
Definition: ssusettings_p.h:30
SsuDeviceInfo::contains
bool contains(const QString &model=QString())
Definition: ssudeviceinfo.cpp:128
SsuDeviceInfo::repos
QStringList repos(bool rnd=false, int filter=Ssu::NoFilter)
Definition: ssudeviceinfo.cpp:411
Ssu::NoFilter
@ NoFilter
All repositories (global + user)
Definition: ssu.h:50
SsuDeviceInfo::deviceUid
Q_INVOKABLE QString deviceUid()
Definition: ssudeviceinfo.cpp:320
SsuLog
Definition: ssulog_p.h:15
ssuvariables_p.h
SsuDeviceInfo::deviceFamily
Q_INVOKABLE QString deviceFamily()
Definition: ssudeviceinfo.cpp:150
SsuVariables::variable
QVariant variable(const QString &section, const QString &key)
Definition: ssuvariables.cpp:150
SSU_BOARD_MAPPING_CONFIGURATION_DIR
#define SSU_BOARD_MAPPING_CONFIGURATION_DIR
Path to config.d for board mappings.
Definition: constants.h:20
SsuDeviceInfo::variable
QVariant variable(const QString &section, const QString &key)
Definition: ssudeviceinfo.cpp:455
SsuDeviceInfo::adaptationRepos
QStringList adaptationRepos()
Definition: ssudeviceinfo.cpp:39
SsuDeviceInfo::displayName
Q_INVOKABLE QString displayName(int type)
Definition: ssudeviceinfo.cpp:366
SSU_REPO_CONFIGURATION
#define SSU_REPO_CONFIGURATION
Path to the ssu domain configuration file.
Definition: constants.h:14
SsuDeviceInfo::variableSection
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
Definition: ssudeviceinfo.cpp:466
ssucoreconfig_p.h
SsuCoreConfig
Definition: ssucoreconfig_p.h:27
SsuDeviceInfo::adaptationVariables
QString adaptationVariables(const QString &adaptationName, QHash< QString, QString > *storageHash)
Definition: ssudeviceinfo.cpp:51
SsuLog::print
void print(int priority, const QString &message)
Definition: ssulog.cpp:27
SsuDeviceInfo::SsuDeviceInfo
SsuDeviceInfo(const QString &model=QString())
Definition: ssudeviceinfo.cpp:26
SsuDeviceInfo::setDeviceModel
Q_INVOKABLE void setDeviceModel(const QString &model=QString())
Definition: ssudeviceinfo.cpp:475
SsuDeviceInfo::disabledRepos
QStringList disabledRepos()
Definition: ssudeviceinfo.cpp:354
sandbox_p.h
SSU_BOARD_MAPPING_CONFIGURATION
#define SSU_BOARD_MAPPING_CONFIGURATION
Path to board / device family mappings file.
Definition: constants.h:18
Ssu::DeviceModel
@ DeviceModel
Marketed device name, like Pogoblaster 3000. Board mappings key "prettyModel".
Definition: ssu.h:83
ssudeviceinfo.h
Ssu::DeviceManufacturer
@ DeviceManufacturer
Manufacturer, like ACME Corp. Board mappings key "deviceManufacturer".
Definition: ssu.h:82
Ssu::BoardFilter
@ BoardFilter
Only global repositories, with user blacklist ignored.
Definition: ssu.h:52
SsuDeviceInfo::deviceModel
Q_INVOKABLE QString deviceModel()
Definition: ssudeviceinfo.cpp:185
Ssu::BoardFilterUserBlacklist
@ BoardFilterUserBlacklist
Only global repositories, with user blacklist applied.
Definition: ssu.h:53
ssulog_p.h
SSU_REPO_CONFIGURATION_DIR
#define SSU_REPO_CONFIGURATION_DIR
Path to config.d for ssu domain configurations.
Definition: constants.h:16
SsuDeviceInfo::deviceVariant
Q_INVOKABLE QString deviceVariant(bool fallback=false)
Definition: ssudeviceinfo.cpp:170
Ssu::DeviceDesignation
@ DeviceDesignation
Type designation, like NCC-1701. Beard mappings key "deviceDesignation".
Definition: ssu.h:84
SsuDeviceInfo::value
QVariant value(const QString &key, const QVariant &value=QVariant())
Definition: ssudeviceinfo.cpp:486