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.
This commit is contained in:
q66 2019-11-14 17:40:54 +01:00 committed by Juan RP
parent 5c9be1eefd
commit 85b8b3bbb7
2 changed files with 218 additions and 30 deletions

View File

@ -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
remove_obsoletes(struct xbps_handle *xhp, xbps_dictionary_t pkgd, xbps_dictionary_t repod)
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, 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++) {

View File

@ -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
}