Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
jobs: before scheduling a DELETE jobs verify if it is really needed
Baptiste Daroussin committed 1 month ago
commit cdf3254138ece358c4d75f59bacf51a89b9fe6dc
parent dfc63e2
2 files changed +164 -1
modified libpkg/pkg_solve.c
@@ -1303,6 +1303,78 @@ pkg_solve_dimacs_export(struct pkg_solve_problem *problem, FILE *f)
	return (EPKG_OK);
}

+
/*
+
 * Check whether a non-requested installed package can safely be kept:
+
 * - All its dependencies are still satisfied (at least one version of
+
 *   each dep has PKG_VAR_INSTALL set).
+
 * - It does not conflict with any package being installed.
+
 *
+
 * Returns true if the package can stay.
+
 */
+
static bool
+
pkg_solve_can_keep(struct pkg_solve_problem *problem,
+
    struct pkg_solve_variable *var)
+
{
+
	struct pkg *pkg = var->unit->pkg;
+
	struct pkg_dep *dep = NULL;
+
	struct pkg_conflict *conflict = NULL;
+

+
	/* Check dependencies */
+
	while (pkg_deps(pkg, &dep) == EPKG_OK) {
+
		struct pkg_solve_variable *depvar;
+

+
		depvar = pkghash_get_value(problem->variables_by_uid, dep->uid);
+
		if (depvar == NULL) {
+
			/*
+
			 * Dep not in the universe at all – it is either
+
			 * already installed outside the solver scope or
+
			 * genuinely missing.  Assume satisfied.
+
			 */
+
			continue;
+
		}
+

+
		/* Check if any version of this dep is being installed/kept */
+
		bool dep_ok = false;
+
		struct pkg_solve_variable *cv;
+
		LL_FOREACH(depvar, cv) {
+
			if (cv->flags & PKG_VAR_INSTALL) {
+
				dep_ok = true;
+
				break;
+
			}
+
		}
+

+
		if (!dep_ok) {
+
			dbg(4, "dep %s of %s-%s not satisfied",
+
			    dep->uid, pkg->name, pkg->version);
+
			return (false);
+
		}
+
	}
+

+
	/* Check conflicts: if any conflicting package is being installed,
+
	 * this package must be removed */
+
	LL_FOREACH(pkg->conflicts, conflict) {
+
		struct pkg_solve_variable *confvar;
+

+
		confvar = pkghash_get_value(problem->variables_by_uid,
+
		    conflict->uid);
+
		if (confvar == NULL)
+
			continue;
+

+
		struct pkg_solve_variable *cv;
+
		LL_FOREACH(confvar, cv) {
+
			if (cv->flags & PKG_VAR_INSTALL) {
+
				dbg(4, "%s-%s conflicts with %s-%s being installed",
+
				    pkg->name, pkg->version,
+
				    cv->unit->pkg->name,
+
				    cv->unit->pkg->version);
+
				return (false);
+
			}
+
		}
+
	}
+

+
	return (true);
+
}
+

static void
pkg_solve_insert_res_job (struct pkg_solve_variable *var,
		struct pkg_solve_problem *problem)
@@ -1364,6 +1436,26 @@ pkg_solve_insert_res_job (struct pkg_solve_variable *var,
				if (seen_add > 0 && cur_var == del_var)
					continue;

+
				/*
+
				 * For install/upgrade jobs, avoid spurious
+
				 * removal of packages that entered the universe
+
				 * via rdeps but whose dependencies are all still
+
				 * satisfied.  Only allow removal if the package
+
				 * was explicitly requested for deletion, or if
+
				 * at least one of its dependencies will no
+
				 * longer be met.  (issue #2566)
+
				 */
+
				if ((j->type == PKG_JOBS_INSTALL ||
+
				    j->type == PKG_JOBS_UPGRADE) &&
+
				    pkghash_get(j->request_delete, cur_var->uid) == NULL &&
+
				    pkg_solve_can_keep(problem, cur_var)) {
+
					dbg(2, "keeping %s-%s: deps still satisfied",
+
					    cur_var->unit->pkg->name,
+
					    cur_var->unit->pkg->version);
+
					cur_var->flags |= PKG_VAR_INSTALL;
+
					continue;
+
				}
+

				res = xcalloc(1, sizeof(struct pkg_solved));
				res->items[0] = cur_var->unit;
				res->type = PKG_SOLVED_DELETE;
modified tests/frontend/force_reinstall_shlib.sh
@@ -3,7 +3,8 @@
. $(atf_get_srcdir)/test_environment.sh

tests_init \
-
	force_install_no_unrelated_reinstall
+
	force_install_no_unrelated_reinstall \
+
	force_install_no_rdep_removal

force_install_no_unrelated_reinstall_body() {
	atf_skip_on Darwin The macOS linker uses different flags
@@ -78,3 +79,73 @@ EOF
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" -o PKG_CACHEDIR="${TMPDIR}" \
		install -fn target
}
+

+
force_install_no_rdep_removal_body() {
+
	# Regression test for issue #2566: pkg install -f of a package
+
	# that many other packages depend on should NOT propose to remove
+
	# those reverse dependencies.
+

+
	# base: the package we will force-reinstall
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg base base 1 /usr/local
+

+
	# rdep1, rdep2: packages that depend on base
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg rdep1 rdep1 1 /usr/local
+
	cat << EOF >> rdep1.ucl
+
deps: {
+
	base: {
+
		origin: "base",
+
		version: "1"
+
	}
+
}
+
EOF
+

+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg rdep2 rdep2 1 /usr/local
+
	cat << EOF >> rdep2.ucl
+
deps: {
+
	base: {
+
		origin: "base",
+
		version: "1"
+
	}
+
}
+
EOF
+

+
	# Create packages and repo
+
	for p in base rdep1 rdep2; do
+
		atf_check \
+
			-o ignore \
+
			-e empty \
+
			-s exit:0 \
+
			pkg create -M ${p}.ucl
+
	done
+

+
	atf_check \
+
		-o ignore \
+
		-e empty \
+
		-s exit:0 \
+
		pkg repo .
+

+
	mkdir reposconf
+
	cat << EOF > reposconf/repo.conf
+
local: {
+
	url: file:///$TMPDIR,
+
	enabled: true
+
}
+
EOF
+

+
	# Install all three packages
+
	atf_check \
+
		-o ignore \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" -o PKG_CACHEDIR="${TMPDIR}" \
+
		install -y base rdep1 rdep2
+

+
	# Force reinstall base: rdep1 and rdep2 must NOT be removed
+
	atf_check \
+
		-o match:"base" \
+
		-o not-match:"REMOVED" \
+
		-o not-match:"rdep1" \
+
		-o not-match:"rdep2" \
+
		-s exit:1 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" -o PKG_CACHEDIR="${TMPDIR}" \
+
		install -fn base
+
}