xbps: add patch to properly prune alternatives on updates

Required for https://github.com/void-linux/void-packages/pull/16458.

The next OpenJDK8 update will involve renaming to `openjdk8`. With
current xbps that will result in failures as we need to create a
transitional package that, unlike the real one, has no alternatives,
and xbps currently does not account for that.
This commit is contained in:
q66 2019-11-18 01:44:17 +01:00 committed by Helmut Pozimski
parent 11a1f7f4f4
commit d188a1b967
2 changed files with 356 additions and 1 deletions

View file

@ -0,0 +1,355 @@
From 85b8b3bbb72ab6de6f4d72c9a2ca3f2be053eeb1 Mon Sep 17 00:00:00 2001
From: q66 <daniel@octaforge.org>
Date: Thu, 14 Nov 2019 17:40:54 +0100
Subject: [PATCH] lib/package_alternatives.c: prune obsolete alternatives
groups
In the edge case when an updated package has different (or no)
alternatives groups, make sure to prune those that are in pkgdb
but not in the newly installed package.
A potentially common case of this is when a package that formerly
had alternatives gets removed and a transitional metapackage
takes its place (which has no alternatives).
When the new package has no dependencies, oldest next possible
alternatives group will be used. This is because that indicates
a removed package. When there are dependencies, the newest one
will be used; as this indicates a transitional package.
---
lib/package_alternatives.c | 155 +++++++++++++++++-----
tests/xbps/xbps-alternatives/main_test.sh | 93 +++++++++++++
2 files changed, 218 insertions(+), 30 deletions(-)
diff --git a/lib/package_alternatives.c b/lib/package_alternatives.c
index 5e4f2b36..ecdc40ae 100644
--- lib/package_alternatives.c
+++ lib/package_alternatives.c
@@ -309,6 +309,22 @@ xbps_alternatives_set(struct xbps_handle *xhp, const char *pkgname,
return rv;
}
+static int
+switch_alt_group(struct xbps_handle *xhp, const char *grpn, const char *pkgn,
+ xbps_dictionary_t *pkg_alternatives)
+{
+ xbps_dictionary_t curpkgd, pkgalts;
+
+ curpkgd = xbps_pkgdb_get_pkg(xhp, pkgn);
+ assert(curpkgd);
+
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL,
+ "Switched '%s' alternatives group to '%s'", grpn, pkgn);
+ pkgalts = xbps_dictionary_get(curpkgd, "alternatives");
+ if (pkg_alternatives) *pkg_alternatives = pkgalts;
+ return create_symlinks(xhp, xbps_dictionary_get(pkgalts, grpn), grpn);
+}
+
int
xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
{
@@ -339,7 +355,6 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
xbps_array_t array;
xbps_object_t keysym;
- xbps_dictionary_t curpkgd = pkgd;
bool current = false;
const char *first = NULL, *keyname;
@@ -377,15 +392,7 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
continue;
/* get the new alternative group package */
- curpkgd = xbps_pkgdb_get_pkg(xhp, first);
- assert(curpkgd);
- xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL,
- "Switched '%s' alternatives group to '%s'", keyname, first);
- pkg_alternatives = xbps_dictionary_get(curpkgd, "alternatives");
- rv = create_symlinks(xhp,
- xbps_dictionary_get(pkg_alternatives, keyname),
- keyname);
- if (rv != 0)
+ if (switch_alt_group(xhp, keyname, first, &pkg_alternatives) != 0)
break;
}
xbps_object_release(allkeys);
@@ -394,25 +401,119 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
return rv;
}
+/*
+ * Prune the alternatives group from the db. This will first unregister
+ * it for the package and if there's no other package left providing the
+ * same, also ditch the whole group. When this is called, it is guranteed
+ * that what is happening is an upgrade, because it's only invoked when
+ * the repo and installed alternatives sets differ for a specific package.
+ */
+static void
+prune_altgroup(struct xbps_handle *xhp, xbps_dictionary_t repod,
+ char *pkgname, const char *pkgver, const char *keyname) {
+ const char *newpkg, *curpkg;
+ xbps_array_t array;
+ xbps_dictionary_t alternatives;
+ xbps_string_t kstr;
+ unsigned int grp_count;
+ bool current = false;
+
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
+ "%s: unregistered '%s' alternatives group", pkgver, keyname);
+
+ alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
+ assert(alternatives);
+ array = xbps_dictionary_get(alternatives, keyname);
+
+ /* if using alt group from another package, we won't switch anything */
+ xbps_array_get_cstring_nocopy(array, 0, &curpkg);
+ current = (strcmp(pkgname, curpkg) == 0);
+
+ /* actually prune the alt group for the current package */
+ xbps_remove_string_from_array(array, pkgname);
+ grp_count = xbps_array_count(array);
+ if (grp_count == 0) {
+ /* it was the last one, ditch the whole thing */
+ xbps_dictionary_remove(alternatives, keyname);
+ return;
+ }
+ if (!current) {
+ /* not the last one, and ours wasn't the one being used */
+ return;
+ }
+
+ if (xbps_array_count(xbps_dictionary_get(repod, "run_depends")) == 0 &&
+ xbps_array_count(xbps_dictionary_get(repod, "shlib-requires")) == 0) {
+ /*
+ * Empty dependencies indicate a removed package (pure meta),
+ * use the first available group after ours has been pruned
+ */
+ xbps_array_get_cstring_nocopy(array, 0, &newpkg);
+ switch_alt_group(xhp, keyname, newpkg, NULL);
+ return;
+ }
+
+ /*
+ * Use the last group, as this indicates that a transitional metapackage
+ * is replacing the original and therefore a new package has registered
+ * a replacement group, which should be last in the array (most recent).
+ */
+ xbps_array_get_cstring_nocopy(array, grp_count - 1, &newpkg);
+
+ /* put the new package as head */
+ kstr = xbps_string_create_cstring(newpkg);
+ xbps_remove_string_from_array(array, newpkg);
+ xbps_array_add_first(array, kstr);
+ xbps_array_get_cstring_nocopy(array, 0, &newpkg);
+ xbps_object_release(kstr);
+
+ switch_alt_group(xhp, keyname, newpkg, NULL);
+}
+
+
static void
-remove_obsoletes(struct xbps_handle *xhp, xbps_dictionary_t pkgd, xbps_dictionary_t repod)
+remove_obsoletes(struct xbps_handle *xhp, char *pkgname, const char *pkgver,
+ xbps_dictionary_t repod)
{
xbps_array_t allkeys;
+ xbps_dictionary_t pkgd, pkgd_alts, repod_alts;
- allkeys = xbps_dictionary_all_keys(pkgd);
+ pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
+ if (xbps_object_type(pkgd) != XBPS_TYPE_DICTIONARY) {
+ return;
+ }
+
+ pkgd_alts = xbps_dictionary_get(pkgd, "alternatives");
+ repod_alts = xbps_dictionary_get(repod, "alternatives");
+
+ if (xbps_object_type(pkgd_alts) != XBPS_TYPE_DICTIONARY) {
+ return;
+ }
+
+ allkeys = xbps_dictionary_all_keys(pkgd_alts);
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
xbps_array_t array, array_repo;
xbps_object_t keysym;
const char *keyname;
keysym = xbps_array_get(allkeys, i);
- array = xbps_dictionary_get_keysym(pkgd, keysym);
+ array = xbps_dictionary_get_keysym(pkgd_alts, keysym);
keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
- array_repo = xbps_dictionary_get(repod, keyname);
+ array_repo = xbps_dictionary_get(repod_alts, keyname);
if (!xbps_array_equals(array, array_repo)) {
remove_symlinks(xhp, array, keyname);
}
+
+ /*
+ * There is nothing left in the alternatives group, which means
+ * the package is being upgraded and is removing it; if we don't
+ * prune it, the system will keep it set after removal of its
+ * parent package, but it will be empty and invalid...
+ */
+ if (xbps_array_count(array_repo) == 0) {
+ prune_altgroup(xhp, repod, pkgname, pkgver, keyname);
+ }
}
xbps_object_release(allkeys);
}
@@ -421,7 +522,7 @@ int
xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
{
xbps_array_t allkeys;
- xbps_dictionary_t alternatives, pkg_alternatives, pkgd, pkgd_alts;
+ xbps_dictionary_t alternatives, pkg_alternatives;
const char *pkgver;
char *pkgname;
int rv = 0;
@@ -431,10 +532,6 @@ xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
if (xhp->pkgdb == NULL)
return EINVAL;
- pkg_alternatives = xbps_dictionary_get(pkg_repod, "alternatives");
- if (!xbps_dictionary_count(pkg_alternatives))
- return 0;
-
alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
if (alternatives == NULL) {
alternatives = xbps_dictionary_create();
@@ -449,17 +546,15 @@ xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
if (pkgname == NULL)
return EINVAL;
- pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
- if (xbps_object_type(pkgd) == XBPS_TYPE_DICTIONARY) {
- /*
- * Compare alternatives from pkgdb and repo and
- * then remove obsolete symlinks.
- */
- pkgd_alts = xbps_dictionary_get(pkgd, "alternatives");
- if (xbps_object_type(pkgd_alts) == XBPS_TYPE_DICTIONARY) {
- remove_obsoletes(xhp, pkgd_alts, pkg_alternatives);
- }
- }
+ /*
+ * Compare alternatives from pkgdb and repo and then remove obsolete
+ * symlinks, also remove obsolete (empty) alternatives groups.
+ */
+ remove_obsoletes(xhp, pkgname, pkgver, pkg_repod);
+
+ pkg_alternatives = xbps_dictionary_get(pkg_repod, "alternatives");
+ if (!xbps_dictionary_count(pkg_alternatives))
+ return 0;
allkeys = xbps_dictionary_all_keys(pkg_alternatives);
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
diff --git a/tests/xbps/xbps-alternatives/main_test.sh b/tests/xbps/xbps-alternatives/main_test.sh
index fd66bcbf..4eddc411 100644
--- tests/xbps/xbps-alternatives/main_test.sh
+++ tests/xbps/xbps-alternatives/main_test.sh
@@ -674,6 +674,98 @@ respect_current_provider_body() {
atf_check_equal $rv 0
}
+atf_test_case prune_leftover_groups
+
+prune_leftover_groups_head() {
+ atf_set "descr" "xbps-alternatives: prune leftover groups on upgrades"
+}
+prune_leftover_groups_body() {
+ mkdir -p repo pkg_A/usr/bin pkg_B/usr/bin
+ touch pkg_A/usr/bin/fileA pkg_B/usr/bin/fileB
+ cd repo
+ xbps-create -A noarch -n A-1.1_1 -s "A pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileA" ../pkg_A
+ atf_check_equal $? 0
+ xbps-create -A noarch -n B-1.1_1 -s "B pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileB" ../pkg_B
+ atf_check_equal $? 0
+ xbps-rindex -d -a $PWD/*.xbps
+ atf_check_equal $? 0
+ cd ..
+
+ # A is the current provider now
+ xbps-install -r root --repository=repo -ydv A
+ atf_check_equal $? 0
+
+ out=$(xbps-query -r root -p pkgver A)
+ atf_check_equal $out A-1.1_1
+
+ # C will replace it via a transitional package
+ mkdir -p pkg_C/usr/bin
+ touch pkg_C/usr/bin/fileC
+ rm pkg_A/usr/bin/fileA
+ cd repo
+ xbps-create -A noarch -n C-1.2_1 -s "C pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileC" ../pkg_C
+ atf_check_equal $? 0
+ xbps-create -A noarch -n A-1.2_1 -s "A pkg" --dependencies "C>=1.2_1" ../pkg_A
+ atf_check_equal $? 0
+ xbps-rindex -d -a $PWD/*.xbps
+ atf_check_equal $? 0
+ cd ..
+
+ # C is now the current provider, via upgraded A
+ # also install B, to make sure it doesn't get that first
+ xbps-install -r root --repository=repo -ydv B A
+
+ out=$(xbps-query -r root -p pkgver A)
+ atf_check_equal $out A-1.2_1
+ out=$(xbps-query -r root -p pkgver B)
+ atf_check_equal $out B-1.1_1
+ out=$(xbps-query -r root -p pkgver C)
+ atf_check_equal $out C-1.2_1
+
+ lnk=$(readlink -f root/usr/bin/file)
+ rv=1
+ if [ "$lnk" = "$PWD/root/usr/bin/fileC" ]; then
+ rv=0
+ fi
+ echo "lnk: $lnk"
+ atf_check_equal $rv 0
+
+ # Create a new provider, D; then make C a removed package
+ mkdir -p pkg_D/usr/bin
+ touch pkg_D/usr/bin/fileD
+ rm pkg_C/usr/bin/fileC
+ cd repo
+ xbps-create -A noarch -n D-1.4_1 -s "D pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileD" ../pkg_D
+ atf_check_equal $? 0
+ xbps-create -A noarch -n C-1.3_1 -s "C pkg" ../pkg_C
+ atf_check_equal $? 0
+ xbps-rindex -d -a $PWD/*.xbps
+ atf_check_equal $? 0
+ cd ..
+
+ # B is now the current provider, as it's the first group available after
+ # pruning C; the system special cases packages without dependencies as
+ # removed packages, so it will not assume a renamed replacement
+ xbps-install -r root --repository=repo -ydv C D
+
+ out=$(xbps-query -r root -p pkgver A)
+ atf_check_equal $out A-1.2_1
+ out=$(xbps-query -r root -p pkgver B)
+ atf_check_equal $out B-1.1_1
+ out=$(xbps-query -r root -p pkgver C)
+ atf_check_equal $out C-1.3_1
+ out=$(xbps-query -r root -p pkgver D)
+ atf_check_equal $out D-1.4_1
+
+ lnk=$(readlink -f root/usr/bin/file)
+ rv=1
+ if [ "$lnk" = "$PWD/root/usr/bin/fileB" ]; then
+ rv=0
+ fi
+ echo "lnk: $lnk"
+ atf_check_equal $rv 0
+}
+
atf_init_test_cases() {
atf_add_test_case register_one
atf_add_test_case register_one_dangling
@@ -692,4 +784,5 @@ atf_init_test_cases() {
atf_add_test_case useless_switch
atf_add_test_case remove_current_provider
atf_add_test_case respect_current_provider
+ atf_add_test_case prune_leftover_groups
}

View file

@ -1,7 +1,7 @@
# Template file for 'xbps'
pkgname=xbps
version=0.57.1
revision=2
revision=3
bootstrap=yes
build_style=configure
short_desc="XBPS package system utilities"