#include "manager.h" #include "database.h" #include "utilities.h" #include "list.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace IoUtilities; using namespace ConversionUtilities; namespace PackageManagement { constexpr int defaultSigLevel = ALPM_SIG_PACKAGE | ALPM_SIG_PACKAGE_OPTIONAL | ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL; /*! * \brief The Manager class helps accessing ALPM. * * - It queries the ALPM for database and package information. * - It serializes the information as JSON objects used by the network classes and the web interface. */ /*! * \brief Creates a new manager class; initializes a new ALPM handle. * \param rootdir Specifies the root directory. * \param dbpath Specifies the database directory. */ Manager::Manager(const Config &config, QObject *parent) : QObject(parent), m_config(config), m_sigLevel(defaultSigLevel), m_localFileSigLevel(ALPM_SIG_USE_DEFAULT), m_aur(m_networkAccessManager) { alpm_errno_t err; if(!(m_handle = alpm_initialize(config.alpmRootDir().toLocal8Bit().data(), config.alpmDbPath().toLocal8Bit().data(), &err))) { throw runtime_error(string("Cannot initialize alpm: ") + alpm_strerror(err)); } } /*! * \brief Releases the associated ALPM handle. */ Manager::~Manager() { alpm_release(m_handle); } /*! * \brief Returns the package with the specified name from the specified database. */ AlpmPackage Manager::packageFromSyncDataBase(const QString &dbName, const QString &pkgName) { try { if(dbName == QLatin1String("local")) { return localDataBase().packages().at(pkgName); } else { return syncDataBases().at(dbName).packages().at(pkgName); } } catch(out_of_range &) { return AlpmPackage(); } } /*! * \brief Creates a new package instance for the specified package file. * * Verifies the integrity of the file if the option is set. */ AlpmOwnershipPackage Manager::packageFromFile(const char *fileName, bool verifyIntegrity) { alpm_pkg_t *pkg; if(alpm_pkg_load(m_handle, fileName, verifyIntegrity, static_cast(m_localFileSigLevel), &pkg) != 0) { throw runtime_error(string("Unable to load package file: ") + alpm_strerror(alpm_errno(m_handle))); } else { return AlpmOwnershipPackage(pkg); } } /*! * \brief Sets the install reason for the specified \a package. */ void Manager::setInstallReason(AlpmPackage package, alpm_pkgreason_t reason) { if(alpm_pkg_set_reason(package.ptr(), reason)) { throw runtime_error(string("Unable to set install reason of the package ") + package.name() + ": " + alpm_strerror(alpm_errno(m_handle))); } } /*! * \brief Returns the last value with the specified \a key in the specified multimap. */ const string &lastValue(const multimap &mm, const string &key) { static const string defaultValue; const auto i = find_if(mm.crbegin(), mm.crend(), [&key] (const pair &i) { return i.first == key; }); return i != mm.crend() ? i->second : defaultValue; } /*! * \brief Parses a "SigLevel" denotation from pacman config file. */ int parseSigLevel(const string &sigLevelStr = string()) { int sigLevel = defaultSigLevel; // split sig level denotation into parts const auto parts = splitString >(sigLevelStr, " "); for(const auto &part : parts) { // determine whether part affect packages, databases or both bool package = true, db = true; const char *partStart = part.data(); if(!strncmp(partStart, "Package", 7)) { db = false; // package only part partStart += 7; } else if(!strncmp(partStart, "Database", 8)) { package = false; // db only part partStart += 8; } // set sig level according part if(!strcmp(partStart, "Never")) { if(package) { sigLevel &= ~ALPM_SIG_PACKAGE; } if(db) { sigLevel &= ~ALPM_SIG_DATABASE; } } else if(!strcmp(partStart, "Optional")) { if(package) { sigLevel |= ALPM_SIG_PACKAGE | ALPM_SIG_PACKAGE_OPTIONAL; } if(db) { sigLevel |= ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL; } } else if(!strcmp(partStart, "Required")) { if(package) { sigLevel |= ALPM_SIG_PACKAGE; sigLevel &= ~ALPM_SIG_PACKAGE_OPTIONAL; } if(db) { sigLevel |= ALPM_SIG_DATABASE; sigLevel &= ~ALPM_SIG_DATABASE_OPTIONAL; } } else if(!strcmp(partStart, "TrustedOnly")) { if(package) { sigLevel &= ~(ALPM_SIG_PACKAGE_MARGINAL_OK | ALPM_SIG_PACKAGE_UNKNOWN_OK); } if(db) { sigLevel &= ~(ALPM_SIG_DATABASE_MARGINAL_OK | ALPM_SIG_DATABASE_UNKNOWN_OK); } } else if(!strcmp(partStart, "TrustAll")) { if(package) { sigLevel |= ALPM_SIG_PACKAGE_MARGINAL_OK | ALPM_SIG_PACKAGE_UNKNOWN_OK; } if(db) { sigLevel |= ALPM_SIG_DATABASE_MARGINAL_OK | ALPM_SIG_DATABASE_UNKNOWN_OK; } } else { cerr << "Warning: Invalid value \"" << part << "\" for \"SigLevel\" in pacman config file will be ignored." << endl; } } return sigLevel; } /*! * \brief Parses a "Usage" denotation from pacman config file. */ int parseUsage(const string &usageStr) { int usage = 0; const auto parts = splitString >(usageStr, " "); for(const auto &part : parts) { if(part == "Sync") { usage |= ALPM_DB_USAGE_SYNC; } else if(part == "Search") { usage |= ALPM_DB_USAGE_SEARCH; } else if(part == "Install") { usage |= ALPM_DB_USAGE_INSTALL; } else if(part == "Upgrade") { usage |= ALPM_DB_USAGE_UPGRADE; } else { cerr << "Warning: Invalid value \"" << part << "\" for \"Usage\" in pacman config file will be ignored." << endl; } } return usage ? usage : ALPM_DB_USAGE_ALL; } /*! * \brief Parses the Pacman configuration. Registers the listed sync databases. */ void Manager::parsePacmanConfig() { // open config file and parse as ini try { IniFile configIni; { fstream configFile; configFile.exceptions(ios_base::failbit | ios_base::badbit); configFile.open(m_config.pacmanConfFile().toLocal8Bit().data(), ios_base::in); configIni.parse(configFile); } // determine current cpu archtitecture (required for server URLs) static const string sysArch(QSysInfo::currentCpuArchitecture().toStdString()); string arch = sysArch; const auto &config = configIni.data(); // read relevant options static const string sigLevelKey("SigLevel"); static const string usageKey("Usage"); int globalSigLevel; try { const auto &options = config.at("options"); const auto &specifiedArch = lastValue(options, "Architecture"); if(!specifiedArch.empty() && specifiedArch != "auto") { arch = specifiedArch; } const auto &specifiedDir = lastValue(options, "CacheDir"); if(!specifiedDir.empty()) { m_cacheDir = QString::fromStdString(specifiedDir); } globalSigLevel = parseSigLevel(lastValue(options, sigLevelKey)); } catch(const out_of_range &) { // no options specified globalSigLevel = defaultSigLevel; } // register sync databases unordered_map includedInis; for(const auto &scope : config) { if(scope.first != "options") { // read and validate database name QString dbName = QString::fromLocal8Bit(scope.first.c_str()); if(dbName == QLatin1String("local")) { cerr << "Error: Unable to add database from pacman config: The database name mustn't be \"local\" because this name is reserved for the local database." << endl; } else if(dbName.startsWith(QLatin1String("aur"))) { cerr << "Error: Unable to add database from pacman config: The database name mustn't start with \"aur\" because this name is reserved for the Arch Linux User Repository." << endl; } else if(m_syncDbs.count(dbName)) { cerr << "Error: Unable to add database from pacman config: Database names must be unique. Ignoring second occurance of database \"" << scope.first << "\"." << endl; } else { // read sig level and usage const auto &sigLevelStr = lastValue(scope.second, sigLevelKey); int sigLevel = sigLevelStr.empty() ? globalSigLevel : parseSigLevel(sigLevelStr); int usage = parseUsage(lastValue(scope.second, usageKey)); // try to register database in the ALPM system if(alpm_db_t *db = alpm_register_syncdb(m_handle, scope.first.c_str(), static_cast(sigLevel))) { // set usage if(alpm_db_set_usage(db, static_cast(usage)) == 0) { cerr << "Added database [" << scope.first << "]" << endl; } else { cerr << "Warning: Added database [" << scope.first << "] but failed to set usage" << endl; } // add servers for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) { string url = range.first->second; findAndReplace(url, "$repo", scope.first); findAndReplace(url, "$arch", arch); alpm_db_add_server(db, url.c_str()); cerr << "Added server: " << url << endl; } // add included servers for(auto range = scope.second.equal_range("Include"); range.first != range.second; ++range.first) { const auto &path = range.first->second; auto &includedIni = includedInis[path]; if(includedIni.data().empty()) { try { fstream includedFile; includedFile.exceptions(ios_base::failbit | ios_base::badbit); includedFile.open(path, ios_base::in); includedIni.parse(includedFile); } catch (const ios_base::failure &) { cerr << "Error: An IO exception occured when parsing the included file \"" << path << "\"." << endl; } } try { const auto &includedScope = includedIni.data().at(string()); for(auto range = includedScope.equal_range("Server"); range.first != range.second; ++range.first) { string url = range.first->second; findAndReplace(url, "$repo", scope.first); findAndReplace(url, "$arch", arch); alpm_db_add_server(db, url.c_str()); cerr << "Added server: " << url << endl; } } catch (const out_of_range &) { cerr << "Warning: Included file \"" << path << "\" has no values." << endl; } } // add sync db to internal map if(usage & ALPM_DB_USAGE_UPGRADE) { // -> db is used to upgrade local database localDataBase().upgradeSources() << dbName; } m_syncDbs.emplace(dbName, db); } else { cerr << "Unable to add sync database [" << scope.first << "]" << endl; } } } } } catch (const ios_base::failure &) { throw ios_base::failure("An IO exception occured when parsing the config file."); } } /*! * \brief Unregisters all registred sync databases. */ void Manager::unregisterSyncDataBases() { if(alpm_unregister_all_syncdbs(m_handle)) { throw runtime_error(string("Cannot unregister sync databases: ") + alpm_strerror(alpm_errno(m_handle))); } } /*! * \brief Returns the local data base. */ const AlpmDataBase &Manager::localDataBase() const { if(!m_localDb) { m_localDb = alpm_get_localdb(m_handle); } return m_localDb; } /*! * \brief Returns the local data base. */ AlpmDataBase &Manager::localDataBase() { if(!m_localDb) { m_localDb = alpm_get_localdb(m_handle); } return m_localDb; } /*! * \brief Returns a list of all sync databases. * \remarks Sync databases must be registered with parsePacmanConfig() before. */ const std::map &Manager::syncDataBases() const { return m_syncDbs; // m_syncDbs has been filled when the databases were registered } /*! * \brief Returns basic information about the specified repository. */ QJsonObject Manager::basicRepoInfo(AlpmDataBase db, const QString &name, const QString &desc) const { QJsonObject repoInfo; repoInfo.insert(QStringLiteral("name"), name); repoInfo.insert(QStringLiteral("desc"), desc); repoInfo.insert(QStringLiteral("servers"), db.serverUrls()); repoInfo.insert(QStringLiteral("usage"), Utilities::usageStrings(db.usage())); repoInfo.insert(QStringLiteral("sigLevel"), Utilities::sigLevelStrings(db.sigLevel())); repoInfo.insert(QStringLiteral("packages"), db.packageNameJsonArray()); return repoInfo; } /*! * \brief Returns basic information about all repositories known to the manager. * * The results include the local database ("local") and the names of * the registered sync databases. */ const QJsonArray &Manager::basicRepoInfo() const { if(m_basicRepoInfo.isEmpty()) { m_basicRepoInfo << basicRepoInfo(localDataBase(), QStringLiteral("local"), QStringLiteral("The local database.")); auto const &syncDbs = syncDataBases(); for(const auto &syncDb : syncDbs) { // check if the "sync" database is actually used for syncing auto usage = syncDb.second.usage(); if((usage & ALPM_DB_USAGE_SYNC) || (usage & ALPM_DB_USAGE_INSTALL) || (usage & ALPM_DB_USAGE_UPGRADE)) { m_basicRepoInfo << basicRepoInfo(syncDb.second, syncDb.first, QStringLiteral("The sync database »%1«.").arg(syncDb.first)); } else { m_basicRepoInfo << basicRepoInfo(syncDb.second, syncDb.first, QStringLiteral("The database »%1«.").arg(syncDb.first)); } } } return m_basicRepoInfo; } /*! * \brief Returns package information for the specified selection of packages. */ const QJsonArray Manager::packageInfo(const QJsonArray &pkgSelection, bool full) { QJsonArray pkgInfos; for(const auto &pkgSelJsonVal : pkgSelection) { QJsonObject pkgSel = pkgSelJsonVal.toObject(); if(!pkgSel.isEmpty()) { QString repoName = pkgSel.value(QStringLiteral("repo")).toString(); QString pkgName = pkgSel.value(QStringLiteral("name")).toString(); AlpmPackage pkg; QJsonObject pkgInfo; if(!repoName.isEmpty() && !pkgName.isEmpty() && (pkg = packageFromSyncDataBase(repoName, pkgName))) { pkgInfo = full ? pkg.fullInfo() : pkg.basicInfo(); } else { pkgInfo.insert(QStringLiteral("error"), QStringLiteral("na")); } pkgInfo.insert(QStringLiteral("name"), pkgName); pkgInfo.insert(QStringLiteral("repo"), repoName); pkgInfo.insert(QStringLiteral("index"), pkgSel.value(QStringLiteral("index"))); pkgInfos << pkgInfo; } } return pkgInfos; } /*! * \brief Returns group information for the local database and all registred sync databases. */ const QJsonArray &Manager::groupInfo() const { if(m_groupInfo.empty()) { m_groupInfo << localDataBase().groupInfo(); for(const auto &db : m_syncDbs) { m_groupInfo << db.second.groupInfo(); } } return m_groupInfo; } /*! * \brief Returns the ALPM database with the specified name. * \throws Throws std::out_of_range if the specified database is unknown to the manager. */ const AlpmDataBase &Manager::dataBaseByName(const QString &dbName) const { if(dbName == QLatin1String("local")) { return localDataBase(); } else { return m_syncDbs.at(dbName); } } /*! * \brief Returns the ALPM database with the specified name. * \throws Throws std::out_of_range if the specified database is unknown to the manager. */ AlpmDataBase &Manager::dataBaseByName(const QString &dbName) { if(dbName == QLatin1String("local")) { return localDataBase(); } else { return m_syncDbs.at(dbName); } } /*! * \brief Checks for upgrades availabel to the specified database. * * The \a request must have the following values: * - db: Specifies the name of the database to check for upgrades. * - syncdbs: Array with the names of the databases used as upgrade sources. * If not present, appropriate upgrade sources will be determined automatically. */ QJsonObject Manager::invokeUpgradeLookup(const QJsonObject &request) const { QJsonObject result; QJsonArray errors; const AlpmDataBase &db = dataBaseByName(request.value(QStringLiteral("db")).toString()); QJsonValue syncDbsValue = request.value(QStringLiteral("syncdbs")); if(!db) { errors << QStringLiteral("Database to be checked not found."); } else { QJsonArray warnings; bool searchAur = request.value(QStringLiteral("aur")).toBool(false); if(searchAur) { m_aur.requestPackageInfo(db.packageNames()); } const auto results = checkForUpgrades(db, syncDbsValue); if(searchAur || !results.noSources) { QJsonArray softwareUpdates; QJsonArray packageOnlyUpdates; QJsonArray downgrades; QJsonArray orphanedPackages; if(!results.noSources) { for(const auto pkg : results.newVersions) { softwareUpdates << pkg.basicInfo(true); } for(const auto pkg : results.newReleases) { packageOnlyUpdates << pkg.basicInfo(true); } for(const auto pkg : results.downgrades) { downgrades << pkg.basicInfo(true); } for(const auto pkg : results.orphaned) { orphanedPackages << pkg.basicInfo(true); } } if(!warnings.isEmpty()) { result.insert(QStringLiteral("warnings"), warnings); } result.insert(QStringLiteral("softwareUpdates"), softwareUpdates); result.insert(QStringLiteral("packageOnlyUpdates"), packageOnlyUpdates); result.insert(QStringLiteral("downgrades"), downgrades); result.insert(QStringLiteral("orphanedPackages"), orphanedPackages); } else { errors << QStringLiteral("No update sources associated for database \"%1\".").arg(QString::fromLocal8Bit(db.name())); } } if(!errors.isEmpty()) { result.insert(QStringLiteral("errors"), errors); } return result; } /*! * \brief Checks the specified database for upgrades. */ const UpdateLookupResults Manager::checkForUpgrades(const AlpmDataBase &db, const QJsonValue &syncDbsValue) const { UpdateLookupResults results; const auto &syncDbs = syncDataBases(); QList syncDbSel; if(syncDbsValue.type() == QJsonValue::Array) { for(const auto &syncDbVal : syncDbsValue.toArray()) { const auto syncDbName = syncDbVal.toString(); if(syncDbName == QLatin1String("local")) { syncDbSel << &(localDataBase()); } else { try { syncDbSel << &(syncDbs.at(syncDbName)); } catch(out_of_range &) { results.warnings << QStringLiteral("The specified sync database \"%1\" can not be found.").arg(syncDbName); } } } } else { for(const auto &syncDbName : db.upgradeSources()) { if(syncDbName == QLatin1String("local")) { syncDbSel << &(localDataBase()); } else { try { syncDbSel << &(syncDbs.at(syncDbName)); } catch(out_of_range &) { results.warnings << QStringLiteral("The associated upgrade database \"%1\" can not be found.").arg(syncDbName); } } } } db.checkForUpgrades(syncDbSel, results); return results; } /*! * \brief Checks the specified database for upgrades. * * Appropriate upgrade sources will be determined automatically. */ const UpdateLookupResults Manager::checkForUpgrades(const AlpmDataBase &db) const { UpdateLookupResults results; QList syncDbSel; for(const auto &syncDbName : db.upgradeSources()) { try { syncDbSel << &(syncDataBases().at(syncDbName)); } catch(out_of_range &) { ; } } db.checkForUpgrades(syncDbSel, results); return results; } //void Manager::addTask() //{ // QUuid uuid; // while(m_tasks.find(uuid = QUuid::createUuid()) != m_tasks.cend()); // m_tasks.emplace(uuid); //} }