Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
HardenedBSD-pkg libpkg pkg_solve.c
/*-
 * Copyright (c) 2013-2017 Vsevolod Stakhov <vsevolod@FreeBSD.org>
 * Copyright (c) 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
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/mount.h>

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include "pkg.h"
#include "pkghash.h"
#include "private/event.h"
#include "private/pkg.h"
#include "private/pkgdb.h"
#include "private/pkg_jobs.h"
#include "picosat.h"

struct pkg_solve_item;

#define dbg(x, ...) pkg_dbg(PKG_DBG_SOLVER, x, __VA_ARGS__)

enum pkg_solve_rule_type {
	PKG_RULE_DEPEND = 0,
	PKG_RULE_UPGRADE_CONFLICT,
	PKG_RULE_EXPLICIT_CONFLICT,
	PKG_RULE_REQUEST_CONFLICT,
	PKG_RULE_REQUEST,
	PKG_RULE_REQUIRE,
	PKG_RULE_VITAL,
	PKG_RULE_MAX
};

static const char *rule_reasons[] = {
	[PKG_RULE_DEPEND] = "dependency",
	[PKG_RULE_UPGRADE_CONFLICT] = "upgrade",
	[PKG_RULE_REQUEST_CONFLICT] = "candidates",
	[PKG_RULE_EXPLICIT_CONFLICT] = "conflict",
	[PKG_RULE_REQUEST] = "request",
	[PKG_RULE_REQUIRE] = "require",
	[PKG_RULE_VITAL] = "vital",
	[PKG_RULE_MAX] = NULL
};

enum pkg_solve_variable_flags {
	PKG_VAR_INSTALL = (1 << 0),
	PKG_VAR_TOP = (1 << 1),
	PKG_VAR_FAILED = (1 << 2),
	PKG_VAR_ASSUMED = (1 << 3),
	PKG_VAR_ASSUMED_TRUE = (1 << 4)
};
struct pkg_solve_variable {
	struct pkg_job_universe_item *unit;
	unsigned int flags;
	int order;
	const char *digest;
	const char *uid;
	const char *assumed_reponame;
	struct pkg_solve_variable *next, *prev;
};

struct pkg_solve_item {
	int nitems;
	int nresolved;
	struct pkg_solve_variable *var;
	int inverse;
	struct pkg_solve_item *prev,*next;
};

struct pkg_solve_rule {
	enum pkg_solve_rule_type reason;
	struct pkg_solve_item *items;
};

struct pkg_solve_problem {
	struct pkg_jobs *j;
	vec_t(struct pkg_solve_rule *) rules;
	pkghash *variables_by_uid;
	struct pkg_solve_variable *variables;
	PicoSAT *sat;
	size_t nvars;
};

struct pkg_solve_impl_graph {
	struct pkg_solve_variable *var;
	struct pkg_solve_impl_graph *prev, *next;
};

/*
 * Utilities to convert jobs to SAT rule
 */

static void
pkg_solve_item_new(struct pkg_solve_rule *rule, struct pkg_solve_variable *var,
    int inverse)
{
	struct pkg_solve_item *it;

	it = xcalloc(1, sizeof(struct pkg_solve_item));
	it->var = var;
	it->inverse = inverse;
	it->nitems = rule->items ? rule->items->nitems + 1 : 1;
	DL_APPEND(rule->items, it);
}

static struct pkg_solve_rule *
pkg_solve_rule_new(enum pkg_solve_rule_type reason)
{
	struct pkg_solve_rule *result;

	result = xcalloc(1, sizeof(struct pkg_solve_rule));
	result->reason = reason;

	return (result);
}

static void
pkg_solve_variable_set(struct pkg_solve_variable *var,
	struct pkg_job_universe_item *item)
{
	var->unit = item;
	/* XXX: Is it safe to save a ptr here ? */
	var->digest = item->pkg->digest;
	var->uid = item->pkg->uid;
	var->prev = var;
}

static void
pkg_solve_rule_free(struct pkg_solve_rule *rule)
{
	struct pkg_solve_item *it, *tmp;

	LL_FOREACH_SAFE(rule->items, it, tmp) {
		free(it);
	}
	free(rule);
}

void
pkg_solve_problem_free(struct pkg_solve_problem *problem)
{
	vec_free_and_free(&problem->rules, pkg_solve_rule_free);
	pkghash_destroy(problem->variables_by_uid);
	picosat_reset(problem->sat);
	free(problem->variables);
	free(problem);
}

static void
pkg_print_rule_buf(struct pkg_solve_rule *rule, xstring *sb)
{
	struct pkg_solve_item *it = rule->items, *key_elt = NULL;

	fprintf(sb->fp, "%s rule: ", rule_reasons[rule->reason]);
	switch(rule->reason) {
	case PKG_RULE_DEPEND:
		LL_FOREACH(rule->items, it) {
			if (it->inverse == -1) {
				key_elt = it;
				break;
			}
		}
		if (key_elt) {
			fprintf(sb->fp, "package %s%s depends on: ", key_elt->var->uid,
				(key_elt->var->unit->pkg->type == PKG_INSTALLED) ? "(l)" : "(r)");
		}
		LL_FOREACH(rule->items, it) {
			if (it != key_elt) {
				fprintf(sb->fp, "%s%s", it->var->uid,
					(it->var->unit->pkg->type == PKG_INSTALLED) ? "(l)" : "(r)");
			}
		}
		break;
	case PKG_RULE_UPGRADE_CONFLICT:
		fprintf(sb->fp, "upgrade local %s-%s to remote %s-%s",
			it->var->uid, it->var->unit->pkg->version,
			it->next->var->uid, it->next->var->unit->pkg->version);
		break;
	case PKG_RULE_EXPLICIT_CONFLICT:
		fprintf(sb->fp, "The following packages conflict with each other: ");
		LL_FOREACH(rule->items, it) {
			fprintf(sb->fp, "%s-%s%s%s", it->var->unit->pkg->uid, it->var->unit->pkg->version,
				(it->var->unit->pkg->type == PKG_INSTALLED) ? "(l)" : "(r)",
				it->next ? ", " : "");
		}
		break;
	case PKG_RULE_REQUIRE:
		LL_FOREACH(rule->items, it) {
			if (it->inverse == -1) {
				key_elt = it;
				break;
			}
		}
		if (key_elt) {
			fprintf(sb->fp, "package %s%s depends on a requirement provided by: ",
				key_elt->var->uid,
				(key_elt->var->unit->pkg->type == PKG_INSTALLED) ? "(l)" : "(r)");
		}
		LL_FOREACH(rule->items, it) {
			if (it != key_elt) {
				fprintf(sb->fp, "%s%s", it->var->uid,
					(it->var->unit->pkg->type == PKG_INSTALLED) ? "(l)" : "(r)");
			}
		}
		break;
	case PKG_RULE_REQUEST_CONFLICT:
		fprintf(sb->fp, "The following packages in request are candidates for installation: ");
		LL_FOREACH(rule->items, it) {
			fprintf(sb->fp, "%s-%s%s", it->var->uid, it->var->unit->pkg->version,
					it->next ? ", " : "");
		}
		break;
	case PKG_RULE_VITAL:
		fprintf(sb->fp, "The following packages are vital: ");
		LL_FOREACH(rule->items, it) {
			fprintf(sb->fp, "%s-%s%s", it->var->uid, it->var->unit->pkg->version,
					it->next ? ", " : "");
		}
		break;
	default:
		break;
	}
}

static void
pkg_debug_print_rule(struct pkg_solve_rule *rule)
{
	xstring *sb;

	if (ctx.debug_level < 3)
		return;

	sb = xstring_new();

	pkg_print_rule_buf(rule, sb);

	fflush(sb->fp);
	dbg(2, "rule: %s", sb->buf);
	xstring_free(sb);
}

static void
pkg_solve_handle_provide(struct pkg_solve_problem *problem,
    struct pkg_job_provide *pr, struct pkg_solve_rule *rule, struct pkg *orig,
    const char *reponame, int *cnt)
{
	struct pkg_solve_variable *var, *curvar;
	struct pkg_job_universe_item *un;
	struct pkg *pkg;

	/* Find the first package in the universe list */
	un = pr->un;
	while (un->prev->next != NULL) {
		un = un->prev;
	}

	/* Find the corresponding variables chain */

	var = pkghash_get_value(problem->variables_by_uid, un->pkg->uid);
	LL_FOREACH(var, curvar) {
		/*
		 * For each provide we need to check whether this package
		 * actually provides this require
		 */
		pkg = curvar->unit->pkg;
		if (pr->is_shlib) {
			struct pkg_abi oabi, pabi;

			/*
			 * If this remote package doesn't provide the shared
			 * library in question, then skip it... unless
			 * BACKUP_LIBRARIES is configured, in which case we know
			 * that upgrading to this remote package will not remove
			 * the installed package's shared libraries.
			 */
			if (charv_search(&pkg->shlibs_provided, pr->provide) ==
			    NULL) {
				struct pkg_solve_variable *tmp;

				if (!ctx.backup_libraries ||
				    pkg->type == PKG_INSTALLED ||
				    (problem->j->type != PKG_JOBS_UPGRADE &&
				     problem->j->type != PKG_JOBS_INSTALL))
					continue;
				LL_FOREACH(var, tmp) {
					struct pkg *tpkg;

					/*
					 * Does the installed version provide
					 * the library in question?  If so, we
					 * can safely upgrade away from it in
					 * the backup_libraries case.
					 */
					tpkg = tmp->unit->pkg;
					if (tpkg->type == PKG_INSTALLED &&
					    charv_search(&tpkg->shlibs_provided,
					    pr->provide) != NULL)
						break;
				}
				if (tmp == NULL)
					continue;
			}

			/*
			 * Make sure the package ABI matches.  If the OS or
			 * architecture don't match, we're done.  Otherwise, on
			 * FreeBSD, the provider's ABI version should be at
			 * least that of the required ABI.  For example, it
			 * should be ok for FreeBSD:15:amd64 packages to depend
			 * on libc.so.7 from a FreeBSD:16:amd64 pkgbase package.
			 * In general we assume that shared libraries provide
			 * backward compatibility.
			 *
			 * This might be reasonable behaviour on other OSes as
			 * well.
			 */
			dbg(2, "origin %s: %s", orig->name, orig->abi);
			dbg(2, "tgt %s: %s", pkg->name, pkg->abi);
			if (!pkg_abi_from_string(&oabi, orig->abi) ||
			    !pkg_abi_from_string(&pabi, pkg->abi))
				continue;
			if (oabi.os != pabi.os ||
			    (oabi.arch != pabi.arch && oabi.arch != PKG_ARCH_ANY && pabi.arch != PKG_ARCH_ANY)  ||
			    (oabi.os != PKG_OS_FREEBSD && oabi.os != PKG_OS_ANY)) {
				dbg(2,
		"require %s: package %s-%s(%c) provides ABI %s, want %s",
				    pr->provide, pkg->name, pkg->version,
				    pkg->type == PKG_INSTALLED ? 'l' : 'r',
				    pkg->abi, orig->abi);
				continue;
			}
			if (pabi.major < oabi.major ||
			    (pabi.major == oabi.major &&
			     pabi.minor < oabi.minor) ||
			    (pabi.major == oabi.major &&
			     pabi.minor == oabi.minor &&
			     pabi.patch < oabi.patch)) {
				continue;
			}
		} else if (charv_search(&pkg->provides, pr->provide) == NULL) {
			dbg(4, "%s provide is not satisfied by %s-%s(%c)",
			    pr->provide, pkg->name, pkg->version,
			    pkg->type == PKG_INSTALLED ? 'l' : 'r');
			continue;
		}

		if (curvar->assumed_reponame == NULL)
			curvar->assumed_reponame = reponame;

		dbg(4, "%s provide is satisfied by %s-%s(%c)",
		    pr->provide, pkg->name, pkg->version,
		    pkg->type == PKG_INSTALLED ? 'l' : 'r');

		pkg_solve_item_new(rule, curvar, 1);
		(*cnt)++;
	}
}

static void
pkg_solve_add_depend_rule(struct pkg_solve_problem *problem,
    struct pkg_solve_variable *var, struct pkg_dep *dep, const char *reponame)
{
	const char *uid;
	struct pkg_solve_variable *depvar, *curvar;
	struct pkg_solve_rule *rule = NULL;
	int cnt = 0;
	struct pkg_dep *cur;

	/* Dependency rule: (!A | B1 | B2 | B3...) must be true */
	rule = pkg_solve_rule_new(PKG_RULE_DEPEND);
	/* !A */
	pkg_solve_item_new(rule, var, -1);

	LL_FOREACH2(dep, cur, alt_next) {
		uid = cur->uid;
		depvar = NULL;
		depvar = pkghash_get_value(problem->variables_by_uid, uid);
		if (depvar == NULL) {
			dbg(2, "cannot find variable dependency %s", uid);
			continue;
		}

		/* B1 | B2 | ... */
		cnt = 1;
		LL_FOREACH(depvar, curvar) {
			/* Propagate reponame */
			if (curvar->assumed_reponame == NULL) {
				curvar->assumed_reponame = reponame;
			}

			pkg_solve_item_new(rule, curvar, 1);
			cnt++;
		}
	}

	if (cnt == 0) {
		dbg(2, "cannot find any suitable dependency for %s", var->uid);
		pkg_solve_rule_free(rule);
	} else {
		vec_push(&problem->rules, rule);
	}
}

static void
pkg_solve_add_conflict_rule(struct pkg_solve_problem *problem,
    struct pkg *pkg, struct pkg_solve_variable *var,
    struct pkg_conflict *conflict)
{
	const char *uid;
	struct pkg_solve_variable *confvar, *curvar;
	struct pkg_solve_rule *rule = NULL;
	struct pkg *other;

	uid = conflict->uid;
	confvar = pkghash_get_value(problem->variables_by_uid, uid);
	if (confvar == NULL) {
		dbg(2, "cannot find conflict %s", uid);
		return;
	}

	/* Add conflict rule from each of the alternative */
	LL_FOREACH(confvar, curvar) {
		other = curvar->unit->pkg;
		if (conflict->type == PKG_CONFLICT_REMOTE_LOCAL) {
			/* Skip inappropriate packages */
			if (pkg->type == PKG_INSTALLED) {
				if (other->type == PKG_INSTALLED)
					continue;
			}
			else {
				if (other->type != PKG_INSTALLED)
					continue;
			}
		}
		else if (conflict->type == PKG_CONFLICT_REMOTE_REMOTE) {
			if (pkg->type == PKG_INSTALLED)
				continue;

			if (other->type == PKG_INSTALLED)
				continue;
		}
		/*
		 * Also if a conflict is digest specific then we skip
		 * variables with mismatched digests
		 */
		if (conflict->digest) {
			if (!STREQ(conflict->digest, other->digest))
				continue;
		}

		/* Conflict rule: (!A | !Bx) must be true */
		rule = pkg_solve_rule_new(PKG_RULE_EXPLICIT_CONFLICT);
		/* !A */
		pkg_solve_item_new(rule, var, -1);
		/* !Bx */
		pkg_solve_item_new(rule, curvar, -1);

		vec_push(&problem->rules, rule);
	}
}

static void
pkg_solve_add_require_rule(struct pkg_solve_problem *problem,
    struct pkg_solve_variable *var, const char *requirement,
    const char *reponame)
{
	struct pkg_solve_rule *rule;
	struct pkg_job_provide *pr, *prhead;
	struct pkg *pkg;
	int cnt;

	pkg = var->unit->pkg;

	prhead = pkghash_get_value(problem->j->universe->provides, requirement);
	if (prhead != NULL) {
		dbg(4, "Add require rule: %s-%s(%c) wants %s",
			pkg->name, pkg->version, pkg->type == PKG_INSTALLED ? 'l' : 'r',
			requirement);
		/* Require rule: ( !A | P1 | P2 | P3 ... ) must be true */
		rule = pkg_solve_rule_new(PKG_RULE_REQUIRE);
		/* !A */
		pkg_solve_item_new(rule, var, -1);
		/* P1 | P2 | ... */
		cnt = 1;
		LL_FOREACH(prhead, pr) {
			pkg_solve_handle_provide(problem, pr, rule, pkg,
			    reponame, &cnt);
		}

		if (cnt > 1) {
			vec_push(&problem->rules, rule);
		} else {
			/* Missing dependencies... */
			free(rule);
		}
	} else {
		/*
		 * XXX:
		 * This is terribly broken now so ignore till provides/requires
		 * are really fixed.
		 */
		dbg(1, "for package: %s cannot find provide for requirement: %s",
		    pkg->name, requirement);
	}
}

static void
pkg_solve_add_vital_rule(struct pkg_solve_problem *problem,
    struct pkg_solve_variable *var)
{
	struct pkg_solve_variable *cur_var, *local_var = NULL, *remote_var = NULL;
	struct pkg_solve_rule *rule = NULL;
	struct pkg *pkg;

	LL_FOREACH(var, cur_var) {
		pkg = cur_var->unit->pkg;

		if (pkg->type == PKG_INSTALLED) {
			local_var = cur_var;
		} else {
			remote_var = cur_var;
		}
	}

	if (local_var && remote_var) {
		/* Vital upgrade rule: ( L | R ) must be true */
		dbg(4, "Add vital rule: want either %s(l) or %s(r)", local_var->unit->pkg->uid, remote_var->unit->pkg->uid);
		rule = pkg_solve_rule_new(PKG_RULE_VITAL);
		/* L */
		pkg_solve_item_new(rule, local_var, 1);
		/* R */
		pkg_solve_item_new(rule, remote_var, 1);
	} else if(local_var) {
		/* Vital keep local rule: ( L ) must be true */
		dbg(4, "Add vital rule: want %s(l) to stay", local_var->unit->pkg->uid);
		rule = pkg_solve_rule_new(PKG_RULE_VITAL);
		/* L */
		pkg_solve_item_new(rule, local_var, 1);
	}

	if (rule)
		vec_push(&problem->rules, rule);
}

static struct pkg_solve_variable *
pkg_solve_find_var_in_chain(struct pkg_solve_variable *var,
	struct pkg_job_universe_item *item)
{
	struct pkg_solve_variable *cur;

	assert(var != NULL);
	LL_FOREACH(var, cur) {
		if (cur->unit == item) {
			return (cur);
		}
	}

	return (NULL);
}

static int
pkg_solve_add_request_rule(struct pkg_solve_problem *problem,
	struct pkg_solve_variable *var, struct pkg_job_request *req, int inverse)
{
	struct pkg_solve_rule *rule;
	struct pkg_job_request_item *item, *confitem;
	struct pkg_solve_variable *confvar, *curvar;
	int cnt;

	dbg(4, "add variable from %s request with uid %s-%s",
		inverse < 0 ? "delete" : "install", var->uid, var->digest);

	/*
	 * Get the suggested item
	 */
	var = pkghash_get_value(problem->variables_by_uid, req->item->pkg->uid);
	var = pkg_solve_find_var_in_chain(var, req->item->unit);
	assert(var != NULL);
	/* Assume the most significant variable */
	picosat_assume(problem->sat, var->order * inverse);

	/*
	 * Add clause for any of candidates:
	 * A1 | A2 | ... | An
	 */
	rule = pkg_solve_rule_new(PKG_RULE_REQUEST);

	cnt = 0;

	LL_FOREACH(req->item, item) {
		curvar = pkg_solve_find_var_in_chain(var, item->unit);
		assert(curvar != NULL);
		pkg_solve_item_new(rule, curvar, inverse);
		/* All request variables are top level */
		curvar->flags |= PKG_VAR_TOP;

		if (inverse > 0) {
			curvar->flags |= PKG_VAR_INSTALL;
		}

		cnt++;
	}

	if (cnt > 1 && var->unit->inhash != 0) {
		vec_push(&problem->rules, rule);
		/* Also need to add pairs of conflicts */
		LL_FOREACH(req->item, item) {
			curvar = pkg_solve_find_var_in_chain(var, item->unit);
			assert(curvar != NULL);
			if (item->next == NULL)
				continue;
			LL_FOREACH(item->next, confitem) {
				confvar = pkg_solve_find_var_in_chain(var, confitem->unit);
				assert(confvar != NULL && confvar != curvar && confvar != var);
				/* Conflict rule: (!A | !Bx) must be true */
				rule = pkg_solve_rule_new(PKG_RULE_REQUEST_CONFLICT);
				/* !A */
				pkg_solve_item_new(rule, curvar, -1);
				/* !Bx */
				pkg_solve_item_new(rule, confvar, -1);

				vec_push(&problem->rules, rule);
			}
		}
	}
	else {
		/* No need to add unary rules as we added the assumption already */
		pkg_solve_rule_free(rule);
	}

	var->flags |= PKG_VAR_TOP;
	if (inverse > 0) {
		var->flags |= PKG_VAR_INSTALL;
	}

	return (EPKG_OK);
}

static void
pkg_solve_add_chain_rule(struct pkg_solve_problem *problem,
    struct pkg_solve_variable *var)
{
	struct pkg_solve_variable *curvar, *confvar;
	struct pkg_solve_rule *rule;

	/* Rewind to first */
	while (var->prev->next != NULL) {
		var = var->prev;
	}

	LL_FOREACH(var, curvar) {
		/* Conflict rule: (!Ax | !Ay) must be true */
		if (curvar->next == NULL) {
			break;
		}

		LL_FOREACH(curvar->next, confvar) {
			rule = pkg_solve_rule_new(PKG_RULE_UPGRADE_CONFLICT);
			/* !Ax */
			pkg_solve_item_new(rule, curvar, -1);
			/* !Ay */
			pkg_solve_item_new(rule, confvar, -1);

			vec_push(&problem->rules, rule);
		}
	}
}

static void
pkg_solve_process_universe_variable(struct pkg_solve_problem *problem,
    struct pkg_solve_variable *var)
{
	struct pkg_dep *dep;
	struct pkg_conflict *conflict;
	struct pkg *pkg;
	struct pkg_solve_variable *cur_var;
	struct pkg_jobs *j = problem->j;
	struct pkg_job_request *jreq = NULL;
	bool chain_added = false;
	bool force = j->flags & PKG_FLAG_FORCE;
	bool force_overrides_vital = pkg_object_bool(pkg_config_get("FORCE_CAN_REMOVE_VITAL"));
	bool add_vital = force_overrides_vital ? !force : true;

	LL_FOREACH(var, cur_var) {
		pkg = cur_var->unit->pkg;

		/* Request */
		if (!(cur_var->flags & PKG_VAR_TOP)) {
			jreq = pkghash_get_value(j->request_add, cur_var->uid);
			if (jreq != NULL)
				pkg_solve_add_request_rule(problem, cur_var, jreq, 1);
			jreq = pkghash_get_value(j->request_delete, cur_var->uid);
			if (jreq != NULL)
				pkg_solve_add_request_rule(problem, cur_var, jreq, -1);
		}

		if (jreq) {
			cur_var->assumed_reponame = pkg->reponame;
		}

		/* Depends */
		LL_FOREACH(pkg->depends, dep) {
			pkg_solve_add_depend_rule(problem, cur_var, dep,
			    cur_var->assumed_reponame);
		}

		/* Conflicts */
		LL_FOREACH(pkg->conflicts, conflict) {
			pkg_solve_add_conflict_rule(problem, pkg, cur_var,
			    conflict);
		}

		/* Shlibs */
		vec_foreach(pkg->shlibs_required, i) {
			const char *s = pkg->shlibs_required.d[i];
			/* Ignore 32 bit libraries */
			if (j->ignore_compat32 && str_ends_with(s, ":32"))
				continue;
			if (charv_search(&j->system_shlibs, s) != NULL) {
				/* The shlib is provided by the system */
				continue;
			}
			pkg_solve_add_require_rule(problem, cur_var, s,
			    cur_var->assumed_reponame);
		}
		vec_foreach(pkg->requires, i) {
			pkg_solve_add_require_rule(problem, cur_var,
			    pkg->requires.d[i], cur_var->assumed_reponame);
		}

		/* Vital flag */
		if (pkg->vital && add_vital)
			pkg_solve_add_vital_rule(problem, cur_var);

		/*
		 * If this var chain contains mutually conflicting vars
		 * we need to register conflicts with all following
		 * vars
		 */
		if (!chain_added && (cur_var->next != NULL || cur_var->prev != var)) {
			pkg_solve_add_chain_rule(problem, cur_var);
			chain_added = true;
		}
	}
}

static void
pkg_solve_add_variable(struct pkg_job_universe_item *un,
    struct pkg_solve_problem *problem, size_t *n)
{
	struct pkg_job_universe_item *ucur;
	struct pkg_solve_variable *var = NULL, *tvar = NULL;

	LL_FOREACH(un, ucur) {
		assert(*n < problem->nvars);

		/* Add new variable */
		var = &problem->variables[*n];
		pkg_solve_variable_set(var, ucur);

		if (tvar == NULL) {
			dbg(4, "add variable from universe with uid %s", var->uid);
			pkghash_safe_add(problem->variables_by_uid, var->uid, var, NULL);
			tvar = var;
		}
		else {
			/* Insert a variable to a chain */
			DL_APPEND(tvar, var);
		}
		(*n) ++;
		var->order = *n;
	}
}

struct pkg_solve_problem *
pkg_solve_jobs_to_sat(struct pkg_jobs *j)
{
	struct pkg_solve_problem *problem;
	struct pkg_job_universe_item *un;
	size_t i = 0;
	pkghash_it it;

	problem = xcalloc(1, sizeof(struct pkg_solve_problem));

	problem->j = j;
	problem->nvars = j->universe->nitems;
	problem->variables = xcalloc(problem->nvars, sizeof(struct pkg_solve_variable));
	problem->sat = picosat_init();

	if (problem->sat == NULL) {
		pkg_emit_errno("picosat_init", "pkg_solve_sat_problem");
		return (NULL);
	}

	picosat_adjust(problem->sat, problem->nvars);

	/* Parse universe */
	it = pkghash_iterator(j->universe->items);
	while (pkghash_next(&it)) {
		un = (struct pkg_job_universe_item *)it.value;
		/* Add corresponding variables */
		pkg_solve_add_variable(un, problem, &i);
	}

	/* Add rules for all conflict chains */
	it = pkghash_iterator(j->universe->items);
	while (pkghash_next(&it)) {
		struct pkg_solve_variable *var;

		un = (struct pkg_job_universe_item *)it.value;
		var = pkghash_get_value(problem->variables_by_uid, un->pkg->uid);
		if (var == NULL) {
			pkg_emit_error("internal solver error: variable %s is not found",
			    un->pkg->uid);
			return (NULL);
		}
		pkg_solve_process_universe_variable(problem, var);
	}

	if (problem->rules.len == 0)
		dbg(1, "problem has no requests");

	return (problem);
}

static int
pkg_solve_picosat_iter(struct pkg_solve_problem *problem, int iter __unused)
{
	size_t i;
	int res;
	struct pkg_solve_variable *var, *cur;
	bool is_installed = false;

	picosat_reset_phases(problem->sat);
	picosat_reset_scores(problem->sat);
	/* Set initial guess */
	for (i = 0; i < problem->nvars; i ++) {
		var = &problem->variables[i];
		is_installed = false;

		LL_FOREACH(var, cur) {
			if (cur->unit->pkg->type == PKG_INSTALLED) {
				is_installed = true;
				break;
			}
		}

		if (var->flags & PKG_VAR_TOP)
			continue;

		if (!(var->flags & (PKG_VAR_FAILED|PKG_VAR_ASSUMED))) {
			if (is_installed) {
				picosat_set_default_phase_lit(problem->sat, i + 1, 1);
				picosat_set_more_important_lit(problem->sat, i + 1);
			}
			else if  (!var->next && var->prev == var) {
				/* Prefer not to install if have no local version */
				picosat_set_default_phase_lit(problem->sat, i + 1, -1);
				picosat_set_less_important_lit(problem->sat, i + 1);
			}
		}
		else if (var->flags & PKG_VAR_FAILED) {
			if (var->unit->pkg->type == PKG_INSTALLED) {
				picosat_set_default_phase_lit(problem->sat, i + 1, -1);
				picosat_set_less_important_lit(problem->sat, i + 1);
			}
			else {
				picosat_set_default_phase_lit(problem->sat, i + 1, 1);
				picosat_set_more_important_lit(problem->sat, i + 1);
			}

			var->flags &= ~PKG_VAR_FAILED;
		}
	}

	res = picosat_sat(problem->sat, -1);

	return (res);
}

static void
pkg_solve_set_initial_assumption(struct pkg_solve_problem *problem,
		struct pkg_solve_rule *rule)
{
	struct pkg_job_universe_item *selected, *cur, *local, *first;
	struct pkg_solve_item *item;
	struct pkg_solve_variable *var, *cvar;
	bool conservative = false, prefer_local = false;
	const char *assumed_reponame = NULL;

	if (problem->j->type == PKG_JOBS_INSTALL) {
		/* Avoid upgrades on INSTALL job */
		conservative = true;
		prefer_local = true;
	}
	else {
		conservative = pkg_object_bool(pkg_config_get("CONSERVATIVE_UPGRADE"));
	}

	switch (rule->reason) {
	case PKG_RULE_DEPEND:
		/*
		 * The first item is dependent item, the next items are
		 * dependencies. We assume that all deps belong to a single
		 * upgrade chain.
		 */
		assert (rule->items != NULL);
		item = rule->items;
		var = item->var;
		assumed_reponame = var->assumed_reponame;

		/* Check what we are depending on */
		if (!(var->flags & (PKG_VAR_TOP|PKG_VAR_ASSUMED_TRUE))) {
			/*
			 * We are interested merely in dependencies of top variables
			 * or of previously assumed dependencies
			 */
			dbg(4, "not interested in dependencies for %s-%s",
					var->unit->pkg->name, var->unit->pkg->version);
			return;
		}
		else {
			dbg(4, "examine dependencies for %s-%s",
					var->unit->pkg->name, var->unit->pkg->version);
		}


		item = rule->items->next;
		assert (item != NULL);
		var = item->var;
		first = var->unit;

		/* Rewind chains */
		while (first->prev->next != NULL) {
			first = first->prev;
		}
		while (var->prev->next != NULL) {
			var = var->prev;
		}

		LL_FOREACH(var, cvar) {
			if (cvar->flags & PKG_VAR_ASSUMED) {
				/* Do not reassume packages */
				return;
			}
		}
		/* Forward chain to find local package */
		local = NULL;

		DL_FOREACH (first, cur) {
			if (cur->pkg->type == PKG_INSTALLED) {
				local = cur;
				break;
			}
		}

		if (prefer_local && local != NULL) {
			selected = local;
		}
		else {
			selected = pkg_jobs_universe_select_candidate(first, local,
			    conservative, assumed_reponame, true);

			if (local && (STREQ(selected->pkg->digest, local->pkg->digest) ||
				      !pkg_jobs_need_upgrade(&problem->j->system_shlibs, selected->pkg, local->pkg))) {
				selected = local;
			}
		}

		/* Now we can find the according var */
		if (selected != NULL) {

			LL_FOREACH(var, cvar) {
				if (cvar->unit == selected) {
					picosat_set_default_phase_lit(problem->sat, cvar->order, 1);
					dbg(4, "assumed %s-%s(%s) to be installed",
							selected->pkg->name, selected->pkg->version,
							selected->pkg->type == PKG_INSTALLED ? "l" : "r");
					cvar->flags |= PKG_VAR_ASSUMED_TRUE;
				}
				else {
					dbg(4, "assumed %s-%s(%s) to be NOT installed",
							cvar->unit->pkg->name, cvar->unit->pkg->version,
							cvar->unit->pkg->type == PKG_INSTALLED ? "l" : "r");
					picosat_set_default_phase_lit(problem->sat, cvar->order, -1);
				}

				cvar->flags |= PKG_VAR_ASSUMED;
			}

		}
		break;
	case PKG_RULE_REQUIRE:
		/* XXX: deal with require rules somehow */
		break;
	default:
		/* No nothing */
		return;
	}
}

int
pkg_solve_sat_problem(struct pkg_solve_problem *problem)
{
	struct pkg_solve_rule *rule;
	struct pkg_solve_item *item;
	int res, iter = 0;
	size_t i;
	bool need_reiterate = false;
	const int *failed = NULL;
	int attempt = 0;
	struct pkg_solve_variable *var;

	vec_rforeach(problem->rules, j) {
		rule = problem->rules.d[j];

		LL_FOREACH(rule->items, item) {
			picosat_add(problem->sat, item->var->order * item->inverse);
		}

		picosat_add(problem->sat, 0);
		pkg_debug_print_rule(rule);
	}

	vec_rforeach(problem->rules, j) {
		rule = problem->rules.d[j];
		pkg_solve_set_initial_assumption(problem, rule);
	}

reiterate:

	res = pkg_solve_picosat_iter(problem, iter);

	if (res != PICOSAT_SATISFIABLE) {
		/*
		 * in case we cannot satisfy the problem it appears by
		 * experience that the culprit seems to always be the latest of
		 * listed in the failed assumptions.
		 * So try to remove them for the given problem.
		 * To avoid endless loop allow a maximum of 10 iterations no
		 * more
		 */
		failed = picosat_failed_assumptions(problem->sat);
		attempt++;

		/* get the last failure */
		while (*failed) {
			failed++;
		}
		failed--;

		if (attempt >= 10) {
			pkg_emit_error("Cannot solve problem using SAT solver");
			xstring *sb = xstring_new();

			while (*failed) {
				var = &problem->variables[abs(*failed) - 1];
				vec_rforeach(problem->rules, j) {
					rule = problem->rules.d[j];

					if (rule->reason != PKG_RULE_DEPEND) {
						LL_FOREACH(rule->items, item) {
							if (item->var == var) {
								pkg_print_rule_buf(rule, sb);
								fputc('\n', sb->fp);
								break;
							}
						}
					}
				}

				fprintf(sb->fp, "cannot %s package %s, remove it from request? ",
						var->flags & PKG_VAR_INSTALL ? "install" : "remove", var->uid);

				fflush(sb->fp);
				if (pkg_emit_query_yesno(true, sb->buf)) {
					var->flags |= PKG_VAR_FAILED;
				}

				failed++;
				need_reiterate = true;
			}
			xstring_free(sb);
		} else {
			var = &problem->variables[abs(*failed) - 1];

			/* Check if the failure is caused by a vital package */
			bool vital_found = false;
			vec_rforeach(problem->rules, vj) {
				struct pkg_solve_rule *vrule = problem->rules.d[vj];
				if (vrule->reason != PKG_RULE_DEPEND)
					continue;
				/* In depend rules, the key element (inverse == -1) is the
				 * dependent package, positive items are its dependencies */
				struct pkg_solve_item *dep_pkg = NULL;
				bool depends_on_var = false;
				LL_FOREACH(vrule->items, item) {
					if (item->inverse == -1)
						dep_pkg = item;
					else if (item->var->uid == var->uid ||
					    STREQ(item->var->uid, var->uid))
						depends_on_var = true;
				}
				if (dep_pkg != NULL && depends_on_var &&
				    dep_pkg->var->unit->pkg->vital) {
					pkg_emit_error("Cannot remove %s: "
					    "required by vital package %s",
					    var->uid, dep_pkg->var->uid);
					vital_found = true;
				}
			}
			if (!vital_found)
				pkg_emit_notice("Cannot solve problem using SAT "
				    "solver, trying another plan");

			var->flags |= PKG_VAR_FAILED;

			need_reiterate = true;
		}

#if 0
		failed = picosat_next_maximal_satisfiable_subset_of_assumptions(problem->sat);

		while (*failed) {
			struct pkg_solve_variable *lvar = &problem->variables[*failed - 1];

			pkg_emit_notice("lvar: %s", lvar->uid);

			failed ++;
		}

		return (EPKG_AGAIN);
#endif
	}
	else {

		/* Assign vars */
		for (i = 0; i < problem->nvars; i ++) {
			int val = picosat_deref(problem->sat, i + 1);
			struct pkg_solve_variable *lvar = &problem->variables[i];

			if (val > 0)
				lvar->flags |= PKG_VAR_INSTALL;
			else
				lvar->flags &= ~PKG_VAR_INSTALL;

			dbg(2, "decided %s %s-%s to %s",
					lvar->unit->pkg->type == PKG_INSTALLED ? "local" : "remote",
							lvar->uid, lvar->digest,
							lvar->flags & PKG_VAR_INSTALL ? "install" : "delete");
		}

		/* Check for reiterations */
		if ((problem->j->type == PKG_JOBS_INSTALL ||
				problem->j->type == PKG_JOBS_UPGRADE) && iter == 0) {
			for (i = 0; i < problem->nvars; i ++) {
				bool failed_var = false;
				struct pkg_solve_variable *lvar = &problem->variables[i], *cur;

				if (!(lvar->flags & PKG_VAR_INSTALL)) {
					LL_FOREACH(lvar, cur) {
						if (cur->flags & PKG_VAR_INSTALL) {
							failed_var = false;
							break;
						}
						else if (cur->unit->pkg->type == PKG_INSTALLED) {
							failed_var = true;
						}
					}
				}

				/*
				 * If we want to delete local packages on installation, do one more SAT
				 * iteration to ensure that we have no other choices
				 */
				if (failed_var) {
					dbg (1, "trying to delete local package %s-%s on install/upgrade,"
							" reiterate on SAT",
							lvar->unit->pkg->name, lvar->unit->pkg->version);
					need_reiterate = true;

					LL_FOREACH(lvar, cur) {
						cur->flags |= PKG_VAR_FAILED;
					}
				}
			}
		}
	}

	if (need_reiterate) {
		iter ++;

		/* Restore top-level assumptions */
		for (i = 0; i < problem->nvars; i ++) {
			struct pkg_solve_variable *lvar = &problem->variables[i];

			if (lvar->flags & PKG_VAR_TOP) {
				if (lvar->flags & PKG_VAR_FAILED) {
					lvar->flags ^= PKG_VAR_INSTALL | PKG_VAR_FAILED;
				}

				picosat_assume(problem->sat, lvar->order *
						(lvar->flags & PKG_VAR_INSTALL ? 1 : -1));
			}
		}

		need_reiterate = false;

		goto reiterate;
	}

	return (EPKG_OK);
}

void
pkg_solve_dot_export(struct pkg_solve_problem *problem, FILE *file)
{
	struct pkg_solve_rule *rule;
	size_t i;

	fprintf(file, "digraph {\n");

	for (i = 0; i < problem->nvars; i ++) {
		struct pkg_solve_variable *var = &problem->variables[i];

		fprintf(file, "\tp%d [shape=%s label=\"%s-%s\"]\n", var->order,
				var->unit->pkg->type == PKG_INSTALLED ? "ellipse" : "octagon",
				var->uid, var->unit->pkg->version);
	}

	/* Print all variables as nodes */

	vec_rforeach(problem->rules, j) {
		rule = problem->rules.d[j];
		struct pkg_solve_item *it = rule->items, *key_elt = NULL;

		switch(rule->reason) {
		case PKG_RULE_DEPEND:
			LL_FOREACH(rule->items, it) {
				if (it->inverse == -1) {
					key_elt = it;
					break;
				}
			}
			assert (key_elt != NULL);

			LL_FOREACH(rule->items, it) {
				if (it != key_elt) {
					fprintf(file, "\tp%d -> p%d;\n", key_elt->var->order,
							it->var->order);
				}
			}
			break;
		case PKG_RULE_UPGRADE_CONFLICT:
		case PKG_RULE_EXPLICIT_CONFLICT:
		case PKG_RULE_REQUEST_CONFLICT:
			fprintf(file, "\tp%d -> p%d [arrowhead=none,color=red];\n",
					it->var->order, it->next->var->order);
			break;
		case PKG_RULE_REQUIRE:
			LL_FOREACH(rule->items, it) {
				if (it->inverse == -1) {
					key_elt = it;
					break;
				}
			}
			assert (key_elt != NULL);

			LL_FOREACH(rule->items, it) {
				if (it != key_elt) {
					fprintf(file, "\tp%d -> p%d[arrowhead=diamond];\n", key_elt->var->order,
							it->var->order);
				}
			}
			break;
		default:
			break;
		}
	}

	fprintf(file, "}\n");
}

int
pkg_solve_dimacs_export(struct pkg_solve_problem *problem, FILE *f)
{
	struct pkg_solve_rule *rule;
	struct pkg_solve_item *it;

	fprintf(f, "p cnf %d %zu\n", (int)problem->nvars, problem->rules.len);

	vec_rforeach(problem->rules, i) {
		rule = problem->rules.d[i];
		LL_FOREACH(rule->items, it) {
			size_t order = it->var - problem->variables;
			if (order < problem->nvars)
				fprintf(f, "%ld ", (long)((order + 1) * it->inverse));
		}
		fprintf(f, "0\n");
	}
	return (EPKG_OK);
}

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

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

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

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

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

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

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

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

	return (true);
}

static void
pkg_solve_insert_res_job (struct pkg_solve_variable *var,
		struct pkg_solve_problem *problem)
{
	struct pkg_solved *res;
	struct pkg_solve_variable *cur_var, *del_var = NULL, *add_var = NULL;
	int seen_add = 0, seen_del = 0;
	struct pkg_jobs *j = problem->j;

	LL_FOREACH(var, cur_var) {
		if ((cur_var->flags & PKG_VAR_INSTALL) &&
				cur_var->unit->pkg->type != PKG_INSTALLED) {
			add_var = cur_var;
			seen_add ++;
		}
		else if (!(cur_var->flags & PKG_VAR_INSTALL)
				&& cur_var->unit->pkg->type == PKG_INSTALLED) {
			del_var = cur_var;
			seen_del ++;
		}
	}

	if (seen_add > 1) {
		pkg_emit_error("internal solver error: more than two packages to install(%d) "
				"from the same uid: %s", seen_add, var->uid);
		return;
	}
	else if (seen_add != 0 || seen_del != 0) {
		if (seen_add > 0) {
			res = xcalloc(1, sizeof(struct pkg_solved));
			/* Pure install */
			if (seen_del == 0) {
				res->items[0] = add_var->unit;
				res->type = (j->type == PKG_JOBS_FETCH) ?
								PKG_SOLVED_FETCH : PKG_SOLVED_INSTALL;
				vec_push(&j->jobs, res);
				dbg(3, "pkg_solve: schedule installation of %s %s",
					add_var->uid, add_var->digest);
			}
			else {
				/* Upgrade */
				res->items[0] = add_var->unit;
				res->items[1] = del_var->unit;
				res->type = PKG_SOLVED_UPGRADE;
				vec_push(&j->jobs, res);
				dbg(3, "pkg_solve: schedule upgrade of %s from %s to %s",
					del_var->uid, del_var->digest, add_var->digest);
			}
		}

		/*
		 * For delete requests there could be multiple delete requests per UID,
		 * so we need to re-process vars and add all delete jobs required.
		 */
		LL_FOREACH(var, cur_var) {
			if (!(cur_var->flags & PKG_VAR_INSTALL) &&
					cur_var->unit->pkg->type == PKG_INSTALLED) {
				/* Skip already added items */
				if (seen_add > 0 && cur_var == del_var)
					continue;

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

				res = xcalloc(1, sizeof(struct pkg_solved));
				res->items[0] = cur_var->unit;
				res->type = PKG_SOLVED_DELETE;
				vec_push(&j->jobs, res);
				dbg(3, "schedule deletion of %s %s",
					cur_var->uid, cur_var->digest);
			}
		}
	}
	else {
		dbg(2, "ignoring package %s(%s) as its state has not been changed",
				var->uid, var->digest);
	}
}

int
pkg_solve_sat_to_jobs(struct pkg_solve_problem *problem)
{
	struct pkg_solve_variable *var;
	pkghash_it it = pkghash_iterator(problem->variables_by_uid);

	while (pkghash_next(&it)) {
		var = (struct pkg_solve_variable *)it.value;
		dbg(4, "check variable with uid %s", var->uid);
		pkg_solve_insert_res_job(var, problem);
	}

	return (EPKG_OK);
}

static bool
pkg_solve_parse_sat_output_store(struct pkg_solve_problem *problem, const char *var_str)
{
	struct pkg_solve_variable *var;
	ssize_t order;

	order = strtol(var_str, NULL, 10);
	if (order == 0)
		return (true);
	if (order < 0) {
		/* negative value means false */
		order = - order - 1;
		if ((size_t)order < problem->nvars) {
			var = problem->variables + order;
			var->flags &= ~PKG_VAR_INSTALL;
		}
	} else {
		/* positive value means true */
		order = order - 1;
		if ((size_t)order < problem->nvars) {
			var = problem->variables + order;
			var->flags |= PKG_VAR_INSTALL;
		}
	}
	return (false);
}

int
pkg_solve_parse_sat_output(FILE *f, struct pkg_solve_problem *problem)
{
	int ret = EPKG_OK;
	char *line = NULL, *var_str, *begin;
	size_t linecap = 0;
	bool got_sat = false, done = false;

	while (getline(&line, &linecap, f) > 0) {
		if (strncmp(line, "SAT", 3) == 0) {
			got_sat = true;
		}
		else if (got_sat) {
			begin = line;
			do {
				var_str = strsep(&begin, " \t");
				/* Skip unexpected lines */
				if (var_str == NULL || (!isdigit(*var_str) && *var_str != '-'))
					continue;
				if (pkg_solve_parse_sat_output_store(problem, var_str))
					done = true;
			} while (begin != NULL);
		}
		else if (strncmp(line, "v ", 2) == 0) {
			begin = line + 2;
			do {
				var_str = strsep(&begin, " \t");
				/* Skip unexpected lines */
				if (var_str == NULL || (!isdigit(*var_str) && *var_str != '-'))
					continue;
				if (pkg_solve_parse_sat_output_store(problem, var_str))
					done = true;
			} while (begin != NULL);
		}
		else {
			/* Slightly ignore anything from solver */
			continue;
		}
	}

	if (done)
		ret = pkg_solve_sat_to_jobs(problem);
	else {
		pkg_emit_error("cannot parse sat solver output");
		ret = EPKG_FATAL;
	}

	free(line);

	return (ret);
}