From 7b9f9c80e693d9402ffd61d01c1abc762547d105 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Wed, 31 Dec 2025 01:03:04 -0500 Subject: [PATCH] lib: handle replaces if replaced pkg is installed in the same transaction replaces isn't honoured if the package to be replaced is currently being installed. This fixes a corner case where a transitional package could be installed along with its replacement, when it should be "ignored" and only the new package installed. This requires transaction commits to not fail if a replaced package isn't installed when it should be "removed". Also, indicate to the user that a package is being replaced, with details about what replaced what shown in verbose output, not just debug output. fixes: #667 --- bin/xbps-install/util.c | 5 +++++ lib/transaction_check_replaces.c | 8 +++++-- lib/transaction_commit.c | 13 +++++++++++- tests/xbps/libxbps/shell/replace_test.sh | 27 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/bin/xbps-install/util.c b/bin/xbps-install/util.c index d0eadf2c0..af696f3a5 100644 --- a/bin/xbps-install/util.c +++ b/bin/xbps-install/util.c @@ -157,15 +157,20 @@ print_trans_colmode(struct transaction *trans, unsigned int cols) while ((obj = xbps_object_iterator_next(trans->iter)) != NULL) { bool dload = false; + bool replaced = false; pkgver = pkgname = ipkgver = ver = iver = NULL; xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); xbps_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname); xbps_dictionary_get_uint64(obj, "filename-size", &dlsize); xbps_dictionary_get_bool(obj, "download", &dload); + xbps_dictionary_get_bool(obj, "replaced", &replaced); ttype = xbps_transaction_pkg_type(obj); tract = ttype2str(obj); + if (replaced) { + tract = "replaced"; + } if (trans->xhp->flags & XBPS_FLAG_DOWNLOAD_ONLY) { tract = "download"; } diff --git a/lib/transaction_check_replaces.c b/lib/transaction_check_replaces.c index 622152880..2b585b0d6 100644 --- a/lib/transaction_check_replaces.c +++ b/lib/transaction_check_replaces.c @@ -80,10 +80,12 @@ xbps_transaction_check_replaces(struct xbps_handle *xhp, xbps_array_t pkgs) /* * Find the installed package that matches the pattern - * to be replaced. + * to be replaced. Also check if the package would be + * installed in the transaction. */ if (((instd = xbps_pkgdb_get_pkg(xhp, pattern)) == NULL) && - ((instd = xbps_pkgdb_get_virtualpkg(xhp, pattern)) == NULL)) + ((instd = xbps_pkgdb_get_virtualpkg(xhp, pattern)) == NULL) && + ((instd = xbps_find_pkg_in_array(pkgs, pattern, XBPS_TRANS_INSTALL)) == NULL)) continue; if (!xbps_dictionary_get_cstring_nocopy(instd, "pkgver", &curpkgver)) { @@ -142,6 +144,7 @@ xbps_transaction_check_replaces(struct xbps_handle *xhp, xbps_array_t pkgs) xbps_object_iterator_release(iter); return false; } + xbps_verbose_printf("Package `%s' will be replaced by `%s'\n", curpkgver, pkgver); xbps_dbg_printf( "Package `%s' in transaction will be " "replaced by `%s', matched with `%s'\n", @@ -174,6 +177,7 @@ xbps_transaction_check_replaces(struct xbps_handle *xhp, xbps_array_t pkgs) xbps_object_iterator_release(iter); return false; } + xbps_verbose_printf("Package `%s' will be replaced by `%s'\n", curpkgver, pkgver); xbps_dbg_printf( "Package `%s' will be replaced by `%s', " "matched with `%s'\n", curpkgver, pkgver, pattern); diff --git a/lib/transaction_commit.c b/lib/transaction_commit.c index 8ef8a8de6..b2b0594de 100644 --- a/lib/transaction_commit.c +++ b/lib/transaction_commit.c @@ -109,7 +109,7 @@ xbps_transaction_commit(struct xbps_handle *xhp) xbps_trans_type_t ttype; const char *pkgver = NULL, *pkgname = NULL; int rv = 0; - bool update; + bool update, replaced; setlocale(LC_ALL, ""); @@ -232,6 +232,11 @@ xbps_transaction_commit(struct xbps_handle *xhp) } if ((pkgdb_pkgd = xbps_pkgdb_get_pkg(xhp, pkgname)) == NULL) { + replaced = false; + xbps_dictionary_get_bool(obj, "replaced", &replaced); + if (replaced) { + continue; + } rv = ENOENT; xbps_dbg_printf("[trans] cannot find %s in pkgdb: %s\n", pkgname, strerror(rv)); @@ -302,6 +307,7 @@ xbps_transaction_commit(struct xbps_handle *xhp) while ((obj = xbps_object_iterator_next(iter)) != NULL) { xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); + xbps_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname); ttype = xbps_transaction_pkg_type(obj); if (ttype == XBPS_TRANS_REMOVE) { @@ -310,6 +316,11 @@ xbps_transaction_commit(struct xbps_handle *xhp) */ update = false; xbps_dictionary_get_bool(obj, "remove-and-update", &update); + replaced = false; + xbps_dictionary_get_bool(obj, "replaced", &replaced); + if (((pkgdb_pkgd = xbps_pkgdb_get_pkg(xhp, pkgname)) == NULL) && replaced) { + continue; + } rv = xbps_remove_pkg(xhp, pkgver, update); if (rv != 0) { xbps_dbg_printf("[trans] failed to " diff --git a/tests/xbps/libxbps/shell/replace_test.sh b/tests/xbps/libxbps/shell/replace_test.sh index 20eb1a218..0efbbfd03 100644 --- a/tests/xbps/libxbps/shell/replace_test.sh +++ b/tests/xbps/libxbps/shell/replace_test.sh @@ -419,6 +419,32 @@ replace_transitional_pkg_automatically_installed3_body() { atf_check_equal $(xbps-query -C xbps.d -r root -p automatic-install A) "" } +atf_test_case replace_transitional_pkg_during_install + +replace_transitional_pkg_during_install_head() { + atf_set "descr" "Tests for package replace: install a transitional package and replace it during the transaction" +} + +replace_transitional_pkg_during_install_body() { + mkdir some_repo root + mkdir -p pkg_A/usr/bin empty + echo "A-1.0_1" > pkg_A/usr/bin/foo + cd some_repo + xbps-create -A noarch -n A-1.0_1 -s "A pkg" --replaces "B>=0" ../pkg_A + atf_check_equal $? 0 + xbps-create -A noarch -n B-1.0_1 -s "A pkg - transitional dummy package" --dependencies="A>=0" ../empty + atf_check_equal $? 0 + xbps-rindex -d -a $PWD/*.xbps + atf_check_equal $? 0 + cd .. + xbps-install -C xbps.d -r root --repository=$PWD/some_repo -yd B + atf_check_equal $? 0 + result=$(xbps-query -r root -l | wc -l) + atf_check_equal $result 1 + atf_check_equal $(xbps-query -C xbps.d -r root -p state A) installed + atf_check_equal $(xbps-query -C xbps.d -r root -p state B) "" +} + atf_test_case replace_automatically_installed_dep replace_automatically_installed_dep_head() { @@ -600,6 +626,7 @@ atf_init_test_cases() { atf_add_test_case replace_transitional_pkg_automatically_installed atf_add_test_case replace_transitional_pkg_automatically_installed2 atf_add_test_case replace_transitional_pkg_automatically_installed3 + atf_add_test_case replace_transitional_pkg_during_install atf_add_test_case replace_automatically_installed_dep atf_add_test_case replace_automatically_installed_dep2 atf_add_test_case replace_automatically_installed_dep3