#include <sys/param.h>
#include <sys/types.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "pkg.h"
#include "private/event.h"
#include "private/pkg.h"
#include "private/pkgdb.h"
#include "private/pkg_jobs.h"
#include "siphash.h"
typedef vec_t(struct pkg_job_request *) conflict_chain_t;
TREE_DEFINE(pkg_jobs_conflict_item, entry);
static struct sipkey *
pkg_conflicts_sipkey_init(void)
{
static struct sipkey *kinit;
if (kinit == NULL) {
kinit = xmalloc(sizeof(*kinit));
arc4random_buf((unsigned char*)kinit, sizeof(*kinit));
}
return (kinit);
}
static int
pkg_conflicts_chain_cmp(const void *jra, const void *jrb)
{
const struct pkg_job_request *a = *(const struct pkg_job_request **)jra;
const struct pkg_job_request *b = *(const struct pkg_job_request **)jrb;
const char *vera, *verb;
if (a->skip || b->skip) {
return (a->skip - b->skip);
}
vera = a->item->pkg->version;
verb = b->item->pkg->version;
return (pkg_version_cmp(vera, verb));
}
static int
pkg_conflicts_request_resolve_chain(struct pkg *req, conflict_chain_t *chain)
{
struct pkg_job_request *selected = NULL;
const char *slash_pos;
vec_foreach(*chain, i) {
slash_pos = strrchr(chain->d[i]->item->pkg->origin, '/');
if (slash_pos != NULL) {
if (STREQ(slash_pos + 1, req->name)) {
selected = chain->d[i];
break;
}
}
}
if (selected == NULL) {
qsort(chain->d, chain->len, sizeof(chain->d[0]), pkg_conflicts_chain_cmp);
selected = chain->d[0];
}
pkg_debug(2, "select %s in the chain of conflicts for %s",
selected->item->pkg->name, req->name);
vec_foreach(*chain, i) {
if (chain->d[i] != selected)
chain->d[i]->skip = true;
}
return (EPKG_OK);
}
int
pkg_conflicts_request_resolve(struct pkg_jobs *j)
{
struct pkg_job_request *req, *found;
struct pkg_conflict *c;
struct pkg_job_universe_item *unit;
pkghash_it it;
it = pkghash_iterator(j->request_add);
while (pkghash_next(&it)) {
req = it.value;
conflict_chain_t chain = vec_init();
if (req->skip)
continue;
LL_FOREACH(req->item->pkg->conflicts, c) {
unit = pkg_jobs_universe_find(j->universe, c->uid);
if (unit != NULL) {
found = pkghash_get_value(j->request_add, unit->pkg->uid);
if (found != NULL && !found->skip) {
vec_push(&chain, found);
}
}
}
if (chain.len > 0) {
vec_push(&chain, req);
if (pkg_conflicts_request_resolve_chain(req->item->pkg, &chain) != EPKG_OK) {
vec_free(&chain);
return (EPKG_FATAL);
}
}
vec_free(&chain);
}
return (EPKG_OK);
}
static int
pkg_conflicts_item_cmp(struct pkg_jobs_conflict_item *a,
struct pkg_jobs_conflict_item *b)
{
return (b->hash - a->hash);
}
static bool
pkg_conflicts_need_conflict(struct pkg_jobs *j, struct pkg *p1, struct pkg *p2)
{
struct pkg_file *fcur;
if (pkgdb_ensure_loaded(j->db, p1, PKG_LOAD_FILES|PKG_LOAD_DIRS) != EPKG_OK ||
pkgdb_ensure_loaded(j->db, p2, PKG_LOAD_FILES|PKG_LOAD_DIRS)
!= EPKG_OK) {
pkg_debug(1, "cannot load files from %s and %s to check conflicts",
p1->name, p2->name);
return (false);
}
if (pkghash_get(p1->conflictshash, p2->uid) != NULL &&
pkghash_get(p2->conflictshash, p1->uid) != NULL)
return false;
LL_FOREACH(p1->files, fcur) {
if (pkg_has_file(p2, fcur->path))
return (true);
if (pkg_has_dir(p2, fcur->path))
return (true);
}
return (false);
}
static void
pkg_conflicts_register_one(struct pkg *p, struct pkg *op,
enum pkg_conflict_type type)
{
struct pkg_conflict *conflict;
conflict = pkghash_get_value(p->conflictshash, op->uid);
if (conflict != NULL)
return;
conflict = xcalloc(1, sizeof(*conflict));
conflict->type = type;
conflict->uid = xstrdup(op->uid);
conflict->digest = xstrdup(op->digest);
pkghash_safe_add(p->conflictshash, op->uid, conflict, NULL);
DL_APPEND(p->conflicts, conflict);
}
static void
pkg_conflicts_register(struct pkg *p1, struct pkg *p2, const char *path,
enum pkg_conflict_type type)
{
pkg_conflicts_register_one(p1, p2, type);
pkg_conflicts_register_one(p2, p1, type);
pkg_debug(2, "registering conflict between %s(%s) and %s(%s) on path %s",
p1->uid, p1->type == PKG_INSTALLED ? "local" : "remote",
p2->uid, p2->type == PKG_INSTALLED ? "local" : "remote", path);
}
static bool
pkg_conflicts_register_chain(struct pkg_jobs *j, struct pkg_job_universe_item *u1,
struct pkg_job_universe_item *u2, const char *path)
{
struct pkg_job_universe_item *cur1, *cur2;
enum pkg_conflict_type type;
bool ret = false;
cur1 = u1;
do {
cur2 = u2;
do {
struct pkg *p1 = cur1->pkg, *p2 = cur2->pkg;
if (p1->type == PKG_INSTALLED && p2->type == PKG_INSTALLED)
type = PKG_CONFLICT_LOCAL_LOCAL;
else if (p1->type == PKG_INSTALLED || p2->type == PKG_INSTALLED)
type = PKG_CONFLICT_REMOTE_LOCAL;
else
type = PKG_CONFLICT_REMOTE_REMOTE;
if (type != PKG_CONFLICT_LOCAL_LOCAL &&
pkg_conflicts_need_conflict(j, p1, p2)) {
pkg_emit_conflicts(p1, p2, path);
pkg_conflicts_register(p1, p2, path, type);
j->conflicts_registered++;
ret = true;
}
cur2 = cur2->prev;
} while (cur2 != u2);
cur1 = cur1->prev;
} while (cur1 != u1);
return (ret);
}
static struct pkg *
pkg_conflicts_check_local_path(const char *path, const char *uid,
struct pkg_jobs *j)
{
const char sql_local_conflict[] = ""
"SELECT p.name as uniqueid FROM packages AS p "
"INNER JOIN files AS f "
"ON p.id = f.package_id "
"WHERE f.path = ?1;";
sqlite3_stmt *stmt;
int ret;
struct pkg *p = NULL;
ret = sqlite3_prepare_v2(j->db->sqlite, sql_local_conflict, -1,
&stmt, NULL);
if (ret != SQLITE_OK) {
ERROR_SQLITE(j->db->sqlite, sql_local_conflict);
return (NULL);
}
sqlite3_bind_text(stmt, 1,
path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2,
uid, -1, SQLITE_STATIC);
pkgdb_debug(4, stmt);
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char *uid_local = sqlite3_column_text(stmt, 0);
p = pkg_jobs_universe_get_local(j->universe,
uid_local, 0);
assert(p != NULL);
assert(!STREQ(uid, p->uid));
if (pkghash_get(p->conflictshash, uid) == NULL) {
sqlite3_finalize(stmt);
return (p);
}
}
sqlite3_finalize(stmt);
return (NULL);
}
static struct pkg_job_universe_item *
pkg_conflicts_check_all_paths(struct pkg_jobs *j, const char *path,
struct pkg_job_universe_item *it, struct sipkey *k)
{
const char *uid1, *uid2;
struct pkg_jobs_conflict_item *cit, test;
struct pkg_conflict *c;
uint64_t hv;
hv = siphash24(path, strlen(path), k);
test.hash = hv;
cit = TREE_FIND(j->conflict_items, pkg_jobs_conflict_item, entry, &test);
if (cit == NULL) {
cit = xcalloc(1, sizeof(*cit));
cit->hash = hv;
cit->item = it;
TREE_INSERT(j->conflict_items, pkg_jobs_conflict_item, entry, cit);
}
else {
if (cit->item == it)
return (NULL);
uid1 = it->pkg->uid;
uid2 = cit->item->pkg->uid;
if (STREQ(uid1, uid2)) {
cit->item = it;
return (NULL);
}
c = pkghash_get_value(it->pkg->conflictshash, uid2);
if (c != NULL || !pkg_conflicts_register_chain(j, it, cit->item, path)) {
struct sipkey nk;
pkg_debug(2, "found a collision on path %s between %s and %s, key: %lu",
path, uid1, uid2, (unsigned long)k->k[0]);
nk = *k;
nk.k[0] ++;
return (pkg_conflicts_check_all_paths(j, path, it, &nk));
}
return (cit->item);
}
return (NULL);
}
static void
pkg_conflicts_check_chain_conflict(struct pkg_job_universe_item *it,
struct pkg_job_universe_item *local, struct pkg_jobs *j)
{
struct pkg_file *fcur;
struct pkg *p;
struct pkg_job_universe_item *cun;
struct sipkey *k;
LL_FOREACH(it->pkg->files, fcur) {
k = pkg_conflicts_sipkey_init();
cun = pkg_conflicts_check_all_paths(j, fcur->path, it, k);
if (local != NULL) {
if (pkg_has_file(local->pkg, fcur->path))
continue;
}
p = pkg_conflicts_check_local_path(fcur->path, it->pkg->uid, j);
pkg_debug(4, "integrity: check path %s of package %s", fcur->path,
it->pkg->uid);
if (p != NULL) {
if (pkg_jobs_universe_process_item(j->universe, p, &cun))
continue;
assert(cun != NULL);
pkg_conflicts_register_chain(j, it, cun, fcur->path);
}
}
#if 0
struct pkg_dir *dcur, *dtmp, *df;
HASH_ITER(hh, it->pkg->dirs, dcur, dtmp) {
memset(&k, 0, sizeof(k));
cun = pkg_conflicts_check_all_paths(j, dcur->path, it, &k);
if (local != NULL) {
HASH_FIND_STR(local->pkg->dirs, dcur->path, df);
if (df != NULL)
continue;
}
p = pkg_conflicts_check_local_path(dcur->path, uid, j);
if (p != NULL) {
pkg_jobs_universe_process_item(j->universe, p, &cun);
assert(cun != NULL);
pkg_conflicts_register_chain(j, it, cun, dcur->path);
}
}
#endif
}
int
pkg_conflicts_append_chain(struct pkg_job_universe_item *it,
struct pkg_jobs *j)
{
struct pkg_job_universe_item *lp = NULL, *cur;
if (j->conflict_items == NULL) {
j->conflict_items = xmalloc(sizeof(*j->conflict_items));
TREE_INIT(j->conflict_items, pkg_conflicts_item_cmp);
}
cur = it->prev;
while (cur != it) {
if (cur->pkg->type == PKG_INSTALLED) {
lp = cur;
if (pkgdb_ensure_loaded(j->db, cur->pkg, PKG_LOAD_FILES|PKG_LOAD_DIRS)
!= EPKG_OK)
return (EPKG_FATAL);
break;
}
cur = cur->prev;
}
cur = it;
do {
if (cur != lp) {
if (pkgdb_ensure_loaded(j->db, cur->pkg, PKG_LOAD_FILES|PKG_LOAD_DIRS)
!= EPKG_OK) {
pkg_debug (3, "cannot load files from %s to check integrity",
cur->pkg->name);
}
else {
pkg_conflicts_check_chain_conflict(cur, lp, j);
}
}
cur = cur->prev;
} while (cur != it);
return (EPKG_OK);
}