Commit 38362440 authored by Aard's avatar Aard

Use only one instance of global settings, removing sync issues and unnecessary reads

Some bits got moved to more appropriate modules, others (like credentials
handling) still need to be moved. ssu CLI got a bit more readable.

This is part of a big refactoring due to SSU doing mostly repo management
and URL handling instead of the originally planned credentials management
with URL handling as a nice bonus feature.
parent 306d4e89
......@@ -11,11 +11,7 @@
/// The group ID SSU expects to run as. This is usually the GID of the main phone user
#define SSU_GROUP_ID 1000
/// Path to the main SSU configuration file
#define SSU_CONFIGURATION "/etc/ssu/ssu.ini"
/// Path to the main SSU configuration file
#define SSU_REPO_CONFIGURATION "/usr/share/ssu/repos.ini"
/// Path to the main SSU configuration file
#define SSU_DEFAULT_CONFIGURATION "/usr/share/ssu/ssu-defaults.ini"
/// Path to board / device family mappings file
#define SSU_BOARD_MAPPING_CONFIGURATION "/usr/share/ssu/board-mappings.ini"
/// Path to config.d for board mappings
......
BUILD = ../build/libssu
HEADERS = ssu.h \
ssucoreconfig.h \
ssudeviceinfo.h \
ssulog.h \
ssuvariables.h \
......@@ -7,6 +8,7 @@ HEADERS = ssu.h \
ssurepomanager.h \
../constants.h
SOURCES = ssu.cpp \
ssucoreconfig.cpp \
ssudeviceinfo.cpp \
ssulog.cpp \
ssuvariables.cpp \
......
This diff is collapsed.
......@@ -23,7 +23,7 @@ class Ssu: public QObject {
Q_OBJECT
public:
Ssu(QString fallbackLog="/tmp/ssu.log");
Ssu();
/**
* Find a username/password pair for the given scope
* @return a QPair with username and password, or an empty QPair if scope is invalid
......@@ -49,41 +49,12 @@ class Ssu: public QObject {
* @retval false last operation failed, you should check lastError() for details
*/
Q_INVOKABLE bool error();
/**
* Get the current flavour when RnD repositories are used
* @return current flavour (usually something like testing, release, ..)
*/
Q_INVOKABLE QString flavour();
/**
* Get the current mode bits for the device
*/
Q_INVOKABLE int deviceMode();
/**
* Get the current domain used in registration
* @return domain, or "" if not set
*/
Q_INVOKABLE QString domain();
/**
* Return devices RND registration status
* @retval true device is registered
* @retval false device is not registered
*/
Q_INVOKABLE bool isRegistered();
/**
* Return the date/time when the credentials to access internal
* SSU servers were updated the last time
*/
Q_INVOKABLE QDateTime lastCredentialsUpdate();
/**
* Return an error message for the last error encountered. The message
* will not be cleared, check error() to see if the last operation was
* successful.
*/
Q_INVOKABLE QString lastError();
/**
* Return the release version string for either a release, or a RnD snapshot
*/
Q_INVOKABLE QString release(bool rnd=false);
/**
* Resolve a repository url
* @return the repository URL on success, an empty string on error
......@@ -91,22 +62,6 @@ class Ssu: public QObject {
QString repoUrl(QString repoName, bool rndRepo=false,
QHash<QString, QString> repoParameters=QHash<QString, QString>(),
QHash<QString, QString> parametersOverride=QHash<QString, QString>());
/**
* Set mode bits for the device
*/
Q_INVOKABLE void setDeviceMode(int mode, int editMode=Replace);
/**
* Set the flavour used when resolving RND repositories
*/
Q_INVOKABLE void setFlavour(QString flavour);
/**
* Set the release version string for either a release, or a RnD snapshot
*/
Q_INVOKABLE void setRelease(QString release, bool rnd=false);
/**
* Set the domain string (usually something like nemo, jolla, ..)
*/
Q_INVOKABLE void setDomain(QString domain);
/**
* Unregister a device. This will clean all registration data from a device,
* though will not touch the information on SSU server; the information there
......@@ -114,13 +69,35 @@ class Ssu: public QObject {
* overwritten next time the device gets registered
*/
Q_INVOKABLE void unregister();
/**
* Return configuration settings regarding ssl verification
* @retval true SSL verification must be used; that's the default if not configured
* @retval false SSL verification should be disabled
*/
// wrappers around SsuCoreConfig
// not all of those belong into SsuCoreConfig, but will go there
// in the first phase of refactoring
/// See SsuCoreConfig::flavour
Q_INVOKABLE QString flavour();
/// See SsuCoreConfig::deviceMode
Q_INVOKABLE int deviceMode();
/// See SsuCoreConfig::domain
Q_INVOKABLE QString domain();
/// See SsuCoreConfig::isRegistered
Q_INVOKABLE bool isRegistered();
/// See SsuCoreConfig::lastCredentialsUpdate
Q_INVOKABLE QDateTime lastCredentialsUpdate();
/// See SsuCoreConfig::release
Q_INVOKABLE QString release(bool rnd=false);
/// See SsuCoreConfig::setDeviceMode
Q_INVOKABLE void setDeviceMode(int mode, int editMode=Replace);
/// See SsuCoreConfig::setFlavour
Q_INVOKABLE void setFlavour(QString flavour);
/// See SsuCoreConfig::setRelease
Q_INVOKABLE void setRelease(QString release, bool rnd=false);
/// See SsuCoreConfig::setDomain
Q_INVOKABLE void setDomain(QString domain);
/// See SsuCoreConfig::useSslVerify
Q_INVOKABLE bool useSslVerify();
/**
* List of possible device modes
*
......@@ -144,18 +121,20 @@ class Ssu: public QObject {
Remove = 0x4 ///< Make sure the given value is not set in the bitmask
};
// compat stuff, might go away when refactoring is finished
/// See SsuDeviceInfo::deviceFamily
Q_INVOKABLE QString deviceFamily(){ return deviceInfo.deviceFamily(); };
/// See SsuDeviceInfo::deviceModel
Q_INVOKABLE QString deviceModel(){ return deviceInfo.deviceModel(); };
/// See SsuDeviceInfo::deviceUid
Q_INVOKABLE QString deviceUid(){ return deviceInfo.deviceUid(); };
private:
QString errorString, fallbackLogPath;
QString errorString;
bool errorFlag;
QNetworkAccessManager *manager;
int pendingRequests;
QSettings *repoSettings;
SsuSettings *settings;
SsuDeviceInfo deviceInfo;
bool registerDevice(QDomDocument *response);
bool setCredentials(QDomDocument *response);
......@@ -199,12 +178,11 @@ class Ssu: public QObject {
* Emitted after an asynchronous operation finished
*/
void done();
// we don't get notifications from settings -> this won't work over different instances (yet)
void flavourChanged();
void releaseChanged();
/**
* Emitted after the devices registration status has changed
*/
void registrationStatusChanged();
void credentialsChanged();
};
#endif
/**
* @file ssucoreconfig.cpp
* @copyright 2013 Jolla Ltd.
* @author Bernd Wachter <bwachter@lart.info>
* @date 2013
*/
#include <QFile>
#include <QTextStream>
#include "ssucoreconfig.h"
#include "ssulog.h"
SsuCoreConfig *SsuCoreConfig::ssuCoreConfig = 0;
SsuCoreConfig *SsuCoreConfig::instance(){
if (!ssuCoreConfig)
ssuCoreConfig = new SsuCoreConfig();
return ssuCoreConfig;
}
QPair<QString, QString> SsuCoreConfig::credentials(QString scope){
QPair<QString, QString> ret;
beginGroup("credentials-" + scope);
ret.first = value("username").toString();
ret.second = value("password").toString();
endGroup();
return ret;
}
QString SsuCoreConfig::credentialsScope(QString repoName, bool rndRepo){
if (contains("credentials-scope"))
return value("credentials-scope").toString();
else
return "your-configuration-is-broken-and-does-not-contain-credentials-scope";
}
QString SsuCoreConfig::credentialsUrl(QString scope){
if (contains("credentials-url-" + scope))
return value("credentials-url-" + scope).toString();
else
return "your-configuration-is-broken-and-does-not-contain-credentials-url-for-" + scope;
}
QString SsuCoreConfig::flavour(){
if (contains("flavour"))
return value("flavour").toString();
else
return "release";
}
int SsuCoreConfig::deviceMode(){
if (!contains("deviceMode")){
setValue("deviceMode", Ssu::ReleaseMode);
sync();
return Ssu::ReleaseMode;
} else
return value("deviceMode").toInt();
}
QString SsuCoreConfig::domain(){
if (contains("domain"))
return value("domain").toString();
else
return "";
}
bool SsuCoreConfig::isRegistered(){
if (!contains("privateKey"))
return false;
if (!contains("certificate"))
return false;
return value("registered").toBool();
}
QDateTime SsuCoreConfig::lastCredentialsUpdate(){
return value("lastCredentialsUpdate").toDateTime();
}
QString SsuCoreConfig::release(bool rnd){
if (rnd)
return value("rndRelease").toString();
else
return value("release").toString();
}
void SsuCoreConfig::setDeviceMode(int mode, int editMode){
int oldMode = value("deviceMode").toInt();
if ((editMode & Ssu::Add) == Ssu::Add){
oldMode |= mode;
} else if ((editMode & Ssu::Remove) == Ssu::Remove){
oldMode &= ~mode;
} else
oldMode = mode;
setValue("deviceMode", oldMode);
sync();
}
void SsuCoreConfig::setFlavour(QString flavour){
setValue("flavour", flavour);
// flavour is RnD only, so enable RnD mode
setDeviceMode(Ssu::RndMode, Ssu::Add);
sync();
}
void SsuCoreConfig::setRelease(QString release, bool rnd){
if (rnd) {
setValue("rndRelease", release);
// switch rndMode on/off when setting releases
setDeviceMode(Ssu::RndMode, Ssu::Add);
} else {
setValue("release", release);
setDeviceMode(Ssu::RndMode, Ssu::Remove);
}
sync();
}
void SsuCoreConfig::setDomain(QString domain){
setValue("domain", domain);
sync();
}
bool SsuCoreConfig::useSslVerify(){
if (contains("ssl-verify"))
return value("ssl-verify").toBool();
else
return true;
}
/**
* @file ssucoreconfig.h
* @copyright 2013 Jolla Ltd.
* @author Bernd Wachter <bwachter@lart.info>
* @date 2013
*/
#ifndef _SSUCORECONFIG_H
#define _SSUCORECONFIG_H
#include <QObject>
#include <QSettings>
#include <ssusettings.h>
#include <ssu.h>
#ifndef SSU_CONFIGURATION
/// Path to the main SSU configuration file
#define SSU_CONFIGURATION "/etc/ssu/ssu.ini"
#endif
#ifndef SSU_DEFAULT_CONFIGURATION
/// Path to the main SSU configuration file
#define SSU_DEFAULT_CONFIGURATION "/usr/share/ssu/ssu-defaults.ini"
#endif
class SsuCoreConfig: public SsuSettings {
Q_OBJECT
public:
static SsuCoreConfig *instance();
/**
* Find a username/password pair for the given scope
* @return a QPair with username and password, or an empty QPair if scope is invalid
*/
QPair<QString, QString> credentials(QString scope);
/**
* Get the scope for a repository, taking into account different scopes for
* release and RnD repositories
*
* Please note that variable scope is not yet implemented -- one default scope is
* read from the configuration file.
*
* @return a string containing the scope; it can be used to look up login credentials using credentials()
*/
QString credentialsScope(QString repoName, bool rndRepo=false);
/**
* Return the URL for which credentials scope is valid
*/
QString credentialsUrl(QString scope);
/**
* Get the current flavour when RnD repositories are used
* @return current flavour (usually something like testing, release, ..)
*/
Q_INVOKABLE QString flavour();
/**
* Get the current mode bits for the device
*/
Q_INVOKABLE int deviceMode();
/**
* Get the current domain used in registration
* @return domain, or "" if not set
*/
Q_INVOKABLE QString domain();
/**
* Return devices RND registration status
* @retval true device is registered
* @retval false device is not registered
*/
Q_INVOKABLE bool isRegistered();
/**
* Return the date/time when the credentials to access internal
* SSU servers were updated the last time
*/
Q_INVOKABLE QDateTime lastCredentialsUpdate();
/**
* Return the release version string for either a release, or a RnD snapshot
*/
Q_INVOKABLE QString release(bool rnd=false);
/**
* Set mode bits for the device
*/
Q_INVOKABLE void setDeviceMode(int mode, int editMode=Ssu::Replace);
/**
* Set the flavour used when resolving RND repositories
*/
Q_INVOKABLE void setFlavour(QString flavour);
/**
* Set the release version string for either a release, or a RnD snapshot
*/
Q_INVOKABLE void setRelease(QString release, bool rnd=false);
/**
* Set the domain string (usually something like nemo, jolla, ..)
*/
Q_INVOKABLE void setDomain(QString domain);
/**
* Return configuration settings regarding ssl verification
* @retval true SSL verification must be used; that's the default if not configured
* @retval false SSL verification should be disabled
*/
Q_INVOKABLE bool useSslVerify();
private:
SsuCoreConfig(): SsuSettings(SSU_CONFIGURATION, QSettings::IniFormat, SSU_DEFAULT_CONFIGURATION) {};
SsuCoreConfig(const SsuCoreConfig &); // hide copy constructor
static SsuCoreConfig *ssuCoreConfig;
};
#endif
......@@ -10,6 +10,7 @@
#include <QDir>
#include "ssudeviceinfo.h"
#include "ssucoreconfig.h"
#include "../constants.h"
......@@ -124,10 +125,10 @@ QString SsuDeviceInfo::deviceModel(){
boardMappings->beginGroup("arch.equals");
keys = boardMappings->allKeys();
QSettings settings(SSU_CONFIGURATION, QSettings::IniFormat);
SsuCoreConfig *settings = SsuCoreConfig::instance();
foreach (const QString &key, keys){
QString value = boardMappings->value(key).toString();
if (settings.value("arch").toString() == value){
if (settings->value("arch").toString() == value){
cachedModel = key;
break;
}
......@@ -185,10 +186,10 @@ QStringList SsuDeviceInfo::repos(bool rnd){
// read user-defined repositories from ssu.ini
// TODO: in strict mode, filter the repository list from there
QSettings ssuSettings(SSU_CONFIGURATION, QSettings::IniFormat);
ssuSettings.beginGroup("repository-urls");
result.append(ssuSettings.allKeys());
ssuSettings.endGroup();
SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
ssuSettings->beginGroup("repository-urls");
result.append(ssuSettings->allKeys());
ssuSettings->endGroup();
result.removeDuplicates();
......@@ -197,8 +198,8 @@ QStringList SsuDeviceInfo::repos(bool rnd){
result.removeAll(key);
// read the disabled repositories from user configuration
if (ssuSettings.contains("disabled-repos")){
foreach (const QString &key, ssuSettings.value("disabled-repos").toStringList())
if (ssuSettings->contains("disabled-repos")){
foreach (const QString &key, ssuSettings->value("disabled-repos").toStringList())
result.removeAll(key);
}
......
......@@ -11,7 +11,9 @@
#include "ssudeviceinfo.h"
#include "ssurepomanager.h"
#include "ssucoreconfig.h"
#include "ssulog.h"
#include "ssuvariables.h"
#include "ssu.h"
#include "../constants.h"
......@@ -28,8 +30,8 @@ void SsuRepoManager::update(){
SsuDeviceInfo deviceInfo;
QStringList ssuFilters;
QSettings ssuSettings(SSU_CONFIGURATION, QSettings::IniFormat);
int deviceMode = ssuSettings.value("deviceMode").toInt();
SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
int deviceMode = ssuSettings->value("deviceMode").toInt();
SsuLog *ssuLog = SsuLog::instance();
......@@ -44,8 +46,13 @@ void SsuRepoManager::update(){
if ((deviceMode & Ssu::RndMode) == Ssu::RndMode)
rndMode = true;
// strict mode enabled -> delete all repositories not prefixed by ssu_
if ((deviceMode & Ssu::StrictMode) == Ssu::StrictMode){
// get list of device-specific repositories...
QStringList repos = deviceInfo.repos(rndMode);
// strict mode enabled -> delete all repositories not prefixed by ssu
// assume configuration error if there are no device repos, and don't delete
// anything, even in strict mode
if ((deviceMode & Ssu::StrictMode) == Ssu::StrictMode && !repos.isEmpty()){
QDirIterator it(ZYPP_REPO_PATH, QDir::AllEntries|QDir::NoDot|QDir::NoDotDot);
while (it.hasNext()){
it.next();
......@@ -56,9 +63,6 @@ void SsuRepoManager::update(){
}
}
// get list of device-specific repositories...
QStringList repos = deviceInfo.repos(rndMode);
// ... delete all ssu-managed repositories not valid for this device ...
ssuFilters.append("ssu_*");
QDirIterator it(ZYPP_REPO_PATH, ssuFilters);
......@@ -102,3 +106,139 @@ void SsuRepoManager::update(){
}
}
}
// RND repos have flavour (devel, testing, release), and release (latest, next)
// Release repos only have release (latest, next, version number)
QString SsuRepoManager::url(QString repoName, bool rndRepo,
QHash<QString, QString> repoParameters,
QHash<QString, QString> parametersOverride){
QString r;
QStringList configSections;
SsuVariables var;
SsuLog *ssuLog = SsuLog::instance();
SsuCoreConfig *settings = SsuCoreConfig::instance();
QSettings *repoSettings = new QSettings(SSU_REPO_CONFIGURATION, QSettings::IniFormat);
SsuDeviceInfo deviceInfo;
//errorFlag = false;
settings->sync();
// fill in all arbitrary variables from ssu.inie
var.resolveSection(settings, "repository-url-variables", &repoParameters);
// add/overwrite some of the variables with sane ones
if (rndRepo){
repoParameters.insert("flavour",
repoSettings->value(
settings->flavour()+"-flavour/flavour-pattern").toString());
repoParameters.insert("flavourPattern",
repoSettings->value(
settings->flavour()+"-flavour/flavour-pattern").toString());
repoParameters.insert("flavourName", settings->flavour());
configSections << settings->flavour()+"-flavour" << "rnd" << "all";
// Make it possible to give any values with the flavour as well.
// These values can be overridden later with domain if needed.
var.resolveSection(repoSettings, settings->flavour()+"-flavour", &repoParameters);
} else {
configSections << "release" << "all";
}
repoParameters.insert("release", settings->release(rndRepo));
if (!repoParameters.contains("debugSplit"))
repoParameters.insert("debugSplit", "packages");
if (!repoParameters.contains("arch"))
repoParameters.insert("arch", settings->value("arch").toString());
// Override device model (and therefore all the family, ... stuff)
if (parametersOverride.contains("model"))
deviceInfo.setDeviceModel(parametersOverride.value("model"));
QStringList adaptationRepos = deviceInfo.adaptationRepos();
// read adaptation from settings, in case it can't be determined from
// board mappings. this is obsolete, and will be dropped soon
if (settings->contains("adaptation"))
repoParameters.insert("adaptation", settings->value("adaptation").toString());
repoParameters.insert("deviceFamily", deviceInfo.deviceFamily());
repoParameters.insert("deviceModel", deviceInfo.deviceModel());
// Those keys have now been obsoleted by generic variables, support for
// it will be removed soon
QStringList keys;
keys << "chip" << "adaptation" << "vendor";
foreach(QString key, keys){
QString value;
if (deviceInfo.getValue(key,value))
repoParameters.insert(key, value);
}
// 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
QRegExp regex("adaptation\\\d*", Qt::CaseSensitive, QRegExp::RegExp2);
if (regex.exactMatch(repoName)){
regex.setPattern("\\\d*");
regex.lastIndexIn(repoName);
int n = regex.cap().toInt();
if (!adaptationRepos.isEmpty()){
if (adaptationRepos.size() <= n) {
ssuLog->print(LOG_INFO, "Note: repo index out of bounds, substituting 0" + repoName);
n = 0;
}
QString adaptationRepo = adaptationRepos.at(n);
repoParameters.insert("adaptation", adaptationRepo);
ssuLog->print(LOG_DEBUG, "Found first adaptation " + repoName);
QHash<QString, QString> h = deviceInfo.variableSection(adaptationRepo);
QHash<QString, QString>::const_iterator i = h.constBegin();
while (i != h.constEnd()){
repoParameters.insert(i.key(), i.value());
i++;
}
} else
ssuLog->print(LOG_INFO, "Note: adaptation repo for invalid repo requested " + repoName);
repoName = "adaptation";
}
// Domain variables
// first read all variables from default-domain
var.resolveSection(repoSettings, "default-domain", &repoParameters);
// then overwrite with domain specific things if that block is available
var.resolveSection(repoSettings, settings->domain()+"-domain", &repoParameters);
// override arbitrary variables, mostly useful for generating mic URLs
QHash<QString, QString>::const_iterator i = parametersOverride.constBegin();
while (i != parametersOverride.constEnd()){
repoParameters.insert(i.key(), i.value());
i++;
}
if (settings->contains("repository-urls/" + repoName))
r = settings->value("repository-urls/" + repoName).toString();
else {
foreach (const QString &section, configSections){
repoSettings->beginGroup(section);
if (repoSettings->contains(repoName)){
r = repoSettings->value(repoName).toString();
repoSettings->endGroup();
break;
}
repoSettings->endGroup();
}
}
return var.resolveString(r, &repoParameters);
}
......@@ -23,6 +23,13 @@ class SsuRepoManager: public QObject {
* QHash
*/
void update();
/**
* Resolve a repository url
* @return the repository URL on success, an empty string on error
*/
QString url(QString repoName, bool rndRepo=false,
QHash<QString, QString> repoParameters=QHash<QString, QString>(),
QHash<QString, QString> parametersOverride=QHash<QString, QString>());
};
#endif
......@@ -10,6 +10,9 @@
#include <termios.h>
#include <ssudeviceinfo.h>
#include <ssurepomanager.h>
#include <QDebug>
#include "rndssucli.h"
......@@ -18,6 +21,7 @@ RndSsuCli::RndSsuCli(): QObject(){
QCoreApplication::instance(),SLOT(quit()), Qt::DirectConnection);
connect(&ssu, SIGNAL(done()),
this, SLOT(handleResponse()));
state = Idle;
}
void RndSsuCli::handleResponse(){
......@@ -33,17 +37,45 @@ void RndSsuCli::handleResponse(){
}
void RndSsuCli::optFlavour(QString newFlavour){
void RndSsuCli::optFlavour(QStringList opt){
QTextStream qout(stdout);
if (newFlavour != ""){
if (opt.count() == 3){
qout << "Changing flavour from " << ssu.flavour()
<< " to " << newFlavour << endl;
ssu.setFlavour(newFlavour);
} else
<< " to " << opt.at(2) << endl;
ssu.setFlavour(opt.at(2));
state = Idle;
} else if (opt.count() == 2) {
qout << "Device flavour is currently: " << ssu.flavour() << endl;
state = Idle;
}
}
QCoreApplication::exit(0);
void RndSsuCli::optMode(QStringList opt){
QTextStream qout(stdout);
qout << "Mode handling is currently not implemented" << endl;
state = Idle;
}
void RndSsuCli::optModifyRepo(int action, QStringList opt){
SsuRepoManager repoManager;
QTextStream qout(stdout);
qout << "Repository management is currently not implemented" << endl;
if (opt.count() == 3){
} else if (opt.count() == 4){
}
/*
switch(action){
case Add:
case Remove:
case Disable:
case Enable:
}
*/
}
void RndSsuCli::optRegister(){
......@@ -72,9 +104,58 @@ void RndSsuCli::optRegister(){
tcsetattr(STDIN_FILENO, TCSANOW, &termOld);
ssu.sendRegistration(username, password);
state = Busy;
}
void RndSsuCli::