agama/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx

692 lines
23 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/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>
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] %s", zypp_group.c_str(), 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 {
// 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() {
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 {
const std::string root_str(root);
try {
zypp->zypp_pointer->initializeTarget(root_str,
false /* rebuild rpmdb: no */);
} 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 {
try {
set_zypp_resolvable_download_callbacks(download_callbacks);
set_zypp_security_callbacks(security_callbacks);
set_zypp_install_callbacks(install_callbacks);
zypp::ZYppCommitPolicy policy;
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.noError();
} 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 {
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();
} 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);
}
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.");
}
}
// 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 {
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 {
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 {
MIL << "Resetting status of all resolvables" << std::endl;
for (auto &item : zypp::ResPool::instance())
item.statusReset();
}
struct PatternInfos get_patterns_info(struct Zypp *_zypp,
struct PatternNames names,
struct Status *status) noexcept {
PatternInfos result = {
(struct PatternInfo *)malloc(names.size * sizeof(PatternInfo)),
0 // initialize with zero and increase after each successful add of
// pattern info
};
for (unsigned j = 0; j < names.size; ++j) {
zypp::ui::Selectable::constPtr selectable =
zypp::ui::Selectable::get(zypp::ResKind::pattern, names.names[j]);
// we do not find any pattern
if (!selectable.get())
continue;
// we know here that we get only patterns
zypp::Pattern::constPtr pattern =
zypp::asKind<zypp::Pattern>(selectable->theObj().resolvable());
unsigned i = result.size;
result.infos[i].name = strdup(pattern->name().c_str());
result.infos[i].category = strdup(pattern->category().c_str());
result.infos[i].description = strdup(pattern->description().c_str());
result.infos[i].icon = strdup(pattern->icon().c_str());
result.infos[i].summary = strdup(pattern->summary().c_str());
result.infos[i].order = strdup(pattern->order().c_str());
auto &status = selectable->theObj().status();
if (status.isToBeInstalled()) {
switch (status.getTransactByValue()) {
case zypp::ResStatus::TransactByValue::USER:
result.infos[i].selected = RESOLVABLE_SELECTED::USER_SELECTED;
break;
case zypp::ResStatus::TransactByValue::APPL_HIGH:
case zypp::ResStatus::TransactByValue::APPL_LOW:
result.infos[i].selected = RESOLVABLE_SELECTED::APPLICATION_SELECTED;
break;
case zypp::ResStatus::TransactByValue::SOLVER:
result.infos[i].selected = RESOLVABLE_SELECTED::SOLVER_SELECTED;
break;
}
} else {
result.infos[i].selected = RESOLVABLE_SELECTED::NOT_SELECTED;
}
result.size++;
};
STATUS_OK(status);
return result;
}
void free_pattern_infos(const struct PatternInfos *infos) noexcept {
for (unsigned i = 0; i < infos->size; ++i) {
free(infos->infos[i].name);
free(infos->infos[i].category);
free(infos->infos[i].icon);
free(infos->infos[i].description);
free(infos->infos[i].summary);
free(infos->infos[i].order);
}
free(infos->infos);
}
bool run_solver(struct Zypp *zypp, struct Status *status) noexcept {
try {
STATUS_OK(status);
return zypp->zypp_pointer->resolver()->resolvePool();
} catch (zypp::Exception &excpt) {
STATUS_EXCEPT(status, excpt);
return false; // do not matter much as status indicate failure
}
}
void refresh_repository(struct Zypp *zypp, const char *alias,
struct Status *status,
struct DownloadProgressCallbacks *callbacks,
struct SecurityCallbacks *security_callbacks) noexcept {
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;
}
}
static bool package_check(Zypp *zypp, const char *tag, bool selected,
Status *status) noexcept {
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,
struct Status *status, ZyppProgressCallback callback,
void *user_data) noexcept {
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_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 {
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 {
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 {
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 {
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());
}
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 {
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 {
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 {
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 {
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);
}
}
}