diff --git a/bin/xbps-alternatives/main.c b/bin/xbps-alternatives/main.c
index e3aec59e3..e76f85b6f 100644
--- a/bin/xbps-alternatives/main.c
+++ b/bin/xbps-alternatives/main.c
@@ -149,8 +149,10 @@ list_alternatives(struct xbps_handle *xhp, const char *pkgname, const char *grp)
xbps_array_get_cstring_nocopy(array, x, &str);
printf(" - %s%s\n", str, x == 0 ? " (current)" : "");
pkgd = xbps_pkgdb_get_pkg(xhp, str);
- assert(pkgd);
- list_pkg_alternatives(pkgd, keyname, false);
+ if (pkgd)
+ list_pkg_alternatives(pkgd, keyname, false);
+ else
+ xbps_dbg_printf(xhp, "Not installed package '%s' registered as alternative for '%s'\n", str, grp);
}
}
xbps_object_release(allkeys);
diff --git a/lib/package_alternatives.c b/lib/package_alternatives.c
index f23b440d0..8015347d1 100644
--- a/lib/package_alternatives.c
+++ b/lib/package_alternatives.c
@@ -327,6 +327,31 @@ switch_alt_group(struct xbps_handle *xhp, const char *grpn, const char *pkgn,
return create_symlinks(xhp, xbps_dictionary_get(pkgalts, grpn), grpn);
}
+/*
+ * Removes packages that do not provide alternatives for group.
+ * Old xbps versions didn't clean up list on time.
+ */
+static void
+remove_outdated_packages(struct xbps_handle *xhp, const char *groupname, xbps_array_t packages)
+{
+ for (unsigned int i = 0; i < xbps_array_count(packages);) {
+ const char *pkgname = NULL;
+ xbps_dictionary_t pkgdict;
+ xbps_dictionary_t alts;
+ xbps_array_t alts_group;
+ xbps_array_get_cstring_nocopy(packages, i, &pkgname);
+ if (!(pkgdict = xbps_pkgdb_get_pkg(xhp, pkgname)) ||
+ !(alts = xbps_dictionary_get(pkgdict, "alternatives")) ||
+ !(alts_group = xbps_dictionary_get(alts, groupname)) ||
+ xbps_array_count(alts_group) == 0
+ ) {
+ xbps_array_remove(packages, i);
+ continue;
+ }
+ i++;
+ }
+}
+
int
xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
{
@@ -380,18 +405,20 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
"%s: unregistered '%s' alternatives group", pkgver, keyname);
xbps_remove_string_from_array(array, pkgname);
- xbps_array_get_cstring_nocopy(array, 0, &first);
}
+ remove_outdated_packages(xhp, keyname, array);
if (xbps_array_count(array) == 0) {
xbps_dictionary_remove(alternatives, keyname);
continue;
}
+ /* XXX: ... && remove_outdated_packages didn't removed current package) */
if (update || !current)
continue;
/* get the new alternative group package */
+ xbps_array_get_cstring_nocopy(array, 0, &first);
if (switch_alt_group(xhp, keyname, first, &pkg_alternatives) != 0)
break;
}
@@ -415,7 +442,8 @@ prune_altgroup(struct xbps_handle *xhp, xbps_dictionary_t repod,
xbps_array_t array;
xbps_dictionary_t alternatives;
xbps_string_t kstr;
- unsigned int grp_count;
+ unsigned int grp_count, depends_count;
+ uint64_t size = 0;
bool current = false;
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
@@ -442,31 +470,35 @@ prune_altgroup(struct xbps_handle *xhp, xbps_dictionary_t repod,
return;
}
- if (xbps_array_count(xbps_dictionary_get(repod, "run_depends")) == 0 &&
- xbps_array_count(xbps_dictionary_get(repod, "shlib-requires")) == 0) {
+ xbps_dictionary_get_uint64(repod, "installed_size", &size);
+ depends_count = xbps_array_count(xbps_dictionary_get(repod, "run_depends"));
+
+ /*
+ * Non-empty package is an ordinary package dropping alternatives.
+ * Empty dependencies indicate a removed package (pure meta).
+ */
+ if (size == 0 && 0 < depends_count) {
/*
- * Empty dependencies indicate a removed package (pure meta),
- * use the first available group after ours has been pruned
+ * 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, 0, &newpkg);
- switch_alt_group(xhp, keyname, newpkg, NULL);
- return;
+ 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_object_release(kstr);
}
- /*
- * 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);
+ remove_outdated_packages(xhp, keyname, array);
+ if (xbps_array_count(array) == 0) {
+ /* it was the last one, ditch the whole thing */
+ xbps_dictionary_remove(alternatives, keyname);
+ return;
+ }
- /* 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);
}
diff --git a/tests/xbps/xbps-alternatives/main_test.sh b/tests/xbps/xbps-alternatives/main_test.sh
index 084069f05..b0976b5f9 100644
--- a/tests/xbps/xbps-alternatives/main_test.sh
+++ b/tests/xbps/xbps-alternatives/main_test.sh
@@ -263,6 +263,84 @@ unregister_multi_body() {
atf_check_equal $rv 0
}
+atf_test_case alternative_unregister
+
+alternative_unregister_head() {
+ atf_set "descr" "xbps-alternatives: removal of the alternative group from pkgdb"
+}
+alternative_unregister_body() {
+ mkdir -p repo pkg_A/usr/bin
+ mkdir -p repo pkg_B/usr/bin
+ touch pkg_A/usr/bin/gcc
+ touch pkg_B/usr/bin/clang
+
+ cd repo
+ xbps-create -A noarch -n pkgA-1.1_1 -s "A pkg" --alternatives "cc:cc:/usr/bin/A" ../pkg_A
+ atf_check_equal $? 0
+ xbps-create -A noarch -n pkgB-1.1_1 -s "B pkg" --alternatives "cc:cc:/usr/bin/B" ../pkg_B
+ atf_check_equal $? 0
+ xbps-rindex -d -a $PWD/*.xbps
+ atf_check_equal $? 0
+ cd ..
+
+ xbps-install -r root --repository=repo -ydv pkgA pkgB
+ atf_check_equal $? 0
+ atf_check_equal "$(grep -c ' pkgA' root/var/db/xbps/pkgdb*.plist)"A 1A
+ atf_check_equal "$(grep -c ' pkgB' root/var/db/xbps/pkgdb*.plist)"B 1B
+
+ cd repo
+ xbps-create -A noarch -n pkgA-1.1_2 -s "A pkg" ../pkg_A
+ atf_check_equal $? 0
+ xbps-create -A noarch -n pkgB-1.1_2 -s "B pkg" --alternatives "cc:cc:/usr/bin/B" ../pkg_B
+ atf_check_equal $? 0
+ xbps-rindex -d -a $PWD/*.xbps
+ atf_check_equal $? 0
+ cd ..
+
+ xbps-install -r root --repository=repo -dvyu
+ atf_check_equal $? 0
+ xbps-remove -r root -dvy pkgA
+ atf_check_equal $? 0
+ atf_check_equal "$(grep -c ' pkgA' root/var/db/xbps/pkgdb*.plist)"A 0A
+ atf_check_equal "$(grep -c ' pkgB' root/var/db/xbps/pkgdb*.plist)"B 1B
+}
+
+
+atf_test_case handle_0_57_1_pkgdb
+
+handle_0_57_1_pkgdb_head() {
+ atf_set "descr" "xbps-alternatives: processing old pkgdb containing removed packages in _XBPS_ALTERNATIVES_"
+}
+handle_0_57_1_pkgdb_body() {
+ mkdir -p repo pkg_A/usr/bin
+ mkdir -p repo pkg_B/usr/bin
+ touch pkg_A/usr/bin/gcc
+ touch pkg_B/usr/bin/clang
+
+ cd repo
+ xbps-create -A noarch -n pkgA-1.1_1 -s "A pkg" ../pkg_A
+ atf_check_equal $? 0
+ xbps-create -A noarch -n pkgB-1.1_1 -s "B pkg" --alternatives "cc:cc:/usr/bin/B" ../pkg_B
+ atf_check_equal $? 0
+ xbps-rindex -d -a $PWD/*.xbps
+ atf_check_equal $? 0
+ cd ..
+
+ xbps-install -r root --repository=repo -ydv pkgB
+ atf_check_equal $? 0
+
+ atf_check_equal "$(grep -c ' pkgA' root/var/db/xbps/pkgdb*.plist)"A 0A
+ atf_check_equal "$(grep -c ' pkgB' root/var/db/xbps/pkgdb*.plist)"B 1B
+ sed -e 's:pkgB:& pkgA:' -i root/var/db/xbps/pkgdb*.plist
+ atf_check_equal "$(grep -c ' pkgA' root/var/db/xbps/pkgdb*.plist)"A 1A
+
+ xbps-alternatives -r root -l
+ atf_check_equal $? 0
+
+ xbps-remove -r root -dvy pkgB
+ atf_check_equal $? 0
+}
+
atf_test_case set_pkg
set_pkg_head() {
@@ -951,6 +1029,8 @@ atf_init_test_cases() {
atf_add_test_case unregister_one
atf_add_test_case unregister_one_relative
atf_add_test_case unregister_multi
+ atf_add_test_case alternative_unregister
+ atf_add_test_case handle_0_57_1_pkgdb
atf_add_test_case set_pkg
atf_add_test_case set_pkg_group
atf_add_test_case update_pkgs