Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
Merge remote-tracking branch 'upstream/main'
Shawn Webb committed 1 year ago
commit 76191f796d973de4277675a5090e4241e3a16a78
parent 3088caa
13 files changed +335 -250
modified compat/bsd_compat.h
@@ -196,15 +196,15 @@ FILE * funopen(const void *cookie, int (*readfn)(void *, char *, int),
#endif

#if !HAVE_GETPROGNAME
-
#if defined(__linux__)
-
extern char *__progname;
-
# define getprogname() __progname
-
#elif defined(__GLIBC__)
+
# if defined (__linux__) && defined (__GLIBC__)
extern char *program_invocation_short_name;
-
# define getprogname() program_invocation_short_name
-
#else
-
# error "Don't know how to replace getprogname()"
-
#endif
+
#  define getprogname()    program_invocation_short_name
+
# elif defined (__linux__) && !defined (__GLIBC__)
+
extern char *__progname;
+
#  define getprogname()    __progname
+
# else
+
#  error "Don't know how to replace getprogname()"
+
# endif
#endif

#endif
modified libpkg/pkg.h.in
@@ -6,8 +6,8 @@
 * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
 * Copyright (c) 2013-2014 Matthew Seaman <matthew@FreeBSD.org>
 * Copyright (c) 2014-2016 Vsevolod Stakhov <vsevolod@FreeBSD.org>
-
 * Copyright (c) 2023 Serenity Cyber Security, LLC <license@futurecrew.ru>
-
 *                    Author: Gleb Popov <arrowd@FreeBSD.org>
+
 * Copyright (c) 2023-2024 Serenity Cyber Security, LLC <license@futurecrew.ru>
+
 *                         Author: Gleb Popov <arrowd@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
@@ -843,6 +843,7 @@ void pkg_repo_create_set_output_dir(struct pkg_repo_create *prc, const char *);
void pkg_repo_create_set_metafile(struct pkg_repo_create *prc, const char *);
void pkg_repo_create_set_sign(struct pkg_repo_create *prc, char **argv, int argc, pkg_password_cb *cb);
void pkg_repo_create_set_groups(struct pkg_repo_create *prc, const char *);
+
void pkg_repo_create_set_expired_packages(struct pkg_repo_create *prc, const char *);
int pkg_repo_create(struct pkg_repo_create *, char *path);

/**
modified libpkg/pkg_add.c
@@ -2,6 +2,8 @@
 * Copyright (c) 2011-2022 Baptiste Daroussin <bapt@FreeBSD.org>
 * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
 * Copyright (c) 2016, Vsevolod Stakhov
+
 * Copyright (c) 2024, Future Crew, LLC
+
 *                     Author: Gleb Popov <arrowd@FreeBSD.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
@@ -40,6 +42,7 @@
#include <pwd.h>
#include <grp.h>
#include <sys/time.h>
+
#include <sys/wait.h>
#include <time.h>
#include <xstring.h>
#include <tllist.h>
@@ -59,14 +62,143 @@
#define NOCHANGESFLAGS	(UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
#endif

+
struct external_merge_tmp_file {
+
	int fd;
+
	const char *template;
+
	char path[MAXPATHLEN];
+
	const char *content;
+
	size_t content_len;
+
};
+

+
static merge_status
+
merge_with_external_tool(const char *merge_tool, struct pkg_config_file *lcf,
+
    size_t lcf_len, struct pkg_config_file *rcf, char *localconf, char **mergedconf)
+
{
+
	pid_t wait_res;
+
	int status;
+
	FILE *inout[2];
+

+
	char *tmpdir = getenv("TMPDIR");
+
	if (tmpdir == NULL)
+
		tmpdir = "/tmp";
+

+
	int output_fd;
+
	char output_path[MAXPATHLEN];
+
	off_t output_sz;
+

+
	strlcpy(output_path, tmpdir, sizeof(output_path));
+
	strlcat(output_path, "/OUTPUT.XXXXXXXXXX", sizeof(output_path));
+
	output_fd = mkstemp(output_path);
+
	if (output_fd == -1) {
+
		pkg_emit_error("Can't create %s", output_path);
+
		return MERGE_FAILED;
+
	}
+
	close(output_fd);
+

+
	struct external_merge_tmp_file tmp_files[] = {
+
		{-1, "/BASE.XXXXXXXXXX", {0}, lcf->content, lcf_len},
+
		{-1, "/LOCAL.XXXXXXXXXX", {0}, localconf, strlen(localconf)},
+
		{-1, "/REMOTE.XXXXXXXXXX", {0}, rcf->content, strlen(rcf->content)}
+
	};
+
	bool tmp_files_ok = true;
+
	for (int i = 0; i < NELEM(tmp_files); i++) {
+
		int copied = strlcpy(tmp_files[i].path, tmpdir, sizeof(tmp_files[i].path));
+
		if (copied >= sizeof(tmp_files[i].path)) {
+
			pkg_emit_error("Temporary path too long: %s", tmp_files[i].path);
+
			return MERGE_FAILED;
+
		}
+
		copied = strlcat(tmp_files[i].path, tmp_files[i].template, sizeof(tmp_files[i].path));
+
		if (copied >= sizeof(tmp_files[i].path)) {
+
			pkg_emit_error("Temporary path too long: %s", tmp_files[i].path);
+
			return MERGE_FAILED;
+
		}
+

+
		tmp_files[i].fd = mkstemp(tmp_files[i].path);
+
		if (tmp_files[i].fd == -1) {
+
			pkg_emit_error("Can't create %s", tmp_files[i].path);
+
			tmp_files_ok = false;
+
			break;
+
		}
+
		if (write(tmp_files[i].fd, tmp_files[i].content, tmp_files[i].content_len) == -1) {
+
			pkg_emit_error("Failed to write %s", tmp_files[i].path);
+
			tmp_files_ok = false;
+
			break;
+
		}
+
		close(tmp_files[i].fd);
+
		tmp_files[i].fd = -1;
+
	}
+
	if (!tmp_files_ok) {
+
		for (int i = 0; i < NELEM(tmp_files); i++) {
+
			if (tmp_files[i].fd != -1)
+
				close(tmp_files[i].fd);
+
			if (strlen(tmp_files[i].path))
+
				unlink(tmp_files[i].path);
+
		}
+
		return MERGE_FAILED;
+
	}
+

+
	char command[MAXPATHLEN];
+
	for (int i = 0; *merge_tool != '\0'; i++, merge_tool++) {
+
		if (*merge_tool != '%') {
+
			command[i] = *merge_tool;
+
			continue;
+
		}
+
		merge_tool++;
+
		int tmp_files_index;
+
		switch (*merge_tool) {
+
		case 'b':
+
			tmp_files_index = 0;
+
			break;
+
		case 'l':
+
			tmp_files_index = 1;
+
			break;
+
		case 'r':
+
			tmp_files_index = 2;
+
			break;
+
		case 'n':
+
			i += strlcpy(&command[i], RELATIVE_PATH(rcf->path), sizeof(command) - i) - 1;
+
			continue;
+
		case 'o':
+
			i += strlcpy(&command[i], output_path, sizeof(command) - i) - 1;
+
			continue;
+
		default:
+
			pkg_emit_error("Unknown format string in the MERGETOOL command");
+
			merge_tool--;
+
			continue;
+
		}
+
		i += strlcpy(&command[i], tmp_files[tmp_files_index].path, sizeof(command) - i) - 1;
+
	}
+

+
	pid_t pid = process_spawn_pipe(inout, command);
+
	wait_res = waitpid(pid, &status, 0);
+

+
	fclose(inout[0]);
+
	fclose(inout[1]);
+
	for (int i = 0; i < sizeof(tmp_files); i++) {
+
		unlink(tmp_files[i].path);
+
	}
+

+
	if (wait_res == -1 || WIFSIGNALED(status) || WEXITSTATUS(status)) {
+
		unlink(output_path);
+
		pkg_emit_error("External merge tool failed, retrying with builtin algorithm");
+
		return MERGE_FAILED;
+
	}
+

+
	file_to_bufferat(AT_FDCWD, output_path, mergedconf, &output_sz);
+
	unlink(output_path);
+

+
	return MERGE_SUCCESS;
+
}
+

static void
attempt_to_merge(int rootfd, struct pkg_config_file *rcf, struct pkg *local,
-
    bool merge)
+
    bool merge, const char *merge_tool)
{
	const struct pkg_file *lf = NULL;
	struct stat st;
	xstring *newconf;
	struct pkg_config_file *lcf = NULL;
+
	size_t lcf_len;

	char *localconf = NULL;
	off_t sz;
@@ -97,26 +229,32 @@ attempt_to_merge(int rootfd, struct pkg_config_file *rcf, struct pkg *local,

	pkg_debug(2, "size: %jd vs %jd", (intmax_t)sz, (intmax_t)strlen(lcf->content));

-
	if (sz == strlen(lcf->content)) {
+
	lcf_len = strlen(lcf->content);
+
	if (sz == lcf_len) {
		pkg_debug(2, "Ancient vanilla and deployed conf are the same size testing checksum");
		localsum = pkg_checksum_data(localconf, sz,
		    PKG_HASH_TYPE_SHA256_HEX);
		if (localsum && STREQ(localsum, lf->sum)) {
			pkg_debug(2, "Checksum are the same %jd", (intmax_t)strlen(localconf));
-
			free(localconf);
			free(localsum);
-
			return;
+
			goto ret;
		}
		free(localsum);
		pkg_debug(2, "Checksum are different %jd", (intmax_t)strlen(localconf));
	}
	rcf->status = MERGE_FAILED;
	if (!merge) {
-
		free(localconf);
-
		return;
+
		goto ret;
	}

	pkg_debug(1, "Attempting to merge %s", rcf->path);
+
	if (merge_tool) {
+
		char* mergedconf = NULL;
+
		rcf->status = merge_with_external_tool(merge_tool, lcf, lcf_len, rcf, localconf, &mergedconf);
+
		rcf->newcontent = mergedconf;
+
		if (rcf->status == MERGE_SUCCESS)
+
			goto ret;
+
	}
	newconf = xstring_new();
	if (merge_3way(lcf->content, localconf, rcf->content, newconf) != 0) {
		xstring_free(newconf);
@@ -126,6 +264,7 @@ attempt_to_merge(int rootfd, struct pkg_config_file *rcf, struct pkg *local,
		rcf->newcontent = conf;
		rcf->status = MERGE_SUCCESS;
	}
+
ret:
	free(localconf);
}

@@ -651,6 +790,7 @@ create_regfile(struct pkg *pkg, struct pkg_file *f, struct archive *a,
		if (f->config) {
			const char *cfdata;
			bool merge = pkg_object_bool(pkg_config_get("AUTOMERGE"));
+
			const char *merge_tool = pkg_object_string(pkg_config_get("MERGETOOL"));

			pkg_debug(1, "Populating config_file %s", f->path);
			len = archive_entry_size(ae);
@@ -658,7 +798,7 @@ create_regfile(struct pkg *pkg, struct pkg_file *f, struct archive *a,
			archive_read_data(a, f->config->content, len);
			f->config->content[len] = '\0';
			cfdata = f->config->content;
-
			attempt_to_merge(pkg->rootfd, f->config, local, merge);
+
			attempt_to_merge(pkg->rootfd, f->config, local, merge, merge_tool);
			if (f->config->status == MERGE_SUCCESS)
				cfdata = f->config->newcontent;
			dprintf(fd, "%s", cfdata);
modified libpkg/pkg_config.c
@@ -367,6 +367,12 @@ static struct config_entry c[] = {
	},
	{
		PKG_STRING,
+
		"MERGETOOL",
+
		NULL,
+
		"Path to a program to be used for solving conflicts during the 3-way merging"
+
	},
+
	{
+
		PKG_STRING,
		"VERSION_SOURCE",
		NULL,
		"Version source for pkg-version (I, P, R), default is auto detect"
modified libpkg/pkg_jobs.c
@@ -101,7 +101,7 @@ pkg_jobs_new(struct pkg_jobs **j, pkg_jobs_t t, struct pkgdb *db)

	(*j)->db = db;
	(*j)->type = t;
-
	(*j)->solved = 0;
+
	(*j)->solved = false;
	(*j)->pinning = true;
	(*j)->flags = PKG_FLAG_NONE;
	(*j)->conservative = pkg_object_bool(pkg_config_get("CONSERVATIVE_UPGRADE"));
@@ -870,20 +870,6 @@ pkg_jobs_process_remote_pkg(struct pkg_jobs *j, struct pkg *rp,
	return (nrit != NULL ? EPKG_OK : EPKG_FATAL);
}

-
static bool
-
pkg_jobs_has_replacement(struct pkg_jobs *j, const char *uid)
-
{
-
	struct pkg_job_replace *cur;
-

-
	LL_FOREACH(j->universe->uid_replaces, cur) {
-
		if (STREQ(cur->new_uid, uid)) {
-
			return (true);
-
		}
-
	}
-

-
	return (false);
-
}
-

static int
pkg_jobs_try_remote_candidate(struct pkg_jobs *j, const char *cond, const char *pattern, match_t m)
{
@@ -901,9 +887,6 @@ pkg_jobs_try_remote_candidate(struct pkg_jobs *j, const char *cond, const char *

	while (pkgdb_it_next(it, &p, flags) == EPKG_OK) {
		xstring_renew(qmsg);
-
		if (pkg_jobs_has_replacement(j, p->uid)) {
-
			dbg(1, "replacement %s is already used", p->uid);
-
		}
	}


@@ -1517,7 +1500,7 @@ jobs_solve_deinstall(struct pkg_jobs *j)
		pkgdb_it_free(it);
	}

-
	j->solved = 1;
+
	j->solved = true;

	return (pkg_jobs_process_delete_request(j));
}
@@ -1628,6 +1611,8 @@ jobs_solve_full_upgrade(struct pkg_jobs *j)
	unsigned flags = PKG_LOAD_BASIC|PKG_LOAD_OPTIONS|PKG_LOAD_DEPS|PKG_LOAD_REQUIRES|
			PKG_LOAD_SHLIBS_REQUIRED|PKG_LOAD_ANNOTATIONS|PKG_LOAD_CONFLICTS;

+
	assert(!j->solved);
+

	candidates = pkg_jobs_find_install_candidates(j);
	jcount = tll_length(*candidates);

@@ -1679,6 +1664,8 @@ jobs_solve_partial_upgrade(struct pkg_jobs *j)
	int retcode;
	pkghash_it it;

+
	assert(!j->solved);
+

	LL_FOREACH(j->patterns, jp) {
		retcode = pkg_jobs_find_remote_pattern(j, jp);
		if (retcode == EPKG_FATAL) {
@@ -1742,7 +1729,7 @@ jobs_solve_install_upgrade(struct pkg_jobs *j)
		return (EPKG_FATAL);
	}

-
	if (j->solved == 0) {
+
	if (!j->solved) {
		if (j->patterns == NULL) {
			retcode = jobs_solve_full_upgrade(j);
			if (retcode != EPKG_OK)
@@ -1774,7 +1761,7 @@ jobs_solve_install_upgrade(struct pkg_jobs *j)

order:

-
	j->solved ++;
+
	j->solved = true;

	return (EPKG_OK);
}
@@ -1789,6 +1776,8 @@ jobs_solve_fetch(struct pkg_jobs *j)
	pkghash_it hit;
	pkg_error_t rc;

+
	assert(!j->solved);
+

	if ((j->flags & PKG_FLAG_UPGRADES_FOR_INSTALLED) == PKG_FLAG_UPGRADES_FOR_INSTALLED) {
		if ((it = pkgdb_query(j->db, NULL, MATCH_ALL)) == NULL)
			return (EPKG_FATAL);
@@ -1820,45 +1809,11 @@ jobs_solve_fetch(struct pkg_jobs *j)
		}
	}

-
	j->solved ++;
+
	j->solved = true;

	return (EPKG_OK);
}

-
static void
-
pkg_jobs_apply_replacements(struct pkg_jobs *j)
-
{
-
	struct pkg_job_replace *r;
-
	static const char sql[] = ""
-
		"UPDATE packages SET name=?1 "
-
		" WHERE name=?2;" ;
-
	sqlite3_stmt *stmt;
-
	int ret;
-

-
	if (j->universe->uid_replaces == NULL)
-
		return;
-

-
	dbg(4, "running '%s'", sql);
-
	ret = sqlite3_prepare_v2(j->db->sqlite, sql, -1, &stmt, NULL);
-
	if (ret != SQLITE_OK) {
-
		ERROR_SQLITE(j->db->sqlite, sql);
-
		return;
-
	}
-

-
	LL_FOREACH(j->universe->uid_replaces, r) {
-
		dbg(4, "changing uid %s -> %s", r->old_uid, r->new_uid);
-
		sqlite3_bind_text(stmt, 1, r->new_uid, -1, SQLITE_TRANSIENT);
-
		sqlite3_bind_text(stmt, 2, r->old_uid, -1, SQLITE_TRANSIENT);
-

-
		if (sqlite3_step(stmt) != SQLITE_DONE)
-
			ERROR_STMT_SQLITE(j->db->sqlite, stmt);
-

-
		sqlite3_reset(stmt);
-
	}
-

-
	sqlite3_finalize(stmt);
-
}
-

static int
solve_with_external_cudf_solver(struct pkg_jobs *j, const char *solver)
{
@@ -1918,7 +1873,7 @@ solve_with_sat_solver(struct pkg_jobs *j)
	problem = pkg_solve_jobs_to_sat(j);
	if (problem == NULL) {
		pkg_emit_error("cannot convert job to SAT problem");
-
		j->solved = 0;
+
		j->solved = false;
		return (EPKG_FATAL);
	}

@@ -1934,7 +1889,7 @@ solve_with_sat_solver(struct pkg_jobs *j)
	if (ret == EPKG_FATAL) {
		pkg_emit_error("cannot solve job using SAT solver");
		pkg_solve_problem_free(problem);
-
		j->solved = 0;
+
		j->solved = false;
	} else {
		ret = pkg_solve_sat_to_jobs(problem);
	}
@@ -1955,8 +1910,8 @@ solve_with_sat_solver(struct pkg_jobs *j)
	return (ret);
}

-
int
-
pkg_jobs_solve(struct pkg_jobs *j)
+
static int
+
pkg_jobs_run_solver(struct pkg_jobs *j)
{
	int ret;
	const char *cudf_solver;
@@ -1997,12 +1952,46 @@ pkg_jobs_solve(struct pkg_jobs *j)

	pkgdb_end_solver(j->db);

+
	return (ret);
+
}
+

+
static int
+
pkg_jobs_check_and_solve_conflicts(struct pkg_jobs *j, bool *found_conflicts)
+
{
+
	int rc;
+

+
	/* An inital solver run must be completed before this function is called */
+
	assert(j->solved);
+

+
	while ((rc = pkg_jobs_check_conflicts(j)) == EPKG_CONFLICT) {
+
		if (found_conflicts) {
+
			*found_conflicts = true;
+
		}
+
		/* Cleanup solver results */
+
		tll_free_and_free(j->jobs, free);
+
		j->count = 0;
+
		rc = pkg_jobs_run_solver(j);
+
		if (rc != EPKG_OK) {
+
			break;
+
		}
+
	}
+

+
	return (rc);
+
}
+

+
int
+
pkg_jobs_solve(struct pkg_jobs *j)
+
{
+
	int ret = pkg_jobs_run_solver(j);
+

	if (ret != EPKG_OK)
		return (ret);

-
	pkg_jobs_apply_replacements(j);
-

-
	/* Check if we need to fetch and re-run the solver */
+
	/*
+
	 * We can avoid asking the user for confirmation twice in the case of
+
	 * conflicts if we can check for and solve conflicts without first
+
	 * needing to fetch.
+
	 */
	tll_foreach(j->jobs, job) {
		struct pkg *p;

@@ -2017,23 +2006,8 @@ pkg_jobs_solve(struct pkg_jobs *j)
		}
	}

-
	if (j->solved == 1 && !j->need_fetch && j->type != PKG_JOBS_FETCH) {
-
		int rc;
-
		bool has_conflicts = false;
-
		do {
-
			j->conflicts_registered = 0;
-
			rc = pkg_jobs_check_conflicts(j);
-
			if (rc == EPKG_CONFLICT) {
-
				/* Cleanup results */
-
				tll_free_and_free(j->jobs, free);
-
				j->count = 0;
-
				has_conflicts = true;
-
				pkg_jobs_solve(j);
-
			}
-
			else if (rc == EPKG_OK && !has_conflicts) {
-
				break;
-
			}
-
		} while (j->conflicts_registered > 0);
+
	if (!j->need_fetch && j->type != PKG_JOBS_FETCH) {
+
		ret = pkg_jobs_check_and_solve_conflicts(j, NULL);
	}

	return (ret);
@@ -2254,7 +2228,6 @@ int
pkg_jobs_apply(struct pkg_jobs *j)
{
	int rc;
-
	bool has_conflicts = false;

	if (!j->solved) {
		pkg_emit_error("The jobs hasn't been solved");
@@ -2271,33 +2244,13 @@ pkg_jobs_apply(struct pkg_jobs *j)
			rc = pkg_jobs_fetch(j);
			pkg_plugins_hook_run(PKG_PLUGIN_HOOK_POST_FETCH, j, j->db);
			if (rc == EPKG_OK) {
-
				/* Check local conflicts in the first run */
-
				if (j->solved == 1) {
-
					do {
-
						j->conflicts_registered = 0;
-
						rc = pkg_jobs_check_conflicts(j);
-
						if (rc == EPKG_CONFLICT) {
-
							/* Cleanup results */
-
							tll_free_and_free(j->jobs, free);
-
							j->count = 0;
-
							has_conflicts = true;
-
							rc = pkg_jobs_solve(j);
-
						}
-
						else if (rc == EPKG_OK && !has_conflicts) {
-
							rc = pkg_jobs_execute(j);
-
							break;
-
						}
-
					} while (j->conflicts_registered > 0);
-

-
					if (has_conflicts) {
-
						if (j->conflicts_registered == 0)
-
							pkg_jobs_set_priorities(j);
-

-
						return (EPKG_CONFLICT);
-
					}
-
				}
-
				else {
-
					/* Not the first run, conflicts are resolved already */
+
				j->need_fetch = false;
+
				bool found_conflicts = false;
+
				rc = pkg_jobs_check_and_solve_conflicts(j, &found_conflicts);
+
				if (found_conflicts) {
+
					pkg_jobs_set_priorities(j);
+
					rc = EPKG_CONFLICT;
+
				} else if (rc == EPKG_OK) {
					rc = pkg_jobs_execute(j);
				}
			}
modified libpkg/pkg_jobs_universe.c
@@ -827,14 +827,6 @@ pkg_jobs_universe_provide_free(struct pkg_job_provide *pr)
	}
}

-
static void
-
pkg_jobs_universe_replacement_free(struct pkg_job_replace *r)
-
{
-
	free(r->new_uid);
-
	free(r->old_uid);
-
	free(r);
-
}
-

void
pkg_jobs_universe_free(struct pkg_jobs_universe *universe)
{
@@ -856,7 +848,6 @@ pkg_jobs_universe_free(struct pkg_jobs_universe *universe)
	while (pkghash_next(&it))
		pkg_jobs_universe_provide_free(it.value);
	pkghash_destroy(universe->provides);
-
	LL_FREE(universe->uid_replaces, pkg_jobs_universe_replacement_free);
}

struct pkg_jobs_universe *
@@ -876,58 +867,6 @@ pkg_jobs_universe_find(struct pkg_jobs_universe *universe, const char *uid)
	return (pkghash_get_value(universe->items, uid));
}

-
void
-
pkg_jobs_universe_change_uid(struct pkg_jobs_universe *universe,
-
	struct pkg_job_universe_item *unit,
-
	const char *new_uid, bool update_rdeps)
-
{
-
	struct pkg_dep *rd = NULL, *d = NULL;
-
	struct pkg_job_universe_item *found, *tmp;
-

-
	struct pkg *lp;
-
	struct pkg_job_replace *replacement;
-

-
	if (update_rdeps) {
-
		/* For all rdeps update deps accordingly */
-
		while (pkg_rdeps(unit->pkg, &rd) == EPKG_OK) {
-
			found = pkg_jobs_universe_find(universe, rd->uid);
-
			if (found == NULL) {
-
				lp = pkg_jobs_universe_get_local(universe, rd->uid, 0);
-
				/* XXX */
-
				assert(lp != NULL);
-
				pkg_jobs_universe_process_item(universe, lp, &found);
-
			}
-

-
			if (found != NULL) {
-
				while (pkg_deps(found->pkg, &d) == EPKG_OK) {
-
					if (STREQ(d->uid, unit->pkg->uid)) {
-
						free(d->uid);
-
						d->uid = xstrdup(new_uid);
-
					}
-
				}
-
			}
-
		}
-
	}
-

-
	replacement = xcalloc(1, sizeof(*replacement));
-
	replacement->old_uid = xstrdup(unit->pkg->uid);
-
	replacement->new_uid = xstrdup(new_uid);
-
	LL_PREPEND(universe->uid_replaces, replacement);
-

-
	tmp = pkghash_delete(universe->items, unit->pkg->uid);
-
	if (tmp != NULL)
-
		tmp->inhash = false;
-
	free(unit->pkg->uid);
-
	unit->pkg->uid = xstrdup(new_uid);
-

-
	found = pkghash_get_value(universe->items, new_uid);
-
	if (found != NULL)
-
		DL_APPEND(found, unit);
-
	else
-
		pkghash_safe_add(universe->items, new_uid, unit, NULL);
-

-
}
-

static struct pkg_job_universe_item *
pkg_jobs_universe_select_max_ver(struct pkg_job_universe_item *chain)
{
modified libpkg/pkg_repo_create.c
@@ -422,8 +422,8 @@ pkg_repo_create_free(struct pkg_repo_create *prc)
	free(prc);
}

-
static void
-
group_load(struct pkg_repo_create *prc, int dfd, const char *name, ucl_object_t *schema)
+
static ucl_object_t*
+
ucl_load(int dfd, const char *name, ucl_object_t *schema)
{
	struct ucl_parser *p;
	ucl_object_t *obj = NULL;
@@ -432,34 +432,33 @@ group_load(struct pkg_repo_create *prc, int dfd, const char *name, ucl_object_t

	fd = openat(dfd, name, O_RDONLY);
	if (fd == -1) {
-
		pkg_emit_error("Unable to open group: %s", name);
-
		return;
+
		pkg_emit_error("Unable to open UCL file: %s", name);
+
		return (NULL);
	}

	p = ucl_parser_new(0);
	if (!ucl_parser_add_fd(p, fd)) {
-
		pkg_emit_error("Error parsing group '%s': %s'",
+
		pkg_emit_error("Error parsing UCL file '%s': %s'",
		    name, ucl_parser_get_error(p));
		ucl_parser_free(p);
		close(fd);
-
		return;
+
		return (NULL);
	}
	close(fd);

	obj = ucl_parser_get_object(p);
	ucl_parser_free(p);
	if (obj == NULL)
-
		return;
+
		return (NULL);

	if (!ucl_object_validate(schema, obj, &err)) {
-
		pkg_emit_error("group definition %s cannot be validated: %s",
+
		pkg_emit_error("UCL definition %s cannot be validated: %s",
		    name, err.msg);
		ucl_object_unref(obj);
-
		return;
+
		return (NULL);
	}
-
	if (prc->groups == NULL)
-
		prc->groups = ucl_object_typed_new(UCL_ARRAY);
-
	ucl_array_append(prc->groups, obj);
+

+
	return (obj);
}

static const char group_schema_str[] = ""
@@ -480,6 +479,17 @@ static const char group_schema_str[] = ""
	"  required = [ name, comment ];"
	"};";

+
static const char expired_schema_str[] = ""
+
	"{"
+
	"  type = object;"
+
	"  properties: {"
+
	"    name: { type = string };"
+
	"    reason: { type = string };"
+
	"    replaced_by: { type = string };"
+
	"  };"
+
	"  required = [ name ];"
+
	"};";
+

static ucl_object_t *
open_schema(const char* schema_str, size_t schema_str_len)
{
@@ -488,7 +498,7 @@ open_schema(const char* schema_str, size_t schema_str_len)
	parser = ucl_parser_new(UCL_PARSER_NO_FILEVARS);
	if (!ucl_parser_add_chunk(parser, schema_str,
	    schema_str_len - 1)) {
-
		pkg_emit_error("Cannot parse schema for group: %s",
+
		pkg_emit_error("Cannot parse schema string: %s",
		    ucl_parser_get_error(parser));
		    ucl_parser_free(parser);
		    return (NULL);
@@ -498,31 +508,29 @@ open_schema(const char* schema_str, size_t schema_str_len)
	return (schema);
}

-
void
-
pkg_repo_create_set_groups(struct pkg_repo_create *prc, const char *path)
+
static void
+
read_ucl_dir(struct pkg_repo_create *prc, const char *path, ucl_object_t *schema, void (*callback)(struct pkg_repo_create *prc, ucl_object_t* parsed_obj))
{
	int dfd = open(path, O_DIRECTORY);
	DIR *d;
	struct dirent *e;
	struct stat st;
-
	ucl_object_t *schema;

	if (dfd == -1) {
-
		pkg_emit_error("Unable to open the groups directory '%s'", path);
+
		pkg_emit_error("Unable to open directory '%s'", path);
		return;
	}

	d = fdopendir(dfd);
	if (d == NULL) {
-
		pkg_emit_error("Unable to open the groups directory '%s'", path);
+
		pkg_emit_error("Unable to open directory '%s'", path);
		close(dfd);
		return;
	}

-
	schema = open_schema(group_schema_str, sizeof(group_schema_str));
-

	while ((e = readdir(d)) != NULL) {
		const char *ext;
+
		ucl_object_t* parsed_obj;
		/* ignore all hidden files */
		if (e->d_name[0] == '.')
			continue;
@@ -530,7 +538,7 @@ pkg_repo_create_set_groups(struct pkg_repo_create *prc, const char *path)
		ext = strrchr(e->d_name, '.');
		if (ext == NULL)
			continue;
-
		if (!STREQ(ext, ".ucl"))
+
		if (strcmp(ext, ".ucl") != 0)
			continue;
		/* only regular files are considered */
		if (fstatat(dfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) {
@@ -539,10 +547,49 @@ pkg_repo_create_set_groups(struct pkg_repo_create *prc, const char *path)
		}
		if (!S_ISREG(st.st_mode))
			continue;
-
		group_load(prc, dfd, e->d_name, schema);
+
		parsed_obj = ucl_load(dfd, e->d_name, schema);
+
		if (parsed_obj)
+
			callback (prc, parsed_obj);
	}
cleanup:
	closedir(d);
+
}
+

+
static void
+
append_groups(struct pkg_repo_create *prc, ucl_object_t* groups_obj)
+
{
+
	if (prc->groups == NULL)
+
		prc->groups = ucl_object_typed_new(UCL_ARRAY);
+
	ucl_array_append(prc->groups, groups_obj);
+
}
+

+
void
+
pkg_repo_create_set_groups(struct pkg_repo_create *prc, const char *path)
+
{
+
	ucl_object_t *schema;
+
	schema = open_schema(group_schema_str, sizeof(group_schema_str));
+

+
	read_ucl_dir(prc, path, schema, append_groups);
+

+
	ucl_object_unref(schema);
+
}
+

+
static void
+
append_expired_packages(struct pkg_repo_create *prc, ucl_object_t* expired_packages_obj)
+
{
+
	if (prc->expired_packages == NULL)
+
		prc->expired_packages = ucl_object_typed_new(UCL_ARRAY);
+
	ucl_array_append(prc->expired_packages, expired_packages_obj);
+
}
+

+
void
+
pkg_repo_create_set_expired_packages(struct pkg_repo_create *prc, const char *path)
+
{
+
	ucl_object_t *schema;
+
	schema = open_schema(expired_schema_str, sizeof(expired_schema_str));
+

+
	read_ucl_dir(prc, path, schema, append_expired_packages);
+

	ucl_object_unref(schema);
}

@@ -815,6 +862,9 @@ pkg_repo_create(struct pkg_repo_create *prc, char *path)
	ucl_object_insert_key(obj,
	    prc->groups == NULL ? ucl_object_typed_new(UCL_ARRAY) : prc->groups,
	    "groups", 0, false);
+
	ucl_object_insert_key(obj,
+
	    prc->expired_packages == NULL ? ucl_object_typed_new(UCL_ARRAY) : prc->expired_packages,
+
	    "expired_packages", 0, false);
	f = ucl_object_emit_fd_funcs(te.dfd);
	te.ctx = ucl_object_emit_streamline_new(obj, UCL_EMIT_JSON_COMPACT, f);
	ucl_object_t *ar = ucl_object_typed_new(UCL_ARRAY);
modified libpkg/private/pkg.h
@@ -311,6 +311,7 @@ struct pkg_repo_create {
	const char *metafile;
	struct pkg_repo_meta *meta;
	ucl_object_t *groups;
+
	ucl_object_t *expired_packages;
	struct {
		char **argv;
		int argc;
modified libpkg/private/pkg_jobs.h
@@ -39,6 +39,11 @@
struct pkg_jobs;
struct job_pattern;

+
/*
+
 * Each item in pkg_jobs_universe->items is the head of a doubly linked list
+
 * and has inhash set to true. Other items with the same uid are added to
+
 * this doubly linked list and have inhash set to false.
+
 */
struct pkg_job_universe_item {
	struct pkg *pkg;
	int priority;
@@ -75,17 +80,10 @@ struct pkg_job_provide {
	struct pkg_job_provide *next, *prev;
};

-
struct pkg_job_replace {
-
	char *new_uid;
-
	char *old_uid;
-
	struct pkg_job_replace *next;
-
};
-

struct pkg_jobs_universe {
	pkghash *items;		/* package uid, pkg_job_universe_item */
	pkghash *seen;		/* package digest, pkg_job_universe_item */
	pkghash *provides;	/* shlibs, pkg_job_provide */
-
	struct pkg_job_replace *uid_replaces;
	struct pkg_jobs *j;
	size_t nitems;
};
@@ -104,7 +102,7 @@ struct pkg_jobs {
	struct pkgdb	*db;
	pkg_jobs_t	 type;
	pkg_flags	 flags;
-
	int		 solved;
+
	bool solved;
	int count;
	int total;
	int conflicts_registered;
@@ -185,13 +183,6 @@ int pkg_jobs_universe_add_pkg(struct pkg_jobs_universe *universe,
	struct pkg *pkg, bool force, struct pkg_job_universe_item **found);

/*
-
 * Change uid for universe item
-
 */
-
void pkg_jobs_universe_change_uid(struct pkg_jobs_universe *universe,
-
	struct pkg_job_universe_item *unit,
-
	const char *new_uid, bool update_rdeps);
-

-
/*
 * Find local package in db or universe
 */
struct pkg* pkg_jobs_universe_get_local(struct pkg_jobs_universe *universe,
@@ -209,11 +200,6 @@ int pkg_conflicts_append_chain(struct pkg_job_universe_item *it,
	struct pkg_jobs *j);

/*
-
 * Perform integrity check for the jobs specified
-
 */
-
int pkg_conflicts_integrity_check(struct pkg_jobs *j);
-

-
/*
 * Check whether `rp` is an upgrade for `lp`
 */
bool pkg_jobs_need_upgrade(struct pkg *rp, struct pkg *lp);
modified libpkg/repo/binary/update.c
@@ -2,6 +2,8 @@
 * Copyright (c) 2014, Vsevolod Stakhov
 * Copyright (c) 2012-2024 Baptiste Daroussin <bapt@FreeBSD.org>
 * Copyright (c) 2012 Julien Laffaye <jlaffaye@FreeBSD.org>
+
 * Copyright (c) 2024 Serenity Cyber Security, LLC
+
 *                    Author: Gleb Popov <arrowd@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
@@ -498,18 +500,18 @@ rollback_repo(void *data)
}

static void
-
save_groups(struct pkg_repo *repo, ucl_object_t *groups)
+
save_ucl(struct pkg_repo *repo, ucl_object_t *obj, const char* dst_name)
{
-
	if (groups == NULL)
+
	if (obj == NULL)
		return;
	if (repo->dfd == -1 && pkg_repo_open(repo) == EPKG_FATAL)
		return;
-
	int fd = openat(repo->dfd, "groups.ucl", O_CREAT|O_TRUNC|O_RDWR, 0644);
+
	int fd = openat(repo->dfd, dst_name, O_CREAT|O_TRUNC|O_RDWR, 0644);
	if (fd == -1) {
-
		pkg_emit_errno("openat", "repo groups");
+
		pkg_emit_errno("openat", "repo save_ucl");
		return;
	}
-
	ucl_object_emit_fd(groups, UCL_EMIT_JSON_COMPACT, fd);
+
	ucl_object_emit_fd(obj, UCL_EMIT_JSON_COMPACT, fd);
	close(fd);
}

@@ -636,8 +638,11 @@ pkg_repo_binary_update_proceed(const char *name, struct pkg_repo *repo,
				break;
		}
		pkg_emit_progress_tick(cnt, nbel);
-
		save_groups(repo,
-
		    ucl_object_ref(ucl_object_find_key(data, "groups")));
+
		save_ucl(repo,
+
		    ucl_object_ref(ucl_object_find_key(data, "groups")), "groups.ucl");
+
		save_ucl(repo,
+
		    ucl_object_ref(ucl_object_find_key(data, "expired_packages")),
+
		    "expired_packages.ucl");
	}

	if (rc == EPKG_OK)
modified libpkg/scripts.c
@@ -256,7 +256,9 @@ cleanup:
		killemall.rk_sig = SIGKILL;
		killemall.rk_flags = 0;
		if (procctl(P_PID, mypid, PROC_REAP_KILL, &killemall) != 0) {
-
			pkg_errno("%s", "Fail to kill all processes");
+
			if (errno != ESRCH || killemall.rk_killed != 0 ) {
+
				pkg_errno("%s", "Fail to kill all processes");
+
			}
		}
	}
	procctl(P_PID, mypid, PROC_REAP_RELEASE, NULL);
modified src/event.c
@@ -183,16 +183,18 @@ job_status_begin(xstring *msg)
void
progressbar_start(const char *pmsg)
{
-
	free(progress_message);
-
	progress_message = NULL;
+
	if (progress_message) {
+
		free(progress_message);
+
		progress_message = NULL;
+
	}

	if (quiet)
		return;
	if (pmsg != NULL)
-
		progress_message = strdup(pmsg);
+
		progress_message = xstrdup(pmsg);
	else {
		fflush(msg_buf->fp);
-
		progress_message = strdup(msg_buf->buf);
+
		progress_message = xstrdup(msg_buf->buf);
	}
	last_progress_percent = -1;
	last_tick = 0;
modified src/updating.c
@@ -194,18 +194,21 @@ matcher(const char *affects, const char *origin, bool ignorecase)
			}
		}
		if (found == 0) {
-
			ent = malloc(sizeof(struct regex_cache));
-
			if (ent == NULL)
-
				goto err;
+
			if ((ent = malloc(sizeof(struct regex_cache))) == NULL) {
+
				ret = 0;
+
				goto out;
+
			}
			if ((ent->pattern = strdup(words[i])) == NULL) {
				free(ent);
-
				goto err;
+
				ret = 0;
+
				goto out;
			}
			re = convert_re(words[i]);
			if (re == NULL) {
				free(ent->pattern);
				free(ent);
-
				goto err;
+
				ret = 0;
+
				goto out;
			}
			regcomp(&ent->reg, re, (ignorecase) ? REG_ICASE|REG_EXTENDED : REG_EXTENDED);
			free(re);
@@ -217,13 +220,10 @@ matcher(const char *affects, const char *origin, bool ignorecase)
		}
	}

+
out:
	free(words);
	free(buf);
-
	return ret;
-
 err:
-
	free(words);
-
	free(buf);
-
	return 0;
+
	return (ret);
}

int