980 lines
32 KiB
C++
980 lines
32 KiB
C++
#include "lib.h"
|
|
#include "callbacks.h"
|
|
#include "callbacks.hxx"
|
|
#include "helpers.hxx"
|
|
#include "repository.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdlib>
|
|
#include <exception>
|
|
#include <zypp-core/Pathname.h>
|
|
#include <zypp-core/Url.h>
|
|
#include <zypp/DiskUsageCounter.h>
|
|
#include <zypp/Pattern.h>
|
|
#include <zypp/Product.h>
|
|
#include <zypp/PublicKey.h>
|
|
#include <zypp/RepoInfo.h>
|
|
#include <zypp/RepoManager.h>
|
|
#include <zypp/RepoManagerFlags.h>
|
|
#include <zypp/ResKind.h>
|
|
#include <zypp/ResObject.h>
|
|
#include <zypp/ResStatus.h>
|
|
#include <zypp/Resolvable.h>
|
|
#include <zypp/ZYpp.h>
|
|
#include <zypp/ZYppCommit.h>
|
|
#include <zypp/ZYppFactory.h>
|
|
#define ZYPP_BASE_LOGGER_LOGGROUP "rust-bindings"
|
|
#include <zypp/base/LogControl.h>
|
|
#include <zypp/base/Logger.h>
|
|
|
|
#include <zypp/ui/Selectable.h>
|
|
|
|
extern "C" {
|
|
|
|
#include <systemd/sd-journal.h>
|
|
|
|
// helper macro for logging the code location from where libzypp is called
|
|
#define LOG_LOCATION(message) \
|
|
do { \
|
|
std::string line("CODE_LINE="); \
|
|
line.append(std::to_string(__LINE__)); \
|
|
sd_journal_send_with_location("CODE_FILE=" __FILE__, line.c_str(), \
|
|
__func__, "PRIORITY=%i", LOG_NOTICE, \
|
|
"MESSAGE=%s", (message), \
|
|
"COMPONENT=zypp-agama-sys", NULL); \
|
|
} while (0)
|
|
|
|
// helper function to hide the password in URL
|
|
std::string filter_url(const char *url) {
|
|
std::string result(url);
|
|
try {
|
|
zypp::Url z_url(url);
|
|
// asString() by default does not include the password
|
|
result = z_url.asString();
|
|
} catch (const zypp::Exception &excpt) {
|
|
// if the URL is invalid, just return the original string
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct Zypp {
|
|
zypp::ZYpp::Ptr zypp_pointer;
|
|
zypp::RepoManager *repo_manager;
|
|
};
|
|
|
|
static struct Zypp the_zypp{
|
|
.zypp_pointer = NULL,
|
|
.repo_manager = NULL,
|
|
};
|
|
|
|
// formatter which actually logs the messages to the systemd journal,
|
|
// that is a bit hacky but in the logger we receive an already formatted
|
|
// message as a single string and it would not be easy to get back the original
|
|
// components of the message
|
|
struct AgamaFormatter : public zypp::base::LogControl::LineFormater {
|
|
virtual std::string format(const std::string &zypp_group,
|
|
zypp::base::logger::LogLevel zypp_level,
|
|
const char *zypp_file, const char *zypp_func,
|
|
int zypp_line, const std::string &zypp_message) {
|
|
// the systemd/syslog compatible log level
|
|
int level;
|
|
|
|
// convert the zypp log level to the systemd/syslog log level
|
|
switch (zypp_level) {
|
|
// for details about the systemd levels see
|
|
// https://www.freedesktop.org/software/systemd/man/latest/sd-daemon.html
|
|
case zypp::base::logger::E_DBG:
|
|
level = LOG_DEBUG;
|
|
break;
|
|
case zypp::base::logger::E_MIL:
|
|
level = LOG_INFO;
|
|
break;
|
|
case zypp::base::logger::E_WAR:
|
|
level = LOG_WARNING;
|
|
break;
|
|
case zypp::base::logger::E_ERR:
|
|
level = LOG_ERR;
|
|
break;
|
|
case zypp::base::logger::E_SEC:
|
|
// security error => critical
|
|
level = LOG_CRIT;
|
|
break;
|
|
case zypp::base::logger::E_INT:
|
|
// internal error => critical
|
|
level = LOG_CRIT;
|
|
break;
|
|
// libzypp specific level
|
|
case zypp::base::logger::E_USR:
|
|
level = LOG_INFO;
|
|
break;
|
|
// libzypp specific level
|
|
case zypp::base::logger::E_XXX:
|
|
level = LOG_CRIT;
|
|
break;
|
|
}
|
|
|
|
// unlike the other values, the location needs to be sent in an already
|
|
// formatted strings
|
|
std::string file("CODE_FILE=");
|
|
file.append(zypp_file);
|
|
std::string line("CODE_LINE=");
|
|
line.append(std::to_string(zypp_line));
|
|
|
|
// this will log the message with libzypp location, not from *this* file,
|
|
// see "man sd_journal_send_with_location"
|
|
sd_journal_send_with_location(
|
|
file.c_str(), line.c_str(), zypp_func, "PRIORITY=%i", level,
|
|
"MESSAGE=%s", zypp_message.c_str(),
|
|
// some custom data to allow easy filtering of the libzypp messages
|
|
"COMPONENT=libzypp", "ZYPP_GROUP=%s", zypp_group.c_str(),
|
|
"ZYPP_LEVEL=%i", zypp_level, NULL);
|
|
|
|
// libzypp aborts when the returned message is empty,
|
|
// return some static fake data to make it happy
|
|
return "msg";
|
|
}
|
|
};
|
|
|
|
// a dummy logger
|
|
struct AgamaLogger : public zypp::base::LogControl::LineWriter {
|
|
virtual void writeOut(const std::string &formatted) {
|
|
// do nothing, the message has been already logged by the formatter
|
|
}
|
|
};
|
|
|
|
void free_zypp(struct Zypp *zypp) noexcept {
|
|
LOG_LOCATION("Dropping libzypp");
|
|
// ensure that target is unloaded otherwise nasty things can happen if new
|
|
// zypp is created in different thread
|
|
zypp->zypp_pointer->getTarget()->unload();
|
|
zypp->zypp_pointer =
|
|
NULL; // shared ptr assignment operator will free original pointer
|
|
delete (zypp->repo_manager);
|
|
zypp->repo_manager = NULL;
|
|
}
|
|
|
|
static zypp::ZYpp::Ptr zypp_ptr() {
|
|
LOG_LOCATION("Initializing libzypp");
|
|
sd_journal_print(LOG_NOTICE, "Redirecting libzypp logs to systemd journal");
|
|
|
|
// log to systemd journal using our specific formatter
|
|
boost::shared_ptr<AgamaFormatter> formatter(new AgamaFormatter);
|
|
zypp::base::LogControl::instance().setLineFormater(formatter);
|
|
// use a dummy logger, using a NULL logger would skip the formatter completely
|
|
// so the messages would not be logged in the end
|
|
boost::shared_ptr<AgamaLogger> logger(new AgamaLogger);
|
|
zypp::base::LogControl::instance().setLineWriter(logger);
|
|
|
|
// do not do any magic waiting for lock as in agama context we work
|
|
// on our own root, so there should be no need to wait
|
|
return zypp::getZYpp();
|
|
}
|
|
|
|
void switch_target(struct Zypp *zypp, const char *root,
|
|
struct Status *status) noexcept {
|
|
std::string message("Switching target to: ");
|
|
message.append(root);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
const std::string root_str(root);
|
|
try {
|
|
zypp->zypp_pointer->initializeTarget(root_str,
|
|
false /* rebuild rpmdb: no */);
|
|
|
|
// switch cache for repositories, otherwise we run out of space in tmpfs
|
|
// see
|
|
// https://github.com/yast/yast-pkg-bindings/blob/853496f527543e6d51730fd7e3126ad94b13c303/src/PkgFunctions.cc#L496
|
|
zypp::RepoManagerOptions repo_options(root);
|
|
zypp::Pathname packages_prefix = repo_options.repoPackagesCachePath;
|
|
|
|
zypp::ResPool pool = zypp->zypp_pointer->pool();
|
|
for_(it, pool.knownRepositoriesBegin(), pool.knownRepositoriesEnd()) {
|
|
zypp::RepoInfo repo = it->info();
|
|
repo.setPackagesPath(packages_prefix / repo.escaped_alias());
|
|
|
|
MIL << "Setting package cache for repository " << repo.alias().c_str()
|
|
<< ": " << repo.packagesPath().asString().c_str() << std::endl;
|
|
|
|
it->setInfo(repo);
|
|
}
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
return;
|
|
}
|
|
|
|
STATUS_OK(status);
|
|
}
|
|
|
|
bool commit(struct Zypp *zypp, struct Status *status,
|
|
struct DownloadResolvableCallbacks *download_callbacks,
|
|
struct SecurityCallbacks *security_callbacks,
|
|
struct InstallCallbacks *install_callbacks) noexcept {
|
|
LOG_LOCATION("Starting package installation");
|
|
|
|
try {
|
|
set_zypp_resolvable_download_callbacks(download_callbacks);
|
|
set_zypp_security_callbacks(security_callbacks);
|
|
set_zypp_install_callbacks(install_callbacks);
|
|
zypp::ZYppCommitPolicy policy;
|
|
|
|
// enable preload of rpms to speed up installation
|
|
policy.downloadMode(zypp::DownloadInAdvance);
|
|
zypp::ZYppCommitResult result = zypp->zypp_pointer->commit(policy);
|
|
STATUS_OK(status);
|
|
unset_zypp_resolvable_download_callbacks();
|
|
unset_zypp_security_callbacks();
|
|
unset_zypp_install_callbacks();
|
|
return result.allDone();
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
unset_zypp_resolvable_download_callbacks();
|
|
unset_zypp_security_callbacks();
|
|
unset_zypp_install_callbacks();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// TODO: split init target into set of repo manager, initialize target and load
|
|
// target and merge it in rust
|
|
struct Zypp *init_target(const char *root, struct Status *status,
|
|
ProgressCallback progress, void *user_data) noexcept {
|
|
std::string message("Initializing target: ");
|
|
message.append(root);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (the_zypp.zypp_pointer != NULL) {
|
|
STATUS_ERROR(status, "Cannot have two init_target concurrently, "
|
|
"libzypp not ready for this. Call free_zypp first.");
|
|
return NULL;
|
|
}
|
|
|
|
const std::string root_str(root);
|
|
|
|
// create the libzypp lock also in the target directory
|
|
setenv("ZYPP_LOCKFILE_ROOT", root, 1 /* allow overwrite */);
|
|
|
|
struct Zypp *zypp = NULL;
|
|
try {
|
|
zypp::RepoManagerOptions repo_manager_options(root);
|
|
// repository manager options cannot be replaced, a new repository manager
|
|
// is needed
|
|
zypp::RepoManager *new_repo_manager =
|
|
new zypp::RepoManager(repo_manager_options);
|
|
|
|
// replace the old repository manager
|
|
if (the_zypp.repo_manager)
|
|
delete the_zypp.repo_manager;
|
|
the_zypp.repo_manager = new_repo_manager;
|
|
|
|
// TODO: localization
|
|
if (progress != NULL)
|
|
progress("Initializing the Target System", 0, 2, user_data);
|
|
the_zypp.zypp_pointer = zypp_ptr();
|
|
if (the_zypp.zypp_pointer == NULL) {
|
|
STATUS_ERROR(status, "Failed to obtain zypp pointer. "
|
|
"See journalctl for details.");
|
|
return NULL;
|
|
}
|
|
zypp = &the_zypp;
|
|
zypp->zypp_pointer->initializeTarget(root_str, false);
|
|
if (progress != NULL)
|
|
progress("Reading Installed Packages", 1, 2, user_data);
|
|
zypp->zypp_pointer->target()->load();
|
|
|
|
// Prefer the local medium to download, useful when registering the system
|
|
// with the Full installation medium, then it works like a local cache.
|
|
// However, if the SCC contains updated packages then they will be preferred
|
|
// to the local medium.
|
|
// This needs to be done before the solver run as the solver chooses the
|
|
// right package to install according to this flag.
|
|
zypp::ZConfig::instance().set_download_media_prefer_download(false);
|
|
MIL << "Prefer download to local packages: "
|
|
<< (zypp::ZConfig::instance().download_media_prefer_download()
|
|
? "true"
|
|
: "false")
|
|
<< std::endl;
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
the_zypp.zypp_pointer = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
STATUS_OK(status);
|
|
return zypp;
|
|
}
|
|
|
|
void free_repository(struct Repository *repo) {
|
|
free(repo->url);
|
|
free(repo->alias);
|
|
free(repo->userName);
|
|
free(repo->serviceName);
|
|
}
|
|
|
|
void free_repository_list(struct RepositoryList *list) noexcept {
|
|
for (unsigned i = 0; i < list->size; ++i) {
|
|
free_repository(list->repos + i);
|
|
}
|
|
free(list->repos);
|
|
}
|
|
|
|
void free_status(struct Status *status) noexcept {
|
|
if (status->error != NULL) {
|
|
free(status->error);
|
|
status->error = NULL;
|
|
}
|
|
}
|
|
|
|
static zypp::Resolvable::Kind kind_to_zypp_kind(RESOLVABLE_KIND kind) {
|
|
switch (kind) {
|
|
case RESOLVABLE_PACKAGE:
|
|
return zypp::Resolvable::Kind::package;
|
|
case RESOLVABLE_SRCPACKAGE:
|
|
return zypp::Resolvable::Kind::srcpackage;
|
|
case RESOLVABLE_PATTERN:
|
|
return zypp::Resolvable::Kind::pattern;
|
|
case RESOLVABLE_PRODUCT:
|
|
return zypp::Resolvable::Kind::product;
|
|
case RESOLVABLE_PATCH:
|
|
return zypp::Resolvable::Kind::patch;
|
|
}
|
|
PANIC("Unhandled case in resolvable kind switch %i", kind);
|
|
}
|
|
|
|
static zypp::ResStatus::TransactByValue
|
|
transactby_from(enum RESOLVABLE_SELECTED who) {
|
|
switch (who) {
|
|
case RESOLVABLE_SELECTED::SOLVER_SELECTED:
|
|
return zypp::ResStatus::SOLVER;
|
|
case RESOLVABLE_SELECTED::APPLICATION_SELECTED:
|
|
return zypp::ResStatus::APPL_HIGH;
|
|
case RESOLVABLE_SELECTED::USER_SELECTED:
|
|
return zypp::ResStatus::USER;
|
|
case RESOLVABLE_SELECTED::NOT_SELECTED: {
|
|
PANIC("Unexpected value RESOLVABLE_SELECTED::NOT_SELECTED.");
|
|
}
|
|
case RESOLVABLE_SELECTED::USER_REMOVED: {
|
|
PANIC("Unexpected value RESOLVABLE_SELECTED::USER_REMOVED.");
|
|
}
|
|
}
|
|
|
|
// should not happen
|
|
PANIC("Unexpected RESOLVABLE_SELECT value %i", who);
|
|
}
|
|
|
|
void resolvable_select(struct Zypp *_zypp, const char *name,
|
|
enum RESOLVABLE_KIND kind, enum RESOLVABLE_SELECTED who,
|
|
struct Status *status) noexcept {
|
|
std::string message("Selecting resolvable: ");
|
|
message.append(name);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (who == RESOLVABLE_SELECTED::NOT_SELECTED) {
|
|
STATUS_OK(status);
|
|
return;
|
|
}
|
|
|
|
zypp::Resolvable::Kind z_kind = kind_to_zypp_kind(kind);
|
|
auto selectable = zypp::ui::Selectable::get(z_kind, name);
|
|
if (!selectable) {
|
|
STATUS_ERROR(status, "Failed to find %s with name '%s'", z_kind.c_str(),
|
|
name);
|
|
return;
|
|
}
|
|
|
|
STATUS_OK(status);
|
|
auto value = transactby_from(who);
|
|
selectable->setToInstall(value);
|
|
}
|
|
|
|
void resolvable_unselect(struct Zypp *_zypp, const char *name,
|
|
enum RESOLVABLE_KIND kind,
|
|
enum RESOLVABLE_SELECTED who,
|
|
struct Status *status) noexcept {
|
|
std::string message("Unselecting resolvable: ");
|
|
message.append(name);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
STATUS_OK(status);
|
|
if (who == RESOLVABLE_SELECTED::NOT_SELECTED) {
|
|
return;
|
|
}
|
|
|
|
zypp::Resolvable::Kind z_kind = kind_to_zypp_kind(kind);
|
|
auto selectable = zypp::ui::Selectable::get(z_kind, name);
|
|
if (!selectable) {
|
|
STATUS_ERROR(status, "Failed to find %s with name '%s'", z_kind.c_str(),
|
|
name);
|
|
return;
|
|
}
|
|
|
|
auto value = transactby_from(who);
|
|
selectable->unset(value);
|
|
}
|
|
|
|
void resolvable_reset_all(struct Zypp *_zypp) noexcept {
|
|
LOG_LOCATION("Resetting status of all resolvables");
|
|
|
|
for (auto &item : zypp::ResPool::instance())
|
|
item.statusReset();
|
|
}
|
|
|
|
static RESOLVABLE_SELECTED convert_selected(zypp::ResStatus status) {
|
|
if (status.isToBeInstalled()) {
|
|
switch (status.getTransactByValue()) {
|
|
case zypp::ResStatus::TransactByValue::USER:
|
|
return RESOLVABLE_SELECTED::USER_SELECTED;
|
|
case zypp::ResStatus::TransactByValue::APPL_HIGH:
|
|
case zypp::ResStatus::TransactByValue::APPL_LOW:
|
|
return RESOLVABLE_SELECTED::APPLICATION_SELECTED;
|
|
case zypp::ResStatus::TransactByValue::SOLVER:
|
|
return RESOLVABLE_SELECTED::SOLVER_SELECTED;
|
|
}
|
|
} else {
|
|
// distinguish between the "not selected" and "explicitly removed by user"
|
|
// states
|
|
if (status.getTransactByValue() == zypp::ResStatus::TransactByValue::USER)
|
|
return RESOLVABLE_SELECTED::USER_REMOVED;
|
|
else
|
|
return RESOLVABLE_SELECTED::NOT_SELECTED;
|
|
}
|
|
|
|
return RESOLVABLE_SELECTED::NOT_SELECTED;
|
|
}
|
|
|
|
struct Patterns get_patterns(struct Zypp *zypp,
|
|
struct Status *status) noexcept {
|
|
LOG_LOCATION("Getting patterns");
|
|
|
|
auto iterator =
|
|
zypp->zypp_pointer->pool().proxy().byKind(zypp::ResKind::pattern);
|
|
|
|
Patterns result = {
|
|
(struct Pattern *)malloc(iterator.size() * sizeof(Pattern)),
|
|
0 // initialize with zero and increase after each successful add of
|
|
// pattern info
|
|
};
|
|
|
|
for (const auto &iter : iterator) {
|
|
Pattern &pattern = result.list[result.size];
|
|
auto zypp_pattern = iter->candidateAsKind<zypp::Pattern>();
|
|
pattern.name = strdup(iter->name().c_str());
|
|
pattern.category = strdup(zypp_pattern->category().c_str());
|
|
pattern.description = strdup(zypp_pattern->description().c_str());
|
|
pattern.icon = strdup(zypp_pattern->icon().c_str());
|
|
pattern.summary = strdup(zypp_pattern->summary().c_str());
|
|
pattern.order = strdup(zypp_pattern->order().c_str());
|
|
pattern.repo_alias = strdup(zypp_pattern->repoInfo().alias().c_str());
|
|
pattern.selected = convert_selected(iter->theObj().status());
|
|
result.size++;
|
|
}
|
|
|
|
STATUS_OK(status);
|
|
return result;
|
|
}
|
|
|
|
void free_patterns(const struct Patterns *patterns) noexcept {
|
|
for (unsigned i = 0; i < patterns->size; ++i) {
|
|
free((void *)patterns->list[i].name);
|
|
free((void *)patterns->list[i].category);
|
|
free((void *)patterns->list[i].description);
|
|
free((void *)patterns->list[i].icon);
|
|
free((void *)patterns->list[i].summary);
|
|
free((void *)patterns->list[i].order);
|
|
free((void *)patterns->list[i].repo_alias);
|
|
}
|
|
free((void *)patterns->list);
|
|
}
|
|
|
|
struct Products get_products(struct Zypp *zypp,
|
|
struct Status *status) noexcept {
|
|
LOG_LOCATION("Getting products");
|
|
auto iterator =
|
|
zypp->zypp_pointer->pool().proxy().byKind(zypp::ResKind::product);
|
|
|
|
Products result = {
|
|
(struct Product *)malloc(iterator.size() * sizeof(Product)),
|
|
0 // initialize with zero and increase after each successful add of
|
|
// product info
|
|
};
|
|
|
|
for (const auto &iter : iterator) {
|
|
Product &product = result.list[result.size];
|
|
auto zypp_product = iter->candidateAsKind<zypp::Product>();
|
|
product.name = strdup(iter->name().c_str());
|
|
product.repo_alias = strdup(zypp_product->repoInfo().alias().c_str());
|
|
product.service_alias = strdup(zypp_product->repoInfo().service().c_str());
|
|
result.size++;
|
|
}
|
|
|
|
STATUS_OK(status);
|
|
return result;
|
|
}
|
|
|
|
void free_products(const struct Products *products) noexcept {
|
|
for (unsigned i = 0; i < products->size; ++i) {
|
|
free((void *)products->list[i].name);
|
|
free((void *)products->list[i].repo_alias);
|
|
free((void *)products->list[i].service_alias);
|
|
}
|
|
|
|
free((void *)products->list);
|
|
}
|
|
|
|
void select_locale(struct Zypp *_zypp, const char *language,
|
|
const char *country) noexcept {
|
|
LOG_LOCATION("Selecting locale packages");
|
|
|
|
if (strlen(language) == 0) {
|
|
WAR << "Locale language not specified" << std::endl;
|
|
return;
|
|
}
|
|
|
|
MIL << "Locale to install: " << language << std::endl;
|
|
zypp::LocaleSet locales;
|
|
zypp::Locale locale(language);
|
|
locales.insert(locale);
|
|
|
|
if (strlen(country) > 0) {
|
|
std::string locale_str(language);
|
|
locale_str.append("_");
|
|
locale_str.append(country);
|
|
MIL << "Locale to install: " << locale_str << std::endl;
|
|
|
|
zypp::Locale full_locale = zypp::Locale(locale_str);
|
|
locales.insert(full_locale);
|
|
}
|
|
|
|
zypp::sat::Pool::instance().setRequestedLocales(locales);
|
|
}
|
|
|
|
bool run_solver(struct Zypp *zypp, bool only_required,
|
|
struct Status *status) noexcept {
|
|
LOG_LOCATION("Running solver");
|
|
try {
|
|
STATUS_OK(status);
|
|
auto resolver = zypp->zypp_pointer->resolver();
|
|
resolver->setOnlyRequires(only_required);
|
|
// needed to get hardware and locale specific provisioning
|
|
// @ma: On a fresh install I'd recommend to set INR if you
|
|
// want HW/Filesystem/Language supporting packages to be selected.
|
|
// INR is the mode e.g. 'zypper inr' operates in, AKA InstallNewRecommends.
|
|
// Recommendations of already installed packages are usually not evaluated
|
|
// again. With INR they are.
|
|
// The Resolver flag is setIgnoreAlreadyRecommended=0.
|
|
// (addalreadyrecommended=1 is libsolv world)
|
|
resolver->setIgnoreAlreadyRecommended(false);
|
|
return resolver->resolvePool();
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
return false; // do not matter much as status indicate failure
|
|
}
|
|
}
|
|
|
|
void add_service(struct Zypp *zypp, const char *alias, const char *url,
|
|
struct Status *status) noexcept {
|
|
std::string message("Adding service: ");
|
|
message.append(alias);
|
|
message.append(" (");
|
|
message.append(filter_url(url));
|
|
message.append(")");
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
zypp::ServiceInfo zypp_service = zypp::ServiceInfo(alias);
|
|
zypp_service.setUrl(zypp::Url(url));
|
|
|
|
const zypp::ServiceInfo stored = zypp->repo_manager->getService(alias);
|
|
if (stored == zypp::ServiceInfo::noService) {
|
|
// the service does not exist, add it
|
|
zypp->repo_manager->addService(zypp_service);
|
|
} else {
|
|
// the service already exists, modify the existing one
|
|
zypp->repo_manager->modifyService(zypp_service);
|
|
}
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
bool create_solver_testcase(struct Zypp *zypp, const char *dir) noexcept {
|
|
std::string message("Creating solver testcase in directory ");
|
|
message.append(dir);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
return zypp->zypp_pointer->resolver()->createSolverTestcase(dir);
|
|
}
|
|
|
|
void refresh_service(struct Zypp *zypp, const char *alias,
|
|
struct Status *status) noexcept {
|
|
std::string message("Refreshing service: ");
|
|
message.append(alias);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
zypp::ServiceInfo service = zypp->repo_manager->getService(alias);
|
|
if (service == zypp::ServiceInfo::noService) {
|
|
STATUS_ERROR(status,
|
|
"Cannot refresh service with alias %s. Service not found.",
|
|
alias);
|
|
return;
|
|
}
|
|
zypp->repo_manager->refreshService(service);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void refresh_repository(struct Zypp *zypp, const char *alias,
|
|
struct Status *status,
|
|
struct DownloadProgressCallbacks *callbacks,
|
|
struct SecurityCallbacks *security_callbacks) noexcept {
|
|
std::string message("Refreshing repository: ");
|
|
message.append(alias);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
zypp::RepoInfo zypp_repo = zypp->repo_manager->getRepo(alias);
|
|
if (zypp_repo == zypp::RepoInfo::noRepo) {
|
|
STATUS_ERROR(status, "Cannot refresh repo with alias %s. Repo not found.",
|
|
alias);
|
|
return;
|
|
}
|
|
|
|
set_zypp_download_callbacks(callbacks);
|
|
set_zypp_security_callbacks(security_callbacks);
|
|
zypp->repo_manager->refreshMetadata(
|
|
zypp_repo,
|
|
zypp::RepoManager::RawMetadataRefreshPolicy::RefreshIfNeeded);
|
|
STATUS_OK(status);
|
|
unset_zypp_download_callbacks();
|
|
unset_zypp_security_callbacks();
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
unset_zypp_security_callbacks();
|
|
unset_zypp_download_callbacks(); // TODO: we can add C++ final action helper
|
|
// if it is more common
|
|
}
|
|
}
|
|
|
|
bool is_local_url(const char *url, struct Status *status) noexcept {
|
|
try {
|
|
zypp::Url z_url(url);
|
|
STATUS_OK(status);
|
|
return z_url.schemeIsLocal();
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
unsigned packages_to_install(struct Zypp *zypp) noexcept {
|
|
LOG_LOCATION("Getting number of packages to install");
|
|
return zypp::ResPool::instance()
|
|
.byStatus(&zypp::ResStatus::isToBeInstalled)
|
|
.size();
|
|
}
|
|
|
|
static bool package_check(Zypp *zypp, const char *tag, bool selected,
|
|
Status *status) noexcept {
|
|
std::string message("Checking package: ");
|
|
message.append(tag);
|
|
if (selected)
|
|
message.append(" (selected)");
|
|
else
|
|
message.append(" (available)");
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
try {
|
|
std::string s_tag(tag);
|
|
if (s_tag.empty()) {
|
|
STATUS_ERROR(status, "Internal Error: Package tag is empty.");
|
|
return false;
|
|
}
|
|
|
|
// look for packages
|
|
zypp::Capability cap(s_tag, zypp::ResKind::package);
|
|
zypp::sat::WhatProvides possibleProviders(cap);
|
|
|
|
// if we check only for availability, then just check that quickly
|
|
if (!selected)
|
|
return !possibleProviders.empty();
|
|
|
|
for (auto iter = possibleProviders.begin(); iter != possibleProviders.end();
|
|
++iter) {
|
|
zypp::PoolItem provider = zypp::ResPool::instance().find(*iter);
|
|
// is it installed? if so return true, otherwise check next candidate
|
|
if (provider.status().isToBeInstalled())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool is_package_available(Zypp *zypp, const char *tag,
|
|
Status *status) noexcept {
|
|
return package_check(zypp, tag, false, status);
|
|
}
|
|
|
|
bool is_package_selected(Zypp *zypp, const char *tag, Status *status) noexcept {
|
|
return package_check(zypp, tag, true, status);
|
|
}
|
|
|
|
void add_repository(struct Zypp *zypp, const char *alias, const char *url,
|
|
unsigned priority, struct Status *status,
|
|
ZyppProgressCallback callback, void *user_data) noexcept {
|
|
std::string message("Adding repository: ");
|
|
message.append(alias);
|
|
message.append(" (");
|
|
message.append(filter_url(url));
|
|
message.append(")");
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
auto zypp_callback = create_progress_callback(callback, user_data);
|
|
zypp::RepoInfo zypp_repo = zypp::RepoInfo();
|
|
zypp_repo.setBaseUrl(zypp::Url(url));
|
|
zypp_repo.setAlias(alias);
|
|
zypp_repo.setPriority(priority);
|
|
|
|
zypp->repo_manager->addRepository(zypp_repo, zypp_callback);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void disable_repository(struct Zypp *zypp, const char *alias,
|
|
struct Status *status) noexcept {
|
|
std::string message("Disabling repository: ");
|
|
message.append(alias);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
zypp::RepoInfo r_info = zypp->repo_manager->getRepo(alias);
|
|
r_info.setEnabled(false);
|
|
zypp->repo_manager->modifyRepository(r_info);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void set_repository_url(struct Zypp *zypp, const char *alias, const char *url,
|
|
struct Status *status) noexcept {
|
|
|
|
std::string message("Setting repository url: ");
|
|
message.append(alias);
|
|
message.append(" (");
|
|
message.append(filter_url(url));
|
|
message.append(")");
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
zypp::RepoInfo r_info = zypp->repo_manager->getRepo(alias);
|
|
zypp::Url z_url(url);
|
|
r_info.setBaseUrl(z_url);
|
|
zypp->repo_manager->modifyRepository(r_info);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void remove_repository(struct Zypp *zypp, const char *alias,
|
|
struct Status *status, ZyppProgressCallback callback,
|
|
void *user_data) noexcept {
|
|
std::string message("Removing repository: ");
|
|
message.append(alias);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
auto zypp_callback = create_progress_callback(callback, user_data);
|
|
zypp::RepoInfo zypp_repo = zypp::RepoInfo();
|
|
zypp_repo.setAlias(alias); // alias should be unique, so it should always
|
|
// match correct repo
|
|
|
|
zypp->repo_manager->removeRepository(zypp_repo, zypp_callback);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
struct RepositoryList list_repositories(struct Zypp *zypp,
|
|
struct Status *status) noexcept {
|
|
LOG_LOCATION("Listing repositories");
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return {0, NULL};
|
|
}
|
|
|
|
std::list<zypp::RepoInfo> zypp_repos =
|
|
zypp->repo_manager->knownRepositories();
|
|
const std::list<zypp::RepoInfo>::size_type size = zypp_repos.size();
|
|
struct Repository *repos =
|
|
(struct Repository *)malloc(size * sizeof(struct Repository));
|
|
// TODO: error handling
|
|
unsigned res_i = 0;
|
|
for (auto iter = zypp_repos.begin(); iter != zypp_repos.end(); ++iter) {
|
|
struct Repository *new_repo = repos + res_i++;
|
|
new_repo->enabled = iter->enabled();
|
|
new_repo->url = strdup(iter->url().asString().c_str());
|
|
new_repo->alias = strdup(iter->alias().c_str());
|
|
new_repo->userName = strdup(iter->asUserString().c_str());
|
|
new_repo->serviceName = strdup(iter->service().c_str());
|
|
}
|
|
|
|
struct RepositoryList result = {static_cast<unsigned>(size), repos};
|
|
STATUS_OK(status);
|
|
return result;
|
|
}
|
|
|
|
void load_repository_cache(struct Zypp *zypp, const char *alias,
|
|
struct Status *status) noexcept {
|
|
std::string message("Loading repository cache: ");
|
|
message.append(alias);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
}
|
|
try {
|
|
zypp::RepoInfo zypp_repo = zypp->repo_manager->getRepo(alias);
|
|
if (zypp_repo == zypp::RepoInfo::noRepo) {
|
|
STATUS_ERROR(status, "Cannot load repo with alias %s. Repo not found.",
|
|
alias);
|
|
return;
|
|
}
|
|
|
|
// NOTE: loadFromCache has an optional `progress` parameter but it ignores
|
|
// it anyway
|
|
zypp->repo_manager->loadFromCache(zypp_repo);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void build_repository_cache(struct Zypp *zypp, const char *alias,
|
|
struct Status *status,
|
|
ZyppProgressCallback callback,
|
|
void *user_data) noexcept {
|
|
std::string message("Building repository cache: ");
|
|
message.append(alias);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
if (zypp->repo_manager == NULL) {
|
|
STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
|
|
return;
|
|
}
|
|
try {
|
|
zypp::RepoInfo zypp_repo = zypp->repo_manager->getRepo(alias);
|
|
if (zypp_repo == zypp::RepoInfo::noRepo) {
|
|
STATUS_ERROR(status, "Cannot load repo with alias %s. Repo not found.",
|
|
alias);
|
|
return;
|
|
}
|
|
|
|
auto progress = create_progress_callback(callback, user_data);
|
|
zypp->repo_manager->buildCache(
|
|
zypp_repo, zypp::RepoManagerFlags::BuildIfNeeded, progress);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void import_gpg_key(struct Zypp *zypp, const char *const pathname,
|
|
struct Status *status) noexcept {
|
|
std::string message("Importing GPG key: ");
|
|
message.append(pathname);
|
|
LOG_LOCATION(message.c_str());
|
|
|
|
try {
|
|
zypp::filesystem::Pathname path(pathname);
|
|
zypp::PublicKey key(path);
|
|
// Keys that are unknown (not imported).
|
|
// or known-but-untrusted (weird in-between state, see KeyRing_test.cc)
|
|
// will trigger "Trust this?" callbacks.
|
|
bool trusted = true;
|
|
zypp->zypp_pointer->keyRing()->importKey(key, trusted);
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
|
|
void get_space_usage(struct Zypp *zypp, struct Status *status,
|
|
struct MountPoint *mount_points,
|
|
unsigned mount_points_size) noexcept {
|
|
LOG_LOCATION("Getting disk space usage");
|
|
|
|
try {
|
|
zypp::DiskUsageCounter::MountPointSet mount_points_set;
|
|
for (unsigned i = 0; i < mount_points_size; ++i) {
|
|
enum zypp::DiskUsageCounter::MountPoint::Hint hint =
|
|
mount_points[i].grow_only
|
|
? zypp::DiskUsageCounter::MountPoint::Hint::Hint_growonly
|
|
: zypp::DiskUsageCounter::MountPoint::Hint::NoHint;
|
|
zypp::DiskUsageCounter::MountPoint mp(mount_points[i].directory,
|
|
mount_points[i].filesystem, 0, 0, 0,
|
|
0, hint);
|
|
mount_points_set.insert(mp);
|
|
}
|
|
zypp->zypp_pointer->setPartitions(mount_points_set);
|
|
zypp::DiskUsageCounter::MountPointSet computed_set =
|
|
zypp->zypp_pointer->diskUsage();
|
|
for (unsigned i = 0; i < mount_points_size; ++i) {
|
|
auto mp =
|
|
std::find_if(computed_set.begin(), computed_set.end(),
|
|
[mount_points, i](zypp::DiskUsageCounter::MountPoint m) {
|
|
return m.dir == mount_points[i].directory;
|
|
});
|
|
if (mp == mount_points_set.end()) {
|
|
// mount point not found. Should not happen.
|
|
STATUS_ERROR(status, "Internal Error:Mount point not found.");
|
|
return;
|
|
}
|
|
mount_points[i].used_size = mp->pkg_size;
|
|
}
|
|
STATUS_OK(status);
|
|
} catch (zypp::Exception &excpt) {
|
|
STATUS_EXCEPT(status, excpt);
|
|
}
|
|
}
|
|
}
|