Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
HardenedBSD-pkg libpkg repo binary query.c
/*
 * Copyright (c) 2014, Vsevolod Stakhov
 * Copyright (c) 2024, Baptiste Daroussin <bapt@FreeBSD.org>
 * Copyright (c) 2023, Serenity Cyber Security, LLC
 *                     Author: Gleb Popov <arrowd@FreeBSD.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * 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 ''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 AUTHOR 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 <assert.h>
#include <errno.h>
#include <regex.h>
#include <grp.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <fcntl.h>
#include <fnmatch.h>

#include <sqlite3.h>

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

static struct pkg_repo_it* pkg_repo_binary_it_new(struct pkg_repo *repo,
	sqlite3_stmt *s, short flags);

struct pkg_repo_group {
	size_t index;
	ucl_object_t *groups;
};
static int pkg_repo_binary_it_next(struct pkg_repo_it *it, struct pkg **pkg_p, unsigned flags);
static void pkg_repo_binary_it_free(struct pkg_repo_it *it);
static void pkg_repo_binary_it_reset(struct pkg_repo_it *it);

static int pkg_repo_binary_group_it_next(struct pkg_repo_it *it, struct pkg **pkg_p, unsigned flags);
static void pkg_repo_binary_group_it_free(struct pkg_repo_it *it);
static void pkg_repo_binary_group_it_reset(struct pkg_repo_it *it);

static const struct pkg_repo_it_ops pkg_repo_binary_it_ops = {
	.next = pkg_repo_binary_it_next,
	.free = pkg_repo_binary_it_free,
	.reset = pkg_repo_binary_it_reset
};

static const struct pkg_repo_it_ops pkg_repo_binary_group_it_ops = {
	.next = pkg_repo_binary_group_it_next,
	.free = pkg_repo_binary_group_it_free,
	.reset = pkg_repo_binary_group_it_reset
};

static struct pkg_repo_it*
pkg_repo_binary_it_new(struct pkg_repo *repo, sqlite3_stmt *s, short flags)
{
	struct pkg_repo_it *it;
	struct pkgdb fakedb;

	it = xmalloc(sizeof(*it));

	it->ops = &pkg_repo_binary_it_ops;
	it->flags = flags;
	it->repo = repo;

	fakedb.sqlite = PRIV_GET(repo);
	it->data = pkgdb_it_new_sqlite(&fakedb, s, PKG_REMOTE, flags);

	if (it->data == NULL) {
		free(it);
		return (NULL);
	}

	return (it);
}

static struct pkg_repo_it *
pkg_repo_binary_group_it_new(struct pkg_repo *repo, ucl_object_t *matching)
{
	struct pkg_repo_group *prg;
	struct pkg_repo_it *it;

	it = xcalloc(1, sizeof(*it));
	prg = xcalloc(1, sizeof(*prg));
	prg->groups = matching;
	it->repo = repo;
	it->ops = &pkg_repo_binary_group_it_ops;
	it->data = prg;

	return (it);
}

static int
pkg_repo_binary_it_next(struct pkg_repo_it *it, struct pkg **pkg_p, unsigned flags)
{
	return (pkgdb_it_next(it->data, pkg_p, flags));
}

static int
pkg_repo_binary_group_it_next(struct pkg_repo_it *it, struct pkg **pkg_p, unsigned flags __unused)
{
	int ret;
	struct pkg_repo_group *prg;
	const ucl_object_t *o, *el, *ar;
	ucl_object_iter_t oit = NULL;

	prg = it->data;
	if (prg->index == ucl_array_size(prg->groups))
		return (EPKG_END);

	el = ucl_array_find_index(prg->groups, prg->index);
	prg->index++;
	pkg_free(*pkg_p);
	if ((ret = pkg_new(pkg_p, PKG_GROUP_REMOTE)) != EPKG_OK)
		return (ret);
	o = ucl_object_find_key(el, "name");
	xasprintf(&(*pkg_p)->name, ucl_object_tostring(o));
	xasprintf(&(*pkg_p)->uid, "@%s", (*pkg_p)->name);
	o = ucl_object_find_key(el, "comment");
	xasprintf(&(*pkg_p)->comment, ucl_object_tostring(o));
	ar = ucl_object_find_key(el, "depends");
	while ((o = ucl_iterate_object(ar, &oit, true))) {
		pkg_adddep(*pkg_p, ucl_object_tostring(o), NULL, NULL, false);
	}
	pkg_kv_add(&(*pkg_p)->annotations, "repository",   it->repo->name, "annotation");

	return (EPKG_OK);
}

static void
pkg_repo_binary_it_free(struct pkg_repo_it *it)
{
	pkgdb_it_free(it->data);
	free(it);
}

static void
pkg_repo_binary_group_it_free(struct pkg_repo_it *it)
{
	struct pkg_repo_group *prg = it->data;
	free(prg->groups);
	free(prg);
	free(it);
}

static void
pkg_repo_binary_it_reset(struct pkg_repo_it *it)
{
	pkgdb_it_reset(it->data);
}

static void
pkg_repo_binary_group_it_reset(struct pkg_repo_it *it)
{
	struct pkg_repo_group *prg = it->data;

	prg->index = 0;
}

struct pkg_repo_it *
pkg_repo_binary_groupquery(struct pkg_repo *repo, const char *pattern, match_t match)
{
	return (pkg_repo_binary_groupsearch(repo, pattern, match, FIELD_NAME));
}

struct pkg_repo_it *
pkg_repo_binary_query(struct pkg_repo *repo, const char *cond, const char *pattern, match_t match)
{
	sqlite3 *sqlite = PRIV_GET(repo);
	sqlite3_stmt	*stmt = NULL;
	char *sql = NULL;
	const char	*comp = NULL;
	const char basesql_quick[] = ""
		"SELECT DISTINCT(p.id), origin, p.name, p.name as uniqueid, version, comment, "
		"prefix, desc, arch, maintainer, www, "
		"licenselogic, flatsize, pkgsize, "
		"cksum, manifestdigest, path AS repopath, '%s' AS dbname "
		"FROM packages  as p "
		" %s "
		"%s%s%s "
		"ORDER BY p.name;";
	const char basesql[] = ""
		"WITH flavors AS "
		"  (SELECT package_id, value.annotation AS flavor FROM pkg_annotation "
		"   LEFT JOIN annotation tag ON pkg_annotation.tag_id = tag.annotation_id "
		"   LEFT JOIN annotation value ON pkg_annotation.value_id = value.annotation_id "
		"   WHERE tag.annotation = 'flavor') "

		"SELECT DISTINCT(p.id), origin, p.name, p.name as uniqueid, version, comment, "
		"prefix, desc, arch, maintainer, www, "
		"licenselogic, flatsize, pkgsize, "
		"cksum, manifestdigest, path AS repopath, '%s' AS dbname "
		"FROM packages  as p "
		"LEFT JOIN pkg_categories ON p.id = pkg_categories.package_id "
		"LEFT JOIN categories ON categories.id = pkg_categories.category_id "
		"LEFT JOIN flavors ON flavors.package_id = p.id "
		" %s "
		"%s%s%s "
		"ORDER BY p.name;";

	const char *bsql = (match == MATCH_INTERNAL) ? basesql_quick : basesql;

	if (match != MATCH_ALL && (pattern == NULL || pattern[0] == '\0'))
		return (NULL);

	comp = pkgdb_get_pattern_query(pattern, match);
	if (comp == NULL)
		comp = "";
	if (cond == NULL)
		xasprintf(&sql, bsql, repo->name, comp, "", "", "");
	else
		xasprintf(&sql, bsql, repo->name, comp,
		    comp[0] != '\0' ? "AND (" : "WHERE ( ", cond + 7, " )");

	stmt = prepare_sql(sqlite, sql);
	free(sql);
	if (stmt == NULL)
		return (NULL);

	if (match != MATCH_ALL)
		sqlite3_bind_text(stmt, 1, pattern, -1, SQLITE_TRANSIENT);
	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

struct pkg_repo_it *
pkg_repo_binary_shlib_provide(struct pkg_repo *repo, const char *require)
{
	sqlite3_stmt	*stmt;
	sqlite3 *sqlite = PRIV_GET(repo);
	char *sql = NULL;
	const char	 basesql[] = ""
			"SELECT p.id, p.origin, p.name, p.version, p.comment, "
			"p.name as uniqueid, "
			"p.prefix, p.desc, p.arch, p.maintainer, p.www, "
			"p.licenselogic, p.flatsize, p.pkgsize, "
			"p.cksum, p.manifestdigest, p.path AS repopath, '%s' AS dbname "
			"FROM packages AS p INNER JOIN pkg_shlibs_provided AS ps ON "
			"p.id = ps.package_id "
			"WHERE ps.shlib_id IN (SELECT id FROM shlibs WHERE "
			"name BETWEEN ?1 AND ?1 || '.9');";

	xasprintf(&sql, basesql, repo->name);

	stmt = prepare_sql(sqlite, sql);
	free(sql);
	if (stmt == NULL)
		return (NULL);

	sqlite3_bind_text(stmt, 1, require, -1, SQLITE_TRANSIENT);
	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

struct pkg_repo_it *
pkg_repo_binary_provide(struct pkg_repo *repo, const char *require)
{
	sqlite3_stmt	*stmt;
	sqlite3 *sqlite = PRIV_GET(repo);
	char *sql = NULL;
	const char	 basesql[] = ""
			"SELECT p.id, p.origin, p.name, p.version, p.comment, "
			"p.name as uniqueid, "
			"p.prefix, p.desc, p.arch, p.maintainer, p.www, "
			"p.licenselogic, p.flatsize, p.pkgsize, "
			"p.cksum, p.manifestdigest, p.path AS repopath, '%s' AS dbname "
			"FROM packages AS p INNER JOIN pkg_provides AS ps ON "
			"p.id = ps.package_id "
			"WHERE ps.provide_id IN (SELECT id from provides WHERE "
			"provide = ?1 );";

	xasprintf(&sql, basesql, repo->name);

	stmt = prepare_sql(sqlite, sql);
	free(sql);
	if (stmt == NULL)
		return (NULL);

	sqlite3_bind_text(stmt, 1, require, -1, SQLITE_TRANSIENT);
	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

struct pkg_repo_it *
pkg_repo_binary_shlib_require(struct pkg_repo *repo, const char *provide)
{
	sqlite3_stmt	*stmt;
	sqlite3 *sqlite = PRIV_GET(repo);
	char *sql = NULL;
	const char	 basesql[] = ""
			"SELECT p.id, p.origin, p.name, p.version, p.comment, "
			"p.name as uniqueid, "
			"p.prefix, p.desc, p.arch, p.maintainer, p.www, "
			"p.licenselogic, p.flatsize, p.pkgsize, "
			"p.cksum, p.manifestdigest, p.path AS repopath, '%s' AS dbname "
			"FROM packages AS p INNER JOIN pkg_shlibs_required AS ps ON "
			"p.id = ps.package_id "
			"WHERE ps.shlib_id = (SELECT id FROM shlibs WHERE name=?1);";

	xasprintf(&sql, basesql, repo->name);

	stmt = prepare_sql(sqlite, sql);
	free(sql);
	if (stmt == NULL)
		return (NULL);

	pkg_debug(1, "> loading provides");
	sqlite3_bind_text(stmt, 1, provide, -1, SQLITE_TRANSIENT);
	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

struct pkg_repo_it *
pkg_repo_binary_require(struct pkg_repo *repo, const char *provide)
{
	sqlite3_stmt	*stmt;
	sqlite3 *sqlite = PRIV_GET(repo);
	char *sql = NULL;
	const char	 basesql[] = ""
			"SELECT p.id, p.origin, p.name, p.version, p.comment, "
			"p.name as uniqueid, "
			"p.prefix, p.desc, p.arch, p.maintainer, p.www, "
			"p.licenselogic, p.flatsize, p.pkgsize, "
			"p.cksum, p.manifestdigest, p.path AS repopath, '%s' AS dbname "
			"FROM packages AS p INNER JOIN pkg_requires AS ps ON "
			"p.id = ps.package_id "
			"WHERE ps.require_id = (SELECT id FROM requires WHERE require=?1);";

	xasprintf(&sql, basesql, repo->name);

	stmt = prepare_sql(sqlite, sql);
	free(sql);
	if (stmt == NULL)
		return (NULL);

	sqlite3_bind_text(stmt, 1, provide, -1, SQLITE_TRANSIENT);
	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

struct pkg_repo_it *
pkg_repo_binary_file_which(struct pkg_repo *repo, const char *path, bool glob)
{
	sqlite3_stmt	*stmt;
	sqlite3 *sqlite = PRIV_GET(repo);
	char *sql = NULL;
	int64_t has_files = 0;

	/* Check if file_dirs table exists (filesite may not have been loaded) */
	if (get_pragma(sqlite,
	    "SELECT count(name) FROM sqlite_master "
	    "WHERE type='table' AND name='file_dirs';",
	    &has_files, false) != EPKG_OK || has_files == 0) {
		return (NULL);
	}

	if (glob) {
		const char basesql[] = ""
		    "SELECT p.id, p.origin, p.name, p.version, p.comment, "
		    "p.name as uniqueid, "
		    "p.prefix, p.desc, p.arch, p.maintainer, p.www, "
		    "p.licenselogic, p.flatsize, p.pkgsize, "
		    "p.cksum, p.manifestdigest, p.path AS repopath, '%s' AS dbname "
		    "FROM packages AS p "
		    "INNER JOIN pkg_files AS pf ON p.id = pf.package_id "
		    "INNER JOIN file_dirs AS fd ON pf.dir_id = fd.id "
		    "WHERE fd.path || '/' || pf.name GLOB ?1 "
		    "GROUP BY p.id;";

		xasprintf(&sql, basesql, repo->name);
		stmt = prepare_sql(sqlite, sql);
		free(sql);
		if (stmt == NULL)
			return (NULL);

		sqlite3_bind_text(stmt, 1, path, -1, SQLITE_TRANSIENT);
	} else {
		const char *last_slash = strrchr(path, '/');
		if (last_slash == NULL)
			return (NULL);

		const char basesql[] = ""
		    "SELECT p.id, p.origin, p.name, p.version, p.comment, "
		    "p.name as uniqueid, "
		    "p.prefix, p.desc, p.arch, p.maintainer, p.www, "
		    "p.licenselogic, p.flatsize, p.pkgsize, "
		    "p.cksum, p.manifestdigest, p.path AS repopath, '%s' AS dbname "
		    "FROM packages AS p "
		    "INNER JOIN pkg_files AS pf ON p.id = pf.package_id "
		    "INNER JOIN file_dirs AS fd ON pf.dir_id = fd.id "
		    "WHERE fd.path = ?1 AND pf.name = ?2 "
		    "GROUP BY p.id;";

		xasprintf(&sql, basesql, repo->name);
		stmt = prepare_sql(sqlite, sql);
		free(sql);
		if (stmt == NULL)
			return (NULL);

		size_t dir_len = last_slash - path;
		if (dir_len == 0) dir_len = 1;
		sqlite3_bind_text(stmt, 1, path, dir_len, SQLITE_TRANSIENT);
		sqlite3_bind_text(stmt, 2, last_slash + 1, -1, SQLITE_TRANSIENT);
	}

	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

static const char *
pkg_repo_binary_search_how(match_t match)
{
	const char	*how = NULL;

	switch (match) {
	case MATCH_ALL:
		how = "TRUE";
		break;
	case MATCH_INTERNAL:
		how = "%s = ?1";
		break;
	case MATCH_EXACT:
		if (pkgdb_case_sensitive())
			how = "%s = ?1";
		else
			how = "%s = ?1 COLLATE NOCASE";
		break;
	case MATCH_GLOB:
		if (pkgdb_case_sensitive())
			how = "%s GLOB ?1";
		else
			how = "lower(%s) GLOB lower(?1)";
		break;
	case MATCH_REGEX:
		how = "%s REGEXP ?1";
		break;
	}

	return (how);
}

static int
pkg_repo_binary_build_search_query(xstring *sql, match_t match,
    pkgdb_field field, pkgdb_field sort)
{
	const char	*how;
	const char	*what = NULL;
	const char	*orderby = NULL;

	how = pkg_repo_binary_search_how(match);

	switch (field) {
	case FIELD_NONE:
		what = NULL;
		break;
	case FIELD_ORIGIN:
		what = "categories.name || substr(origin, instr(origin, '/'))";
		break;
	case FIELD_FLAVOR:
		what = "categories.name || substr(origin, instr(origin, '/')) || '@' || flavor";
		break;
	case FIELD_NAME:
		what = "p.name";
		break;
	case FIELD_NAMEVER:
		what = "p.name || '-' || version";
		break;
	case FIELD_COMMENT:
		what = "comment";
		break;
	case FIELD_DESC:
		what = "desc";
		break;
	case FIELD_COMMENT_DESC:
		break;
	}

	if (field == FIELD_COMMENT_DESC && how != NULL) {
		fprintf(sql->fp, "(");
		fprintf(sql->fp, how, "comment");
		fprintf(sql->fp, " OR ");
		fprintf(sql->fp, how, "desc");
		fprintf(sql->fp, ")");
	} else if (what != NULL && how != NULL)
		fprintf(sql->fp, how, what);

	switch (sort) {
	case FIELD_NONE:
		orderby = NULL;
		break;
	case FIELD_ORIGIN:
		orderby = " ORDER BY origin";
		break;
	case FIELD_FLAVOR:
		orderby = " ORDER BY p.name";
	case FIELD_NAME:
		orderby = " ORDER BY p.name";
		break;
	case FIELD_NAMEVER:
		orderby = " ORDER BY p.name, version";
		break;
	case FIELD_COMMENT:
		orderby = " ORDER BY comment";
		break;
	case FIELD_DESC:
		orderby = " ORDER BY desc";
		break;
	case FIELD_COMMENT_DESC:
		orderby = " ORDER BY comment";
		break;
	}

	if (orderby != NULL)
		fprintf(sql->fp, "%s", orderby);

	return (EPKG_OK);
}

struct pkg_repo_it *
pkg_repo_binary_search(struct pkg_repo *repo, const char *pattern, match_t match,
    pkgdb_field field, pkgdb_field sort)
{
	sqlite3 *sqlite = PRIV_GET(repo);
	sqlite3_stmt	*stmt = NULL;
	xstring	*sql = NULL;
	char *sqlcmd = NULL;
	const char	*multireposql = ""
		"WITH flavors AS "
		"  (SELECT package_id, value.annotation AS flavor FROM pkg_annotation "
		"   LEFT JOIN annotation tag ON pkg_annotation.tag_id = tag.annotation_id "
		"   LEFT JOIN annotation value ON pkg_annotation.value_id = value.annotation_id "
		"   WHERE tag.annotation = 'flavor') "
		"SELECT DISTINCT p.id, origin, p.name, version, comment, "
		"prefix, desc, arch, maintainer, www, "
		"licenselogic, flatsize, pkgsize, "
		"cksum, path AS repopath, '%1$s' AS dbname, '%2$s' AS repourl "
		"FROM packages  as p "
		"LEFT JOIN pkg_categories ON p.id = pkg_categories.package_id "
		"LEFT JOIN categories ON categories.id = pkg_categories.category_id "
		"LEFT JOIN flavors ON flavors.package_id = p.id ";

	if (match != MATCH_ALL && (pattern == NULL || pattern[0] == '\0'))
		return (NULL);

	sql = xstring_new();
	fprintf(sql->fp, multireposql, repo->name, repo->url);

	/* close the UNIONs and build the search query */
	fprintf(sql->fp, "%s", "WHERE ");

	pkg_repo_binary_build_search_query(sql, match, field, sort);
	fprintf(sql->fp, "%s", ";");
	sqlcmd = xstring_get(sql);

	stmt = prepare_sql(sqlite, sqlcmd);
	free(sqlcmd);
	if (stmt == NULL)
		return (NULL);

	sqlite3_bind_text(stmt, 1, pattern, -1, SQLITE_TRANSIENT);
	pkgdb_debug(4, stmt);

	return (pkg_repo_binary_it_new(repo, stmt, PKGDB_IT_FLAG_ONCE));
}

struct pkg_repo_it *
pkg_repo_binary_groupsearch(struct pkg_repo *repo, const char *pattern, match_t match,
    pkgdb_field field)
{
	ucl_object_t *groups, *ar, *el;
	const ucl_object_t *o;
	const char *cmp;
	int fd;
	regex_t *re = NULL;
	int flag = 0;
	bool in_comment = false;
	bool start_with = false;

	switch (field) {
		case FIELD_NAME:
		case FIELD_NAMEVER:
			break;
		case FIELD_COMMENT:
			in_comment = true;
			break;
		default:
			/* we cannot search in other fields */
			return (NULL);
	}

	if (repo->dfd == -1 && pkg_repo_open(repo) == EPKG_FATAL)
		return (NULL);
	fd = openat(repo->dfd, "groups.ucl", O_RDONLY|O_CLOEXEC);
	if (fd == -1)
		return (NULL);
	groups = ucl_parse_fd(fd, repo->name);
	close(fd);
	if (groups == NULL)
		return (NULL);

	if (ucl_object_type(groups) != UCL_ARRAY) {
		ucl_object_unref(groups);
		return (NULL);
	}
	if (*pattern == '@') {
		pattern++;
		start_with = true;
	}

	ar = NULL;
	while (ucl_array_size(groups) > 0) {
		el = ucl_array_pop_first(groups);
		if (in_comment) {
			o = ucl_object_find_key(el, "comment");
		} else {
			o = ucl_object_find_key(el, "name");
		}
		if (o == NULL) {
			ucl_object_unref(el);
			continue;
		}
		cmp = ucl_object_tostring(o);
		switch (match) {
		case MATCH_ALL:
			break;
		case MATCH_INTERNAL:
			if (!STREQ(cmp, pattern))
				continue;
			break;
		case MATCH_EXACT:
			if (pkgdb_case_sensitive()) {
				if (!STREQ(cmp, pattern))
					continue;
			} else {
				if (!STRIEQ(cmp, pattern))
					continue;
			}
			break;
		case MATCH_GLOB:
			if (pkgdb_case_sensitive() != 0)
				flag = FNM_CASEFOLD;
			if (fnmatch(cmp, pattern, flag) == FNM_NOMATCH)
				continue;
			break;
		case MATCH_REGEX:
			if (re == NULL) {
				char *newpattern = NULL;
				const char *pat = pattern;
				flag = REG_EXTENDED | REG_NOSUB;
				if (pkgdb_case_sensitive() != 0)
					flag |= REG_ICASE;
				re = xmalloc(sizeof(regex_t));
				if (start_with) {
					xasprintf(&newpattern, "^%s", pattern);
					pat = newpattern;
				}
				if (regcomp(re, pat, flag) != 0) {
					pkg_emit_error("Invalid regex: 'pattern'");
					ucl_object_unref(groups);
					if (ar != NULL)
						ucl_object_unref(ar);
					free(newpattern);
					return (NULL);
				}
				free(newpattern);
			}
			if (regexec(re, cmp, 0, NULL, 0) == REG_NOMATCH)
				continue;
			break;
		}
		if (ar == NULL)
			ar = ucl_object_typed_new(UCL_ARRAY);
		ucl_array_append(ar, el);
	}

	if (re != NULL)
		regfree(re);
	ucl_object_unref(groups);

	if (ar == NULL)
		return (NULL);

	return (pkg_repo_binary_group_it_new(repo, ar));
}

int
pkg_repo_binary_ensure_loaded(struct pkg_repo *repo,
	struct pkg *pkg, unsigned flags)
{
	sqlite3 *sqlite = PRIV_GET(repo);
	struct pkg *cached = NULL;
	char path[MAXPATHLEN];
	int rc;

	if (pkg->type == PKG_GROUP_REMOTE)
		return (EPKG_OK);
	flags &= PKG_LOAD_FILES|PKG_LOAD_DIRS;
	/*
	 * If info is already present, done.
	 */
	if ((pkg->flags & flags) == flags) {
		return EPKG_OK;
	}
	if (pkg->type == PKG_INSTALLED) {
		pkg_emit_error("cached package %s-%s: "
			       "attempting to load info from an installed package",
			       pkg->name, pkg->version);
		return EPKG_FATAL;

		/* XXX If package is installed, get info from SQLite ???  */
		rc = pkgdb_ensure_loaded_sqlite(sqlite, pkg, flags);
		if (rc != EPKG_OK) {
			return rc;
		}
		/* probably unnecessary */
		if ((pkg->flags & flags) != flags) {
			return EPKG_FATAL;
		}
		return rc;
	}
	/*
	 * Try to get that information from fetched package in cache
	 */

	if (pkg_repo_cached_name(pkg, path, sizeof(path)) != EPKG_OK)
		return (EPKG_FATAL);

	pkg_debug(1, "Binary> loading %s", path);
	if (pkg_open(&cached, path, PKG_OPEN_TRY) != EPKG_OK) {
		pkg_free(cached);
		return EPKG_FATAL;
	}

	/* Now move required elements to the provided package */
	pkg_list_free(pkg, PKG_FILES);
	pkg_list_free(pkg, PKG_CONFIG_FILES);
	pkg_list_free(pkg, PKG_DIRS);
	pkg->files = cached->files;
	pkg->filehash = cached->filehash;
	pkg->config_files = cached->config_files;
	pkg->config_files_hash = cached->config_files_hash;
	pkg->dirs = cached->dirs;
	pkg->dirhash = cached->dirhash;
	cached->files = NULL;
	cached->filehash = NULL;
	cached->config_files = NULL;
	cached->config_files_hash = NULL;
	cached->dirs = NULL;
	cached->dirhash = NULL;

	pkg_free(cached);
	pkg->flags |= flags;

	return EPKG_OK;
}

int64_t
pkg_repo_binary_stat(struct pkg_repo *repo, pkg_stats_t type)
{
	sqlite3 *sqlite = PRIV_GET(repo);
	sqlite3_stmt	*stmt = NULL;
	int64_t		 stats = 0;
	const char *sql = NULL;

	switch(type) {
	case PKG_STATS_LOCAL_COUNT:
	case PKG_STATS_REMOTE_REPOS:
	case PKG_STATS_LOCAL_SIZE:
		return (stats);
	case PKG_STATS_REMOTE_UNIQUE:
		sql = "SELECT COUNT(id) FROM main.packages;";
		break;
	case PKG_STATS_REMOTE_COUNT:
		sql = "SELECT COUNT(id) FROM main.packages;";
		break;
	case PKG_STATS_REMOTE_SIZE:
		sql = "SELECT SUM(pkgsize) FROM main.packages;";
		break;
	}

	pkg_debug(4, "binary_repo: running '%s'", sql);
	stmt = prepare_sql(sqlite, sql);

	if (stmt == NULL)
		return (stats);

	while (sqlite3_step(stmt) != SQLITE_DONE) {
		stats = sqlite3_column_int64(stmt, 0);
	}

	sqlite3_finalize(stmt);

	return (stats);
}