repoindex/lib/alpm/config.cpp

386 lines
17 KiB
C++

#include "./config.h"
#include "./manager.h"
#include <c++utilities/conversion/stringconversion.h>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QStandardPaths>
#include <iostream>
using namespace std;
using namespace ApplicationUtilities;
using namespace ConversionUtilities;
using namespace ChronoUtilities;
namespace RepoIndex {
bool useShSyntax = false;
const char *shchar = "";
/*!
* \class Alpm::Config
* \brief The ConfigArgs class bundles the command line arguments for the application.
*/
/*!
* \brief Initializes the command line arguments for the configuration of the application.
*/
ConfigArgs::ConfigArgs(ArgumentParser &parser) :
helpArg(parser),
packagesArg("packages", 'p', "specifies the packages"),
buildOrderArg("build-order", 'b', "calculates the build order to build the specified packages"),
addSourceOnlyDepsArg("add-src-only-deps", 'a', "adds source-only dependencies to build chain"),
requireSourcesArg("require-sources", 'q', "requires specified packages to be from a source repository"),
serverArg("server", 's', "runs a websocket server providing the web interface with information"),
repoNameArg("repo", 'r', "specifies the repository"),
upgradeLookupArg("upgrade-lookup", 'u', "checks available upgrades for the specified repository"),
mingwBundleArg("mingw-w64-bundle", 'm', "creates an archive with the runtime-relevant files from the specified mingw-w64-packages and their dependencies"),
repoindexConfArg("repoindex-conf", 'c', "specifies the path of the repo index config file (default is /etc/repoindex.conf"),
rootdirArg("root-dir", 'r', "specifies the root directory (default is /)"),
dbpathArg("db-path", 'd', "specifies the pacman database path (default is /var/lib/pacman)"),
pacmanConfArg("pacman-conf", 'p', "specifies the path of the pacman config file (default is /etc/pacman.conf"),
reposFromPacmanConfEnabled("repos-from-pacman-conf", '\0', "enables repositories from the pacman config file"),
websocketAddrArg("addr", '\0', "specifies the listening address for the websocket server, default is 127.0.0.1"),
websocketPortArg("port", '\0', "specifies the listening port for the websocket server, default is 1234"),
certFileArg("cert-file", '\0', "specifies the SSL certificate"),
keyFileArg("key-file", '\0', "specifies the private SSL key"),
insecureArg("insecure", '\0', "forces the server to run in insecure mode"),
aurArg("aur", '\0', "enables/disables AUR queries"),
verboseArg("verbose", 'v', "be verbose"),
outputFileArg("output-file", 'f', "specifies the output file"),
targetDirArg("target-dir", 't', "the directory to store the target archive"),
targetNameArg("target-name", 'n', "specifies the name of the target archive"),
targetFormatArg("target-format", 'e', "specifies the format of the target archive"),
iconThemesArg("icon-packages", 'i', "specifies the names of the icon packages to include"),
defaultIconThemeArg("default-icon-theme", '\0', "specifies the name of the default icon theme (should be included in --icon-packages)"),
extraPackagesArg("extra-packages", '\0', "specifies extra packages to be included"),
qtPluginsArg("qt-plugins", '\0', "specifies which Qt plugins to be included"),
cacheDirArg("cache-dir", '\0', "specifies the cache directory (default is /var/cache/repoindex)"),
storageDirArg("storage-dir", '\0', "specifies the storage directory (default is /var/lib/repoindex)"),
shSyntaxArg("sh-syntax", '\0', "prints the output using shell syntax: export REPOINDEX_RESULTS=('res1' 'res2' 'res3') or export REPOINDEX_ERROR='some error message'")
{
const initializer_list<const char *> pathValueName = {"path"};
const initializer_list<const char *> pkgValueNames = {"package 1", "package 2", "package 3"};
packagesArg.setRequiredValueCount(-1);
packagesArg.setRequired(true);
packagesArg.setImplicit(true);
packagesArg.setValueNames(pkgValueNames);
buildOrderArg.setDenotesOperation(true);
requireSourcesArg.setCombinable(true);
serverArg.setDenotesOperation(true);
repoNameArg.setRequiredValueCount(1);
repoNameArg.setRequired(true);
repoNameArg.setImplicit(true);
repoNameArg.setValueNames({"repo"});
upgradeLookupArg.setDenotesOperation(true);
mingwBundleArg.setDenotesOperation(true);
repoindexConfArg.setCombinable(true);
repoindexConfArg.setValueNames(pathValueName);
repoindexConfArg.setRequiredValueCount(1);
rootdirArg.setCombinable(true);
rootdirArg.setValueNames({"directory"});
rootdirArg.setRequiredValueCount(1);
dbpathArg.setCombinable(true);
dbpathArg.setValueNames(pathValueName);
dbpathArg.setRequiredValueCount(1);
pacmanConfArg.setCombinable(true);
pacmanConfArg.setValueNames(pathValueName);
pacmanConfArg.setRequiredValueCount(1);
reposFromPacmanConfEnabled.setCombinable(true);
websocketAddrArg.setCombinable(true);
websocketAddrArg.setValueNames({"IP address"});
websocketAddrArg.setRequiredValueCount(1);
websocketPortArg.setCombinable(true);
websocketPortArg.setValueNames({"port number"});
websocketPortArg.setRequiredValueCount(1);
certFileArg.setCombinable(true);
certFileArg.setValueNames(pathValueName);
certFileArg.setRequiredValueCount(1);
keyFileArg.setCombinable(true);
keyFileArg.setValueNames(pathValueName);
keyFileArg.setRequiredValueCount(1);
insecureArg.setCombinable(true);
aurArg.setCombinable(true);
aurArg.setRequiredValueCount(1);
aurArg.setValueNames({"enabled/disabled"});
verboseArg.setCombinable(true);
outputFileArg.setCombinable(true);
outputFileArg.setRequired(true);
outputFileArg.setRequiredValueCount(1);
outputFileArg.setValueNames(pathValueName);
targetDirArg.setCombinable(true);
targetDirArg.setRequiredValueCount(1);
targetDirArg.setValueNames(pathValueName);
targetNameArg.setCombinable(true);
targetNameArg.setRequired(true);
targetNameArg.setRequiredValueCount(1);
targetNameArg.setValueNames({"name"});
targetFormatArg.setCombinable(true);
targetFormatArg.setRequiredValueCount(1);
targetFormatArg.setValueNames({"zip/7z/tar.gz/tar.bz/tar.xz"});
iconThemesArg.setCombinable(true);
iconThemesArg.setRequiredValueCount(-1);
iconThemesArg.setValueNames(pkgValueNames);
defaultIconThemeArg.setCombinable(true);
defaultIconThemeArg.setRequiredValueCount(1);
defaultIconThemeArg.setValueNames({"theme name"});
extraPackagesArg.setCombinable(true);
extraPackagesArg.setRequiredValueCount(-1);
extraPackagesArg.setValueNames(pkgValueNames);
qtPluginsArg.setCombinable(true);
qtPluginsArg.setRequiredValueCount(-1);
qtPluginsArg.setValueNames({"plugin 1", "plugin 2"});
addSourceOnlyDepsArg.setCombinable(true);
shSyntaxArg.setCombinable(true);
serverArg.setSubArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &reposFromPacmanConfEnabled, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg});
upgradeLookupArg.setSubArguments({&repoNameArg, &shSyntaxArg});
buildOrderArg.setSubArguments({&packagesArg, &aurArg, &addSourceOnlyDepsArg, &requireSourcesArg, &verboseArg, &shSyntaxArg});
mingwBundleArg.setSubArguments({&packagesArg, &targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg, &defaultIconThemeArg, &extraPackagesArg, &qtPluginsArg});
cacheDirArg.setCombinable(true);
cacheDirArg.setRequiredValueCount(1);
cacheDirArg.setValueNames(pathValueName);
storageDirArg.setCombinable(true);
storageDirArg.setRequiredValueCount(1);
storageDirArg.setValueNames(pathValueName);
parser.setMainArguments({&helpArg, &buildOrderArg, &upgradeLookupArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &cacheDirArg, &storageDirArg});
}
/*!
* \class Alpm::Config
* \brief The Config class represents the configuration for the application.
*/
/*!
* \brief Creates the default configuration for the application.
*/
Config::Config() :
m_alpmRootDir(QStringLiteral("/")),
m_alpmDbPath(QStringLiteral("/var/lib/pacman")),
m_pacmanConfFile(QStringLiteral("/etc/pacman.conf")),
m_cacheDir(QStringLiteral("/var/cache/repoindex")),
m_storageDir(QStringLiteral("/var/lib/repoindex")),
m_websocketServerListeningAddr(QHostAddress::LocalHost),
m_websocketServerListeningPort(1234),
m_serverInsecure(false),
m_serverCloseable(true),
m_localEnabled(true),
m_reposFromPacmanConfEnabled(false),
m_aurEnabled(true),
m_verbose(false),
m_runServer(false)
{}
/*!
* \cond
*/
inline void assign(quint16 &num, const QJsonValue &val)
{
if(!val.isUndefined()) {
auto n = val.toInt(-1);
if(n > 0 && n <= static_cast<quint16>(-1)) {
num = static_cast<quint16>(n);
} else {
cerr << shchar << "Error: The specified value \"" << n << "\" is not a number." << endl;
}
}
}
inline void assign(QHostAddress &addr, const QString &val)
{
if(!val.isEmpty() && !addr.setAddress(val)) {
// checking some special values might be useful
if(val == QLatin1String("localhost")) {
addr = QHostAddress::LocalHost;
} else if(val == QLatin1String("localhost-IPv6")) {
addr = QHostAddress::LocalHostIPv6;
} else if(val == QLatin1String("any-IPv4")) {
addr = QHostAddress::AnyIPv4;
} else if(val == QLatin1String("any-IPv6")) {
addr = QHostAddress::AnyIPv6;
} else if(val == QLatin1String("any")) {
addr = QHostAddress::Any;
} else {
cerr << shchar << "Error: Unable to parse the specified host address \"" << val.toStdString() << "\"." << endl;
}
}
}
inline void assign(QString &str, const Argument &arg)
{
if(arg.isPresent()) {
str = QString::fromLocal8Bit(arg.values().front());
}
}
inline void assign(quint16 &num, const Argument &arg)
{
if(arg.isPresent()) {
try {
num = stringToNumber<quint16>(arg.values().front());
} catch(const ConversionException &) {
cerr << shchar << "Error: The specified argument value \"" << arg.values().front() << "\" is not a number." << endl;
}
}
}
inline void assign(bool &num, const Argument &arg)
{
num |= arg.isPresent();
}
/*!
* \endcond
*/
/*!
* \brief Loads the configuration from the specified configuration file.
*/
void Config::loadFromConfigFile(const QString &configFilePath)
{
QFile file(configFilePath);
if(file.open(QFile::ReadOnly)) {
QJsonParseError error;
auto doc = QJsonDocument::fromJson(file.readAll(), &error);
if(error.error == QJsonParseError::NoError) {
auto mainObj = doc.object();
auto alpmObj = mainObj.value(QStringLiteral("alpm")).toObject();
m_alpmRootDir = alpmObj.value(QStringLiteral("rootDir")).toString(m_alpmRootDir);
m_alpmDbPath = alpmObj.value(QStringLiteral("dbPath")).toString(m_alpmDbPath);
m_pacmanConfFile = alpmObj.value(QStringLiteral("pacmanConfigFile")).toString(m_pacmanConfFile);
m_cacheDir = mainObj.value(QStringLiteral("cacheDir")).toString(m_cacheDir);
m_storageDir = mainObj.value(QStringLiteral("storageDir")).toString(m_storageDir);
auto aurObj = mainObj.value(QStringLiteral("aur")).toObject();
m_aurEnabled = aurObj.value(QStringLiteral("enabled")).toBool(m_aurEnabled);
auto serverObj = mainObj.value(QStringLiteral("server")).toObject();
assign(m_websocketServerListeningAddr, serverObj.value(QStringLiteral("websocketListeningAddr")).toString());
assign(m_websocketServerListeningPort, serverObj.value(QStringLiteral("websocketListeningPort")));
m_serverCertFile = serverObj.value(QStringLiteral("certFile")).toString(m_serverCertFile);
m_serverKeyFile = serverObj.value(QStringLiteral("keyFile")).toString(m_serverKeyFile);
m_serverInsecure = serverObj.value(QStringLiteral("insecure")).toBool(m_serverInsecure);
auto reposObj = mainObj.value(QStringLiteral("repos")).toObject();
m_localEnabled = reposObj.value(QStringLiteral("localEnabled")).toBool(m_localEnabled);
m_reposFromPacmanConfEnabled = reposObj.value(QStringLiteral("fromPacmanConfig")).toBool(m_reposFromPacmanConfEnabled);
for(const auto &repo : reposObj.value(QStringLiteral("add")).toArray()) {
m_repoEntries << RepoEntry();
m_repoEntries.back().load(repo);
}
} else {
cerr << shchar << "Error: Unable to parse config file \"" << configFilePath.toLocal8Bit().data() << "\": "
<< error.errorString().toLocal8Bit().data() << " at character " << error.offset << endl;
}
} else {
cerr << shchar << "Error: Unable to open config file \"" << configFilePath.toLocal8Bit().data() << "\"." << endl;
}
}
/*!
* \brief Loads the configuration from the configuration file.
*
* The config file is obtained from the specified config args. If no such argument is present,
* some default locations are checked.
*/
void Config::loadFromConfigFile(const ConfigArgs &args)
{
if(args.repoindexConfArg.isPresent()) {
loadFromConfigFile(QString::fromLocal8Bit(args.repoindexConfArg.values().front()));
return;
} else {
for(const auto &defaultPath : {QStringLiteral("./repoindex.conf"), QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QStringLiteral("/repoindex.conf"), QStringLiteral("/etc/repoindex.conf")}) {
if(!defaultPath.isEmpty() && QFile::exists(defaultPath)) {
loadFromConfigFile(defaultPath);
return;
}
}
}
}
/*!
* \brief Loads the config from the specified configuration arguments.
*/
void Config::loadFromArgs(const ConfigArgs &args)
{
assign(m_alpmRootDir, args.rootdirArg);
assign(m_alpmDbPath, args.dbpathArg);
assign(m_pacmanConfFile, args.pacmanConfArg);
if(args.aurArg.isPresent()) {
auto val = args.aurArg.values().front();
if(!strcmp(val, "enabled")) {
m_aurEnabled = true;
} else if(!strcmp(val, "disabled")) {
m_aurEnabled = false;
} else {
cerr << shchar << "Warning: The specified value for the argument --aur-enabled is invalid and will be ignored." << endl;
}
}
assign(m_websocketServerListeningPort, args.websocketPortArg);
assign(m_serverCertFile, args.certFileArg);
assign(m_serverKeyFile, args.keyFileArg);
assign(m_serverInsecure, args.insecureArg);
if(args.websocketAddrArg.isPresent()) {
assign(m_websocketServerListeningAddr, QString::fromLocal8Bit(args.websocketAddrArg.values().front()));
}
m_verbose = args.verboseArg.isPresent();
m_runServer = args.serverArg.isPresent();
if(args.cacheDirArg.isPresent()) {
m_cacheDir = QString::fromLocal8Bit(args.cacheDirArg.values().front());
}
if(args.storageDirArg.isPresent()) {
m_storageDir = QString::fromLocal8Bit(args.storageDirArg.values().front());
}
if(args.reposFromPacmanConfEnabled.isPresent()) {
m_reposFromPacmanConfEnabled = true;
}
}
/*!
* \brief Ensures that the server is only accessable from the local machine.
*/
void Config::loadLocalOnlySetup()
{
m_websocketServerListeningAddr = QHostAddress::LocalHost;
m_serverInsecure = true;
}
RepoEntry::RepoEntry() :
m_sigLevel(SignatureLevel::UseDefault),
m_ignored(false)
{}
/*!
* \brief Loads the values from the specified JSON value.
*/
void RepoEntry::load(const QJsonValue &jsonValue)
{
auto obj = jsonValue.toObject();
m_name = obj.value(QStringLiteral("name")).toString(m_name);
m_dataBaseFile = obj.value(QStringLiteral("dataBaseFile")).toString(m_dataBaseFile);
m_sourceDir = obj.value(QStringLiteral("sourcesDir")).toString(m_sourceDir);
m_packageDir = obj.value(QStringLiteral("packagesDir")).toString(m_packageDir);
for(const auto &server : obj.value(QStringLiteral("server")).toArray()) {
auto str = server.toString();
if(!str.isEmpty()) {
m_server << str;
}
}
for(const auto &upgradeSources : obj.value(QStringLiteral("upgradeSources")).toArray()) {
auto str = upgradeSources.toString();
if(!str.isEmpty()) {
m_upgradeSources << str;
}
}
const auto sigLevelValue = obj.value(QStringLiteral("sigLevel"));
if(sigLevelValue.isString()) {
m_sigLevel = Manager::parseSigLevel(sigLevelValue.toString().toLocal8Bit().data());
}
m_maxDatabaseAge = TimeSpan::fromSeconds(obj.value(QStringLiteral("maxAge")).toDouble());
m_ignored = obj.value(QStringLiteral("ignored")).toBool(m_ignored);
}
} // namespace Alpm