Skip to content

Fix crashes due to incorrectly registered alternatives #457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions bin/xbps-alternatives/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
74 changes: 53 additions & 21 deletions lib/package_alternatives.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
Expand All @@ -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);
}

Expand Down
80 changes: 80 additions & 0 deletions tests/xbps/xbps-alternatives/main_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 ' <string>pkgA</string>' root/var/db/xbps/pkgdb*.plist)"A 1A
atf_check_equal "$(grep -c ' <string>pkgB</string>' 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 ' <string>pkgA</string>' root/var/db/xbps/pkgdb*.plist)"A 0A
atf_check_equal "$(grep -c ' <string>pkgB</string>' 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 ' <string>pkgA</string>' root/var/db/xbps/pkgdb*.plist)"A 0A
atf_check_equal "$(grep -c ' <string>pkgB</string>' root/var/db/xbps/pkgdb*.plist)"B 1B
sed -e 's:<string>pkgB</string>:& <string>pkgA</string>:' -i root/var/db/xbps/pkgdb*.plist
atf_check_equal "$(grep -c ' <string>pkgA</string>' 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() {
Expand Down Expand Up @@ -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
Expand Down