/*-
 * Copyright (c) 2011-2026 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>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#ifdef HAVE_CONFIG_H
#include "pkg_config.h"
#endif

#include <archive.h>
#include <archive_entry.h>
#include <assert.h>
#include <libgen.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <pwd.h>
#include <grp.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <xstring.h>

#include <dirent.h>

#include "pkg.h"
#include "private/event.h"
#include "private/utils.h"
#include "private/pkg.h"
#include "private/pkgdb.h"
#include "private/add.h"

#if defined(UF_NOUNLINK)
#define NOCHANGESFLAGS	(UF_IMMUTABLE | UF_APPEND | UF_NOUNLINK | SF_IMMUTABLE | SF_APPEND | SF_NOUNLINK)
#else
#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 const *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);
			unlink(output_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);
			unlink(output_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);
		}
		unlink(output_path);
		return MERGE_FAILED;
	}

	char command[MAXPATHLEN];
	int i = 0;
	for (; *merge_tool != '\0' && i < (int)sizeof(command) - 1; 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': {
			int nlen = strlcpy(&command[i], RELATIVE_PATH(rcf->path),
			    sizeof(command) - i);
			i += (nlen < (int)sizeof(command) - i - 1 ? nlen :
			    (int)sizeof(command) - i - 1) - 1;
			continue;
		}
		case 'o': {
			int nlen = strlcpy(&command[i], output_path,
			    sizeof(command) - i);
			i += (nlen < (int)sizeof(command) - i - 1 ? nlen :
			    (int)sizeof(command) - i - 1) - 1;
			continue;
		}
		default:
			pkg_emit_error("Unknown format string in the MERGETOOL command");
			merge_tool--;
			continue;
		}
		{
			int nlen = strlcpy(&command[i],
			    tmp_files[tmp_files_index].path,
			    sizeof(command) - i);
			i += (nlen < (int)sizeof(command) - i - 1 ? nlen :
			    (int)sizeof(command) - i - 1) - 1;
		}
	}
	command[i] = '\0';

	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 < NELEM(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, 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;
	char *localsum;

	if (local == NULL) {
		pkg_debug(3, "No local package");
		if (fstatat(rootfd, RELATIVE_PATH(rcf->path), &st, 0) == 0) {
			rcf->status = MERGE_NOT_LOCAL;
		}
		return;
	}

	if (!pkg_is_config_file(local, rcf->path, &lf, &lcf)) {
		pkg_debug(3, "Corresponding file in local package not a config file");
		rcf->status = MERGE_FAILED;
		return;
	}

	if (lcf->content == NULL) {
		pkg_debug(3, "Empty configuration content for local package");
		return;
	}

	pkg_debug(1, "Config file found %s", rcf->path);
	if (file_to_bufferat(rootfd, RELATIVE_PATH(rcf->path), &localconf, &sz) != EPKG_OK)
		return;

	pkg_debug(2, "size: %jd vs %jd", (intmax_t)sz, (intmax_t)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");
		const char *expected = lf->sum;
		pkg_checksum_type_t sum_type;
		sum_type = pkg_checksum_file_get_type(
		    expected, expected != NULL ? strlen(expected) : 0);
		if (sum_type == PKG_HASH_TYPE_UNKNOWN) {
			sum_type = PKG_HASH_TYPE_SHA256_HEX;
		} else {
			expected = strchr(expected, '$');
			if (expected != NULL)
				expected++;
		}
		localsum = pkg_checksum_data(localconf, sz, sum_type);
		if (localsum != NULL && expected != NULL &&
		    STREQ(localsum, expected)) {
			pkg_debug(2, "Checksum are the same %jd", (intmax_t)strlen(localconf));
			free(localsum);
			goto ret;
		}
		if (lf->sum == NULL)
			pkg_emit_error("3way merge: no checksum for the original local file");
		free(localsum);
		pkg_debug(2, "Checksum are different %jd", (intmax_t)strlen(localconf));
	}
	rcf->status = MERGE_FAILED;
	if (!merge) {
		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);
		pkg_emit_error("Unable to merge configuration file: %s", rcf->path);
	} else {
		char *conf = xstring_get(newconf);
		rcf->newcontent = conf;
		rcf->status = MERGE_SUCCESS;
	}
ret:
	free(localconf);
}

static int
set_chflags(int fd, const char *path, u_long fflags)
{
#ifdef HAVE_CHFLAGSAT
	if (getenv("INSTALL_AS_USER"))
		return (EPKG_OK);
	if (fflags == 0)
		return (EPKG_OK);
	if (chflagsat(fd, RELATIVE_PATH(path), fflags, AT_SYMLINK_NOFOLLOW) == -1) {
		pkg_fatal_errno("Failed to chflags %s", path);
	}
#endif
	return (EPKG_OK);
}
int
set_attrsat(int fd, const char *path, mode_t perm, uid_t uid, gid_t gid,
    const struct timespec *ats, const struct timespec *mts)
{
	struct stat st;
	struct timespec times[2];

	times[0] = *ats;
	times[1] = *mts;
	if (utimensat(fd, RELATIVE_PATH(path), times,
	    AT_SYMLINK_NOFOLLOW) == -1 && errno != EOPNOTSUPP){
		pkg_fatal_errno("Failed to set time on %s", path);
	}

	if (getenv("INSTALL_AS_USER") == NULL) {
		if (fchownat(fd, RELATIVE_PATH(path), uid, gid,
				AT_SYMLINK_NOFOLLOW) == -1) {
			if (errno == ENOTSUP) {
				if (fchownat(fd, RELATIVE_PATH(path), uid, gid, 0) == -1) {
					pkg_fatal_errno("Failed to chown(fallback) %s", path);
				}
			}
			else {
				pkg_fatal_errno("Failed to chown %s", path);
			}
		}
	}

	/* zfs drops the setuid on fchownat */
	if (fchmodat(fd, RELATIVE_PATH(path), perm, AT_SYMLINK_NOFOLLOW) == -1) {
		if (errno == ENOTSUP) {
			/*
			 * Executing fchmodat on a symbolic link results in
			 * ENOENT (file not found) on platforms that do not
			 * support AT_SYMLINK_NOFOLLOW. The file mode of
			 * symlinks cannot be modified via file descriptor
			 * reference on these systems. The lchmod function is
			 * also not an option because it is not a posix
			 * standard, nor is implemented everywhere. Since
			 * symlink permissions have never been evaluated and
			 * thus cosmetic, just skip them on these systems.
			 */
			if (fstatat(fd, RELATIVE_PATH(path), &st, AT_SYMLINK_NOFOLLOW) == -1) {
				pkg_fatal_errno("Failed to get file status %s", path);
			}
			if (!S_ISLNK(st.st_mode)) {
				if (fchmodat(fd, RELATIVE_PATH(path), perm, 0) == -1) {
					pkg_fatal_errno("Failed to chmod(fallback) %s", path);
				}
			}
		}
		else {
			pkg_fatal_errno("Failed to chmod %s", path);
		}
	}

	return (EPKG_OK);
}

/*
 * Set file attributes using fd-based syscalls (fchown, fchmod, futimens)
 * rather than the *at variants (fchownat, fchmodat, utimensat).
 * This avoids relying on a directory fd which ZFS can sporadically report
 * as invalid under certain conditions (see bug 281749).
 */
static int
set_attr_tofd(int fd, const char *path, mode_t perm, uid_t uid, gid_t gid,
    const struct timespec *ats, const struct timespec *mts, u_long fflags)
{
	struct timespec times[2];

	times[0] = *ats;
	times[1] = *mts;
	if (futimens(fd, times) == -1 && errno != EOPNOTSUPP) {
		pkg_fatal_errno("Failed to set time on %s", path);
	}

	if (getenv("INSTALL_AS_USER") == NULL) {
		if (fchown(fd, uid, gid) == -1) {
			pkg_fatal_errno("Failed to chown %s", path);
		}
	}

	/* zfs drops the setuid on fchownat */
	if (fchmod(fd, perm) == -1) {
		pkg_fatal_errno("Failed to chmod %s", path);
	}

#ifdef HAVE_CHFLAGSAT
	if (getenv("INSTALL_AS_USER") == NULL && fflags != 0) {
		if (fchflags(fd, fflags) == -1) {
			pkg_fatal_errno("Failed to chflags %s", path);
		}
	}
#endif

	return (EPKG_OK);
}

static void
fill_timespec_buf(const struct stat *aest, struct timespec tspec[2])
{
#ifdef HAVE_STRUCT_STAT_ST_MTIM
	tspec[0].tv_sec = aest->st_atim.tv_sec;
	tspec[0].tv_nsec = 0;
	tspec[1].tv_sec = aest->st_mtim.tv_sec;
	tspec[1].tv_nsec = 0;
#else
# if defined(_DARWIN_C_SOURCE) || defined(__APPLE__)
	tspec[0].tv_sec = aest->st_atimespec.tv_sec;
	tspec[0].tv_nsec = 0;
	tspec[1].tv_sec = aest->st_mtimespec.tv_sec;
	tspec[1].tv_nsec = 0;
# else
	/* Portable unix version */
	tspec[0].tv_sec = aest->st_atime;
	tspec[0].tv_nsec = 0;
	tspec[1].tv_sec = aest->st_mtime;
	tspec[1].tv_nsec = 0;
# endif
#endif
}

static void
reopen_tempdir(int rootfd, struct tempdir *t)
{
	if (t->fd != -1)
		return;
	t->fd = openat(rootfd, RELATIVE_PATH(t->temp), O_DIRECTORY|O_CLOEXEC);
}

static struct tempdir *
get_tempdir(struct pkg_add_context *context, const char *path, tempdirs_t *tempdirs)
{
	struct tempdir *tmpdir = NULL;

	vec_foreach(*tempdirs, i) {
		tmpdir = tempdirs->d[i];
		if (strncmp(tmpdir->name, path, tmpdir->len) == 0 && path[tmpdir->len] == '/') {
			reopen_tempdir(context->rootfd, tmpdir);
			return (tmpdir);
		}
	}

	tmpdir = open_tempdir(context, path);
	if (tmpdir != NULL)
		vec_push(tempdirs, tmpdir);

	return (tmpdir);
}

static void
close_tempdir(struct tempdir *t)
{
	if (t == NULL)
		return;
	if (t->fd != -1)
		close(t->fd);
	t->fd = -1;
}

static int
create_dir(struct pkg_add_context *context, struct pkg_dir *d,
    tempdirs_t *tempdirs)
{
	struct stat st;
	struct tempdir *tmpdir;
	const char *path;
	int error, fd;

	tmpdir = get_tempdir(context, d->path, tempdirs);
	if (tmpdir == NULL) {
		fd = context->rootfd;
		path = d->path;
	} else {
		fd = tmpdir->fd;
		path = d->path + tmpdir->len;
	}

	if (mkdirat(fd, RELATIVE_PATH(path), 0755) == -1) {
		if (!mkdirat_p(fd, RELATIVE_PATH(path))) {
			close_tempdir(tmpdir);
			return (EPKG_FATAL);
		}
	}

	if (fstatat(fd, RELATIVE_PATH(path), &st, 0) == -1) {
		if (errno != ENOENT) {
			close_tempdir(tmpdir);
			pkg_fatal_errno("Failed to stat directory %s", d->path);
		}
		/* path is a dangling symlink. */
		error = unlinkat(fd, RELATIVE_PATH(path), 0);
		if (error != 0) {
			close_tempdir(tmpdir);
			pkg_fatal_errno("Failed to unlink dangling symlink %s",
			    d->path);
		}
		if (mkdirat(fd, RELATIVE_PATH(path), 0755) == -1) {
			if (tmpdir != NULL) {
				close_tempdir(tmpdir);
				pkg_fatal_errno(
				    "Failed to create directory %s/%s",
				    tmpdir->temp, path);
			} else {
				pkg_fatal_errno("Failed to create directory %s",
				    path);
			}
		}
		if (fstatat(fd, RELATIVE_PATH(path), &st, 0) == -1) {
			close_tempdir(tmpdir);
			pkg_fatal_errno("Failed to stat directory %s", d->path);
		}
	}

	if (st.st_uid == d->uid && st.st_gid == d->gid &&
	    (st.st_mode & ~S_IFMT) == (d->perm & ~S_IFMT)) {
		d->noattrs = true;
	}

	close_tempdir(tmpdir);
	return (EPKG_OK);
}

/* In case of directories create the dir and extract the creds */
static int
do_extract_dir(struct pkg_add_context* context, struct archive *a __unused, struct archive_entry *ae,
    const char *path, struct pkg *local __unused, tempdirs_t *tempdirs)
{
	struct pkg_dir *d;
	const struct stat *aest;
	unsigned long clear;

	d = pkg_get_dir(context->pkg, path);
	if (d == NULL) {
		pkg_emit_error("Directory %s not specified in the manifest, skipping",
				path);
		return (EPKG_OK);
	}
	aest = archive_entry_stat(ae);
	d->perm = aest->st_mode;
	d->uid = get_uid_from_uname(archive_entry_uname(ae));
	d->gid = get_gid_from_gname(archive_entry_gname(ae));
	free(d->uname);
	d->uname = xstrdup(archive_entry_uname(ae));
	free(d->gname);
	d->gname = xstrdup(archive_entry_gname(ae));
	fill_timespec_buf(aest, d->time);
	archive_entry_fflags(ae, &d->fflags, &clear);

	if (create_dir(context, d, tempdirs) == EPKG_FATAL) {
		return (EPKG_FATAL);
	}

	metalog_add(PKG_METALOG_DIR, RELATIVE_PATH(path),
	    archive_entry_uname(ae), archive_entry_gname(ae),
	    aest->st_mode & ~S_IFDIR, d->fflags, NULL);

	return (EPKG_OK);
}


static bool
try_mkdir(int fd, const char *path)
{
	char *p = get_dirname(xstrdup(path));

	if (!mkdirat_p(fd, RELATIVE_PATH(p))) {
		free(p);
		return (false);
	}
	free(p);
	return (true);
}

static int
create_symlinks(struct pkg_add_context *context, struct pkg_file *f, const char *target, tempdirs_t *tempdirs)
{
	struct tempdir *tmpdir = NULL;
	int fd;
	const char *path;
	bool tried_mkdir = false;

	tmpdir = get_tempdir(context, f->path, tempdirs);
	if (tmpdir == NULL && errno == 0) {
		char temppath[MAXPATHLEN] = { 0 };
		hidden_tempfile(temppath, sizeof(temppath), f->path);
		f->temppath = xstrdup(temppath);
	}
	if (tmpdir == NULL) {
		if (f->temppath == NULL) {
			pkg_emit_error("Failed to create symlink %s: "
			    "no temporary path", f->path);
			return (EPKG_FATAL);
		}
		fd = context->rootfd;
		path = f->temppath;
	} else {
		fd = tmpdir->fd;
		path = f->path + tmpdir->len;
	}
retry:
	if (symlinkat(target, fd, RELATIVE_PATH(path)) == -1) {
		if (!tried_mkdir) {
			if (!try_mkdir(fd, path)) {
				close_tempdir(tmpdir);
				return (EPKG_FATAL);
			}
			tried_mkdir = true;
			goto retry;
		}

		pkg_fatal_errno("Failed to create symlink: %s", path);
	}

	if (set_attrsat(fd, path, f->perm, f->uid, f->gid,
	    &f->time[0], &f->time[1]) != EPKG_OK) {
		close_tempdir(tmpdir);
		return (EPKG_FATAL);
	}
	if (tmpdir != NULL)
		set_chflags(fd, path, f->fflags);
	close_tempdir(tmpdir);

	return (EPKG_OK);
}

/* In case of a symlink create it directly with a random name */
static int
do_extract_symlink(struct pkg_add_context *context, struct archive *a __unused,
    struct archive_entry *ae, const char *path, struct pkg *local __unused,
    tempdirs_t *tempdirs)
{
	struct pkg_file *f;
	const struct stat *aest;
	unsigned long clear;

	f = pkg_get_file(context->pkg, path);
	if (f == NULL) {
		pkg_emit_error("Symlink %s not specified in the manifest", path);
		return (EPKG_FATAL);
	}

	aest = archive_entry_stat(ae);
	f->uid = get_uid_from_uname(archive_entry_uname(ae));
	f->gid = get_gid_from_gname(archive_entry_gname(ae));
	free(f->uname);
	f->uname = xstrdup(archive_entry_uname(ae));
	free(f->gname);
	f->gname = xstrdup(archive_entry_gname(ae));
	f->perm = aest->st_mode;
	if (f->time[1].tv_sec == 0)
		fill_timespec_buf(aest, f->time);
	archive_entry_fflags(ae, &f->fflags, &clear);

	if (create_symlinks(context, f, archive_entry_symlink(ae), tempdirs) == EPKG_FATAL)
		return (EPKG_FATAL);

	metalog_add(PKG_METALOG_LINK, RELATIVE_PATH(path),
	    archive_entry_uname(ae), archive_entry_gname(ae),
	    aest->st_mode & ~S_IFLNK, f->fflags, archive_entry_symlink(ae));

	return (EPKG_OK);
}

static int
create_hardlink(struct pkg_add_context *context, struct pkg_file *f, const char *path, tempdirs_t *tempdirs)
{
	bool tried_mkdir = false;
	struct pkg_file *fh;
	int fd, fdh;
	const char *pathfrom, *pathto;
	struct tempdir *tmpdir = NULL;
	struct tempdir *tmphdir = NULL;

	tmpdir = get_tempdir(context, f->path, tempdirs);
	if (tmpdir == NULL && errno == 0) {
		char temppath[MAXPATHLEN] = { 0 };
		hidden_tempfile(temppath, sizeof(temppath), f->path);
		f->temppath = xstrdup(temppath);
	}
	if (tmpdir != NULL) {
		fd = tmpdir->fd;
	} else {
		fd = context->rootfd;
	}
	fh = pkg_get_file(context->pkg, path);
	if (fh == NULL) {
		close_tempdir(tmpdir);
		pkg_emit_error("Can't find the file %s is supposed to be"
		    " hardlinked to %s", f->path, path);
		return (EPKG_FATAL);
	}
	if (fh->temppath == NULL) {
		vec_foreach(*tempdirs, i) {
			if (strncmp(tempdirs->d[i]->name, fh->path, tempdirs->d[i]->len) == 0 &&
			    fh->path[tempdirs->d[i]->len] == '/' ) {
				tmphdir = tempdirs->d[i];
				reopen_tempdir(context->rootfd, tmphdir);
				break;
			}
		}
	}
	if (tmpdir == NULL) {
		if (f->temppath == NULL) {
			close_tempdir(tmpdir);
			pkg_emit_error("Failed to create hardlink %s: "
			    "no temporary path", f->path);
			return (EPKG_FATAL);
		}
		pathto = f->temppath;
		fd = context->rootfd;
	} else {
		pathto = f->path + tmpdir->len;
		fd = tmpdir->fd;
	}

	if (tmphdir == NULL) {
		if (fh->temppath == NULL) {
			close_tempdir(tmpdir);
			pkg_emit_error("Failed to create hardlink %s -> %s: "
			    "no temporary path for source", f->path, path);
			return (EPKG_FATAL);
		}
		pathfrom = fh->temppath;
		fdh = context->rootfd;
	} else {
		pathfrom = fh->path + tmphdir->len;
		fdh = tmphdir->fd;
	}

retry:
	if (linkat(fdh, RELATIVE_PATH(pathfrom),
	    fd, RELATIVE_PATH(pathto), 0) == -1) {
		if (!tried_mkdir) {
			if (!try_mkdir(fd, pathto)) {
				close_tempdir(tmpdir);
				close_tempdir(tmphdir);
				return (EPKG_FATAL);
			}
			tried_mkdir = true;
			goto retry;
		}

		close_tempdir(tmpdir);
		close_tempdir(tmphdir);
		pkg_fatal_errno("Failed to create hardlink: %s <-> %s", pathfrom, pathto);
	}
	close_tempdir(tmpdir);
	close_tempdir(tmphdir);

	return (EPKG_OK);
}

static int
do_extract_hardlink(struct pkg_add_context *context, struct archive *a __unused, struct archive_entry *ae,
    const char *path, struct pkg *local __unused, tempdirs_t *tempdirs)
{
	struct pkg_file *f;
	const struct stat *aest;
	const char *lp;

	f = pkg_get_file(context->pkg, path);
	if (f == NULL) {
		pkg_emit_error("Hardlink %s not specified in the manifest", path);
		return (EPKG_FATAL);
	}
	lp = archive_entry_hardlink(ae);
	aest = archive_entry_stat(ae);

	if (create_hardlink(context, f, lp, tempdirs) == EPKG_FATAL)
		return (EPKG_FATAL);

	metalog_add(PKG_METALOG_FILE, RELATIVE_PATH(path),
	    archive_entry_uname(ae), archive_entry_gname(ae),
	    aest->st_mode & ~S_IFREG, 0, NULL);

	return (EPKG_OK);
}

static int
open_tempfile(int rootfd, const char *path, int perm)
{
	int fd;
	bool tried_mkdir = false;

retry:
	fd = openat(rootfd, RELATIVE_PATH(path), O_CREAT|O_WRONLY|O_EXCL, perm);
	if (fd == -1) {
		if (!tried_mkdir) {
			if (!try_mkdir(rootfd, path))
				return (-2);
			tried_mkdir = true;
			goto retry;
		}
		return (-1);
	}
	return (fd);
}

static int
create_regfile(struct pkg_add_context *context, struct pkg_file *f, struct archive *a,
    struct archive_entry *ae, int fromfd, struct pkg *local, tempdirs_t *tempdirs)
{
	int fd = -1;
	size_t len;
	char buf[32768];
	struct tempdir *tmpdir = NULL;

	tmpdir = get_tempdir(context, f->path, tempdirs);
	if (tmpdir == NULL && errno == 0) {
		char temppath[MAXPATHLEN] = { 0 };
		hidden_tempfile(temppath, sizeof(temppath), f->path);
		f->temppath = xstrdup(temppath);
	}

	if (tmpdir != NULL) {
		fd = open_tempfile(tmpdir->fd, f->path + tmpdir->len, f->perm);
	} else if (f->temppath != NULL) {
		fd = open_tempfile(context->rootfd, f->temppath, f->perm);
	} else {
		pkg_emit_error("Failed to create temporary file for %s", f->path);
		return (EPKG_FATAL);
	}
	if (fd == -2) {
		close_tempdir(tmpdir);
		return (EPKG_FATAL);
	}

	if (fd == -1) {
		if (tmpdir != NULL) {
			close_tempdir(tmpdir);
			pkg_fatal_errno("Failed to create temporary file '%s/%s' for %s", tmpdir->name, f->path + tmpdir->len, f->path);
		}
		pkg_fatal_errno("Failed to create temporary file for %s", f->path);
	}

	if (fromfd == -1) {
		/* check if this is a config file */
		f->config = pkg_get_config_file(context->pkg, f->path);
		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);
			if (archive_entry_size(ae) < 0) {
				pkg_emit_error("Invalid config file size for %s", f->path);
				return (EPKG_FATAL);
			}
			len = archive_entry_size(ae);
			f->config->content = xmalloc(len + 1);
			archive_read_data(a, f->config->content, len);
			f->config->content[len] = '\0';
			cfdata = f->config->content;
			attempt_to_merge(context->rootfd, f->config, local, merge, merge_tool);
			if (f->config->status == MERGE_SUCCESS)
				cfdata = f->config->newcontent;
			dprintf(fd, "%s", cfdata);
			if (f->config->newcontent != NULL)
				free(f->config->newcontent);
		} else {
			if (ftruncate(fd, archive_entry_size(ae)) == -1) {
				close(fd);
				close_tempdir(tmpdir);
				pkg_fatal_errno("Failed to truncate file: %s", f->path);
			}
		}

		if (!f->config && archive_read_data_into_fd(a, fd) != ARCHIVE_OK) {
			close(fd);
			close_tempdir(tmpdir);
			pkg_emit_error("Failed to extract %s from package: %s",
			    f->path, archive_error_string(a));
			return (EPKG_FATAL);
		}
	} else {
		while ((len = read(fromfd, buf, sizeof(buf))) > 0)
			if (write(fd, buf, len) == -1) {
				pkg_errno("Failed to write file: %s", f->temppath);
			}
	}
	if (fd != -1) {
		if (set_attr_tofd(fd, f->path, f->perm, f->uid, f->gid,
		    &f->time[0], &f->time[1],
		    tmpdir != NULL ? f->fflags : 0) != EPKG_OK) {
			close(fd);
			close_tempdir(tmpdir);
			return (EPKG_FATAL);
		}
		close(fd);
	}

	close_tempdir(tmpdir);
	return (EPKG_OK);
}

static int
do_extract_regfile(struct pkg_add_context *context, struct archive *a, struct archive_entry *ae,
    const char *path, struct pkg *local, tempdirs_t *tempdirs)
{
	struct pkg_file *f;
	const struct stat *aest;
	unsigned long clear;

	f = pkg_get_file(context->pkg, path);
	if (f == NULL) {
		pkg_emit_error("File %s not specified in the manifest", path);
		return (EPKG_FATAL);
	}

	aest = archive_entry_stat(ae);
	f->perm = aest->st_mode;
	f->uid = get_uid_from_uname(archive_entry_uname(ae));
	f->gid = get_gid_from_gname(archive_entry_gname(ae));
	free(f->uname);
	f->uname = xstrdup(archive_entry_uname(ae));
	free(f->gname);
	f->gname = xstrdup(archive_entry_gname(ae));
	/* Only use the archive entry times if the manifest did not
	 * already provide an mtime.  The manifest is the source of truth;
	 * the archive entry may carry a different timestamp (e.g. when
	 * staged files were copied without preserving mtime). */
	if (f->time[1].tv_sec == 0)
		fill_timespec_buf(aest, f->time);
	archive_entry_fflags(ae, &f->fflags, &clear);

	if (create_regfile(context, f, a, ae, -1, local, tempdirs) == EPKG_FATAL)
		return (EPKG_FATAL);

	metalog_add(PKG_METALOG_FILE, RELATIVE_PATH(path),
	    archive_entry_uname(ae), archive_entry_gname(ae),
	    aest->st_mode & ~S_IFREG, f->fflags, NULL);

	return (EPKG_OK);
}

static int
do_extract(struct archive *a, struct archive_entry *ae,
    int nfiles, struct pkg *local, tempdirs_t *tempdirs,
    struct pkg_add_context *context)
{
	int	retcode = EPKG_OK;
	int	ret = 0, cur_file = 0;
	char	path[MAXPATHLEN];
	int (*extract_cb)(struct pkg_add_context *context, struct archive *a,
	    struct archive_entry *ae, const char *path, struct pkg *local,
	    tempdirs_t *tempdirs);

#ifndef HAVE_ARC4RANDOM
	srand(time(NULL));
#endif

	if (nfiles == 0)
		return (EPKG_OK);

	pkg_emit_extract_begin(context->pkg);
	pkg_emit_progress_start(NULL);

	do {
		pkg_absolutepath(archive_entry_pathname(ae), path, sizeof(path), true);
		if (match_ucl_lists(path,
		    pkg_config_get("FILES_IGNORE_GLOB"),
		    pkg_config_get("FILES_IGNORE_REGEX")))
			continue;
		switch (archive_entry_filetype(ae)) {
		case AE_IFDIR:
			extract_cb = do_extract_dir;
			break;
		case AE_IFLNK:
			extract_cb = do_extract_symlink;
			break;
		case 0: /* HARDLINKS */
			extract_cb = do_extract_hardlink;
			break;
		case AE_IFREG:
			extract_cb = do_extract_regfile;
			break;
		case AE_IFMT:
		case AE_IFSOCK:
		case AE_IFCHR:
		case AE_IFIFO:
		case AE_IFBLK:
		default:
			pkg_emit_error("Archive contains an unsupported filetype (%d): %s", archive_entry_filetype(ae), path);
			retcode = EPKG_FATAL;
			goto cleanup;
		}

		if (extract_cb(context, a, ae, path, local, tempdirs) != EPKG_OK) {
			retcode = EPKG_FATAL;
			goto cleanup;
		}
		if (archive_entry_filetype(ae) != AE_IFDIR) {
			pkg_emit_progress_tick(cur_file++, nfiles);
		}
	} while ((ret = archive_read_next_header(a, &ae)) == ARCHIVE_OK);
	pkg_emit_progress_tick(cur_file++, nfiles);

	if (ret != ARCHIVE_EOF) {
		pkg_emit_error("archive_read_next_header(): %s",
		    archive_error_string(a));
		retcode = EPKG_FATAL;
	}

cleanup:
	pkg_emit_progress_tick(nfiles, nfiles);
	pkg_emit_extract_finished(context->pkg);

	return (retcode);
}

static void
backup_file_if_needed(struct pkg *p, struct pkg_file *f)
{
	char path[MAXPATHLEN];
	struct stat st;
	char *sum;
	pkg_checksum_type_t t;

	if (fstatat(p->rootfd, RELATIVE_PATH(f->path), &st,
	    AT_SYMLINK_NOFOLLOW) == -1)
		return;

	if (S_ISLNK(st.st_mode))
		return;

	if (S_ISREG(st.st_mode)) {
		t = pkg_checksum_file_get_type(f->sum, -1);
		sum = pkg_checksum_generate_fileat(p->rootfd,
		    RELATIVE_PATH(f->path), t);
		if (sum == NULL)
			return;

		if (STREQ(sum, f->sum)) {
			free(sum);
			return;
		}
		free(sum);
	}

	snprintf(path, sizeof(path), "%s.pkgsave", f->path);
	renameat(p->rootfd, RELATIVE_PATH(f->path),
	    p->rootfd, RELATIVE_PATH(path));
}

static int
pkg_extract_finalize(struct pkg *pkg, tempdirs_t *tempdirs)
{
	struct stat st;
	struct pkg_file *f = NULL;
	struct pkg_dir *d = NULL;
	char path[MAXPATHLEN + 8];
	const char *fto;
#ifdef HAVE_CHFLAGSAT
	bool install_as_user;

	install_as_user = (getenv("INSTALL_AS_USER") != NULL);
#endif


	if (tempdirs != NULL) {
		vec_foreach(*tempdirs, i) {
			struct tempdir *t = tempdirs->d[i];
			if (fstatat(pkg->rootfd, RELATIVE_PATH(t->name),
			    &st, AT_SYMLINK_NOFOLLOW) == 0)
				unlinkat(pkg->rootfd,
				    RELATIVE_PATH(t->name), 0);
			if (renameat(pkg->rootfd, RELATIVE_PATH(t->temp),
			    pkg->rootfd, RELATIVE_PATH(t->name)) != 0) {
				pkg_fatal_errno("Failed to rename %s -> %s",
				    t->temp, t->name);
			}
			free(t);
		}
	}
	while (pkg_files(pkg, &f) == EPKG_OK) {

		if (match_ucl_lists(f->path,
		    pkg_config_get("FILES_IGNORE_GLOB"),
		    pkg_config_get("FILES_IGNORE_REGEX")))
			continue;
		append_touched_file(f->path);
		if (f->temppath == NULL)
			continue;
		fto = f->path;
		if (f->config && f->config->status == MERGE_FAILED &&
		    f->previous != PKG_FILE_NONE) {
			snprintf(path, sizeof(path), "%s.pkgnew", f->path);
			fto = path;
		}

		if (f->config && f->config->status == MERGE_NOT_LOCAL) {
			backup_file_if_needed(pkg, f);
		}

		/*
		 * enforce an unlink of the file to workaround a bug that
		 * results in renameat returning 0 of the from file is hardlink
		 * on the to file, but the to file is not removed
		 */
		if (f->previous != PKG_FILE_NONE &&
		    fstatat(pkg->rootfd, RELATIVE_PATH(fto), &st,
		    AT_SYMLINK_NOFOLLOW) != -1) {
#ifdef HAVE_CHFLAGSAT
			if (!install_as_user && st.st_flags & NOCHANGESFLAGS) {
				chflagsat(pkg->rootfd, RELATIVE_PATH(fto), 0,
				    AT_SYMLINK_NOFOLLOW);
			}
#endif
			/* if the files does not belong to any package, we do save it */
			if (f->previous == PKG_FILE_SAVE) {
				backup_file_if_needed(pkg, f);
			}
			unlinkat(pkg->rootfd, RELATIVE_PATH(fto), 0);
		}
		if (renameat(pkg->rootfd, RELATIVE_PATH(f->temppath),
		    pkg->rootfd, RELATIVE_PATH(fto)) == -1) {
			char pkgnew[MAXPATHLEN + 8];
			snprintf(pkgnew, sizeof(pkgnew), "%s.pkgnew", f->path);
			if (renameat(pkg->rootfd, RELATIVE_PATH(f->temppath),
			    pkg->rootfd, RELATIVE_PATH(pkgnew)) == -1) {
				pkg_fatal_errno("Failed to rename %s -> %s",
				    f->temppath, fto);
			}
			pkg_emit_notice("Cannot install %s, "
					"installed as %s", f->path, pkgnew);
		}

		if (set_chflags(pkg->rootfd, fto, f->fflags) != EPKG_OK)
			return (EPKG_FATAL);
	}

	while (pkg_dirs(pkg, &d) == EPKG_OK) {
		append_touched_dir(d->path);
		if (d->noattrs)
			continue;
		if (set_attrsat(pkg->rootfd, d->path, d->perm,
		    d->uid, d->gid, &d->time[0], &d->time[1]) != EPKG_OK)
			return (EPKG_FATAL);
		if (set_chflags(pkg->rootfd, d->path, d->fflags) != EPKG_OK)
			return (EPKG_FATAL);
	}
	if (tempdirs != NULL)
		vec_free(tempdirs);

	return (EPKG_OK);
}

static char *
pkg_globmatch(char *pattern, const char *name)
{
	glob_t g;
	int i;
	char *buf, *buf2;
	char *path = NULL;

	if (glob(pattern, 0, NULL, &g) == GLOB_NOMATCH) {
		globfree(&g);

		return (NULL);
	}

	for (i = 0; i < g.gl_pathc; i++) {
		/* the version starts here */
		buf = strrchr(g.gl_pathv[i], '-');
		if (buf == NULL)
			continue;
		buf2 = strrchr(g.gl_pathv[i], '/');
		if (buf2 == NULL)
			buf2 = g.gl_pathv[i];
		else
			buf2++;
		/* ensure we have match the proper name */
		if (strncmp(buf2, name, buf - buf2) != 0)
			continue;
		if (path == NULL) {
			path = g.gl_pathv[i];
			continue;
		}
		if (pkg_version_cmp(g.gl_pathv[i], path) == 1)
			path = g.gl_pathv[i];
	}
	if (path)
		path = xstrdup(path);
	globfree(&g);

	return (path);
}

bool
append_pkg_if_newer(pkgs_t *localpkgs, struct pkg *p)
{
	/* only keep the highest version if we find one */
	struct pkg **lp = pkgs_insert_sorted(localpkgs, p);
	if (lp != NULL) {
		if (pkg_version_cmp((*lp)->version, p->version) == -1) {
			pkg_free(*lp);
			*lp = p;
			return (true);
		}
		pkg_free(p);
		return (false);
	}
	return (true);
}

static charv_t system_shlibs_cache = vec_init();
static bool system_shlibs_scanned = false;

static charv_t *
get_system_shlibs(void)
{
	if (!system_shlibs_scanned) {
		scan_system_shlibs(&system_shlibs_cache, ctx.pkg_rootdir);
		system_shlibs_scanned = true;
	}
	return (&system_shlibs_cache);
}

/*
 * Select a provider package from a symlink directory.
 * dirpath: e.g. "/repo/shlibs/libfoo.so.1"
 * ext:     package extension including dot (e.g. ".pkg")
 *
 * Selection logic:
 * - Single entry → use it
 * - Multiple → sort alphabetically, pick first (lowest NN. prefix = highest priority)
 * Returns xasprintf'd full path or NULL.
 */
static char *
select_provider_from_dir(const char *dirpath, const char *ext)
{
	DIR *d = opendir(dirpath);
	if (d == NULL)
		return (NULL);

	charv_t entries = vec_init();
	char fullpath[MAXPATHLEN];
	struct dirent *de;
	while ((de = readdir(d)) != NULL) {
		if (de->d_name[0] == '.')
			continue;
		if (!str_ends_with(de->d_name, ext))
			continue;
		/* Skip dangling symlinks */
		snprintf(fullpath, sizeof(fullpath), "%s/%s",
		    dirpath, de->d_name);
		if (access(fullpath, F_OK) != 0)
			continue;
		vec_push(&entries, xstrdup(de->d_name));
	}
	closedir(d);

	if (entries.len == 0) {
		vec_free(&entries);
		return (NULL);
	}

	char *selected = NULL;

	if (entries.len == 1) {
		selected = entries.d[0];
		entries.d[0] = NULL;
	} else {
		/* Sort alphabetically, pick first (lowest NN. prefix wins) */
		qsort(entries.d, entries.len, sizeof(char *), char_cmp);
		selected = entries.d[0];
		entries.d[0] = NULL;
	}

	char *result = NULL;
	if (selected != NULL) {
		xasprintf(&result, "%s/%s", dirpath, selected);
		free(selected);
	}

	vec_autofree(&entries);
	return (result);
}

static int
pkg_add_check_pkg_archive(struct pkgdb *db, struct pkg *pkg,
	const char *path, int flags, const char *location)
{
	const char	*arch;
	int	ret, retcode;
	struct pkg_dep	*dep = NULL;
	char	bd[MAXPATHLEN];
	char	dpath[MAXPATHLEN], *ppath;
	const char	*ext = NULL;
	struct pkg	*pkg_inst = NULL;
	bool	fromstdin;

	arch = pkg->abi != NULL ? pkg->abi : pkg->altabi;

	if (!is_valid_abi(arch, true) && (flags & PKG_ADD_FORCE) == 0) {
		return (EPKG_FATAL);
	}

	if ((flags & PKG_ADD_FORCE) == 0 && !is_valid_os_version(pkg)) {
		return (EPKG_FATAL);
	}

	/* XX check */
	ret = pkg_try_installed(db, pkg->name, &pkg_inst, PKG_LOAD_BASIC);
	if (ret == EPKG_OK) {
		if ((flags & PKG_ADD_FORCE) == 0 &&
		    pkg_version_cmp(pkg_inst->version, pkg->version) >= 0) {
			pkg_emit_already_installed(pkg_inst);
			pkg_free(pkg_inst);
			pkg_inst = NULL;
			return (EPKG_INSTALLED);
		}
		if (pkg_inst->locked) {
			pkg_emit_locked(pkg_inst);
			pkg_free(pkg_inst);
			pkg_inst = NULL;
			return (EPKG_LOCKED);
		}
		pkg_emit_notice("package %s is already installed, forced "
		    "install", pkg->name);
		pkg_free(pkg_inst);
		pkg_inst = NULL;
	} else if (ret != EPKG_END) {
		return (ret);
	}

	/*
	 * Check for dependencies by searching the same directory as
	 * the package archive we're reading.  Of course, if we're
	 * reading from a file descriptor or a unix domain socket or
	 * whatever, there's no valid directory to search.
	 */
	fromstdin = STREQ(path, "-");
	strlcpy(bd, path, sizeof(bd));
	if (!fromstdin) {
		/* In-place truncate bd to the directory components. */
		char *basedir = strrchr(bd, '/');
		if (NULL == basedir) {
			bd[0]='.';
			bd[1]='\0';
		} else {
			*basedir = '\0';
		}
		if ((ext = strrchr(path, '.')) == NULL) {
			pkg_emit_error("%s has no extension", path);
			return (EPKG_FATAL);
		}
	}

	retcode = EPKG_FATAL;
	pkg_emit_add_deps_begin(pkg);

	while (pkg_deps(pkg, &dep) == EPKG_OK) {
		dpath[0] = '\0';

		if (pkg_is_installed(db, dep->name) == EPKG_OK)
			continue;

		if (fromstdin) {
			pkg_emit_missing_dep(pkg, dep);
			if ((flags & PKG_ADD_FORCE_MISSING) == 0)
				goto cleanup;
			continue;
		}

		if (dep->version != NULL && dep->version[0] != '\0') {
			snprintf(dpath, sizeof(dpath), "%s/%s-%s%s", bd,
				dep->name, dep->version, ext);
		}

		if (strlen(dpath) == 0 || access(dpath, F_OK) != 0) {
			snprintf(dpath, sizeof(dpath), "%s/%s-*%s", bd,
			    dep->name, ext);
			ppath = pkg_globmatch(dpath, dep->name);
			if (ppath == NULL) {
				pkg_emit_missing_dep(pkg, dep);
				if ((flags & PKG_ADD_FORCE_MISSING) == 0)
					goto cleanup;
				continue;
			}
			strlcpy(dpath, ppath, sizeof(dpath));
			free(ppath);
		}

		if ((flags & PKG_ADD_UPGRADE) == 0 &&
				access(dpath, F_OK) == 0) {
			ret = pkg_add(db, dpath, PKG_ADD_AUTOMATIC, location);

			if (ret != EPKG_OK)
				goto cleanup;
		} else {
			pkg_emit_missing_dep(pkg, dep);
			if ((flags & PKG_ADD_FORCE_MISSING) == 0)
				goto cleanup;
		}
	}

	/*
	 * Phase 2: Resolve shlibs_required via symlink directory layout.
	 * Look in basedir/../shlibs/<shlibname>/ for provider packages.
	 */
	if (!fromstdin && (flags & PKG_ADD_UPGRADE) == 0) {
		char parentdir[MAXPATHLEN];
		strlcpy(parentdir, bd, sizeof(parentdir));
		char *pslash = strrchr(parentdir, '/');
		if (pslash != NULL)
			*pslash = '\0';

		charv_t *system_shlibs = get_system_shlibs();

		vec_foreach(pkg->shlibs_required, si) {
			const char *shlibname = pkg->shlibs_required.d[si];

			if (charv_search(system_shlibs, shlibname) != NULL)
				continue;

			if (pkgdb_is_shlib_provided(db, shlibname))
				continue;

			char provdir[MAXPATHLEN];
			snprintf(provdir, sizeof(provdir), "%s/shlibs/%s",
			    parentdir, shlibname);
			char *provider = select_provider_from_dir(
			    provdir, ext);

			if (provider == NULL) {
				pkg_emit_error("Missing shlib %s required by %s",
				    pkg->shlibs_required.d[si], pkg->name);
				if ((flags & PKG_ADD_FORCE_MISSING) == 0)
					goto cleanup;
				continue;
			}
			ret = pkg_add(db, provider, PKG_ADD_AUTOMATIC,
			    location);
			free(provider);
			if (ret != EPKG_OK && ret != EPKG_INSTALLED)
				goto cleanup;
		}

		/*
		 * Phase 3: Resolve abstract requires via symlink directory
		 * layout.  Look in basedir/../provides/<label>/ for providers.
		 */
		vec_foreach(pkg->requires, ri) {
			const char *req = pkg->requires.d[ri];

			if (pkgdb_is_provided(db, req))
				continue;

			char provdir[MAXPATHLEN];
			snprintf(provdir, sizeof(provdir), "%s/provides/%s",
			    parentdir, req);
			char *provider = select_provider_from_dir(
			    provdir, ext);

			if (provider == NULL) {
				pkg_emit_error(
				    "Missing provide %s required by %s",
				    req, pkg->name);
				if ((flags & PKG_ADD_FORCE_MISSING) == 0)
					goto cleanup;
				continue;
			}
			ret = pkg_add(db, provider, PKG_ADD_AUTOMATIC,
			    location);
			free(provider);
			if (ret != EPKG_OK && ret != EPKG_INSTALLED)
				goto cleanup;
		}
	}

	retcode = EPKG_OK;
cleanup:
	pkg_emit_add_deps_finished(pkg);

	return (retcode);
}

static int
pkg_add_cleanup_old(struct pkgdb *db, struct pkg *old, struct pkg *new,
    struct triggers *t, int flags, struct deferred_rc *rc)
{
	struct pkg_file *f;
	int ret = EPKG_OK;

	if (rc != NULL)
		pkg_deferred_rc_add(rc, old, PKG_RC_STOP);
	else
		pkg_start_stop_rc_scripts(old, PKG_RC_STOP);

	/*
	 * Execute pre deinstall scripts
	 */
	if ((flags & PKG_ADD_NOSCRIPT) == 0) {
		bool noexec = ((flags & PKG_ADD_NOEXEC) == PKG_ADD_NOEXEC);
		ret = pkg_lua_script_run(old, PKG_LUA_PRE_DEINSTALL, (old != NULL));
		if (ret != EPKG_OK && ctx.developer_mode) {
			return (ret);
		} else {
			ret = pkg_script_run(old, PKG_SCRIPT_PRE_DEINSTALL, (old != NULL),
			    noexec);
			if (ret != EPKG_OK && (ctx.developer_mode || noexec)) {
				return (ret);
			} else {
				ret = EPKG_OK;
			}
		}
	}

	/* Now remove files that no longer exist in the new package */
	if (new != NULL) {
		f = NULL;
		while (pkg_files(old, &f) == EPKG_OK) {
			if (!pkg_has_file(new, f->path) || match_ucl_lists(f->path,
			    pkg_config_get("FILES_IGNORE_GLOB"),
			    pkg_config_get("FILES_IGNORE_REGEX"))) {
				pkg_debug(2, "File %s is not in the new package", f->path);
				pkg_maybe_backup_library(db, old, f->path);
				trigger_is_it_a_cleanup(t, f->path);
				pkg_delete_file(old, f);
			}
		}

		pkg_delete_dirs(db, old, new);
	}

	return (ret);
}

void
pkg_rollback_pkg(struct pkg *p)
{
	struct pkg_file *f = NULL;

	while (pkg_files(p, &f) == EPKG_OK) {
		if (match_ucl_lists(f->path,
		    pkg_config_get("FILES_IGNORE_GLOB"),
		    pkg_config_get("FILES_IGNORE_REGEX")))
			continue;
		if (f->temppath  != NULL) {
			unlinkat(p->rootfd, f->temppath, 0);
		}
	}
}

void
pkg_rollback_cb(void *data)
{
	pkg_rollback_pkg((struct pkg *)data);
}

int
pkg_add_triggers(void)
{
	return (triggers_execute(NULL));
}


static int
populate_config_file_contents(struct archive *a, struct archive_entry *ae,
	struct pkg *pkg)
{
	int	retcode = EPKG_OK;
	int	ret = 0;
	char	path[MAXPATHLEN];

	do {
		if (archive_entry_filetype(ae) != AE_IFREG) {
			continue;
		}

		pkg_absolutepath(archive_entry_pathname(ae), path, sizeof(path), true);

		struct pkg_config_file *config = pkg_get_config_file(pkg, path);
		if (config == NULL) {
			continue;
		}

		if (archive_entry_size(ae) < 0) {
			pkg_emit_error("Invalid config file size for %s", path);
			return (EPKG_FATAL);
		}
		size_t len = archive_entry_size(ae);
		config->content = xmalloc(len + 1);
		archive_read_data(a, config->content, len);
		config->content[len] = '\0';
	} while ((ret = archive_read_next_header(a, &ae)) == ARCHIVE_OK);

	if (ret != ARCHIVE_EOF) {
		pkg_emit_error("archive_read_next_header(): %s",
		    archive_error_string(a));
		retcode = EPKG_FATAL;
	}

	return (retcode);
}

static int
pkg_add_common(struct pkgdb *db, const char *path, unsigned flags,
    const char *reloc, struct pkg *remote,
    struct pkg *local, struct triggers *t, struct deferred_rc *rc)
{
	struct archive		*a = NULL;
	struct archive_entry	*ae = NULL;
	struct pkg		*pkg = NULL;
	xstring			*message = NULL;
	struct pkg_message	*msg;
	struct pkg_file		*f;
	const char		*msgstr;
	bool			 extract = true, openxact = false;
	int			 retcode = EPKG_OK;
	int			 ret;
	int			 nfiles;
	tempdirs_t		 tempdirs = vec_init();
	struct pkg_add_context	 context;

	memset(&context, 0, sizeof(context));

	assert(path != NULL);

	/*
	 * Open the package archive file, read all the meta files and set the
	 * current archive_entry to the first non-meta file.
	 * If there is no non-meta files, EPKG_END is returned.
	 */
	ret = pkg_open2(&pkg, &a, &ae, path, 0, -1);
	context.pkg = pkg;
	context.localpkg = local;
	if (ret == EPKG_END)
		extract = false;
	else if (ret != EPKG_OK) {
		retcode = ret;
		goto cleanup;
	}
	if ((flags & (PKG_ADD_UPGRADE | PKG_ADD_SPLITTED_UPGRADE)) !=
	    PKG_ADD_UPGRADE)
		pkg_emit_install_begin(pkg);
	else
		pkg_emit_upgrade_begin(pkg, local);

	if (pkg_is_valid(pkg) != EPKG_OK) {
		pkg_emit_error("the package is not valid");
		retcode = EPKG_FATAL;
		goto cleanup;
	}

	if (flags & PKG_ADD_AUTOMATIC)
		pkg->automatic = true;

	if (flags & PKG_ADD_REGISTER_ONLY)
		extract = false;

	/*
	 * Additional checks for non-remote package
	 */
	if (remote == NULL) {
		ret = pkg_add_check_pkg_archive(db, pkg, path, flags, reloc);
		if (ret != EPKG_OK) {
			/* Do not return error on installed package */
			retcode = (ret == EPKG_INSTALLED ? EPKG_OK : ret);
			goto cleanup;
		}
	}
	else {
		if (remote->repo != NULL) {
			/* Save reponame */
			pkg_kv_add(&pkg->annotations, "repository", remote->repo->name, "annotation");
			pkg_kv_add(&pkg->annotations, "repo_type", remote->repo->ops->type, "annotation");
		}

		free(pkg->digest);
		pkg->digest = xstrdup(remote->digest);
		/* only preserve flags if -A has not been passed */
		if ((flags & PKG_ADD_AUTOMATIC) == 0)
			pkg->automatic = remote->automatic;
	}

	if (reloc != NULL)
		pkg_kv_add(&pkg->annotations, "relocated", reloc, "annotation");

	pkg_open_root_fd(pkg);
	context.rootfd = pkg->rootfd;
	/* analyse previous files */
	f = NULL;
	while (pkg_files(pkg, &f) == EPKG_OK) {
		if (match_ucl_lists(f->path,
		    pkg_config_get("FILES_IGNORE_GLOB"),
		    pkg_config_get("FILES_IGNORE_REGEX"))) {
			continue;
		}
		if (faccessat(pkg->rootfd, RELATIVE_PATH(f->path), F_OK, 0) == 0) {
			f->previous = PKG_FILE_EXIST;
			if (!pkgdb_file_exists(db, f->path)) {
				f->previous = PKG_FILE_SAVE;
			}
		}
	}

	/*
	 * Register the package before installing it in case there are problems
	 * that could be caught here.
	 */
	retcode = pkgdb_register_pkg(db, pkg, flags & PKG_ADD_FORCE, NULL);
	if (retcode != EPKG_OK)
		goto cleanup;
	openxact = true;

	/*
	 * Execute pre-install scripts
	 */
	if ((flags & PKG_ADD_NOSCRIPT) == 0) {
		if ((retcode = pkg_lua_script_run(pkg, PKG_LUA_PRE_INSTALL, (local != NULL))) != EPKG_OK)
			goto cleanup;
		if ((retcode = pkg_script_run(pkg, PKG_SCRIPT_PRE_INSTALL, (local != NULL),
			    ((flags & PKG_ADD_NOEXEC) == PKG_ADD_NOEXEC))) != EPKG_OK)
			goto cleanup;
	}

	/*
	 * Execute per-package pre-install triggers
	 */
	triggers_execute_perpackage(pkg, TRIGGER_PHASE_PRE_INSTALL, (local != NULL));

	/* add the user and group if necessary */

	nfiles = pkg->files.len + pkg->dirs.len;
	/*
	 * Extract the files on disk.
	 */
	if (extract) {
		pkg_register_cleanup_callback(pkg_rollback_cb, pkg);
		retcode = do_extract(a, ae, nfiles, local, &tempdirs, &context);
		pkg_unregister_cleanup_callback(pkg_rollback_cb, pkg);
		if (retcode != EPKG_OK) {
			/* If the add failed, clean up (silently) */
			pkg_rollback_pkg(pkg);
			pkg_delete_dirs(db, pkg, NULL);
			goto cleanup;
		}
	} else if (flags & PKG_ADD_REGISTER_ONLY && nfiles > 0) {
		/* Need to populate config file contents so they can be stored in
		   the database */
		retcode = populate_config_file_contents(a, ae, pkg);
		if (retcode != EPKG_OK) {
			goto cleanup;
		}
	}

	/*
	 * If this was a split upgrade, the old package has been entirely
	 * removed already.
	 */
	if (local != NULL && (flags & PKG_ADD_SPLITTED_UPGRADE) == 0) {
		pkg_open_root_fd(local);
		pkg_debug(1, "Cleaning up old version");
		if (pkg_add_cleanup_old(db, local, pkg, t, flags, rc) != EPKG_OK) {
			retcode = EPKG_FATAL;
			goto cleanup;
		}
	}


	/* Update configuration file content with db with newer versions */
	pkgdb_update_config_file_content(pkg, db->sqlite);

	if (extract)
		retcode = pkg_extract_finalize(pkg, &tempdirs);

	pkgdb_register_finale(db, retcode, NULL);
	openxact = false;

	/*
	 * Execute post install scripts
	 */

	if (retcode != EPKG_OK)
		goto cleanup;
	if ((flags & PKG_ADD_NOSCRIPT) == 0) {
		bool noexec = ((flags & PKG_ADD_NOEXEC) == PKG_ADD_NOEXEC);
		pkg_lua_script_run(pkg, PKG_LUA_POST_INSTALL, (local != NULL));
		retcode = pkg_script_run(pkg, PKG_SCRIPT_POST_INSTALL, (local != NULL),
		    noexec);
		if (retcode != EPKG_OK && noexec)
			goto cleanup;
		retcode = EPKG_OK;
	}

	/*
	 * Execute per-package post-install triggers
	 */
	triggers_execute_perpackage(pkg, TRIGGER_PHASE_POST_INSTALL, (local != NULL));

	/*
	 * Record rc scripts for (re)start at the end of the transaction,
	 * or start them immediately if running outside a transaction.
	 */
	if (rc != NULL)
		pkg_deferred_rc_add(rc, pkg, PKG_RC_START);
	else
		pkg_start_stop_rc_scripts(pkg, PKG_RC_START);

	if ((flags & (PKG_ADD_UPGRADE | PKG_ADD_SPLITTED_UPGRADE)) !=
	    PKG_ADD_UPGRADE)
		pkg_emit_install_finished(pkg, local);
	else
		pkg_emit_upgrade_finished(pkg, local);

	vec_foreach(pkg->message, i) {
		msg = pkg->message.d[i];
		msgstr = NULL;
		if (msg->type == PKG_MESSAGE_ALWAYS) {
			msgstr = msg->str;
		} else if (local != NULL &&
		     msg->type == PKG_MESSAGE_UPGRADE) {
			if (msg->maximum_version == NULL &&
			    msg->minimum_version == NULL) {
				msgstr = msg->str;
			} else if (msg->maximum_version == NULL) {
				if (pkg_version_cmp(local->version, msg->minimum_version) == 1) {
					msgstr = msg->str;
				}
			} else if (msg->minimum_version == NULL) {
				if (pkg_version_cmp(local->version, msg->maximum_version) == -1) {
					msgstr = msg->str;
				}
			} else if (pkg_version_cmp(local->version, msg->maximum_version) == -1 &&
				    pkg_version_cmp(local->version, msg->minimum_version) == 1) {
				msgstr = msg->str;
			}
		} else if (local == NULL &&
		    msg->type == PKG_MESSAGE_INSTALL) {
			msgstr = msg->str;
		}
		if (msgstr != NULL) {
			if (message == NULL) {
				message = xstring_new();
				pkg_fprintf(message->fp, "=====\nMessage from "
				    "%n-%v:\n\n", pkg, pkg);
			}
			xstring_printf(message, "--\n%s\n", msgstr);
		}
	}
	if (pkg_has_message(pkg) && message != NULL) {
		xstring_flush(message);
		pkg_emit_message(message->buf);
		xstring_free(message);
	}

cleanup:
	if (openxact)
		pkgdb_register_finale(db, retcode, NULL);
	if (a != NULL) {
		archive_read_close(a);
		archive_read_free(a);
	}

	pkg_free(pkg);

	return (retcode);
}

int
pkg_add(struct pkgdb *db, const char *path, unsigned flags,
    const char *location)
{
	return pkg_add_common(db, path, flags, location, NULL, NULL, NULL, NULL);
}

int
pkg_add_from_remote(struct pkgdb *db, const char *path, unsigned flags,
    const char *location, struct pkg *rp, struct triggers *t,
    struct deferred_rc *rc)
{
	return pkg_add_common(db, path, flags, location, rp, NULL, t, rc);
}

int
pkg_add_upgrade(struct pkgdb *db, const char *path, unsigned flags,
    const char *location,
    struct pkg *rp, struct pkg *lp, struct triggers *t,
    struct deferred_rc *rc)
{
	if (pkgdb_ensure_loaded(db, lp,
	    PKG_LOAD_FILES|PKG_LOAD_SCRIPTS|PKG_LOAD_DIRS|PKG_LOAD_LUA_SCRIPTS) != EPKG_OK)
		return (EPKG_FATAL);

	return pkg_add_common(db, path, flags, location, rp, lp, t, rc);
}

static int
pkg_group_dump(int fd, struct pkg *pkg)
{
	ucl_object_t *o, *seq;
	struct pkg_dep	*dep = NULL;

	if (pkg->type != PKG_GROUP_REMOTE)
		return (EPKG_FATAL);
	o = ucl_object_typed_new(UCL_OBJECT);
	ucl_object_insert_key(o, ucl_object_fromstring(pkg->name), "name", 0, false);
	ucl_object_insert_key(o, ucl_object_fromstring(pkg->comment), "comment", 0, false);
	seq = ucl_object_typed_new(UCL_ARRAY);
	while (pkg_deps(pkg, &dep) == EPKG_OK)
		ucl_array_append(seq, ucl_object_fromstring(dep->name));
	ucl_object_insert_key(o, seq, "depends", 0, false);
	ucl_object_emit_fd(o, UCL_EMIT_CONFIG, fd);
	return (EPKG_OK);
}

int
pkg_add_group(struct pkg *pkg)
{
	char temp[MAXPATHLEN];
	int dfd = pkg_get_dbdirfd();
	mkdirat(dfd, "groups", 0755);
	int gfd = openat(dfd, "groups", O_DIRECTORY|O_CLOEXEC);
	hidden_tempfile(temp, MAXPATHLEN, pkg->name);
	int fd = openat(gfd, temp, O_CREAT|O_EXCL|O_WRONLY, 0644);
	if (fd == -1) {
		pkg_emit_errno("Unable to create group file %s", pkg->name);
		return (EPKG_FATAL);
	}
	pkg_group_dump(fd, pkg);
	close(fd);
	if (renameat(gfd, temp, gfd, pkg->name) == -1) {
		unlinkat(gfd, temp, 0);
		pkg_emit_errno("Unable to create group file %s", pkg->name);
		return (EPKG_FATAL);
	}
	return (EPKG_OK);
}

int
pkg_add_fromdir(struct pkg *pkg, const char *src, struct pkgdb *db __unused)
{
	struct stat st;
	struct pkg_dir *d = NULL;
	struct pkg_file *f = NULL;
	char target[MAXPATHLEN];
	struct passwd *pw, pwent;
	struct group *gr, grent;
	int err, fd, fromfd;
	int retcode;
	hardlinks_t hardlinks = vec_init();
	const char *path;
	char *buffer = NULL;
	size_t link_len, bufsize;
	bool install_as_user;
	tempdirs_t tempdirs = vec_init();
	struct pkg_add_context context;

	memset(&context, 0, sizeof(context));

	install_as_user = (getenv("INSTALL_AS_USER") != NULL);

	fromfd = open(src, O_DIRECTORY);
	if (fromfd == -1) {
		pkg_fatal_errno("Unable to open source directory '%s'", src);
	}
	pkg_open_root_fd(pkg);
	context.pkg = pkg;
	context.rootfd = pkg->rootfd;

	while (pkg_dirs(pkg, &d) == EPKG_OK) {
		if (fstatat(fromfd, RELATIVE_PATH(d->path), &st, 0) == -1) {
			close(fromfd);
			pkg_fatal_errno("%s%s", src, d->path);
		}
		if (d->perm == 0)
			d->perm = st.st_mode & ~S_IFMT;
		if (d->uname != NULL) {
			bufsize = 1024;
			for (;;) {
				free(buffer);
				buffer = xcalloc(1, bufsize);
				err = getpwnam_r(d->uname, &pwent, buffer,
				    bufsize, &pw);
				if (err == ERANGE) {
					bufsize *= 2;
					continue;
				}
				break;
			}
			if (err != 0) {
				pkg_emit_error("getpwnam_r(%s): %s", d->uname, strerror(err));
				retcode = EPKG_FATAL;
				goto cleanup;
			}
			d->uid = pwent.pw_uid;
		} else {
			d->uid = install_as_user ? st.st_uid : 0;
		}
		if (d->gname != NULL) {
			bufsize = 1024;
			for (;;) {
				free(buffer);
				buffer = xcalloc(1, bufsize);
				err = getgrnam_r(d->gname, &grent, buffer,
				    bufsize, &gr);
				if (err == ERANGE) {
					bufsize *= 2;
					continue;
				}
				break;
			}
			if (err != 0) {
				pkg_emit_error("getgrnam_r(%s): %s", d->gname, strerror(err));
				retcode = EPKG_FATAL;
				goto cleanup;
			}
			d->gid = grent.gr_gid;
		} else {
			d->gid = st.st_gid;
		}
#ifdef HAVE_STRUCT_STAT_ST_MTIM
		d->time[0] = st.st_atim;
		d->time[1] = st.st_mtim;
#else
#if defined(_DARWIN_C_SOURCE) || defined(__APPLE__)
		d->time[0] = st.st_atimespec;
		d->time[1] = st.st_mtimespec;
#else
		d->time[0].tv_sec = st.st_atime;
		d->time[0].tv_nsec = 0;
		d->time[1].tv_sec = st.st_mtime;
		d->time[1].tv_nsec = 0;
#endif
#endif

		if (create_dir(&context, d, &tempdirs) == EPKG_FATAL) {
			retcode = EPKG_FATAL;
			goto cleanup;
		}
	}

	while (pkg_files(pkg, &f) == EPKG_OK) {
		if (match_ucl_lists(f->path,
		    pkg_config_get("FILES_IGNORE_GLOB"),
		    pkg_config_get("FILES_IGNORE_REGEX")))
			continue;
		if (fstatat(fromfd, RELATIVE_PATH(f->path), &st,
		    AT_SYMLINK_NOFOLLOW) == -1) {
			vec_free_and_free(&hardlinks, free);
			close(fromfd);
			pkg_fatal_errno("%s%s", src, f->path);
		}
		if (f->uname != NULL) {
			bufsize = 1024;
			for (;;) {
				free(buffer);
				buffer = xcalloc(1, bufsize);
				err = getpwnam_r(f->uname, &pwent, buffer,
				    bufsize, &pw);
				if (err == ERANGE) {
					bufsize *= 2;
					continue;
				}
				break;
			}
			if (err != 0) {
				pkg_emit_error("getpwnam_r(%s): %s", f->uname, strerror(err));
				retcode = EPKG_FATAL;
				goto cleanup;
			}
			f->uid = pwent.pw_uid;
		} else {
			f->uid = install_as_user ? st.st_uid : 0;
		}

		if (f->gname != NULL) {
			bufsize = 1024;
			for (;;) {
				free(buffer);
				buffer = xcalloc(1, bufsize);
				err = getgrnam_r(f->gname, &grent, buffer,
				    bufsize, &gr);
				if (err == ERANGE) {
					bufsize *= 2;
					continue;
				}
				break;
			}
			if (err != 0) {
				pkg_emit_error("getgrnam_r(%s): %s", f->gname, strerror(err));
				retcode = EPKG_FATAL;
				goto cleanup;
			}
			f->gid = grent.gr_gid;
		} else {
			f->gid = st.st_gid;
		}

		if (f->perm == 0)
			f->perm = st.st_mode & ~S_IFMT;
		if (f->uid == 0 && install_as_user)
			f->uid = st.st_uid;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
		f->time[0] = st.st_atim;
		f->time[1] = st.st_mtim;
#else
#if defined(_DARWIN_C_SOURCE) || defined(__APPLE__)
		f->time[0] = st.st_atimespec;
		f->time[1] = st.st_mtimespec;
#else
		f->time[0].tv_sec = st.st_atime;
		f->time[0].tv_nsec = 0;
		f->time[1].tv_sec = st.st_mtime;
		f->time[1].tv_nsec = 0;
#endif
#endif

		if (S_ISLNK(st.st_mode)) {
			if ((link_len = readlinkat(fromfd,
			    RELATIVE_PATH(f->path), target,
			    sizeof(target))) == -1) {
				vec_free_and_free(&hardlinks, free);
				close(fromfd);
				pkg_fatal_errno("Unable to read symlinks "
				    "'%s'", f->path);
			}
			target[link_len] = '\0';
			if (create_symlinks(&context, f, target, &tempdirs) == EPKG_FATAL) {
				retcode = EPKG_FATAL;
				goto cleanup;
			}
		} else if (S_ISREG(st.st_mode)) {
			if ((fd = openat(fromfd, RELATIVE_PATH(f->path),
			    O_RDONLY)) == -1) {
				vec_free_and_free(&hardlinks, free);
				close(fromfd);
				pkg_fatal_errno("Unable to open source file"
				    " '%s'", RELATIVE_PATH(f->path));
			}
			path = NULL;
			vec_foreach(hardlinks, i) {
				struct hardlink *hit = hardlinks.d[i];
				if (hit->ino == st.st_ino &&
				    hit->dev == st.st_dev) {
					path = hit->path;
					break;
				}
			}
			if (path != NULL) {
				if (create_hardlink(&context, f, path, &tempdirs) == EPKG_FATAL) {
					close(fd);
					retcode = EPKG_FATAL;
					goto cleanup;
				}
			} else {
				if (create_regfile(&context, f, NULL, NULL, fd, NULL, &tempdirs) == EPKG_FATAL) {
					close(fd);
					retcode = EPKG_FATAL;
					goto cleanup;
				}
				struct hardlink *h = xcalloc(1, sizeof(*h));
				h->ino = st.st_ino;
				h->dev = st.st_dev;
				h->path = f->path;
				vec_push(&hardlinks, h);
			}
			close(fd);
		} else {
			pkg_emit_error("Invalid file type");
			retcode = EPKG_FATAL;
			goto cleanup;
		}
	}

	retcode = pkg_extract_finalize(pkg, &tempdirs);

cleanup:
	vec_free_and_free(&hardlinks, free);
	free(buffer);
	close(fromfd);
	return (retcode);
}


struct tempdir *
open_tempdir(struct pkg_add_context *context, const char *path)
{
	struct stat st;
	struct pkg *localpkg;
	char walk[MAXPATHLEN];
	char *dir;
	size_t cnt = 0, len;
	struct tempdir *t;
	int rootfd;

	rootfd = context->rootfd;
	localpkg = context->localpkg;

	strlcpy(walk, path, sizeof(walk));
	while ((dir = strrchr(walk, '/')) != NULL) {
		*dir = '\0';
		cnt++;
		/* accept symlinks pointing to directories */
		len = strlen(walk);
		if (len == 0 && cnt == 1)
			break;
		if (len > 0) {
			int flag;

			flag = localpkg == NULL ? 0 : AT_SYMLINK_NOFOLLOW;
			if (fstatat(rootfd, RELATIVE_PATH(walk), &st,
			    flag) == -1) {
				/*
				 * A hack to ensure that intermediate
				 * directories not registered with a package are
				 * still logged in the metalog.  This is not
				 * really the right place, but to implement this
				 * properly we need to clean up uses of
				 * try_mkdir().
				 */
				metalog_add(PKG_METALOG_DIR,
				    RELATIVE_PATH(walk),
				    "root", "wheel", 0755, 0, NULL);
				continue;
			}
			if (S_ISLNK(st.st_mode) &&
			    localpkg != NULL &&
			    !pkg_has_file(localpkg, walk) &&
			    fstatat(rootfd, RELATIVE_PATH(walk), &st, 0) == -1)
				continue;
			if (S_ISDIR(st.st_mode) && cnt == 1)
				break;
			if (!S_ISDIR(st.st_mode))
				continue;
		}
		*dir = '/';
		t = xcalloc(1, sizeof(*t));
		hidden_tempfile(t->temp, sizeof(t->temp), walk);
		if (mkdirat(rootfd, RELATIVE_PATH(t->temp), 0755) == -1) {
			pkg_errno("Failed to create temporary directory: %s", t->temp);
			free(t);
			return (NULL);
		}

		strlcpy(t->name, walk, sizeof(t->name));
		t->len = strlen(t->name);
		t->fd = openat(rootfd, RELATIVE_PATH(t->temp), O_DIRECTORY|O_CLOEXEC);
		if (t->fd == -1) {
			pkg_errno("Failed to open directory %s", t->temp);
			free(t);
			return (NULL);
		}
		return (t);
	}
	errno = 0;
	return (NULL);
}
