From eb31a574742ba1fa7ca8c877a9745668a999fde8 Mon Sep 17 00:00:00 2001 From: Bryan Drewery Date: Thu, 12 Dec 2013 17:59:09 +0000 Subject: [PATCH] Fix multi-repository support by properly respecting 'enabled' flag. This will read the REPOS_DIR env/config setting (default is /etc/pkg and /usr/local/etc/pkg/repos) and use the last enabled repository. This can be changed in the environment using a comma-separated list, or in /usr/local/etc/pkg.conf with JSON array syntax of: REPOS_DIR: ["/etc/pkg", "/usr/local/etc/pkg/repos"] Approved by: bapt MFC after: 1 week --- usr.sbin/pkg/config.c | 188 +++++++++++++++++++++++++++++++++++++++--- usr.sbin/pkg/config.h | 2 + usr.sbin/pkg/pkg.7 | 11 ++- 3 files changed, 186 insertions(+), 15 deletions(-) diff --git a/usr.sbin/pkg/config.c b/usr.sbin/pkg/config.c index affcee999cdd..658ff0a3cc36 100644 --- a/usr.sbin/pkg/config.c +++ b/usr.sbin/pkg/config.c @@ -32,8 +32,10 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include +#include #include #include #include @@ -51,11 +53,17 @@ __FBSDID("$FreeBSD$"); #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ +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; }; @@ -65,6 +73,7 @@ static struct config_entry c[] = { "PACKAGESITE", URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest", NULL, + NULL, false, }, [ABI] = { @@ -72,6 +81,7 @@ static struct config_entry c[] = { "ABI", NULL, NULL, + NULL, false, }, [MIRROR_TYPE] = { @@ -79,6 +89,7 @@ static struct config_entry c[] = { "MIRROR_TYPE", "SRV", NULL, + NULL, false, }, [ASSUME_ALWAYS_YES] = { @@ -86,6 +97,7 @@ static struct config_entry c[] = { "ASSUME_ALWAYS_YES", "NO", NULL, + NULL, false, }, [SIGNATURE_TYPE] = { @@ -93,6 +105,7 @@ static struct config_entry c[] = { "SIGNATURE_TYPE", NULL, NULL, + NULL, false, }, [FINGERPRINTS] = { @@ -100,6 +113,15 @@ static struct config_entry c[] = { "FINGERPRINTS", NULL, NULL, + NULL, + false, + }, + [REPOS_DIR] = { + PKG_CONFIG_LIST, + "REPOS_DIR", + NULL, + NULL, + NULL, false, }, }; @@ -474,17 +496,34 @@ subst_packagesite(const char *abi) c[PACKAGESITE].value = strdup(sbuf_data(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(yaml_document_t *doc, yaml_node_t *node, pkg_conf_file_t conftype) { + yaml_node_item_t *item; yaml_node_pair_t *pair; - yaml_node_t *key, *val; + yaml_node_t *key, *val, *item_val; struct sbuf *buf = sbuf_new_auto(); + struct config_entry *temp_config; + struct config_value *cv; int i; size_t j; pair = node->data.mapping.pairs.start; + /* Temporary config for configs that may be disabled. */ + temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry)); + while (pair < node->data.mapping.pairs.top) { key = yaml_document_get_node(doc, pair->key); val = yaml_document_get_node(doc, pair->value); @@ -530,7 +569,12 @@ config_parse(yaml_document_t *doc, yaml_node_t *node, pkg_conf_file_t conftype) else if (strcasecmp(key->data.scalar.value, "fingerprints") == 0) sbuf_cpy(buf, "FINGERPRINTS"); - else { /* Skip unknown entries for future use. */ + else if (strcasecmp(key->data.scalar.value, + "enabled") == 0) { + /* Skip disabled repos. */ + if (!boolstr_to_bool(val->data.scalar.value)) + goto cleanup; + } else { /* Skip unknown entries for future use. */ ++pair; continue; } @@ -554,10 +598,58 @@ config_parse(yaml_document_t *doc, yaml_node_t *node, pkg_conf_file_t conftype) continue; } - c[i].value = strdup(val->data.scalar.value); + /* Parse sequence value ["item1", "item2"] */ + switch (c[i].type) { + case PKG_CONFIG_LIST: + if (val->type != YAML_SEQUENCE_NODE) { + fprintf(stderr, "Skipping invalid array " + "value for %s.\n", c[i].key); + ++pair; + continue; + } + item = val->data.sequence.items.start; + temp_config[i].list = + malloc(sizeof(*temp_config[i].list)); + STAILQ_INIT(temp_config[i].list); + + while (item < val->data.sequence.items.top) { + item_val = yaml_document_get_node(doc, *item); + if (item_val->type != YAML_SCALAR_NODE) { + ++item; + continue; + } + cv = malloc(sizeof(struct config_value)); + cv->value = + strdup(item_val->data.scalar.value); + STAILQ_INSERT_TAIL(temp_config[i].list, cv, + next); + ++item; + } + break; + default: + /* Normal string value. */ + temp_config[i].value = strdup(val->data.scalar.value); + break; + } ++pair; } + /* Repo is enabled, copy over all settings from temp_config. */ + for (i = 0; i < CONFIG_SIZE; i++) { + if (c[i].envset) + 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; + } + } + +cleanup: + free(temp_config); sbuf_delete(buf); } @@ -632,23 +724,84 @@ read_conf_file(const char *confpath, pkg_conf_file_t conftype) return (0); } +static int +load_repositories(const char *repodir) +{ + struct dirent *ent; + DIR *d; + char *p; + size_t n; + char path[MAXPATHLEN]; + int ret; + + ret = 0; + + if ((d = opendir(repodir)) == NULL) + return (1); + + 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 && + read_conf_file(path, CONFFILE_REPO)) { + ret = 1; + goto cleanup; + } + } + } + +cleanup: + closedir(d); + + return (ret); +} + int config_init(void) { - const char *val; + char *val; int i; const char *localbase; + char *env_list_item; char confpath[MAXPATHLEN]; + struct config_value *cv; char abi[BUFSIZ]; for (i = 0; i < CONFIG_SIZE; i++) { val = getenv(c[i].key); if (val != NULL) { - c[i].val = val; 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 = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE; snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase); @@ -657,10 +810,22 @@ config_init(void) CONFFILE_PKG)) goto finalize; - snprintf(confpath, sizeof(confpath), "/etc/pkg/FreeBSD.conf"); - if (access(confpath, F_OK) == 0 && read_conf_file(confpath, - CONFFILE_REPO)) - 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) + if (load_repositories(cv->value)) + goto finalize; finalize: if (c[ABI].val == NULL && c[ABI].value == NULL) { @@ -704,10 +869,7 @@ config_bool(pkg_config_key k, bool *val) else value = c[k].val; - if (strcasecmp(value, "true") == 0 || - strcasecmp(value, "yes") == 0 || - strcasecmp(value, "on") == 0 || - *value == '1') + if (boolstr_to_bool(value)) *val = true; return (0); diff --git a/usr.sbin/pkg/config.h b/usr.sbin/pkg/config.h index 72d2ec0b01a0..ea0d0005d769 100644 --- a/usr.sbin/pkg/config.h +++ b/usr.sbin/pkg/config.h @@ -39,12 +39,14 @@ typedef enum { ASSUME_ALWAYS_YES, SIGNATURE_TYPE, FINGERPRINTS, + REPOS_DIR, CONFIG_SIZE } pkg_config_key; typedef enum { PKG_CONFIG_STRING=0, PKG_CONFIG_BOOL, + PKG_CONFIG_LIST, } pkg_config_t; typedef enum { diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7 index e46f3ddbf7fb..342c93420b6a 100644 --- a/usr.sbin/pkg/pkg.7 +++ b/usr.sbin/pkg/pkg.7 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd November 19, 2013 +.Dd December 12, 2013 .Dt PKG 7 .Os .Sh NAME @@ -152,6 +152,7 @@ MIRROR_TYPE: "srv", SIGNATURE_TYPE: "none", FINGERPRINTS: "/usr/share/keys/pkg", ASSUME_ALWAYS_YES: "yes" +REPOS_DIR: ["/etc/pkg", "/usr/local/etc/pkg/repos"] .Ed .Pp Reference @@ -194,14 +195,20 @@ The URL that .Xr pkg 8 and other packages will be fetched from. +.It Ev REPOS_DIR +Comma-separated list of directories that should be searched for repository +configuration files. .El .Sh FILES Configuration is read from the files in the listed order. -The first enabled repository is the one used for bootstrapping +This path can be changed by setting +.Sy REPOS_DIR . +The last enabled repository is the one used for bootstrapping .Xr pkg 8 . .Bl -tag -width "/usr/local/etc/pkg/repos/*.conf" .It Pa /usr/local/etc/pkg.conf .It Pa /etc/pkg/FreeBSD.conf +.It Pa /usr/local/etc/pkg/repos/*.conf .El .Sh EXAMPLES Some examples are listed here.