Index: stable/10/usr.sbin/pkg/Makefile =================================================================== --- stable/10/usr.sbin/pkg/Makefile (revision 263019) +++ stable/10/usr.sbin/pkg/Makefile (revision 263020) @@ -1,14 +1,14 @@ # $FreeBSD$ PROG= pkg SRCS= pkg.c dns_utils.c config.c MAN= pkg.7 -CFLAGS+=-I${.CURDIR}/../../contrib/libyaml/include -.PATH: ${.CURDIR}/../../contrib/libyaml/include -DPADD= ${LIBARCHIVE} ${LIBELF} ${LIBFETCH} ${LIBYAML} ${LIBSBUF} ${LIBSSL} \ +CFLAGS+=-I${.CURDIR}/../../contrib/libucl/include +.PATH: ${.CURDIR}/../../contrib/libucl/include +DPADD= ${LIBARCHIVE} ${LIBELF} ${LIBFETCH} ${LIBUCL} ${LIBSBUF} ${LIBSSL} \ ${LIBCRYPTO} -LDADD= -larchive -lelf -lfetch -lyaml -lsbuf -lssl -lcrypto -USEPRIVATELIB= yaml +LDADD= -larchive -lelf -lfetch -lucl -lsbuf -lssl -lcrypto +USEPRIVATELIB= ucl .include Index: stable/10/usr.sbin/pkg/config.c =================================================================== --- stable/10/usr.sbin/pkg/config.c (revision 263019) +++ stable/10/usr.sbin/pkg/config.c (revision 263020) @@ -1,885 +1,834 @@ /*- - * Copyright (c) 2013 Baptiste Daroussin + * Copyright (c) 2014 Baptiste Daroussin * Copyright (c) 2013 Bryan Drewery * 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. * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include "elf_tables.h" #include "config.h" #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; }; static struct config_entry c[] = { [PACKAGESITE] = { PKG_CONFIG_STRING, "PACKAGESITE", URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest", NULL, NULL, false, }, [ABI] = { PKG_CONFIG_STRING, "ABI", NULL, NULL, NULL, false, }, [MIRROR_TYPE] = { PKG_CONFIG_STRING, "MIRROR_TYPE", "SRV", NULL, NULL, false, }, [ASSUME_ALWAYS_YES] = { PKG_CONFIG_BOOL, "ASSUME_ALWAYS_YES", "NO", NULL, NULL, false, }, [SIGNATURE_TYPE] = { PKG_CONFIG_STRING, "SIGNATURE_TYPE", NULL, NULL, NULL, false, }, [FINGERPRINTS] = { PKG_CONFIG_STRING, "FINGERPRINTS", NULL, NULL, NULL, false, }, [REPOS_DIR] = { PKG_CONFIG_LIST, "REPOS_DIR", NULL, NULL, NULL, false, }, }; static const char * elf_corres_to_string(struct _elf_corres *m, int e) { int i; for (i = 0; m[i].string != NULL; i++) if (m[i].elf_nb == e) return (m[i].string); return ("unknown"); } static const char * aeabi_parse_arm_attributes(void *data, size_t length) { uint32_t sect_len; uint8_t *section = data; #define MOVE(len) do { \ assert(length >= (len)); \ section += (len); \ length -= (len); \ } while (0) if (length == 0 || *section != 'A') return (NULL); MOVE(1); /* Read the section length */ if (length < sizeof(sect_len)) return (NULL); memcpy(§_len, section, sizeof(sect_len)); /* * The section length should be no longer than the section it is within */ if (sect_len > length) return (NULL); MOVE(sizeof(sect_len)); /* Skip the vendor name */ while (length != 0) { if (*section == '\0') break; MOVE(1); } if (length == 0) return (NULL); MOVE(1); while (length != 0) { uint32_t tag_length; switch(*section) { case 1: /* Tag_File */ MOVE(1); if (length < sizeof(tag_length)) return (NULL); memcpy(&tag_length, section, sizeof(tag_length)); break; case 2: /* Tag_Section */ case 3: /* Tag_Symbol */ default: return (NULL); } /* At least space for the tag and size */ if (tag_length <= 5) return (NULL); tag_length--; /* Check the tag fits */ if (tag_length > length) return (NULL); #define MOVE_TAG(len) do { \ assert(tag_length >= (len)); \ MOVE(len); \ tag_length -= (len); \ } while(0) MOVE(sizeof(tag_length)); tag_length -= sizeof(tag_length); while (tag_length != 0) { uint8_t tag; assert(tag_length >= length); tag = *section; MOVE_TAG(1); /* * These tag values come from: * * Addenda to, and Errata in, the ABI for the * ARM Architecture. Release 2.08, section 2.3. */ if (tag == 6) { /* == Tag_CPU_arch */ uint8_t val; val = *section; /* * We don't support values that require * more than one byte. */ if (val & (1 << 7)) return (NULL); /* We have an ARMv4 or ARMv5 */ if (val <= 5) return ("arm"); else /* We have an ARMv6+ */ return ("armv6"); } else if (tag == 4 || tag == 5 || tag == 32 || tag == 65 || tag == 67) { while (*section != '\0' && length != 0) MOVE_TAG(1); if (tag_length == 0) return (NULL); /* Skip the last byte */ MOVE_TAG(1); } else if ((tag >= 7 && tag <= 31) || tag == 34 || tag == 36 || tag == 38 || tag == 42 || tag == 44 || tag == 64 || tag == 66 || tag == 68 || tag == 70) { /* Skip the uleb128 data */ while (*section & (1 << 7) && length != 0) MOVE_TAG(1); if (tag_length == 0) return (NULL); /* Skip the last byte */ MOVE_TAG(1); } else return (NULL); #undef MOVE_TAG } break; } return (NULL); #undef MOVE } static int pkg_get_myabi(char *dest, size_t sz) { Elf *elf; Elf_Data *data; Elf_Note note; Elf_Scn *scn; char *src, *osname; const char *arch, *abi, *fpu, *endian_corres_str; const char *wordsize_corres_str; GElf_Ehdr elfhdr; GElf_Shdr shdr; int fd, i, ret; uint32_t version; version = 0; ret = -1; scn = NULL; abi = NULL; if (elf_version(EV_CURRENT) == EV_NONE) { warnx("ELF library initialization failed: %s", elf_errmsg(-1)); return (-1); } if ((fd = open(_PATH_BSHELL, O_RDONLY)) < 0) { warn("open()"); return (-1); } if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { ret = -1; warnx("elf_begin() failed: %s.", elf_errmsg(-1)); goto cleanup; } if (gelf_getehdr(elf, &elfhdr) == NULL) { ret = -1; warn("getehdr() failed: %s.", elf_errmsg(-1)); goto cleanup; } while ((scn = elf_nextscn(elf, scn)) != NULL) { if (gelf_getshdr(scn, &shdr) != &shdr) { ret = -1; warn("getshdr() failed: %s.", elf_errmsg(-1)); goto cleanup; } if (shdr.sh_type == SHT_NOTE) break; } if (scn == NULL) { ret = -1; warn("failed to get the note section"); goto cleanup; } data = elf_getdata(scn, NULL); src = data->d_buf; for (;;) { memcpy(¬e, src, sizeof(Elf_Note)); src += sizeof(Elf_Note); if (note.n_type == NT_VERSION) break; src += note.n_namesz + note.n_descsz; } osname = src; src += roundup2(note.n_namesz, 4); if (elfhdr.e_ident[EI_DATA] == ELFDATA2MSB) version = be32dec(src); else version = le32dec(src); for (i = 0; osname[i] != '\0'; i++) osname[i] = (char)tolower(osname[i]); wordsize_corres_str = elf_corres_to_string(wordsize_corres, (int)elfhdr.e_ident[EI_CLASS]); arch = elf_corres_to_string(mach_corres, (int) elfhdr.e_machine); snprintf(dest, sz, "%s:%d", osname, version / 100000); ret = 0; switch (elfhdr.e_machine) { case EM_ARM: endian_corres_str = elf_corres_to_string(endian_corres, (int)elfhdr.e_ident[EI_DATA]); /* FreeBSD doesn't support the hard-float ABI yet */ fpu = "softfp"; if ((elfhdr.e_flags & 0xFF000000) != 0) { const char *sh_name = NULL; size_t shstrndx; /* This is an EABI file, the conformance level is set */ abi = "eabi"; /* Find which TARGET_ARCH we are building for. */ elf_getshdrstrndx(elf, &shstrndx); while ((scn = elf_nextscn(elf, scn)) != NULL) { sh_name = NULL; if (gelf_getshdr(scn, &shdr) != &shdr) { scn = NULL; break; } sh_name = elf_strptr(elf, shstrndx, shdr.sh_name); if (sh_name == NULL) continue; if (strcmp(".ARM.attributes", sh_name) == 0) break; } if (scn != NULL && sh_name != NULL) { data = elf_getdata(scn, NULL); /* * Prior to FreeBSD 10.0 libelf would return * NULL from elf_getdata on the .ARM.attributes * section. As this was the first release to * get armv6 support assume a NULL value means * arm. * * This assumption can be removed when 9.x * is unsupported. */ if (data != NULL) { arch = aeabi_parse_arm_attributes( data->d_buf, data->d_size); if (arch == NULL) { ret = 1; warn("unknown ARM ARCH"); goto cleanup; } } } else { ret = 1; warn("Unable to find the .ARM.attributes " "section"); goto cleanup; } } else if (elfhdr.e_ident[EI_OSABI] != ELFOSABI_NONE) { /* * EABI executables all have this field set to * ELFOSABI_NONE, therefore it must be an oabi file. */ abi = "oabi"; } else { ret = 1; warn("unknown ARM ABI"); goto cleanup; } snprintf(dest + strlen(dest), sz - strlen(dest), ":%s:%s:%s:%s:%s", arch, wordsize_corres_str, endian_corres_str, abi, fpu); break; case EM_MIPS: /* * this is taken from binutils sources: * include/elf/mips.h * mapping is figured out from binutils: * gas/config/tc-mips.c */ switch (elfhdr.e_flags & EF_MIPS_ABI) { case E_MIPS_ABI_O32: abi = "o32"; break; case E_MIPS_ABI_N32: abi = "n32"; break; default: if (elfhdr.e_ident[EI_DATA] == ELFCLASS32) abi = "o32"; else if (elfhdr.e_ident[EI_DATA] == ELFCLASS64) abi = "n64"; break; } endian_corres_str = elf_corres_to_string(endian_corres, (int)elfhdr.e_ident[EI_DATA]); snprintf(dest + strlen(dest), sz - strlen(dest), ":%s:%s:%s:%s", arch, wordsize_corres_str, endian_corres_str, abi); break; default: snprintf(dest + strlen(dest), sz - strlen(dest), ":%s:%s", arch, wordsize_corres_str); } cleanup: if (elf != NULL) elf_end(elf); close(fd); return (ret); } static void subst_packagesite(const char *abi) { struct sbuf *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; newval = sbuf_new_auto(); sbuf_bcat(newval, oldval, variable_string - oldval); sbuf_cat(newval, abi); sbuf_cat(newval, variable_string + strlen("${ABI}")); sbuf_finish(newval); free(c[PACKAGESITE].value); 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) +config_parse(ucl_object_t *obj, pkg_conf_file_t conftype) { - yaml_node_item_t *item; - yaml_node_pair_t *pair; - yaml_node_t *key, *val, *item_val; struct sbuf *buf = sbuf_new_auto(); + ucl_object_t *cur, *seq; + ucl_object_iter_t it = NULL, itseq = NULL; struct config_entry *temp_config; struct config_value *cv; + const char *key; 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); - - /* - * ignoring silently empty keys can be empty lines - * or user mistakes - */ - if (key->data.scalar.length <= 0) { - ++pair; + while ((cur = ucl_iterate_object(obj, &it, true))) { + key = ucl_object_key(cur); + if (key == NULL) continue; - } - - /* - * silently skip on purpose to allow user to leave - * empty lines without complaining - */ - if (val->type == YAML_NO_NODE || - (val->type == YAML_SCALAR_NODE && - val->data.scalar.length <= 0)) { - ++pair; - continue; - } - sbuf_clear(buf); if (conftype == CONFFILE_PKG) { - for (j = 0; j < strlen(key->data.scalar.value); ++j) - sbuf_putc(buf, - toupper(key->data.scalar.value[j])); + for (j = 0; j < strlen(key); ++j) + sbuf_putc(buf, key[j]); sbuf_finish(buf); } else if (conftype == CONFFILE_REPO) { - /* The CONFFILE_REPO type is more restrictive. Only - parse known elements. */ - if (strcasecmp(key->data.scalar.value, "url") == 0) + if (strcasecmp(key, "url") == 0) sbuf_cpy(buf, "PACKAGESITE"); - else if (strcasecmp(key->data.scalar.value, - "mirror_type") == 0) + else if (strcasecmp(key, "mirror_type") == 0) sbuf_cpy(buf, "MIRROR_TYPE"); - else if (strcasecmp(key->data.scalar.value, - "signature_type") == 0) + else if (strcasecmp(key, "signature_type") == 0) sbuf_cpy(buf, "SIGNATURE_TYPE"); - else if (strcasecmp(key->data.scalar.value, - "fingerprints") == 0) + else if (strcasecmp(key, "fingerprints") == 0) sbuf_cpy(buf, "FINGERPRINTS"); - else if (strcasecmp(key->data.scalar.value, - "enabled") == 0) { - /* Skip disabled repos. */ - if (!boolstr_to_bool(val->data.scalar.value)) + else if (strcasecmp(key, "enabled") == 0) { + if ((cur->type != UCL_BOOLEAN) || + !ucl_object_toboolean(cur)) goto cleanup; - } else { /* Skip unknown entries for future use. */ - ++pair; + } else continue; - } sbuf_finish(buf); } for (i = 0; i < CONFIG_SIZE; i++) { if (strcmp(sbuf_data(buf), c[i].key) == 0) break; } /* Silently skip unknown keys to be future compatible. */ - if (i == CONFIG_SIZE) { - ++pair; + if (i == CONFIG_SIZE) continue; - } /* env has priority over config file */ - if (c[i].envset) { - ++pair; + if (c[i].envset) continue; - } /* Parse sequence value ["item1", "item2"] */ switch (c[i].type) { case PKG_CONFIG_LIST: - if (val->type != YAML_SEQUENCE_NODE) { - fprintf(stderr, "Skipping invalid array " + if (cur->type != UCL_ARRAY) { + warnx("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; + while ((seq = ucl_iterate_object(cur, &itseq, true))) { + if (seq->type != UCL_STRING) continue; - } cv = malloc(sizeof(struct config_value)); cv->value = - strdup(item_val->data.scalar.value); + strdup(ucl_object_tostring(seq)); STAILQ_INSERT_TAIL(temp_config[i].list, cv, next); - ++item; } break; default: /* Normal string value. */ - temp_config[i].value = strdup(val->data.scalar.value); + temp_config[i].value = strdup(ucl_object_tostring(cur)); 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); } /*- * Parse new repo style configs in style: * Name: * URL: * MIRROR_TYPE: * etc... */ static void -parse_repo_file(yaml_document_t *doc, yaml_node_t *node) +parse_repo_file(ucl_object_t *obj) { - yaml_node_pair_t *pair; + ucl_object_iter_t it = NULL; + ucl_object_t *cur; + const char *key; - pair = node->data.mapping.pairs.start; - while (pair < node->data.mapping.pairs.top) { - yaml_node_t *key = yaml_document_get_node(doc, pair->key); - yaml_node_t *val = yaml_document_get_node(doc, pair->value); + while ((cur = ucl_iterate_object(obj, &it, true))) { + key = ucl_object_key(cur); - if (key->data.scalar.length <= 0) { - ++pair; + if (key == NULL) continue; - } - if (val->type != YAML_MAPPING_NODE) { - ++pair; + if (cur->type != UCL_OBJECT) continue; - } - config_parse(doc, val, CONFFILE_REPO); - ++pair; + config_parse(cur, CONFFILE_REPO); } } static int read_conf_file(const char *confpath, pkg_conf_file_t conftype) { - FILE *fp; - yaml_parser_t parser; - yaml_document_t doc; - yaml_node_t *node; + struct ucl_parser *p; + ucl_object_t *obj = NULL; - if ((fp = fopen(confpath, "r")) == NULL) { + p = ucl_parser_new(0); + + if (!ucl_parser_add_file(p, confpath)) { if (errno != ENOENT) - err(EXIT_FAILURE, "Unable to open configuration " - "file %s", confpath); + errx(EXIT_FAILURE, "Unable to parse configuration " + "file %s: %s", confpath, ucl_parser_get_error(p)); + ucl_parser_free(p); /* no configuration present */ return (1); } - yaml_parser_initialize(&parser); - yaml_parser_set_input_file(&parser, fp); - yaml_parser_load(&parser, &doc); - - node = yaml_document_get_root_node(&doc); - - if (node == NULL || node->type != YAML_MAPPING_NODE) + 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(&doc, node, conftype); + config_parse(obj, conftype); else if (conftype == CONFFILE_REPO) - parse_repo_file(&doc, node); + parse_repo_file(obj); } - yaml_document_delete(&doc); - yaml_parser_delete(&parser); + ucl_object_free(obj); + ucl_parser_free(p); 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) { 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].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); if (access(confpath, F_OK) == 0 && read_conf_file(confpath, 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) if (load_repositories(cv->value)) goto finalize; finalize: if (c[ABI].val == NULL && c[ABI].value == NULL) { if (pkg_get_myabi(abi, BUFSIZ) != 0) errx(EXIT_FAILURE, "Failed to determine the system " "ABI"); c[ABI].val = abi; } subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val); 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); } void config_finish(void) { int i; for (i = 0; i < CONFIG_SIZE; i++) free(c[i].value); } Index: stable/10/usr.sbin/pkg/pkg.c =================================================================== --- stable/10/usr.sbin/pkg/pkg.c (revision 263019) +++ stable/10/usr.sbin/pkg/pkg.c (revision 263020) @@ -1,971 +1,958 @@ /*- - * Copyright (c) 2012-2013 Baptiste Daroussin + * Copyright (c) 2012-2014 Baptiste Daroussin * Copyright (c) 2013 Bryan Drewery * 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. * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #define _WITH_GETLINE #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include "dns_utils.h" #include "config.h" struct sig_cert { char *name; unsigned char *sig; int siglen; unsigned char *cert; int certlen; bool trusted; }; typedef enum { HASH_UNKNOWN, HASH_SHA256, } hash_t; struct fingerprint { hash_t type; char *name; char hash[BUFSIZ]; STAILQ_ENTRY(fingerprint) next; }; STAILQ_HEAD(fingerprint_list, fingerprint); static int extract_pkg_static(int fd, char *p, int sz) { struct archive *a; struct archive_entry *ae; char *end; int ret, r; ret = -1; a = archive_read_new(); if (a == NULL) { warn("archive_read_new"); return (ret); } archive_read_support_filter_all(a); archive_read_support_format_tar(a); if (lseek(fd, 0, 0) == -1) { warn("lseek"); goto cleanup; } if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) { warnx("archive_read_open_fd: %s", archive_error_string(a)); goto cleanup; } ae = NULL; while ((r = archive_read_next_header(a, &ae)) == ARCHIVE_OK) { end = strrchr(archive_entry_pathname(ae), '/'); if (end == NULL) continue; if (strcmp(end, "/pkg-static") == 0) { r = archive_read_extract(a, ae, ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR); strlcpy(p, archive_entry_pathname(ae), sz); break; } } if (r == ARCHIVE_OK) ret = 0; else warnx("fail to extract pkg-static"); cleanup: archive_read_free(a); return (ret); } static int install_pkg_static(const char *path, const char *pkgpath, bool force) { int pstat; pid_t pid; switch ((pid = fork())) { case -1: return (-1); case 0: if (force) execl(path, "pkg-static", "add", "-f", pkgpath, (char *)NULL); else execl(path, "pkg-static", "add", pkgpath, (char *)NULL); _exit(1); default: break; } while (waitpid(pid, &pstat, 0) == -1) if (errno != EINTR) return (-1); if (WEXITSTATUS(pstat)) return (WEXITSTATUS(pstat)); else if (WIFSIGNALED(pstat)) return (128 & (WTERMSIG(pstat))); return (pstat); } static int fetch_to_fd(const char *url, char *path) { struct url *u; struct dns_srvinfo *mirrors, *current; struct url_stat st; FILE *remote; /* To store _https._tcp. + hostname + \0 */ int fd; int retry, max_retry; off_t done, r; time_t now, last; char buf[10240]; char zone[MAXHOSTNAMELEN + 13]; static const char *mirror_type = NULL; done = 0; last = 0; max_retry = 3; current = mirrors = NULL; remote = NULL; if (mirror_type == NULL && config_string(MIRROR_TYPE, &mirror_type) != 0) { warnx("No MIRROR_TYPE defined"); return (-1); } if ((fd = mkstemp(path)) == -1) { warn("mkstemp()"); return (-1); } retry = max_retry; u = fetchParseURL(url); while (remote == NULL) { if (retry == max_retry) { if (strcmp(u->scheme, "file") != 0 && strcasecmp(mirror_type, "srv") == 0) { snprintf(zone, sizeof(zone), "_%s._tcp.%s", u->scheme, u->host); mirrors = dns_getsrvinfo(zone); current = mirrors; } } if (mirrors != NULL) { strlcpy(u->host, current->host, sizeof(u->host)); u->port = current->port; } remote = fetchXGet(u, &st, ""); if (remote == NULL) { --retry; if (retry <= 0) goto fetchfail; if (mirrors == NULL) { sleep(1); } else { current = current->next; if (current == NULL) current = mirrors; } } } if (remote == NULL) goto fetchfail; while (done < st.size) { if ((r = fread(buf, 1, sizeof(buf), remote)) < 1) break; if (write(fd, buf, r) != r) { warn("write()"); goto fetchfail; } done += r; now = time(NULL); if (now > last || done == st.size) last = now; } if (ferror(remote)) goto fetchfail; goto cleanup; fetchfail: if (fd != -1) { close(fd); fd = -1; unlink(path); } cleanup: if (remote != NULL) fclose(remote); return fd; } static struct fingerprint * -parse_fingerprint(yaml_document_t *doc, yaml_node_t *node) +parse_fingerprint(ucl_object_t *obj) { - yaml_node_pair_t *pair; - yaml_char_t *function, *fp; + ucl_object_t *cur; + ucl_object_iter_t it = NULL; + const char *function, *fp, *key; struct fingerprint *f; hash_t fct = HASH_UNKNOWN; function = fp = NULL; - pair = node->data.mapping.pairs.start; - while (pair < node->data.mapping.pairs.top) { - yaml_node_t *key = yaml_document_get_node(doc, pair->key); - yaml_node_t *val = yaml_document_get_node(doc, pair->value); - - if (key->data.scalar.length <= 0) { - ++pair; + while ((cur = ucl_iterate_object(obj, &it, true))) { + key = ucl_object_key(cur); + if (cur->type != UCL_STRING) continue; + if (strcasecmp(key, "function") == 0) { + function = ucl_object_tostring(cur); + continue; } - - if (val->type != YAML_SCALAR_NODE) { - ++pair; + if (strcasecmp(key, "fingerprint") == 0) { + fp = ucl_object_tostring(cur); continue; } - - if (strcasecmp(key->data.scalar.value, "function") == 0) - function = val->data.scalar.value; - else if (strcasecmp(key->data.scalar.value, "fingerprint") - == 0) - fp = val->data.scalar.value; - - ++pair; - continue; } if (fp == NULL || function == NULL) return (NULL); if (strcasecmp(function, "sha256") == 0) fct = HASH_SHA256; if (fct == HASH_UNKNOWN) { - fprintf(stderr, "Unsupported hashing function: %s\n", function); + warnx("Unsupported hashing function: %s", function); return (NULL); } f = calloc(1, sizeof(struct fingerprint)); f->type = fct; strlcpy(f->hash, fp, sizeof(f->hash)); return (f); } static void free_fingerprint_list(struct fingerprint_list* list) { struct fingerprint *fingerprint, *tmp; STAILQ_FOREACH_SAFE(fingerprint, list, next, tmp) { if (fingerprint->name) free(fingerprint->name); free(fingerprint); } free(list); } static struct fingerprint * load_fingerprint(const char *dir, const char *filename) { - yaml_parser_t parser; - yaml_document_t doc; - yaml_node_t *node; - FILE *fp; + ucl_object_t *obj = NULL; + struct ucl_parser *p = NULL; struct fingerprint *f; char path[MAXPATHLEN]; f = NULL; snprintf(path, MAXPATHLEN, "%s/%s", dir, filename); - if ((fp = fopen(path, "r")) == NULL) + p = ucl_parser_new(0); + if (!ucl_parser_add_file(p, path)) { + warnx("%s: %s", path, ucl_parser_get_error(p)); + ucl_parser_free(p); return (NULL); + } - yaml_parser_initialize(&parser); - yaml_parser_set_input_file(&parser, fp); - yaml_parser_load(&parser, &doc); + obj = ucl_parser_get_object(p); - node = yaml_document_get_root_node(&doc); - if (node == NULL || node->type != YAML_MAPPING_NODE) - goto out; + if (obj->type == UCL_OBJECT) + f = parse_fingerprint(obj); - f = parse_fingerprint(&doc, node); - f->name = strdup(filename); + if (f != NULL) + f->name = strdup(filename); -out: - yaml_document_delete(&doc); - yaml_parser_delete(&parser); - fclose(fp); + ucl_object_free(obj); + ucl_parser_free(p); return (f); } static struct fingerprint_list * load_fingerprints(const char *path, int *count) { DIR *d; struct dirent *ent; struct fingerprint *finger; struct fingerprint_list *fingerprints; *count = 0; fingerprints = calloc(1, sizeof(struct fingerprint_list)); if (fingerprints == NULL) return (NULL); STAILQ_INIT(fingerprints); if ((d = opendir(path)) == NULL) return (NULL); while ((ent = readdir(d))) { if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; finger = load_fingerprint(path, ent->d_name); if (finger != NULL) { STAILQ_INSERT_TAIL(fingerprints, finger, next); ++(*count); } } closedir(d); return (fingerprints); } static void sha256_hash(unsigned char hash[SHA256_DIGEST_LENGTH], char out[SHA256_DIGEST_LENGTH * 2 + 1]) { int i; for (i = 0; i < SHA256_DIGEST_LENGTH; i++) sprintf(out + (i * 2), "%02x", hash[i]); out[SHA256_DIGEST_LENGTH * 2] = '\0'; } static void sha256_buf(char *buf, size_t len, char out[SHA256_DIGEST_LENGTH * 2 + 1]) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha256; out[0] = '\0'; SHA256_Init(&sha256); SHA256_Update(&sha256, buf, len); SHA256_Final(hash, &sha256); sha256_hash(hash, out); } static int sha256_fd(int fd, char out[SHA256_DIGEST_LENGTH * 2 + 1]) { int my_fd; FILE *fp; char buffer[BUFSIZ]; unsigned char hash[SHA256_DIGEST_LENGTH]; size_t r; int ret; SHA256_CTX sha256; my_fd = -1; fp = NULL; r = 0; ret = 1; out[0] = '\0'; /* Duplicate the fd so that fclose(3) does not close it. */ if ((my_fd = dup(fd)) == -1) { warnx("dup"); goto cleanup; } if ((fp = fdopen(my_fd, "rb")) == NULL) { warnx("fdopen"); goto cleanup; } SHA256_Init(&sha256); while ((r = fread(buffer, 1, BUFSIZ, fp)) > 0) SHA256_Update(&sha256, buffer, r); if (ferror(fp) != 0) { warnx("fread"); goto cleanup; } SHA256_Final(hash, &sha256); sha256_hash(hash, out); ret = 0; cleanup: if (fp != NULL) fclose(fp); else if (my_fd != -1) close(my_fd); (void)lseek(fd, 0, SEEK_SET); return (ret); } static EVP_PKEY * load_public_key_buf(const unsigned char *cert, int certlen) { EVP_PKEY *pkey; BIO *bp; char errbuf[1024]; bp = BIO_new_mem_buf(__DECONST(void *, cert), certlen); if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL) warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); BIO_free(bp); return (pkey); } static bool rsa_verify_cert(int fd, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { EVP_MD_CTX *mdctx; EVP_PKEY *pkey; char sha256[(SHA256_DIGEST_LENGTH * 2) + 2]; char errbuf[1024]; bool ret; pkey = NULL; mdctx = NULL; ret = false; /* Compute SHA256 of the package. */ if (lseek(fd, 0, 0) == -1) { warn("lseek"); goto cleanup; } if ((sha256_fd(fd, sha256)) == -1) { warnx("Error creating SHA256 hash for package"); goto cleanup; } if ((pkey = load_public_key_buf(key, keylen)) == NULL) { warnx("Error reading public key"); goto cleanup; } /* Verify signature of the SHA256(pkg) is valid. */ if ((mdctx = EVP_MD_CTX_create()) == NULL) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyUpdate(mdctx, sha256, strlen(sha256)) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyFinal(mdctx, sig, siglen) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } ret = true; printf("done\n"); goto cleanup; error: printf("failed\n"); cleanup: if (pkey) EVP_PKEY_free(pkey); if (mdctx) EVP_MD_CTX_destroy(mdctx); ERR_free_strings(); return (ret); } static struct sig_cert * parse_cert(int fd) { int my_fd; struct sig_cert *sc; FILE *fp; struct sbuf *buf, *sig, *cert; char *line; size_t linecap; ssize_t linelen; buf = NULL; my_fd = -1; sc = NULL; line = NULL; linecap = 0; if (lseek(fd, 0, 0) == -1) { warn("lseek"); return (NULL); } /* Duplicate the fd so that fclose(3) does not close it. */ if ((my_fd = dup(fd)) == -1) { warnx("dup"); return (NULL); } if ((fp = fdopen(my_fd, "rb")) == NULL) { warn("fdopen"); close(my_fd); return (NULL); } sig = sbuf_new_auto(); cert = sbuf_new_auto(); while ((linelen = getline(&line, &linecap, fp)) > 0) { if (strcmp(line, "SIGNATURE\n") == 0) { buf = sig; continue; } else if (strcmp(line, "CERT\n") == 0) { buf = cert; continue; } else if (strcmp(line, "END\n") == 0) { break; } if (buf != NULL) sbuf_bcat(buf, line, linelen); } fclose(fp); /* Trim out unrelated trailing newline */ sbuf_setpos(sig, sbuf_len(sig) - 1); sbuf_finish(sig); sbuf_finish(cert); sc = calloc(1, sizeof(struct sig_cert)); sc->siglen = sbuf_len(sig); sc->sig = calloc(1, sc->siglen); memcpy(sc->sig, sbuf_data(sig), sc->siglen); sc->certlen = sbuf_len(cert); sc->cert = strdup(sbuf_data(cert)); sbuf_delete(sig); sbuf_delete(cert); return (sc); } static bool verify_signature(int fd_pkg, int fd_sig) { struct fingerprint_list *trusted, *revoked; struct fingerprint *fingerprint; struct sig_cert *sc; bool ret; int trusted_count, revoked_count; const char *fingerprints; char path[MAXPATHLEN]; char hash[SHA256_DIGEST_LENGTH * 2 + 1]; sc = NULL; trusted = revoked = NULL; ret = false; /* Read and parse fingerprints. */ if (config_string(FINGERPRINTS, &fingerprints) != 0) { warnx("No CONFIG_FINGERPRINTS defined"); goto cleanup; } snprintf(path, MAXPATHLEN, "%s/trusted", fingerprints); if ((trusted = load_fingerprints(path, &trusted_count)) == NULL) { warnx("Error loading trusted certificates"); goto cleanup; } if (trusted_count == 0 || trusted == NULL) { fprintf(stderr, "No trusted certificates found.\n"); goto cleanup; } snprintf(path, MAXPATHLEN, "%s/revoked", fingerprints); if ((revoked = load_fingerprints(path, &revoked_count)) == NULL) { warnx("Error loading revoked certificates"); goto cleanup; } /* Read certificate and signature in. */ if ((sc = parse_cert(fd_sig)) == NULL) { warnx("Error parsing certificate"); goto cleanup; } /* Explicitly mark as non-trusted until proven otherwise. */ sc->trusted = false; /* Parse signature and pubkey out of the certificate */ sha256_buf(sc->cert, sc->certlen, hash); /* Check if this hash is revoked */ if (revoked != NULL) { STAILQ_FOREACH(fingerprint, revoked, next) { if (strcasecmp(fingerprint->hash, hash) == 0) { fprintf(stderr, "The package was signed with " "revoked certificate %s\n", fingerprint->name); goto cleanup; } } } STAILQ_FOREACH(fingerprint, trusted, next) { if (strcasecmp(fingerprint->hash, hash) == 0) { sc->trusted = true; sc->name = strdup(fingerprint->name); break; } } if (sc->trusted == false) { fprintf(stderr, "No trusted fingerprint found matching " "package's certificate\n"); goto cleanup; } /* Verify the signature. */ printf("Verifying signature with trusted certificate %s... ", sc->name); if (rsa_verify_cert(fd_pkg, sc->cert, sc->certlen, sc->sig, sc->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } ret = true; cleanup: if (trusted) free_fingerprint_list(trusted); if (revoked) free_fingerprint_list(revoked); if (sc) { if (sc->cert) free(sc->cert); if (sc->sig) free(sc->sig); if (sc->name) free(sc->name); free(sc); } return (ret); } static int bootstrap_pkg(bool force) { int fd_pkg, fd_sig; int ret; char url[MAXPATHLEN]; char tmppkg[MAXPATHLEN]; char tmpsig[MAXPATHLEN]; const char *packagesite; const char *signature_type; char pkgstatic[MAXPATHLEN]; fd_sig = -1; ret = -1; if (config_string(PACKAGESITE, &packagesite) != 0) { warnx("No PACKAGESITE defined"); return (-1); } if (config_string(SIGNATURE_TYPE, &signature_type) != 0) { warnx("Error looking up SIGNATURE_TYPE"); return (-1); } printf("Bootstrapping pkg from %s, please wait...\n", packagesite); /* Support pkg+http:// for PACKAGESITE which is the new format in 1.2 to avoid confusion on why http://pkg.FreeBSD.org has no A record. */ if (strncmp(URL_SCHEME_PREFIX, packagesite, strlen(URL_SCHEME_PREFIX)) == 0) packagesite += strlen(URL_SCHEME_PREFIX); snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz", packagesite); snprintf(tmppkg, MAXPATHLEN, "%s/pkg.txz.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); if ((fd_pkg = fetch_to_fd(url, tmppkg)) == -1) goto fetchfail; if (signature_type != NULL && strcasecmp(signature_type, "FINGERPRINTS") == 0) { snprintf(tmpsig, MAXPATHLEN, "%s/pkg.txz.sig.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.sig", packagesite); if ((fd_sig = fetch_to_fd(url, tmpsig)) == -1) { fprintf(stderr, "Signature for pkg not available.\n"); goto fetchfail; } if (verify_signature(fd_pkg, fd_sig) == false) goto cleanup; } if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0) ret = install_pkg_static(pkgstatic, tmppkg, force); goto cleanup; fetchfail: warnx("Error fetching %s: %s", url, fetchLastErrString); fprintf(stderr, "A pre-built version of pkg could not be found for " "your system.\n"); fprintf(stderr, "Consider changing PACKAGESITE or installing it from " "ports: 'ports-mgmt/pkg'.\n"); cleanup: if (fd_sig != -1) { close(fd_sig); unlink(tmpsig); } close(fd_pkg); unlink(tmppkg); return (ret); } static const char confirmation_message[] = "The package management tool is not yet installed on your system.\n" "Do you want to fetch and install it now? [y/N]: "; static int pkg_query_yes_no(void) { int ret, c; c = getchar(); if (c == 'y' || c == 'Y') ret = 1; else ret = 0; while (c != '\n' && c != EOF) c = getchar(); return (ret); } static int bootstrap_pkg_local(const char *pkgpath, bool force) { char path[MAXPATHLEN]; char pkgstatic[MAXPATHLEN]; const char *signature_type; int fd_pkg, fd_sig, ret; fd_sig = -1; ret = -1; fd_pkg = open(pkgpath, O_RDONLY); if (fd_pkg == -1) err(EXIT_FAILURE, "Unable to open %s", pkgpath); if (config_string(SIGNATURE_TYPE, &signature_type) != 0) { warnx("Error looking up SIGNATURE_TYPE"); return (-1); } if (signature_type != NULL && strcasecmp(signature_type, "FINGERPRINTS") == 0) { snprintf(path, sizeof(path), "%s.sig", pkgpath); if ((fd_sig = open(path, O_RDONLY)) == -1) { fprintf(stderr, "Signature for pkg not available.\n"); goto cleanup; } if (verify_signature(fd_pkg, fd_sig) == false) goto cleanup; } if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0) ret = install_pkg_static(pkgstatic, pkgpath, force); cleanup: close(fd_pkg); if (fd_sig != -1) close(fd_sig); return (ret); } int main(__unused int argc, char *argv[]) { char pkgpath[MAXPATHLEN]; const char *pkgarg; bool bootstrap_only, force, yes; bootstrap_only = false; force = false; pkgarg = NULL; yes = false; snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE); if (argc > 1 && strcmp(argv[1], "bootstrap") == 0) { bootstrap_only = true; if (argc == 3 && strcmp(argv[2], "-f") == 0) force = true; } if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) { /* * To allow 'pkg -N' to be used as a reliable test for whether * a system is configured to use pkg, don't bootstrap pkg * when that argument is given as argv[1]. */ if (argv[1] != NULL && strcmp(argv[1], "-N") == 0) errx(EXIT_FAILURE, "pkg is not installed"); config_init(); if (argc > 1 && strcmp(argv[1], "add") == 0) { if (argc > 2 && strcmp(argv[2], "-f") == 0) { force = true; pkgarg = argv[3]; } else pkgarg = argv[2]; if (pkgarg == NULL) { fprintf(stderr, "Path to pkg.txz required\n"); exit(EXIT_FAILURE); } if (access(pkgarg, R_OK) == -1) { fprintf(stderr, "No such file: %s\n", pkgarg); exit(EXIT_FAILURE); } if (bootstrap_pkg_local(pkgarg, force) != 0) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } /* * Do not ask for confirmation if either of stdin or stdout is * not tty. Check the environment to see if user has answer * tucked in there already. */ config_bool(ASSUME_ALWAYS_YES, &yes); if (!yes) { printf("%s", confirmation_message); if (!isatty(fileno(stdin))) exit(EXIT_FAILURE); if (pkg_query_yes_no() == 0) exit(EXIT_FAILURE); } if (bootstrap_pkg(force) != 0) exit(EXIT_FAILURE); config_finish(); if (bootstrap_only) exit(EXIT_SUCCESS); } else if (bootstrap_only) { printf("pkg already bootstrapped at %s\n", pkgpath); exit(EXIT_SUCCESS); } execv(pkgpath, argv); /* NOT REACHED */ return (EXIT_FAILURE); } Index: stable/10 =================================================================== --- stable/10 (revision 263019) +++ stable/10 (revision 263020) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r262400-262401,262418