356 lines
12 KiB
Diff
356 lines
12 KiB
Diff
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
|
|
}
|