Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
repositories: enable/disable/reset
Baptiste Daroussin committed 15 days ago
commit 6ebc458933aef4064d8c9a73512486252de926a7
parent d1488f8
9 files changed +411 -14
modified docs/pkg-repositories.8
@@ -27,24 +27,21 @@
.\"
.\"     @(#)pkg.8
.\"
-
.Dd July 21, 2025
+
.Dd April 18, 2026
.Dt PKG-REPOSITORIES 8
.Os
.Sh NAME
.Nm "pkg repositories"
-
.Nd display configured package repositories
+
.Nd display and manage package repositories
.Sh SYNOPSIS
.Nm
.Op Fl d
.Op Fl e
.Op Fl l
-
.Op Ar repositories
-
.Pp
+
.Op Ar repository
.Nm
-
.Op Fl -disabled
-
.Op Fl -enabled
-
.Op Fl -list
-
.Op Ar repositories
+
.Ar repository
+
.Cm enable | disable | reset
.Sh DESCRIPTION
.Nm
displays configured software repositories for the package manager,
@@ -53,6 +50,30 @@ displays configured software repositories for the package manager,
If given the name of an existing repository as an argument,
only print its configuration.
Without arguments all repositories are printed.
+
.Pp
+
When a repository name is followed by an action
+
.Pq Cm enable , disable , or reset ,
+
.Nm
+
manages a persistent override for the repository's enabled state.
+
The override is stored as a file under
+
.Pa $PKG_DBDIR/repos_state/enable/
+
or
+
.Pa $PKG_DBDIR/repos_state/disable/
+
and takes precedence over the configuration files regardless of the
+
override chain.
+
.Pp
+
The
+
.Cm reset
+
action removes the override, reverting to the value from the
+
configuration files.
+
.Pp
+
When a repository's state is overridden, the
+
.Nm
+
and
+
.Cm pkg -vv
+
commands display
+
.Dq (overridden by pkg)
+
next to the enabled status.
.Sh OPTIONS
The following options are supported by
.Nm :
@@ -64,6 +85,15 @@ Print enabled repositories only.
.It Fl l , Fl -list
Print the list of repository names.
.El
+
.Sh ACTIONS
+
.Bl -tag -width disable
+
.It Cm enable
+
Force the repository to be enabled, overriding the configuration files.
+
.It Cm disable
+
Force the repository to be disabled, overriding the configuration files.
+
.It Cm reset
+
Remove a previous override, reverting to the configuration file value.
+
.El
.Sh FILES
.Bl -tag -width "/usr/local/etc/pkg/pkg.conf.sample"
.It Pa /etc/pkg/FreeBSD.conf
@@ -77,6 +107,10 @@ example
monolithic configuration, including default repository
.It Pa /usr/local/etc/pkg/repos/
location for site specific repositories
+
.It Pa /var/db/pkg/repos_state/enable/
+
directory containing override files for enabled repositories
+
.It Pa /var/db/pkg/repos_state/disable/
+
directory containing override files for disabled repositories
.El
.Sh EXAMPLES
Display all repositories:
@@ -86,6 +120,18 @@ Display all repositories:
Display all repositories configurations:
.Pp
.Dl pkg repositories
+
.Pp
+
Disable a repository:
+
.Pp
+
.Dl pkg repositories FreeBSD-ports disable
+
.Pp
+
Re-enable it:
+
.Pp
+
.Dl pkg repositories FreeBSD-ports enable
+
.Pp
+
Remove the override (revert to configuration):
+
.Pp
+
.Dl pkg repositories FreeBSD-ports reset
.Sh SEE ALSO
.Xr pkg_create 3 ,
.Xr pkg_printf 3 ,
modified libpkg/libpkg.ver
@@ -158,6 +158,7 @@ global:
	pkg_repo_create_set_output_dir;
	pkg_repo_create_set_sign;
	pkg_repo_enabled;
+
	pkg_repo_overridden;
	pkg_repo_fetch_remote_tmp;
	pkg_repo_find;
	pkg_repo_fingerprints;
modified libpkg/pkg.h.in
@@ -1603,6 +1603,7 @@ const char *pkg_repo_fingerprints(struct pkg_repo *r);
const char *pkg_repo_ssh_args(struct pkg_repo *r);
signature_t pkg_repo_signature_type(struct pkg_repo *r);
bool pkg_repo_enabled(struct pkg_repo *r);
+
bool pkg_repo_overridden(struct pkg_repo *r);
mirror_t pkg_repo_mirror_type(struct pkg_repo *r);
int pkg_repo_priority(struct pkg_repo *r);
unsigned pkg_repo_ip_version(struct pkg_repo *r);
modified libpkg/pkg_config.c
@@ -1614,6 +1614,38 @@ pkg_ini(const char *path, const char *reposdir, pkg_init_flags flags)
		add_repo_obj(cur, path, flags);
	}

+
	/* Apply repos_state overrides from $PKG_DBDIR/repos_state/{enable,disable}/ */
+
	{
+
		int dbfd = pkg_get_dbdirfd();
+
		int statefd = -1;
+
		if (dbfd != -1)
+
			statefd = openat(dbfd, "repos_state", O_DIRECTORY | O_CLOEXEC);
+
		if (statefd != -1) {
+
			int enablefd = openat(statefd, "enable",
+
			    O_DIRECTORY | O_CLOEXEC);
+
			int disablefd = openat(statefd, "disable",
+
			    O_DIRECTORY | O_CLOEXEC);
+
			while (pkg_repos(&repo) == EPKG_OK) {
+
				const char *name = pkg_repo_name(repo);
+
				if (enablefd != -1 &&
+
				    faccessat(enablefd, name, F_OK, 0) == 0) {
+
					repo->enable = true;
+
					repo->state = REPO_STATE_ENABLED;
+
				} else if (disablefd != -1 &&
+
				    faccessat(disablefd, name, F_OK, 0) == 0) {
+
					repo->enable = false;
+
					repo->state = REPO_STATE_DISABLED;
+
				}
+
			}
+
			if (enablefd != -1)
+
				close(enablefd);
+
			if (disablefd != -1)
+
				close(disablefd);
+
			close(statefd);
+
		}
+
		repo = NULL;
+
	}
+

	/* validate the different scheme */
	while (pkg_repos(&repo) == EPKG_OK) {
		object = ucl_object_find_key(config, "VALID_URL_SCHEME");
@@ -1840,6 +1872,12 @@ pkg_repo_enabled(struct pkg_repo *r)
	return (r->enable);
}

+
bool
+
pkg_repo_overridden(struct pkg_repo *r)
+
{
+
	return (r->state != REPO_STATE_NONE);
+
}
+

mirror_t
pkg_repo_mirror_type(struct pkg_repo *r)
{
modified libpkg/private/pkg.h
@@ -556,6 +556,16 @@ struct pkg_repo {

	bool enable;

+
	/*
+
	 * Override state from $PKG_DBDIR/repos_state/.
+
	 * REPO_STATE_NONE means the config value is used as-is.
+
	 */
+
	enum {
+
		REPO_STATE_NONE = 0,
+
		REPO_STATE_ENABLED,
+
		REPO_STATE_DISABLED,
+
	} state;
+

	unsigned int priority;

	ip_version_t ip;
modified src/repositories.c
@@ -4,9 +4,13 @@
 * SPDX-License-Identifier: BSD-2-Clause
 */

+
#include <err.h>
+
#include <errno.h>
+
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdbool.h>
+
#include <string.h>
#include <unistd.h>

#include <pkg.h>
@@ -16,7 +20,9 @@
void
usage_repositories(void)
{
-
        fprintf(stderr, "Usage: pkg repositories [-edl] [repository]\n\n");
+
        fprintf(stderr,
+
	    "Usage: pkg repositories [-edl] [repository]\n"
+
	    "       pkg repositories <repository> <enable|disable|reset>\n\n");
}


@@ -26,6 +32,120 @@ typedef enum {
	REPO_SHOW_DISABLED = 1U << 1,
} repo_show_t;

+
/*
+
 * Ensure $PKG_DBDIR/repos_state/<subdir> exists and return its fd.
+
 */
+
static int
+
repos_state_subdir(const char *subdir)
+
{
+
	int dbfd, statefd, subfd;
+

+
	dbfd = pkg_get_dbdirfd();
+
	if (dbfd == -1) {
+
		warnx("Cannot open package database directory");
+
		return (-1);
+
	}
+

+
	statefd = openat(dbfd, "repos_state", O_DIRECTORY | O_CLOEXEC);
+
	if (statefd == -1) {
+
		if (mkdirat(dbfd, "repos_state", 0755) == -1) {
+
			warn("Cannot create repos_state directory");
+
			return (-1);
+
		}
+
		statefd = openat(dbfd, "repos_state", O_DIRECTORY | O_CLOEXEC);
+
		if (statefd == -1)
+
			return (-1);
+
	}
+

+
	subfd = openat(statefd, subdir, O_DIRECTORY | O_CLOEXEC);
+
	if (subfd == -1) {
+
		if (mkdirat(statefd, subdir, 0755) == -1) {
+
			warn("Cannot create repos_state/%s directory", subdir);
+
			close(statefd);
+
			return (-1);
+
		}
+
		subfd = openat(statefd, subdir, O_DIRECTORY | O_CLOEXEC);
+
	}
+
	close(statefd);
+
	return (subfd);
+
}
+

+
static int
+
repo_set_state(const char *reponame, const char *action)
+
{
+
	struct pkg_repo *repo;
+
	int fd;
+

+
	repo = pkg_repo_find(reponame);
+
	if (repo == NULL) {
+
		warnx("Unknown repository: %s", reponame);
+
		return (EXIT_FAILURE);
+
	}
+

+
	if (STREQ(action, "enable")) {
+
		/* Remove any disable override */
+
		fd = repos_state_subdir("disable");
+
		if (fd != -1) {
+
			unlinkat(fd, reponame, 0);
+
			close(fd);
+
		}
+
		/* Create enable override */
+
		fd = repos_state_subdir("enable");
+
		if (fd == -1)
+
			return (EXIT_FAILURE);
+
		int f = openat(fd, reponame, O_WRONLY | O_CREAT | O_TRUNC,
+
		    0644);
+
		close(fd);
+
		if (f == -1) {
+
			warn("Cannot create override for %s", reponame);
+
			return (EXIT_FAILURE);
+
		}
+
		close(f);
+
		printf("Repository '%s' has been enabled (override set)\n",
+
		    reponame);
+
	} else if (STREQ(action, "disable")) {
+
		/* Remove any enable override */
+
		fd = repos_state_subdir("enable");
+
		if (fd != -1) {
+
			unlinkat(fd, reponame, 0);
+
			close(fd);
+
		}
+
		/* Create disable override */
+
		fd = repos_state_subdir("disable");
+
		if (fd == -1)
+
			return (EXIT_FAILURE);
+
		int f = openat(fd, reponame, O_WRONLY | O_CREAT | O_TRUNC,
+
		    0644);
+
		close(fd);
+
		if (f == -1) {
+
			warn("Cannot create override for %s", reponame);
+
			return (EXIT_FAILURE);
+
		}
+
		close(f);
+
		printf("Repository '%s' has been disabled (override set)\n",
+
		    reponame);
+
	} else if (STREQ(action, "reset")) {
+
		fd = repos_state_subdir("enable");
+
		if (fd != -1) {
+
			unlinkat(fd, reponame, 0);
+
			close(fd);
+
		}
+
		fd = repos_state_subdir("disable");
+
		if (fd != -1) {
+
			unlinkat(fd, reponame, 0);
+
			close(fd);
+
		}
+
		printf("Repository '%s' override removed, "
+
		    "using configuration value\n", reponame);
+
	} else {
+
		warnx("Unknown action: %s (expected enable, disable, or reset)",
+
		    action);
+
		return (EXIT_FAILURE);
+
	}
+

+
	return (EXIT_SUCCESS);
+
}
+

int
exec_repositories(int argc, char **argv)
{
@@ -59,12 +179,16 @@ exec_repositories(int argc, char **argv)
		}
	}

-
	if (rs == REPO_SHOW_ALL)
-
		rs |= REPO_SHOW_DISABLED|REPO_SHOW_ENABLED;
-

	argc -= optind;
	argv += optind;

+
	/* pkg repositories <name> <enable|disable|reset> */
+
	if (argc == 2)
+
		return (repo_set_state(argv[0], argv[1]));
+

+
	if (rs == REPO_SHOW_ALL)
+
		rs |= REPO_SHOW_DISABLED|REPO_SHOW_ENABLED;
+

	if (argc == 1)
		r = argv[0];

modified src/utils.c
@@ -1329,12 +1329,13 @@ print_repository(struct pkg_repo *repo, bool pad)
			break;
	}

-
	printf("%s%s: { \n    %-16s: \"%s\",\n    %-16s: %s,\n"
+
	printf("%s%s: { \n    %-16s: \"%s\",\n    %-16s: %s%s,\n"
			"    %-16s: %d",
			pad ? "  " : "",
			pkg_repo_name(repo),
			"url", pkg_repo_url(repo),
			"enabled", pkg_repo_enabled(repo) ? "yes" : "no",
+
			pkg_repo_overridden(repo) ? " (overridden by pkg)" : "",
			"priority", pkg_repo_priority(repo));

	if (pkg_repo_mirror_type(repo) != NOMIRROR)
modified tests/Makefile.autosetup
@@ -79,7 +79,8 @@ TESTS_SH= \
	frontend/http.sh \
	frontend/triggers.sh \
	frontend/info.sh \
-
	frontend/force_reinstall_shlib.sh
+
	frontend/force_reinstall_shlib.sh \
+
	frontend/repositories.sh

#
# These files are mostly simple binaries obtained from
added tests/frontend/repositories.sh
@@ -0,0 +1,175 @@
+
#!/usr/bin/env atf-sh
+

+
. $(atf_get_srcdir)/test_environment.sh
+

+
tests_init \
+
	list_repos \
+
	list_enabled \
+
	list_disabled \
+
	override_disable \
+
	override_enable \
+
	override_reset \
+
	override_shown_in_vv \
+
	override_unknown_repo \
+
	override_unknown_action
+

+
test_setup()
+
{
+
	mkdir -p reposconf
+
	cat > reposconf/test.conf << EOF
+
repo_a: {
+
    url: "file:///tmp/repo_a",
+
    enabled: true
+
}
+
repo_b: {
+
    url: "file:///tmp/repo_b",
+
    enabled: false
+
}
+
EOF
+
}
+

+
list_repos_body()
+
{
+
	test_setup
+

+
	atf_check \
+
		-o match:'repo_a' \
+
		-o match:'repo_b' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories -l
+
}
+

+
list_enabled_body()
+
{
+
	test_setup
+

+
	atf_check \
+
		-o match:'repo_a' \
+
		-o not-match:'repo_b' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories -le
+
}
+

+
list_disabled_body()
+
{
+
	test_setup
+

+
	atf_check \
+
		-o not-match:'repo_a' \
+
		-o match:'repo_b' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories -ld
+
}
+

+
override_disable_body()
+
{
+
	test_setup
+

+
	# repo_a is enabled in config; disable it via override
+
	atf_check \
+
		-o match:'has been disabled' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a disable
+

+
	# The override file should exist
+
	atf_check -s exit:0 test -f repos_state/disable/repo_a
+

+
	# repo_a should now show as disabled with override
+
	atf_check \
+
		-o match:'enabled.*no.*overridden by pkg' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a
+

+
	# It should appear in disabled list now
+
	atf_check \
+
		-o match:'repo_a' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories -ld
+
}
+

+
override_enable_body()
+
{
+
	test_setup
+

+
	# repo_b is disabled in config; enable it via override
+
	atf_check \
+
		-o match:'has been enabled' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_b enable
+

+
	# The override file should exist
+
	atf_check -s exit:0 test -f repos_state/enable/repo_b
+

+
	# repo_b should now show as enabled with override
+
	atf_check \
+
		-o match:'enabled.*yes.*overridden by pkg' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_b
+

+
	# It should appear in enabled list now
+
	atf_check \
+
		-o match:'repo_b' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories -le
+
}
+

+
override_reset_body()
+
{
+
	test_setup
+

+
	# Disable repo_a, then reset
+
	atf_check -o ignore -s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a disable
+

+
	atf_check \
+
		-o match:'override removed' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a reset
+

+
	# Override file should be gone
+
	atf_check -s exit:1 test -f repos_state/disable/repo_a
+

+
	# Back to config value (enabled, no override)
+
	atf_check \
+
		-o match:'enabled.*yes' \
+
		-o not-match:'overridden' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a
+
}
+

+
override_shown_in_vv_body()
+
{
+
	test_setup
+

+
	atf_check -o ignore -s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a disable
+

+
	atf_check \
+
		-o match:'repo_a' \
+
		-o match:'overridden by pkg' \
+
		-s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" -vv
+

+
	atf_check -o ignore -s exit:0 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a reset
+
}
+

+
override_unknown_repo_body()
+
{
+
	test_setup
+

+
	atf_check \
+
		-e match:'Unknown repository' \
+
		-s exit:1 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories nonexistent disable
+
}
+

+
override_unknown_action_body()
+
{
+
	test_setup
+

+
	atf_check \
+
		-e match:'Unknown action' \
+
		-s exit:1 \
+
		pkg -o REPOS_DIR="${TMPDIR}/reposconf" repositories repo_a bogus
+
}