#include "lib.h" #include "callbacks.h" #include "callbacks.hxx" #include "helpers.hxx" #include "repository.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ZYPP_BASE_LOGGER_LOGGROUP "rust-bindings" #include #include #include extern "C" { #include 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 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 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(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_repos = zypp->repo_manager->knownRepositories(); const std::list::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(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); } } }