#include <sys/param.h>
#include <sys/queue.h>
#include <sys/utsname.h>
#include <sys/sysctl.h>
#include <dirent.h>
#include <ucl.h>
#include <err.h>
#include <errno.h>
#include <libutil.h>
#include <paths.h>
#include <stdbool.h>
#include <unistd.h>
#include <ctype.h>
#include "config.h"
struct config_value {
char *value;
STAILQ_ENTRY(config_value) next;
};
struct config_entry {
uint8_t type;
const char *key;
const char *val;
char *value;
STAILQ_HEAD(, config_value) *list;
bool envset;
bool main_only;
};
static struct repositories repositories = STAILQ_HEAD_INITIALIZER(repositories);
static struct config_entry c[] = {
[PACKAGESITE] = {
PKG_CONFIG_STRING,
"PACKAGESITE",
URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
NULL,
NULL,
false,
false,
},
[ABI] = {
PKG_CONFIG_STRING,
"ABI",
NULL,
NULL,
NULL,
false,
true,
},
[MIRROR_TYPE] = {
PKG_CONFIG_STRING,
"MIRROR_TYPE",
"SRV",
NULL,
NULL,
false,
false,
},
[ASSUME_ALWAYS_YES] = {
PKG_CONFIG_BOOL,
"ASSUME_ALWAYS_YES",
"NO",
NULL,
NULL,
false,
true,
},
[SIGNATURE_TYPE] = {
PKG_CONFIG_STRING,
"SIGNATURE_TYPE",
NULL,
NULL,
NULL,
false,
false,
},
[FINGERPRINTS] = {
PKG_CONFIG_STRING,
"FINGERPRINTS",
NULL,
NULL,
NULL,
false,
false,
},
[REPOS_DIR] = {
PKG_CONFIG_LIST,
"REPOS_DIR",
NULL,
NULL,
NULL,
false,
true,
},
[PUBKEY] = {
PKG_CONFIG_STRING,
"PUBKEY",
NULL,
NULL,
NULL,
false,
false
},
[PKG_ENV] = {
PKG_CONFIG_OBJECT,
"PKG_ENV",
NULL,
NULL,
NULL,
false,
false,
}
};
static char *
pkg_get_myabi(void)
{
struct utsname uts;
char machine_arch[255];
char *abi;
size_t len;
int error;
error = uname(&uts);
if (error)
return (NULL);
len = sizeof(machine_arch);
error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
if (error)
return (NULL);
machine_arch[len] = '\0';
error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
machine_arch);
if (error < 0)
return (NULL);
return (abi);
}
static void
subst_packagesite(const char *abi)
{
char *newval;
const char *variable_string;
const char *oldval;
if (c[PACKAGESITE].value != NULL)
oldval = c[PACKAGESITE].value;
else
oldval = c[PACKAGESITE].val;
if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
return;
asprintf(&newval, "%.*s%s%s",
(int)(variable_string - oldval), oldval, abi,
variable_string + strlen("${ABI}"));
if (newval == NULL)
errx(EXIT_FAILURE, "asprintf");
free(c[PACKAGESITE].value);
c[PACKAGESITE].value = newval;
}
static int
boolstr_to_bool(const char *str)
{
if (str != NULL && (strcasecmp(str, "true") == 0 ||
strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
str[0] == '1'))
return (true);
return (false);
}
static void
config_parse(const ucl_object_t *obj)
{
FILE *buffp;
char *buf = NULL;
size_t bufsz = 0;
const ucl_object_t *cur, *seq, *tmp;
ucl_object_iter_t it = NULL, itseq = NULL, it_obj = NULL;
struct config_entry *temp_config;
struct config_value *cv;
const char *key, *evkey;
int i;
size_t j;
temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
buffp = open_memstream(&buf, &bufsz);
if (buffp == NULL)
err(EXIT_FAILURE, "open_memstream()");
while ((cur = ucl_iterate_object(obj, &it, true))) {
key = ucl_object_key(cur);
if (key == NULL)
continue;
if (buf != NULL)
memset(buf, 0, bufsz);
rewind(buffp);
for (j = 0; j < strlen(key); ++j)
fputc(toupper(key[j]), buffp);
fflush(buffp);
for (i = 0; i < CONFIG_SIZE; i++) {
if (strcmp(buf, c[i].key) == 0)
break;
}
if (i == CONFIG_SIZE)
continue;
if (c[i].envset)
continue;
switch (c[i].type) {
case PKG_CONFIG_LIST:
if (cur->type != UCL_ARRAY) {
warnx("Skipping invalid array "
"value for %s.\n", c[i].key);
continue;
}
temp_config[i].list =
malloc(sizeof(*temp_config[i].list));
STAILQ_INIT(temp_config[i].list);
while ((seq = ucl_iterate_object(cur, &itseq, true))) {
if (seq->type != UCL_STRING)
continue;
cv = malloc(sizeof(struct config_value));
cv->value =
strdup(ucl_object_tostring(seq));
STAILQ_INSERT_TAIL(temp_config[i].list, cv,
next);
}
break;
case PKG_CONFIG_BOOL:
temp_config[i].value =
strdup(ucl_object_toboolean(cur) ? "yes" : "no");
break;
case PKG_CONFIG_OBJECT:
if (strcmp(c[i].key, "PKG_ENV") == 0) {
while ((tmp =
ucl_iterate_object(cur, &it_obj, true))) {
evkey = ucl_object_key(tmp);
if (evkey != NULL && *evkey != '\0') {
setenv(evkey, ucl_object_tostring_forced(tmp), 1);
}
}
}
break;
default:
temp_config[i].value = strdup(ucl_object_tostring(cur));
break;
}
}
for (i = 0; i < CONFIG_SIZE; i++) {
if (c[i].envset)
continue;
if (c[i].main_only == true)
continue;
switch (c[i].type) {
case PKG_CONFIG_LIST:
c[i].list = temp_config[i].list;
break;
default:
c[i].value = temp_config[i].value;
break;
}
}
free(temp_config);
fclose(buffp);
free(buf);
}
static void
parse_mirror_type(struct repository *r, const char *mt)
{
if (strcasecmp(mt, "srv") == 0)
r->mirror_type = MIRROR_SRV;
else
r->mirror_type = MIRROR_NONE;
}
static void
repo_free(struct repository *r)
{
free(r->name);
free(r->url);
free(r->fingerprints);
free(r->pubkey);
free(r);
}
static bool
parse_signature_type(struct repository *repo, const char *st)
{
if (strcasecmp(st, "FINGERPRINTS") == 0)
repo->signature_type = SIGNATURE_FINGERPRINT;
else if (strcasecmp(st, "PUBKEY") == 0)
repo->signature_type = SIGNATURE_PUBKEY;
else if (strcasecmp(st, "NONE") == 0)
repo->signature_type = SIGNATURE_NONE;
else {
warnx("Signature type %s is not supported for bootstrapping,"
" ignoring repository %s", st, repo->name);
return (false);
}
return (true);
}
static struct repository *
find_repository(const char *name)
{
struct repository *repo;
STAILQ_FOREACH(repo, &repositories, next) {
if (strcmp(repo->name, name) == 0)
return (repo);
}
return (NULL);
}
static void
parse_repo(const ucl_object_t *o)
{
const ucl_object_t *cur;
const char *key, *reponame;
ucl_object_iter_t it = NULL;
bool newrepo = false;
struct repository *repo;
reponame = ucl_object_key(o);
repo = find_repository(reponame);
if (repo == NULL) {
repo = calloc(1, sizeof(struct repository));
if (repo == NULL)
err(EXIT_FAILURE, "calloc");
repo->name = strdup(reponame);
if (repo->name == NULL)
err(EXIT_FAILURE, "strdup");
newrepo = true;
}
while ((cur = ucl_iterate_object(o, &it, true))) {
key = ucl_object_key(cur);
if (key == NULL)
continue;
if (strcasecmp(key, "url") == 0) {
free(repo->url);
repo->url = strdup(ucl_object_tostring(cur));
if (repo->url == NULL)
err(EXIT_FAILURE, "strdup");
} else if (strcasecmp(key, "mirror_type") == 0) {
parse_mirror_type(repo, ucl_object_tostring(cur));
} else if (strcasecmp(key, "signature_type") == 0) {
if (!parse_signature_type(repo, ucl_object_tostring(cur))) {
if (newrepo)
repo_free(repo);
else
STAILQ_REMOVE(&repositories, repo, repository, next);
return;
}
} else if (strcasecmp(key, "fingerprints") == 0) {
free(repo->fingerprints);
repo->fingerprints = strdup(ucl_object_tostring(cur));
if (repo->fingerprints == NULL)
err(EXIT_FAILURE, "strdup");
} else if (strcasecmp(key, "pubkey") == 0) {
free(repo->pubkey);
repo->pubkey = strdup(ucl_object_tostring(cur));
if (repo->pubkey == NULL)
err(EXIT_FAILURE, "strdup");
} else if (strcasecmp(key, "enabled") == 0) {
if ((cur->type != UCL_BOOLEAN) ||
!ucl_object_toboolean(cur)) {
if (newrepo)
repo_free(repo);
else
STAILQ_REMOVE(&repositories, repo, repository, next);
return;
}
}
}
if (repo->url == NULL) {
repo_free(repo);
return;
}
if (newrepo)
STAILQ_INSERT_TAIL(&repositories, repo, next);
return;
}
static void
parse_repo_file(ucl_object_t *obj, const char *requested_repo)
{
ucl_object_iter_t it = NULL;
const ucl_object_t *cur;
const char *key;
while ((cur = ucl_iterate_object(obj, &it, true))) {
key = ucl_object_key(cur);
if (key == NULL)
continue;
if (cur->type != UCL_OBJECT)
continue;
if (requested_repo != NULL && strcmp(requested_repo, key) != 0)
continue;
parse_repo(cur);
}
}
static int
read_conf_file(const char *confpath, const char *requested_repo,
pkg_conf_file_t conftype)
{
struct ucl_parser *p;
ucl_object_t *obj = NULL;
char *abi = pkg_get_myabi(), *major, *minor;
struct utsname uts;
int ret;
if (uname(&uts))
err(EXIT_FAILURE, "uname");
if (abi == NULL)
errx(EXIT_FAILURE, "Failed to determine ABI");
p = ucl_parser_new(0);
asprintf(&major, "%d", __FreeBSD_version/100000);
if (major == NULL)
err(EXIT_FAILURE, "asprintf");
asprintf(&minor, "%d", (__FreeBSD_version / 1000) % 100);
if (minor == NULL)
err(EXIT_FAILURE, "asprintf");
ucl_parser_register_variable(p, "ABI", abi);
ucl_parser_register_variable(p, "OSNAME", uts.sysname);
ucl_parser_register_variable(p, "RELEASE", major);
ucl_parser_register_variable(p, "VERSION_MAJOR", major);
ucl_parser_register_variable(p, "VERSION_MINOR", minor);
if (!ucl_parser_add_file(p, confpath)) {
if (errno != ENOENT)
errx(EXIT_FAILURE, "Unable to parse configuration "
"file %s: %s", confpath, ucl_parser_get_error(p));
ret = 1;
goto out;
}
obj = ucl_parser_get_object(p);
if (obj->type != UCL_OBJECT)
warnx("Invalid configuration format, ignoring the "
"configuration file %s", confpath);
else {
if (conftype == CONFFILE_PKG)
config_parse(obj);
else if (conftype == CONFFILE_REPO)
parse_repo_file(obj, requested_repo);
}
ucl_object_unref(obj);
ret = 0;
out:
ucl_parser_free(p);
free(abi);
free(major);
free(minor);
return (ret);
}
static void
load_repositories(const char *repodir, const char *requested_repo)
{
struct dirent *ent;
DIR *d;
char *p;
size_t n;
char path[MAXPATHLEN];
if ((d = opendir(repodir)) == NULL)
return;
while ((ent = readdir(d))) {
if ((n = strlen(ent->d_name)) <= 5)
continue;
p = &ent->d_name[n - 5];
if (strcmp(p, ".conf") == 0) {
snprintf(path, sizeof(path), "%s%s%s",
repodir,
repodir[strlen(repodir) - 1] == '/' ? "" : "/",
ent->d_name);
if (access(path, F_OK) != 0)
continue;
if (read_conf_file(path, requested_repo,
CONFFILE_REPO)) {
goto cleanup;
}
}
}
cleanup:
closedir(d);
}
int
config_init(const char *requested_repo)
{
char *val;
int i;
const char *localbase;
char *abi, *env_list_item;
char confpath[MAXPATHLEN];
struct config_value *cv;
for (i = 0; i < CONFIG_SIZE; i++) {
val = getenv(c[i].key);
if (val != NULL) {
c[i].envset = true;
switch (c[i].type) {
case PKG_CONFIG_LIST:
c[i].list = malloc(sizeof(*c[i].list));
STAILQ_INIT(c[i].list);
for (env_list_item = strtok(val, ",");
env_list_item != NULL;
env_list_item = strtok(NULL, ",")) {
cv =
malloc(sizeof(struct config_value));
cv->value =
strdup(env_list_item);
STAILQ_INSERT_TAIL(c[i].list, cv,
next);
}
break;
default:
c[i].val = val;
break;
}
}
}
localbase = getlocalbase();
snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
CONFFILE_PKG))
goto finalize;
if (c[REPOS_DIR].list == NULL) {
c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
STAILQ_INIT(c[REPOS_DIR].list);
cv = malloc(sizeof(struct config_value));
cv->value = strdup("/etc/pkg");
STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
cv = malloc(sizeof(struct config_value));
if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
goto finalize;
STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
}
STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
load_repositories(cv->value, requested_repo);
finalize:
if (c[ABI].val == NULL && c[ABI].value == NULL) {
abi = pkg_get_myabi();
if (abi == NULL)
errx(EXIT_FAILURE, "Failed to determine the system "
"ABI");
c[ABI].val = abi;
}
return (0);
}
int
config_string(pkg_config_key k, const char **val)
{
if (c[k].type != PKG_CONFIG_STRING)
return (-1);
if (c[k].value != NULL)
*val = c[k].value;
else
*val = c[k].val;
return (0);
}
int
config_bool(pkg_config_key k, bool *val)
{
const char *value;
if (c[k].type != PKG_CONFIG_BOOL)
return (-1);
*val = false;
if (c[k].value != NULL)
value = c[k].value;
else
value = c[k].val;
if (boolstr_to_bool(value))
*val = true;
return (0);
}
struct repositories *
config_get_repositories(void)
{
if (STAILQ_EMPTY(&repositories)) {
struct repository *r = calloc(1, sizeof(*r));
if (r == NULL)
err(EXIT_FAILURE, "calloc");
r->name = strdup("fallback");
if (r->name == NULL)
err(EXIT_FAILURE, "strdup");
subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
r->url = c[PACKAGESITE].value;
if (c[SIGNATURE_TYPE].value != NULL)
if (!parse_signature_type(r, c[SIGNATURE_TYPE].value))
exit(EXIT_FAILURE);
if (c[MIRROR_TYPE].value != NULL)
parse_mirror_type(r, c[MIRROR_TYPE].value);
if (c[PUBKEY].value != NULL)
r->pubkey = c[PUBKEY].value;
if (c[FINGERPRINTS].value != NULL)
r->fingerprints = c[FINGERPRINTS].value;
STAILQ_INSERT_TAIL(&repositories, r, next);
}
return (&repositories);
}
void
config_finish(void) {
int i;
for (i = 0; i < CONFIG_SIZE; i++)
free(c[i].value);
}