aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/pkg/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/pkg/config.c')
-rw-r--r--usr.sbin/pkg/config.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/usr.sbin/pkg/config.c b/usr.sbin/pkg/config.c
new file mode 100644
index 000000000000..6649e75b7f6b
--- /dev/null
+++ b/usr.sbin/pkg/config.c
@@ -0,0 +1,708 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2014-2025 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/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; /* Only set in pkg.conf. */
+};
+
+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';
+
+ /*
+ * Use __FreeBSD_version rather than kernel version (uts.release) for
+ * use in jails. This is equivalent to the value of uname -U.
+ */
+ 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;
+
+ /* Temporary config for configs that may be disabled. */
+ 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;
+ }
+
+ /* Silently skip unknown keys to be future compatible. */
+ if (i == CONFIG_SIZE)
+ continue;
+
+ /* env has priority over config file */
+ if (c[i].envset)
+ continue;
+
+ /* Parse sequence value ["item1", "item2"] */
+ 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:
+ /* Normal string value. */
+ temp_config[i].value = strdup(ucl_object_tostring(cur));
+ break;
+ }
+ }
+
+ /* Repo is enabled, copy over all settings from temp_config. */
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ if (c[i].envset)
+ continue;
+ /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
+ 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;
+ 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;
+ }
+ }
+ }
+ /* At least we need an url */
+ if (repo->url == NULL) {
+ repo_free(repo);
+ return;
+ }
+ if (newrepo)
+ STAILQ_INSERT_TAIL(&repositories, repo, next);
+ return;
+}
+
+/*-
+ * Parse new repo style configs in style:
+ * Name:
+ * URL:
+ * MIRROR_TYPE:
+ * etc...
+ */
+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));
+ /* no configuration present */
+ 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))) {
+ /* Trim out 'repos'. */
+ 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:
+ /* Split up comma-separated items from env. */
+ 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;
+ }
+ }
+ }
+
+ /* Read LOCALBASE/etc/pkg.conf first. */
+ 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;
+
+ /* Then read in all repos from REPOS_DIR list of directories. */
+ 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)) {
+ /* Fall back to PACKAGESITE - deprecated - */
+ 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);
+}