From: Theo Buehler Subject: pkgconf 3.0.5 To: tech@openbsd.org Date: Thu, 6 Aug 2026 07:56:44 +0200 This updates pkgconf from 2.4.3 to its latest release 3.0.5. There's been a lot of code shuffling for expanded platform support, enabling fuzzing and testing without the kyua framework and also a lot of new code. The NEWS file is long: https://github.com/pkgconf/pkgconf/blob/main/NEWS Many of these changes don't matter for us since we have pkgconf-lite. Upstream is actively testing on OpenBSD and has added unveil support complementing the pledge we already have. I have added a small patch to modify the unveil code to do a single unveil("/", "r") in unveil_search_paths() instead of fine-grained iterations over files and directories that need to be readable. pkgconf usually runs as a normal user with stdio rpath wpath cpath and locked unveil. Only /dev/null and an optional log file have "rwc". I ran this through amd64 bulks and the only port that noticed (x11/xfce4/orage) has been fixed. Index: AUTHORS =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/AUTHORS,v diff -u -p -r1.1.1.1 AUTHORS --- AUTHORS 3 May 2025 15:09:38 -0000 1.1.1.1 +++ AUTHORS 4 Aug 2026 16:19:06 -0000 @@ -1,41 +1,97 @@ +✈ Graham ✈ A. Wilcox Alexander Tsoy Alexpux Alon Bar-Lev Alyx -Ariadne Conill +Andrea Pappacoda +Andrej Shadura +Antonin Décimo +Ariadne Conill Baptiste Daroussin -Baptiste Daroussin +Ben Bryan Drewery +Christoph Reiter +Colin Gillespie Dag-Erling Smørgrav Dan Kegel -Dan Kegel Dan Nicholson +data-man David Michael +David Seifert +Doug Freed +Dylan Baker +Eli Schwartz +Elizabeth Kiara Regina Ashford Emil Renner Berthing Fabian Groffen +Filipe Laíns Graham Ollis Gregor Richards +h30032433 +Harmen Stoppels +huyubiao Ignacio Casal Quinteiro Igor Gnatenko +Ingo Schwarze +Ismael Luceno Issam Maghni -JD Horelick Jason Dusek Javier Viguera +JD Horelick Jean-Sébastien Pédron +Jeff Moguillansky John Hein +Jonathan Gray +Joshua Watt Jussi Pakkanen +Kai Pastor +L. E. Segovia Leorize Luca Barbato +Marc-André Lureau Marcin Wojdyr +Mattias Hansson Maxin B. John Michał Górny +midipix Mike Frysinger +Mike L +moi15moi +mrgrouse +Neal Gompa +Nicolas Braud-Santoni +Olaf Hering +olf +orbea +Peter Kokot +Petr Písař +Pierce +Pierre Pronchery +psykose +rentianyue-jk +Ross Burton +Ryan Scott +Sam James +Sandro Mani Seungha Yang +Shubham Chakraborty +Simon Josefsson +Stefan Weil +Stéphane Rochoy +Stone Tickle +Taylor R Campbell +Timo Röhling TingPing Tobias Kortkamp +Tobias Stoeckmann Tony Theodore +Tuukka Pasanen +Undefine +Victor Westerhuis +Vincent Torri Volker Braun +wi24rd <14029004+wi24rd@users.noreply.github.com> +Xi Ruoyao Yu Kobayashi -orbea -✈ Graham ✈ +Ziemowit Łąski <15880281+zlaski@users.noreply.github.com> Index: COPYING =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/COPYING,v diff -u -p -r1.1.1.1 COPYING --- COPYING 3 May 2025 15:09:38 -0000 1.1.1.1 +++ COPYING 4 Aug 2026 16:19:06 -0000 @@ -1,5 +1,4 @@ -Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 - pkgconf authors (see AUTHORS file in source directory). +Copyright (c) 2011-2026 pkgconf authors (see AUTHORS file in source directory). Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above Index: Makefile =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/Makefile,v diff -u -p -r1.2 Makefile --- Makefile 7 May 2025 14:58:17 -0000 1.2 +++ Makefile 4 Aug 2026 16:19:06 -0000 @@ -14,17 +14,24 @@ SRCS = argvsplit.c \ audit.c \ bsdstubs.c \ buffer.c \ + bufferset.c \ + bytecode.c \ cache.c \ client.c \ + core.c \ dependency.c \ fileio.c \ fragment.c \ + license.c \ + output.c \ parser.c \ path.c \ personality.c \ pkg.c \ queue.c \ tuple.c \ + variable.c \ + version.c \ getopt_long.c \ main.c Index: cli/core.c =================================================================== RCS file: cli/core.c diff -N cli/core.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cli/core.c 4 Aug 2026 17:06:05 -0000 @@ -0,0 +1,1488 @@ +/* + * core.c + * core, printer functions + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "libpkgconf/config.h" +#include +#include +#include "core.h" +#include "getopt_long.h" +#ifndef PKGCONF_LITE +#include "renderer-msvc.h" +#endif + +static bool +print_list_entry(const pkgconf_pkg_t *entry, void *data) +{ + const pkgconf_node_t *n; + pkgconf_client_t *client = data; + + if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + return false; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%-30s %s - %s\n", entry->id, entry->realname, entry->description); + + PKGCONF_FOREACH_LIST_ENTRY(entry->provides.head, n) + { + const pkgconf_dependency_t *dep = n->data; + + if (!strcmp(dep->package, entry->id)) + continue; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%-30s %s - %s (provided by %s)\n", dep->package, entry->realname, entry->description, entry->id); + } + + return false; +} + +static bool +print_package_entry(const pkgconf_pkg_t *entry, void *data) +{ + const pkgconf_node_t *n; + pkgconf_client_t *client = data; + + if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + return false; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, entry->id); + + PKGCONF_FOREACH_LIST_ENTRY(entry->provides.head, n) + { + const pkgconf_dependency_t *dep = n->data; + + if (!strcmp(dep->package, entry->id)) + continue; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, dep->package); + } + + return false; +} + +static bool +filter_cflags(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + int got_flags = 0; + pkgconf_cli_state_t *state = client->client_data; + (void) data; + + if (!(state->want_flags & PKG_KEEP_SYSTEM_CFLAGS) && pkgconf_fragment_has_system_dir(client, frag)) + return false; + + if (state->want_fragment_filter != NULL && (strchr(state->want_fragment_filter, frag->type) == NULL || !frag->type)) + return false; + + if (frag->type == 'I') + got_flags = PKG_CFLAGS_ONLY_I; + else + got_flags = PKG_CFLAGS_ONLY_OTHER; + + return (state->want_flags & got_flags) != 0; +} + +static bool +filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + int got_flags = 0; + pkgconf_cli_state_t *state = client->client_data; + (void) data; + + if (!(state->want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag)) + return false; + + if (state->want_fragment_filter != NULL && (strchr(state->want_fragment_filter, frag->type) == NULL || !frag->type)) + return false; + + switch (frag->type) + { + case 'L': got_flags = PKG_LIBS_ONLY_LDPATH; break; + case 'l': got_flags = PKG_LIBS_ONLY_LIBNAME; break; + default: got_flags = PKG_LIBS_ONLY_OTHER; break; + } + + return (state->want_flags & got_flags) != 0; +} + +static void +print_variables(pkgconf_output_t *output, pkgconf_pkg_t *pkg) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node) + { + pkgconf_tuple_t *tuple = node->data; + + pkgconf_output_puts(output, PKGCONF_OUTPUT_STDOUT, tuple->key); + } +} + +static void +print_dependency_list(pkgconf_output_t *output, pkgconf_list_t *list) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + pkgconf_dependency_t *dep = node->data; + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "%s", dep->package); + + if (dep->version != NULL) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, " %s %s", + pkgconf_pkg_get_comparator(dep), dep->version); + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "\n"); + } +} + +static bool +apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_dependency_list(client->output, &pkg->provides); + } + + return true; +} + +#ifndef PKGCONF_LITE +static void +print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + (void) iter_flags; + + pkgconf_node_t *node; + pkgconf_pkg_t **last_seen = data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "\"%s\" [fontname=Sans fontsize=8", pkg->id); + + if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " fontcolor=gray color=gray"); + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " label=\"%s@%s\"]\n", pkg->id, pkg->version); + + if (last_seen != NULL) + { + if (*last_seen != NULL) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=red]\n", (*last_seen)->id, pkg->id); + + *last_seen = pkg; + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "\"%s\" -> \"%s\" [fontname=Sans fontsize=8", + pkg->id, dep_id); + + if (dep->flags & PKGCONF_PKG_DEPF_PRIVATE) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " color=gray"); + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "]"); + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); + } + + if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)) + { + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_shared.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=orange]\n", pkg->id, dep_id); + } + } +} + +static bool +apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + pkgconf_cli_state_t *state = client->client_data; + pkgconf_list_t *list = data; + pkgconf_pkg_t *last_seen = NULL; + pkgconf_node_t *iter; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, + "digraph deptree {\n" + "edge [color=blue len=7.5 fontname=Sans fontsize=8]\n" + "node [fontname=Sans fontsize=8]\n"); + + if (state->want_flags & PKG_PRINT_DIGRAPH_QUERY_NODES) + { + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, + "\"user:request\" [fontname=Sans fontsize=8]\n"); + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + pkgconf_queue_t *pkgq = iter->data; + pkgconf_pkg_t *pkg = pkgconf_pkg_find(client, pkgq->package); + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "\"user:request\" -> \"%s\" [fontname=Sans fontsize=8]\n", + pkg == NULL ? pkgq->package : pkg->id); + + if (pkg != NULL) + pkgconf_pkg_unref(client, pkg); + } + } + + eflag = pkgconf_pkg_traverse(client, world, print_digraph_node, &last_seen, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "}"); + return true; +} + +static void +print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused, unsigned int iter_flags) +{ + (void) iter_flags; + + (void) unused; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s (%llu)%s\n", + pkg->id, (unsigned long long) pkg->identifier, + (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) == PKGCONF_PKG_PROPF_VISITED_PRIVATE ? " [private]" : ""); +} + +static bool +apply_print_solution(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_solution_node, unused, maxdepth, 0); + + return eflag == PKGCONF_PKG_ERRF_OK; +} +#endif + +static bool +apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_cli_state_t *state = client->client_data; + pkgconf_node_t *queue_iter; + pkgconf_list_t *pkgq = data; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(pkgq->head, queue_iter) + { + pkgconf_node_t *world_iter; + pkgconf_queue_t *queue_node = queue_iter->data; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + pkgconf_pkg_t *pkg = dep->match; + + const size_t name_len = strlen(pkg->why); + if (name_len > strlen(queue_node->package) || + strncmp(pkg->why, queue_node->package, name_len) || + (queue_node->package[name_len] != 0 && + !isspace((unsigned char)queue_node->package[name_len]) && + !PKGCONF_IS_OPERATOR_CHAR(queue_node->package[name_len]))) + { + continue; + } + + if (pkg->version != NULL) { + if (state->verbosity) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: ", pkg->id); + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, pkg->version); + } + + break; + } + } + + return true; +} + +static bool +apply_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_variables(client->output, pkg); + } + + return true; +} + +static bool +apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + /* a module entry with no filename is either virtual, static (builtin) or synthesized. */ + if (pkg->filename != NULL) + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, pkg->filename); + } + + return true; +} + +static bool +apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, const void *variable, int maxdepth) +{ + pkgconf_node_t *iter; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + char *result; + + if (pkg == NULL) + continue; + + result = pkgconf_variable_eval_name(client, &pkg->vars, variable); + + if (result != NULL) + { + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%s%s", iter->prev != NULL ? " " : "", result); + free(result); + } + } + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, ""); + + return true; +} + +static bool +apply_env_var(const char *prefix, pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth, + unsigned int (*collect_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth), + bool (*filter_fn)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data), + void (*postprocess_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list)) +{ + pkgconf_cli_state_t *state = client->client_data; + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + unsigned int eflag; + bool ret = true; + + eflag = collect_fn(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter_splice(client, &filtered_list, &unfiltered_list, filter_fn, NULL); + + if (postprocess_fn != NULL) + postprocess_fn(client, world, &filtered_list); + + if (filtered_list.head == NULL) + goto out; + + if (!pkgconf_fragment_render_buf(&filtered_list, &render_buf, true, state->want_render_ops, + (state->want_flags & PKG_NEWLINES) ? '\n' : ' ')) + { + ret = false; + goto out; + } + + ret = pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s='%s'\n", + prefix, pkgconf_buffer_str_or_empty(&render_buf)); + +out: + pkgconf_buffer_finalize(&render_buf); + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return ret; +} + +static void +maybe_add_module_definitions(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list) +{ + pkgconf_node_t *world_iter; + pkgconf_cli_state_t *state = client->client_data; + + if ((state->want_flags & PKG_EXISTS_CFLAGS) != PKG_EXISTS_CFLAGS) + return; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + char havebuf[PKGCONF_ITEM_SIZE]; + char *p; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (dep->match == NULL) + continue; + + snprintf(havebuf, sizeof havebuf, "HAVE_%s", dep->match->id); + + for (p = havebuf; *p; p++) + { + switch (*p) + { + case ' ': + case '-': + *p = '_'; + break; + + default: + *p = (char) toupper((unsigned char) *p); + } + } + + pkgconf_fragment_insert(client, fragment_list, 'D', havebuf, false); + } +} + +static void +apply_env_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *env_prefix) +{ + pkgconf_node_t *world_iter; + pkgconf_cli_state_t *state = client->client_data; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + pkgconf_pkg_t *pkg = dep->match; + pkgconf_node_t *tuple_iter; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (dep->match == NULL) + continue; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, tuple_iter) + { + pkgconf_tuple_t *tuple = tuple_iter->data; + char havebuf[PKGCONF_ITEM_SIZE]; + char *p; + + if (state->want_variable != NULL && strcmp(state->want_variable, tuple->key)) + continue; + + snprintf(havebuf, sizeof havebuf, "%s_%s", env_prefix, tuple->key); + + for (p = havebuf; *p; p++) + { + switch (*p) + { + case ' ': + case '-': + *p = '_'; + break; + + default: + *p = (char) toupper((unsigned char) *p); + } + } + + char *val = pkgconf_variable_eval_str(client, &pkg->vars, tuple, NULL); + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%s='%s'\n", havebuf, val); + free(val); + } + } +} + +static bool +apply_env(pkgconf_client_t *client, pkgconf_pkg_t *world, const void *env_prefix_p, int maxdepth) +{ + pkgconf_cli_state_t *state = client->client_data; + const char *want_env_prefix = env_prefix_p, *it; + char workbuf[PKGCONF_ITEM_SIZE]; + + for (it = want_env_prefix; *it != '\0'; it++) + if (!isalpha((unsigned char)*it) && + !isdigit((unsigned char)*it)) + { + return false; + } + + snprintf(workbuf, sizeof workbuf, "%s_CFLAGS", want_env_prefix); + if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_cflags, filter_cflags, maybe_add_module_definitions)) + return false; + + snprintf(workbuf, sizeof workbuf, "%s_LIBS", want_env_prefix); + if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_libs, filter_libs, NULL)) + return false; + + if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || state->want_variable != NULL) + apply_env_variables(client, world, want_env_prefix); + + return true; +} + +static bool +apply_cflags(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *target_list, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter_splice(client, &filtered_list, &unfiltered_list, filter_cflags, NULL); + maybe_add_module_definitions(client, world, &filtered_list); + + if (filtered_list.head == NULL) + goto out; + + pkgconf_list_splice(target_list, &filtered_list); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static bool +apply_libs(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *target_list, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter_splice(client, &filtered_list, &unfiltered_list, filter_libs, NULL); + + if (filtered_list.head == NULL) + goto out; + + pkgconf_list_splice(target_list, &filtered_list); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static bool +apply_requires(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_dependency_list(client->output, &pkg->required); + } + + return true; +} + +static bool +apply_requires_private(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_dependency_list(client->output, &pkg->requires_private); + } + return true; +} + +static void +check_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + (void) iter_flags; + + int *retval = data; + (void) client; + + if (pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + *retval = EXIT_SUCCESS; +} + +static bool +apply_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, check_uninstalled, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +#ifndef PKGCONF_LITE +static void +print_graph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + (void) iter_flags; + + pkgconf_node_t *n; + + (void) data; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "node '%s' {\n", pkg->id); + + if (pkg->version != NULL) + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " version = '%s';\n", pkg->version); + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, n) + { + pkgconf_dependency_t *dep = n->data; + + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " dependency '%s'", dep->package); + + if (dep->compare != PKGCONF_CMP_ANY) + { + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + " {\n" + " comparator = '%s';\n" + " version = '%s';\n" + " };\n", + pkgconf_pkg_get_comparator(dep), dep->version); + } + else + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, ";"); + } + + pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "};"); +} + +static bool +apply_simulate(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_graph_node, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} +#endif + +static void +print_fragment_tree_branch(pkgconf_output_t *output, pkgconf_list_t *fragment_list, int indent) +{ + pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(fragment_list->head, iter) + { + pkgconf_fragment_t *frag = iter->data; + + if (frag->type) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, + "%*s'-%c%s' [type %c, " SIZE_FMT_SPECIFIER " children]\n", indent, "", frag->type, frag->data, frag->type, frag->children.length); + else + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, + "%*s'%s' [untyped, " SIZE_FMT_SPECIFIER " children]\n", indent, "", frag->data, frag->children.length); + + print_fragment_tree_branch(output, &frag->children, indent + 2); + } + + if (fragment_list->head != NULL) + pkgconf_output_puts(output, PKGCONF_OUTPUT_STDOUT, ""); +} + +static bool +apply_fragment_tree(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + (void) data; + + eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + print_fragment_tree_branch(client->output, &unfiltered_list, 0); + pkgconf_fragment_free(&unfiltered_list); + + return true; +} + +static void +print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + (void) iter_flags; + + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + int *retval = data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + if (pkg->license.head == NULL) + { + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, + "%s: NOASSERTION\n", pkg->id); + } + else + { + /* NOASSERTION is the default when the license is unknown, per SPDX spec § 3.15 */ + if (!pkgconf_license_render(client, &pkg->license, &render_buf) || + !pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: ", pkg->id) || + !pkgconf_output_putbuf(client->output, PKGCONF_OUTPUT_STDOUT, &render_buf, true)) + *retval = EXIT_FAILURE; + + pkgconf_buffer_finalize(&render_buf); + } +} + +static bool +apply_license(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_license, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +static bool +apply_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_list_t abis = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_node_t *node; + int *retval = data; + + if (pkgconf_pkg_link_abi(client, world, &abis, maxdepth) != PKGCONF_PKG_ERRF_OK) + { + pkgconf_bufferset_free(&abis); + *retval = EXIT_FAILURE; + return false; + } + + PKGCONF_FOREACH_LIST_ENTRY(abis.head, node) + { + pkgconf_bufferset_t *tag = node->data; + + if ((pkgconf_buffer_len(&render_buf) && + !pkgconf_buffer_push_byte(&render_buf, ' ')) || + !pkgconf_buffer_append(&render_buf, pkgconf_buffer_str(&tag->buffer))) + { + pkgconf_buffer_finalize(&render_buf); + pkgconf_bufferset_free(&abis); + *retval = EXIT_FAILURE; + return false; + } + } + + pkgconf_output_putbuf(client->output, PKGCONF_OUTPUT_STDOUT, &render_buf, true); + + pkgconf_buffer_finalize(&render_buf); + pkgconf_bufferset_free(&abis); + + return true; +} + +static void +print_license_file(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + (void) iter_flags; + + (void) data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + /* If license file location is not available then just print empty */ + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: %s\n", + pkg->id, pkg->license_file != NULL ? pkg->license_file : ""); +} + +static bool +apply_license_file(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_license_file, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +static void +print_source(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + (void) iter_flags; + + (void) data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + /* If source is empty then empty string is printed otherwise URL */ + pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: %s\n", + pkg->id, pkg->source != NULL ? pkg->source : ""); +} + +static bool +apply_source(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_source, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +bool +path_list_to_buffer(const pkgconf_list_t *list, pkgconf_buffer_t *buffer, char delim) +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, n) + { + pkgconf_path_t *pn = n->data; + + if (n != list->head) + { + if (!pkgconf_buffer_push_byte(buffer, delim)) + return false; + } + + if (!pkgconf_buffer_append(buffer, pn->path)) + return false; + } + + return true; +} + +static void +unveil_search_paths(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) +{ + pkgconf_node_t *n; + + client->unveil_handler(client, "/dev/null", "rwc"); + client->unveil_handler(client, "/", "r"); +} + +static bool +register_builtin(pkgconf_client_t *client, const char *id, + const pkgconf_buffer_t *pc_path_buf, + const pkgconf_buffer_t *pc_system_libdirs_buf, + const pkgconf_buffer_t *pc_system_includedirs_buf) +{ + pkgconf_pkg_t *pkg = calloc(1, sizeof(pkgconf_pkg_t)); + if (pkg == NULL) + return false; + + pkg->owner = client; + pkg->id = strdup(id); + pkg->realname = strdup(id); + pkg->description = strdup("virtual package defining pkgconf API version supported"); + pkg->url = strdup(PACKAGE_BUGREPORT); + pkg->version = strdup(PACKAGE_VERSION); + + if (pkg->id == NULL || + pkg->realname == NULL || + pkg->description == NULL || + pkg->url == NULL || + pkg->version == NULL || + pkgconf_tuple_add(client, &pkg->vars, "pc_system_libdirs", pkgconf_buffer_str_or_empty(pc_system_libdirs_buf), false, 0) == NULL || + pkgconf_tuple_add(client, &pkg->vars, "pc_system_includedirs", pkgconf_buffer_str_or_empty(pc_system_includedirs_buf), false, 0) == NULL || + pkgconf_tuple_add(client, &pkg->vars, "pc_path", pkgconf_buffer_str_or_empty(pc_path_buf), false, 0) == NULL || + !pkgconf_client_preload_one(client, pkg)) + { + pkgconf_pkg_free(client, pkg); + return false; + } + + return true; +} + +/* SAFETY: pkgconf_client_t takes ownership of successfully registered package objects */ +static void +register_builtins(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) +{ + pkgconf_buffer_t pc_path_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER; + + if (!path_list_to_buffer(&personality->dir_list, &pc_path_buf, ':') || + !path_list_to_buffer(&personality->filter_libdirs, &pc_system_libdirs_buf, ':') || + !path_list_to_buffer(&personality->filter_includedirs, &pc_system_includedirs_buf, ':')) + goto error; + + if (!register_builtin(client, "pkg-config", &pc_path_buf, &pc_system_libdirs_buf, &pc_system_includedirs_buf) || + !register_builtin(client, "pkgconf", &pc_path_buf, &pc_system_libdirs_buf, &pc_system_includedirs_buf)) + goto error; + +error: + pkgconf_buffer_finalize(&pc_path_buf); + pkgconf_buffer_finalize(&pc_system_libdirs_buf); + pkgconf_buffer_finalize(&pc_system_includedirs_buf); +} + +#ifndef PKGCONF_LITE +static void +dump_personality(pkgconf_output_t *output, const pkgconf_cross_personality_t *p) +{ + pkgconf_buffer_t pc_path_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER; + + if (!path_list_to_buffer(&p->dir_list, &pc_path_buf, ':') || + !path_list_to_buffer(&p->filter_libdirs, &pc_system_libdirs_buf, ':') || + !path_list_to_buffer(&p->filter_includedirs, &pc_system_includedirs_buf, ':')) + goto out; + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "Triplet: %s\n", p->name); + + if (p->sysroot_dir) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SysrootDir: %s\n", p->sysroot_dir); + + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "DefaultSearchPaths: %s\n", pc_path_buf.base); + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SystemIncludePaths: %s\n", pc_system_includedirs_buf.base); + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SystemLibraryPaths: %s\n", pc_system_libdirs_buf.base); + + if (p->want_default_pure) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "WantDefaultPure: true\n"); + + if (p->want_default_static) + pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "WantDefaultStatic: true\n"); + +out: + pkgconf_buffer_finalize(&pc_path_buf); + pkgconf_buffer_finalize(&pc_system_libdirs_buf); + pkgconf_buffer_finalize(&pc_system_includedirs_buf); +} +#endif + +int +pkgconf_cli_run(pkgconf_cli_state_t *state, int argc, char *argv[], int last_argc) +{ + (void) argc; + + int ret = EXIT_SUCCESS; + unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE; + const char *builddir; + const char *sysroot_dir; + pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; + pkgconf_node_t *node; + pkgconf_buffer_t queryparams = PKGCONF_BUFFER_INITIALIZER; + pkgconf_pkg_t world = { + .id = "virtual:world", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY) + { + dump_personality(state->pkg_client.output, state->pkg_client.personality); + + ret = EXIT_SUCCESS; + goto out; + } +#endif + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX) + state->want_render_ops = msvc_renderer_get(); +#endif + + if (pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_FDO_SYSROOT_RULES")) + want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; + + if (pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_PKGCONF1_SYSROOT_RULES")) + want_client_flags |= PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES; + + if ((state->want_flags & PKG_SHORT_ERRORS) == PKG_SHORT_ERRORS) + want_client_flags |= PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS; + + if ((state->want_flags & PKG_DONT_RELOCATE_PATHS) == PKG_DONT_RELOCATE_PATHS) + want_client_flags |= PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS; + + state->error_msgout = stderr; + if ((state->want_flags & PKG_ERRORS_ON_STDOUT) == PKG_ERRORS_ON_STDOUT) + state->error_msgout = stdout; + if ((state->want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS) { + state->error_msgout = fopen(PATH_DEV_NULL, "w"); + state->opened_error_msgout = true; + } + + if ((state->want_flags & PKG_IGNORE_CONFLICTS) == PKG_IGNORE_CONFLICTS || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_IGNORE_CONFLICTS") != NULL) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; + + if ((state->want_flags & PKG_STATIC) == PKG_STATIC || state->pkg_client.personality->want_default_static) + want_client_flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); + + if ((state->want_flags & PKG_SHARED) == PKG_SHARED) + want_client_flags &= ~(PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); + + /* if --static and --pure are both specified, then disable merge-back. + * this allows for a --static which searches private modules, but has the same fragment behaviour as if + * --static were disabled. see for rationale. + */ + if ((state->want_flags & PKG_PURE) == PKG_PURE || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_PURE_DEPGRAPH") != NULL || state->pkg_client.personality->want_default_pure) + want_client_flags &= ~PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS; + + if ((state->want_flags & PKG_ENV_ONLY) == PKG_ENV_ONLY) + want_client_flags |= PKGCONF_PKG_PKGF_ENV_ONLY; + + if ((state->want_flags & PKG_NO_CACHE) == PKG_NO_CACHE) + want_client_flags |= PKGCONF_PKG_PKGF_NO_CACHE; + +/* On Windows we want to always redefine the prefix by default + * but allow that behavior to be manually disabled */ +#if !defined(_WIN32) && !defined(_WIN64) + if ((state->want_flags & PKG_DEFINE_PREFIX) == PKG_DEFINE_PREFIX || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_RELOCATE_PATHS") != NULL) +#endif + want_client_flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX; + + if ((state->want_flags & PKG_NO_UNINSTALLED) == PKG_NO_UNINSTALLED || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_DISABLE_UNINSTALLED") != NULL) + want_client_flags |= PKGCONF_PKG_PKGF_NO_UNINSTALLED; + + if ((state->want_flags & PKG_NO_PROVIDES) == PKG_NO_PROVIDES) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_PROVIDES; + + if ((state->want_flags & PKG_DONT_DEFINE_PREFIX) == PKG_DONT_DEFINE_PREFIX || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_DONT_DEFINE_PREFIX") != NULL) + want_client_flags &= ~PKGCONF_PKG_PKGF_REDEFINE_PREFIX; + + if ((state->want_flags & PKG_INTERNAL_CFLAGS) == PKG_INTERNAL_CFLAGS) + want_client_flags |= PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS; + + /* --static --libs, --exists require the full dependency graph to be solved */ + if ((state->want_flags & (PKG_STATIC|PKG_LIBS)) == (PKG_STATIC|PKG_LIBS) || (state->want_flags & PKG_EXISTS) == PKG_EXISTS) + want_client_flags |= PKGCONF_PKG_PKGF_REQUIRE_INTERNAL; + + /* if these selectors are used, it means that we are querying metadata. + * so signal to libpkgconf that we only want to walk the flattened dependency set. + */ + const bool querying_flattened_metadata = + (state->want_flags & PKG_MODVERSION) == PKG_MODVERSION || + (state->want_flags & PKG_REQUIRES) == PKG_REQUIRES || + (state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || + (state->want_flags & PKG_PROVIDES) == PKG_PROVIDES || + (state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || + (state->want_flags & PKG_PATH) == PKG_PATH || + state->want_variable != NULL; + + if (querying_flattened_metadata) + state->maximum_traverse_depth = 1; + + /* metadata queries only report properties of the modules the user asked about, they + * never combine those modules into a build, so 'Conflicts' rules between them are not + * relevant. see . + */ + if (querying_flattened_metadata || + (state->want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE || + (state->want_flags & PKG_DUMP_LICENSE_FILE) == PKG_DUMP_LICENSE_FILE || + (state->want_flags & PKG_DUMP_SOURCE) == PKG_DUMP_SOURCE) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; + + /* if we are asking for a variable, path or list of variables, this only makes sense + * for a single package. + */ + if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || + (state->want_flags & PKG_PATH) == PKG_PATH || + state->want_variable != NULL) + { + state->maximum_package_count = 1; + } + + if ((state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || + (state->want_flags & PKG_CFLAGS)) + { + want_client_flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + } + + if ((builddir = pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_TOP_BUILD_DIR")) != NULL) + pkgconf_client_set_buildroot_dir(&state->pkg_client, builddir); + + if ((sysroot_dir = pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_SYSROOT_DIR")) != NULL) + { + const char *destdir; + + pkgconf_client_set_sysroot_dir(&state->pkg_client, sysroot_dir); + + if ((destdir = pkgconf_client_getenv(&state->pkg_client, "DESTDIR")) != NULL) + { + if (!strcmp(destdir, sysroot_dir)) + want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES | + PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION; + } + } + + /* we have determined what features we want most likely. in some cases, we override later. */ + pkgconf_client_set_flags(&state->pkg_client, want_client_flags); + + /* at this point, want_client_flags should be set, so build the dir list */ + pkgconf_client_dir_list_build(&state->pkg_client, state->pkg_client.personality); + + /* unveil the entire search path now that we have loaded the personality data and built the dir list. */ + unveil_search_paths(&state->pkg_client, state->pkg_client.personality); + + /* register built-in packages */ + register_builtins(&state->pkg_client, state->pkg_client.personality); + + /* preload any files in PKG_CONFIG_PRELOADED_FILES */ + pkgconf_client_preload_from_environ(&state->pkg_client, "PKG_CONFIG_PRELOADED_FILES"); + + if (state->required_pkgconfig_version != NULL) + { + if (pkgconf_compare_version(PACKAGE_VERSION, state->required_pkgconfig_version) >= 0) + ret = EXIT_SUCCESS; + else + ret = EXIT_FAILURE; + + goto out; + } + + if ((state->want_flags & PKG_LIST) == PKG_LIST) + { + pkgconf_scan_all(&state->pkg_client, &state->pkg_client, print_list_entry); + ret = EXIT_SUCCESS; + goto out; + } + + if ((state->want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES) + { + pkgconf_scan_all(&state->pkg_client, &state->pkg_client, print_package_entry); + ret = EXIT_SUCCESS; + goto out; + } + + while (last_argc < argc && argv[last_argc]) + { + if ((pkgconf_buffer_len(&queryparams) > 0 && + !pkgconf_buffer_push_byte(&queryparams, ' ')) || + !pkgconf_buffer_append(&queryparams, argv[last_argc])) + { + pkgconf_buffer_finalize(&queryparams); + ret = EXIT_FAILURE; + goto out; + } + + last_argc++; + } + + pkgconf_dependency_parse_str(&state->pkg_client, &deplist, pkgconf_buffer_str_or_empty(&queryparams), 0); + pkgconf_buffer_finalize(&queryparams); + + if (state->required_module_version != NULL || state->required_exact_module_version != NULL || state->required_max_module_version != NULL) + { + const char *target_version = NULL; + pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY; + + if (state->required_module_version != NULL) + { + target_version = state->required_module_version; + compare = PKGCONF_CMP_GREATER_THAN_EQUAL; + } + else if (state->required_exact_module_version != NULL) + { + target_version = state->required_exact_module_version; + compare = PKGCONF_CMP_EQUAL; + } + else if (state->required_max_module_version != NULL) + { + target_version = state->required_max_module_version; + compare = PKGCONF_CMP_LESS_THAN_EQUAL; + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *dep = node->data; + + /* already constrained at query level */ + if (dep->compare != PKGCONF_CMP_ANY) + continue; + + dep->compare = compare; + dep->version = strdup(target_version); + if (dep->version == NULL) + { + pkgconf_dependency_free(&deplist); + ret = EXIT_FAILURE; + goto out; + } + } + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *dep = node->data; + + /* check if there is a limit to the number of packages allowed to be included, if so and we have hit + * the limit, stop adding packages to the queue. + */ + if (state->maximum_package_count > 0 && pkgq.length >= state->maximum_package_count) + break; + + pkgconf_queue_push_dependency(&pkgq, dep); + } + + pkgconf_dependency_free(&deplist); + + if (pkgq.head == NULL) + { + pkgconf_output_puts(state->pkg_client.output, PKGCONF_OUTPUT_STDERR, + "Please specify at least one package name on the command line."); + ret = EXIT_FAILURE; + goto out; + } + + ret = EXIT_SUCCESS; + + if (!pkgconf_queue_solve(&state->pkg_client, &pkgq, &world, state->maximum_traverse_depth)) + { + ret = EXIT_FAILURE; + goto out; + } + + /* we shouldn't need to unveil any more filesystem accesses from this point, so lock it down */ + state->pkg_client.unveil_handler(&state->pkg_client, NULL, NULL); + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_SIMULATE) == PKG_SIMULATE) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS); + apply_simulate(&state->pkg_client, &world, NULL, -1); + } +#endif + + if ((state->want_flags & PKG_VALIDATE) == PKG_VALIDATE) + goto out; + + if ((state->want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE) + { + apply_license(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if ((state->want_flags & PKG_LINK_ABI) == PKG_LINK_ABI) + { + /* private dependencies only contribute their ABI when statically linked */ + if (!(state->want_flags & PKG_STATIC)) + pkgconf_client_set_flags(&state->pkg_client, state->pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + + apply_link_abi(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if ((state->want_flags & PKG_DUMP_LICENSE_FILE) == PKG_DUMP_LICENSE_FILE) + { + apply_license_file(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if ((state->want_flags & PKG_DUMP_SOURCE) == PKG_DUMP_SOURCE) + { + apply_source(&state->pkg_client, &world, &ret, 2); + goto out; + } + + + if ((state->want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED) + { + ret = EXIT_FAILURE; + apply_uninstalled(&state->pkg_client, &world, &ret, 2); + goto out; + } + + if (state->want_env_prefix != NULL) + { + apply_env(&state->pkg_client, &world, state->want_env_prefix, 2); + goto out; + } + + if ((state->want_flags & PKG_PROVIDES) == PKG_PROVIDES) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_provides(&state->pkg_client, &world, NULL, 2); + } + +#ifndef PKGCONF_LITE + if ((state->want_flags & PKG_DIGRAPH) == PKG_DIGRAPH) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_digraph(&state->pkg_client, &world, &pkgq, 2); + } + + if ((state->want_flags & PKG_SOLUTION) == PKG_SOLUTION) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_print_solution(&state->pkg_client, &world, NULL, 2); + } +#endif + + if ((state->want_flags & PKG_MODVERSION) == PKG_MODVERSION) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_modversion(&state->pkg_client, &world, &pkgq, 2); + } + + if ((state->want_flags & PKG_PATH) == PKG_PATH) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); + apply_path(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_variables(&state->pkg_client, &world, NULL, 2); + } + + if (state->want_variable != NULL) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); + apply_variable(&state->pkg_client, &world, state->want_variable, 2); + } + + if ((state->want_flags & PKG_REQUIRES) == PKG_REQUIRES) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_requires(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + apply_requires_private(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & PKG_FRAGMENT_TREE)) + { + state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + apply_fragment_tree(&state->pkg_client, &world, NULL, 2); + } + + if ((state->want_flags & (PKG_CFLAGS|PKG_LIBS))) + { + pkgconf_list_t target_list = PKGCONF_LIST_INITIALIZER; + pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER; + + if ((state->want_flags & PKG_CFLAGS)) + apply_cflags(&state->pkg_client, &world, &target_list, 2); + + if ((state->want_flags & PKG_LIBS) && !(state->want_flags & PKG_STATIC)) + pkgconf_client_set_flags(&state->pkg_client, state->pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + + if ((state->want_flags & PKG_LIBS)) + apply_libs(&state->pkg_client, &world, &target_list, 2); + + if (!pkgconf_fragment_render_buf(&target_list, &render_buf, true, state->want_render_ops, + (state->want_flags & PKG_NEWLINES) ? '\n' : ' ') || + !pkgconf_output_putbuf(state->pkg_client.output, PKGCONF_OUTPUT_STDOUT, &render_buf, true)) + ret = EXIT_FAILURE; + pkgconf_buffer_finalize(&render_buf); + + pkgconf_fragment_free(&target_list); + } + +out: + pkgconf_solution_free(&state->pkg_client, &world); + pkgconf_queue_free(&pkgq); + pkgconf_cli_state_reset(state); + + return ret; +} + +void +pkgconf_cli_state_reset(pkgconf_cli_state_t *state) +{ + pkgconf_cross_personality_deinit((void *) state->pkg_client.personality); + pkgconf_client_deinit(&state->pkg_client); + + if (state->logfile_out != NULL) + fclose(state->logfile_out); + if (state->opened_error_msgout) + fclose(state->error_msgout); +} Index: cli/core.h =================================================================== RCS file: cli/core.h diff -N cli/core.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cli/core.h 4 Aug 2026 16:20:03 -0000 @@ -0,0 +1,103 @@ +/* + * core.h + * core, printer functions + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef __CLI_CORE_H +#define __CLI_CORE_H + +#define PKG_CFLAGS_ONLY_I (((uint64_t) 1) << 2) +#define PKG_CFLAGS_ONLY_OTHER (((uint64_t) 1) << 3) +#define PKG_CFLAGS (PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHER) +#define PKG_LIBS_ONLY_LDPATH (((uint64_t) 1) << 5) +#define PKG_LIBS_ONLY_LIBNAME (((uint64_t) 1) << 6) +#define PKG_LIBS_ONLY_OTHER (((uint64_t) 1) << 7) +#define PKG_LIBS (PKG_LIBS_ONLY_LDPATH|PKG_LIBS_ONLY_LIBNAME|PKG_LIBS_ONLY_OTHER) +#define PKG_MODVERSION (((uint64_t) 1) << 8) +#define PKG_REQUIRES (((uint64_t) 1) << 9) +#define PKG_REQUIRES_PRIVATE (((uint64_t) 1) << 10) +#define PKG_VARIABLES (((uint64_t) 1) << 11) +#define PKG_DIGRAPH (((uint64_t) 1) << 12) +#define PKG_KEEP_SYSTEM_CFLAGS (((uint64_t) 1) << 13) +#define PKG_KEEP_SYSTEM_LIBS (((uint64_t) 1) << 14) +#define PKG_VERSION (((uint64_t) 1) << 15) +#define PKG_ABOUT (((uint64_t) 1) << 16) +#define PKG_ENV_ONLY (((uint64_t) 1) << 17) +#define PKG_ERRORS_ON_STDOUT (((uint64_t) 1) << 18) +#define PKG_SILENCE_ERRORS (((uint64_t) 1) << 19) +#define PKG_IGNORE_CONFLICTS (((uint64_t) 1) << 20) +#define PKG_STATIC (((uint64_t) 1) << 21) +#define PKG_NO_UNINSTALLED (((uint64_t) 1) << 22) +#define PKG_UNINSTALLED (((uint64_t) 1) << 23) +#define PKG_LIST (((uint64_t) 1) << 24) +#define PKG_HELP (((uint64_t) 1) << 25) +#define PKG_PRINT_ERRORS (((uint64_t) 1) << 26) +#define PKG_SIMULATE (((uint64_t) 1) << 27) +#define PKG_NO_CACHE (((uint64_t) 1) << 28) +#define PKG_PROVIDES (((uint64_t) 1) << 29) +#define PKG_VALIDATE (((uint64_t) 1) << 30) +#define PKG_LIST_PACKAGE_NAMES (((uint64_t) 1) << 31) +#define PKG_NO_PROVIDES (((uint64_t) 1) << 32) +#define PKG_PURE (((uint64_t) 1) << 33) +#define PKG_PATH (((uint64_t) 1) << 34) +#define PKG_DEFINE_PREFIX (((uint64_t) 1) << 35) +#define PKG_DONT_DEFINE_PREFIX (((uint64_t) 1) << 36) +#define PKG_DONT_RELOCATE_PATHS (((uint64_t) 1) << 37) +#define PKG_DEBUG (((uint64_t) 1) << 38) +#define PKG_SHORT_ERRORS (((uint64_t) 1) << 39) +#define PKG_EXISTS (((uint64_t) 1) << 40) +#define PKG_MSVC_SYNTAX (((uint64_t) 1) << 41) +#define PKG_INTERNAL_CFLAGS (((uint64_t) 1) << 42) +#define PKG_DUMP_PERSONALITY (((uint64_t) 1) << 43) +#define PKG_SHARED (((uint64_t) 1) << 44) +#define PKG_DUMP_LICENSE (((uint64_t) 1) << 45) +#define PKG_SOLUTION (((uint64_t) 1) << 46) +#define PKG_EXISTS_CFLAGS (((uint64_t) 1) << 47) +#define PKG_FRAGMENT_TREE (((uint64_t) 1) << 48) +#define PKG_DUMP_SOURCE (((uint64_t) 1) << 49) +#define PKG_DUMP_LICENSE_FILE (((uint64_t) 1) << 50) +#define PKG_NEWLINES (((uint64_t) 1) << 51) +#define PKG_PRINT_DIGRAPH_QUERY_NODES (((uint64_t) 1) << 52) +#define PKG_LINK_ABI (((uint64_t) 1) << 53) + +typedef struct { + pkgconf_client_t pkg_client; + pkgconf_fragment_render_ops_t *want_render_ops; + + uint64_t want_flags; + int verbosity; + int maximum_traverse_depth; + size_t maximum_package_count; + + const char *want_variable; + const char *want_fragment_filter; + const char *want_env_prefix; + + char *required_pkgconfig_version; + const char *required_exact_module_version; + const char *required_max_module_version; + const char *required_module_version; + + FILE *error_msgout; + FILE *logfile_out; + + bool opened_error_msgout; +} pkgconf_cli_state_t; + +extern bool path_list_to_buffer(const pkgconf_list_t *list, pkgconf_buffer_t *buffer, char delim); +extern int pkgconf_cli_run(pkgconf_cli_state_t *state, int argc, char *argv[], int last_argc); +extern void pkgconf_cli_state_reset(pkgconf_cli_state_t *state); + +#endif Index: cli/getopt_long.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/cli/getopt_long.c,v diff -u -p -r1.1.1.1 getopt_long.c --- cli/getopt_long.c 3 May 2025 15:09:38 -0000 1.1.1.1 +++ cli/getopt_long.c 4 Aug 2026 16:19:06 -0000 @@ -1,7 +1,8 @@ -/* $OpenBSD: getopt_long.c,v 1.1.1.1 2025/05/03 15:09:38 tb Exp $ */ +/* $OpenBSD: getopt_long.c,v 1.21 2006/09/22 17:22:05 millert Exp $ */ /* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ -/* +/* SPDX-License-Identifier: LicenseRef-scancode-sudo AND BSD-4-Clause + * * Copyright (c) 2002 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any Index: cli/getopt_long.h =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/cli/getopt_long.h,v diff -u -p -r1.1.1.1 getopt_long.h --- cli/getopt_long.h 3 May 2025 15:09:38 -0000 1.1.1.1 +++ cli/getopt_long.h 4 Aug 2026 16:19:06 -0000 @@ -2,6 +2,8 @@ /* $FreeBSD$ */ /*- + * SPDX-License-Identifier: BSD-2-Clause + * * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * Index: cli/main.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/cli/main.c,v diff -u -p -r1.2 main.c --- cli/main.c 9 May 2025 06:07:50 -0000 1.2 +++ cli/main.c 4 Aug 2026 16:19:06 -0000 @@ -1,9 +1,10 @@ /* * main.c - * main() routine, printer functions + * main() routine * - * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 - * pkgconf authors (see AUTHORS). + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2025 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,820 +19,100 @@ #include #include #include "getopt_long.h" -#ifndef PKGCONF_LITE -#include "renderer-msvc.h" -#endif -#ifdef _WIN32 -#include /* for _setmode() */ -#include -#endif - -#define PKG_CFLAGS_ONLY_I (((uint64_t) 1) << 2) -#define PKG_CFLAGS_ONLY_OTHER (((uint64_t) 1) << 3) -#define PKG_CFLAGS (PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHER) -#define PKG_LIBS_ONLY_LDPATH (((uint64_t) 1) << 5) -#define PKG_LIBS_ONLY_LIBNAME (((uint64_t) 1) << 6) -#define PKG_LIBS_ONLY_OTHER (((uint64_t) 1) << 7) -#define PKG_LIBS (PKG_LIBS_ONLY_LDPATH|PKG_LIBS_ONLY_LIBNAME|PKG_LIBS_ONLY_OTHER) -#define PKG_MODVERSION (((uint64_t) 1) << 8) -#define PKG_REQUIRES (((uint64_t) 1) << 9) -#define PKG_REQUIRES_PRIVATE (((uint64_t) 1) << 10) -#define PKG_VARIABLES (((uint64_t) 1) << 11) -#define PKG_DIGRAPH (((uint64_t) 1) << 12) -#define PKG_KEEP_SYSTEM_CFLAGS (((uint64_t) 1) << 13) -#define PKG_KEEP_SYSTEM_LIBS (((uint64_t) 1) << 14) -#define PKG_VERSION (((uint64_t) 1) << 15) -#define PKG_ABOUT (((uint64_t) 1) << 16) -#define PKG_ENV_ONLY (((uint64_t) 1) << 17) -#define PKG_ERRORS_ON_STDOUT (((uint64_t) 1) << 18) -#define PKG_SILENCE_ERRORS (((uint64_t) 1) << 19) -#define PKG_IGNORE_CONFLICTS (((uint64_t) 1) << 20) -#define PKG_STATIC (((uint64_t) 1) << 21) -#define PKG_NO_UNINSTALLED (((uint64_t) 1) << 22) -#define PKG_UNINSTALLED (((uint64_t) 1) << 23) -#define PKG_LIST (((uint64_t) 1) << 24) -#define PKG_HELP (((uint64_t) 1) << 25) -#define PKG_PRINT_ERRORS (((uint64_t) 1) << 26) -#define PKG_SIMULATE (((uint64_t) 1) << 27) -#define PKG_NO_CACHE (((uint64_t) 1) << 28) -#define PKG_PROVIDES (((uint64_t) 1) << 29) -#define PKG_VALIDATE (((uint64_t) 1) << 30) -#define PKG_LIST_PACKAGE_NAMES (((uint64_t) 1) << 31) -#define PKG_NO_PROVIDES (((uint64_t) 1) << 32) -#define PKG_PURE (((uint64_t) 1) << 33) -#define PKG_PATH (((uint64_t) 1) << 34) -#define PKG_DEFINE_PREFIX (((uint64_t) 1) << 35) -#define PKG_DONT_DEFINE_PREFIX (((uint64_t) 1) << 36) -#define PKG_DONT_RELOCATE_PATHS (((uint64_t) 1) << 37) -#define PKG_DEBUG (((uint64_t) 1) << 38) -#define PKG_SHORT_ERRORS (((uint64_t) 1) << 39) -#define PKG_EXISTS (((uint64_t) 1) << 40) -#define PKG_MSVC_SYNTAX (((uint64_t) 1) << 41) -#define PKG_INTERNAL_CFLAGS (((uint64_t) 1) << 42) -#define PKG_DUMP_PERSONALITY (((uint64_t) 1) << 43) -#define PKG_SHARED (((uint64_t) 1) << 44) -#define PKG_DUMP_LICENSE (((uint64_t) 1) << 45) -#define PKG_SOLUTION (((uint64_t) 1) << 46) -#define PKG_EXISTS_CFLAGS (((uint64_t) 1) << 47) -#define PKG_FRAGMENT_TREE (((uint64_t) 1) << 48) - -static pkgconf_client_t pkg_client; -static const pkgconf_fragment_render_ops_t *want_render_ops = NULL; - -static uint64_t want_flags; -static int verbosity = 0; -static int maximum_traverse_depth = 2000; -static size_t maximum_package_count = 0; - -static char *want_variable = NULL; -static char *want_fragment_filter = NULL; - -FILE *error_msgout = NULL; -FILE *logfile_out = NULL; - -static bool -error_handler(const char *msg, const pkgconf_client_t *client, void *data) -{ - (void) client; - (void) data; - fprintf(error_msgout, "%s", msg); - return true; -} - -static bool -print_list_entry(const pkgconf_pkg_t *entry, void *data) -{ - (void) data; - - if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) - return false; - - printf("%-30s %s - %s\n", entry->id, entry->realname, entry->description); - - return false; -} - -static bool -print_package_entry(const pkgconf_pkg_t *entry, void *data) -{ - (void) data; - - if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) - return false; - - printf("%s\n", entry->id); - - return false; -} - -static bool -filter_cflags(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) -{ - int got_flags = 0; - (void) client; - (void) data; - - if (!(want_flags & PKG_KEEP_SYSTEM_CFLAGS) && pkgconf_fragment_has_system_dir(client, frag)) - return false; - - if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type)) - return false; - - if (frag->type == 'I') - got_flags = PKG_CFLAGS_ONLY_I; - else - got_flags = PKG_CFLAGS_ONLY_OTHER; - - return (want_flags & got_flags) != 0; -} +#include "core.h" static bool -filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +parse_maximum_traverse_depth(const char *value, int *depth) { - int got_flags = 0; - (void) client; - (void) data; + char *end; + long parsed; - if (!(want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag)) - return false; + errno = 0; + parsed = strtol(value, &end, 10); - if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type)) + if (value == end || *end != '\0' || errno == ERANGE || parsed < -1 || parsed > INT_MAX) return false; - switch (frag->type) - { - case 'L': got_flags = PKG_LIBS_ONLY_LDPATH; break; - case 'l': got_flags = PKG_LIBS_ONLY_LIBNAME; break; - default: got_flags = PKG_LIBS_ONLY_OTHER; break; - } - - return (want_flags & got_flags) != 0; -} - -static void -print_variables(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node) - { - pkgconf_tuple_t *tuple = node->data; - - printf("%s\n", tuple->key); - } -} - -static void -print_requires(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) - { - pkgconf_dependency_t *dep = node->data; - - printf("%s", dep->package); - - if (dep->version != NULL) - printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); - - printf("\n"); - } -} - -static void -print_requires_private(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) - { - pkgconf_dependency_t *dep = node->data; - - printf("%s", dep->package); - - if (dep->version != NULL) - printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); - - printf("\n"); - } -} - -static void -print_provides(pkgconf_pkg_t *pkg) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->provides.head, node) - { - pkgconf_dependency_t *dep = node->data; - - printf("%s", dep->package); - - if (dep->version != NULL) - printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); - - printf("\n"); - } -} - -static bool -apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_provides(pkg); - } - + *depth = (int) parsed; return true; } -#ifndef PKGCONF_LITE -static void -print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +static const char * +environ_lookup_handler(const pkgconf_client_t *client, const char *key) { - pkgconf_node_t *node; (void) client; - pkgconf_pkg_t **last_seen = data; - - if(pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) - return; - if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) - printf("\"%s\" [fontname=Sans fontsize=8 fontcolor=gray color=gray]\n", pkg->id); - else - printf("\"%s\" [fontname=Sans fontsize=8]\n", pkg->id); - - if (last_seen != NULL) - { - if (*last_seen != NULL) - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=red]\n", (*last_seen)->id, pkg->id); - - *last_seen = pkg; - } - - PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) - { - pkgconf_dependency_t *dep = node->data; - const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; - - if ((dep->flags & PKGCONF_PKG_DEPF_PRIVATE) == 0) - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg->id, dep_id); - else - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); - } - - PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) - { - pkgconf_dependency_t *dep = node->data; - const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; - - printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); - } + return getenv(key); } static bool -apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +error_handler(const char *msg, const pkgconf_client_t *client, void *data) { - int eflag; - pkgconf_list_t *list = data; - pkgconf_pkg_t *last_seen = NULL; - pkgconf_node_t *iter; - - printf("digraph deptree {\n"); - printf("edge [color=blue len=7.5 fontname=Sans fontsize=8]\n"); - printf("node [fontname=Sans fontsize=8]\n"); - printf("\"user:request\" [fontname=Sans fontsize=8]\n"); - - PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) - { - pkgconf_queue_t *pkgq = iter->data; - pkgconf_pkg_t *pkg = pkgconf_pkg_find(client, pkgq->package); - printf("\"user:request\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg == NULL ? pkgq->package : pkg->id); - if (pkg != NULL) - pkgconf_pkg_unref(client, pkg); - } - - eflag = pkgconf_pkg_traverse(client, world, print_digraph_node, &last_seen, maxdepth, 0); + (void) data; + pkgconf_cli_state_t *state = client->client_data; - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; + if (state->error_msgout == NULL) + return true; - printf("}\n"); + pkgconf_output_file_fmt(state->error_msgout, "%s", msg); return true; } static void -print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) -{ - (void) client; - (void) unused; - - printf("%s (%"PRIu64")%s\n", pkg->id, pkg->identifier, (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) == PKGCONF_PKG_PROPF_VISITED_PRIVATE ? " [private]" : ""); -} - -static bool -apply_print_solution(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, print_solution_node, unused, maxdepth, 0); - - return eflag == PKGCONF_PKG_ERRF_OK; -} -#endif - -static bool -apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - pkgconf_node_t *queue_iter; - pkgconf_list_t *pkgq = data; - (void) client; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(pkgq->head, queue_iter) - { - pkgconf_node_t *world_iter; - pkgconf_queue_t *queue_node = queue_iter->data; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) - { - pkgconf_dependency_t *dep = world_iter->data; - pkgconf_pkg_t *pkg = dep->match; - - const size_t name_len = strlen(pkg->why); - if (name_len > strlen(queue_node->package) || - strncmp(pkg->why, queue_node->package, name_len) || - (queue_node->package[name_len] != 0 && - !isspace((unsigned char)queue_node->package[name_len]) && - !PKGCONF_IS_OPERATOR_CHAR(queue_node->package[name_len]))) - continue; - - if (pkg->version != NULL) { - if (verbosity) - printf("%s: ", pkg->id); - - printf("%s\n", pkg->version); - } - - break; - } - } - - return true; -} - -static bool -apply_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_variables(pkg); - } - - return true; -} - -static bool -apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions) { - pkgconf_node_t *iter; (void) client; - (void) unused; - (void) maxdepth; - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + if (pkgconf_unveil(path, permissions) == -1 && errno != ENOENT) { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - /* a module entry with no filename is either virtual, static (builtin) or synthesized. */ - if (pkg->filename != NULL) - printf("%s\n", pkg->filename); - } - - return true; -} - -static bool -apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, void *variable, int maxdepth) -{ - pkgconf_node_t *iter; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - const char *var; - - var = pkgconf_tuple_find(client, &pkg->vars, variable); - - if (var != NULL) - printf("%s%s", iter->prev != NULL ? " " : "", var); + pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); } - - printf("\n"); - - return true; -} - -static bool -apply_env_var(const char *prefix, pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth, - unsigned int (*collect_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth), - bool (*filter_fn)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data), - void (*postprocess_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list)) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; - unsigned int eflag; - char *render_buf; - - eflag = collect_fn(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_fn, NULL); - - if (postprocess_fn != NULL) - postprocess_fn(client, world, &filtered_list); - - if (filtered_list.head == NULL) - goto out; - - render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); - printf("%s='%s'\n", prefix, render_buf); - free(render_buf); - -out: - pkgconf_fragment_free(&unfiltered_list); - pkgconf_fragment_free(&filtered_list); - - return true; -} - -static void -maybe_add_module_definitions(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list) -{ - pkgconf_node_t *world_iter; - - if ((want_flags & PKG_EXISTS_CFLAGS) != PKG_EXISTS_CFLAGS) - return; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) - { - pkgconf_dependency_t *dep = world_iter->data; - char havebuf[PKGCONF_ITEM_SIZE]; - char *p; - - if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) - continue; - - if (dep->match == NULL) - continue; - - snprintf(havebuf, sizeof havebuf, "HAVE_%s", dep->match->id); - - for (p = havebuf; *p; p++) - { - switch (*p) - { - case ' ': - case '-': - *p = '_'; - break; - - default: - *p = toupper((unsigned char) *p); - } - } - - pkgconf_fragment_insert(client, fragment_list, 'D', havebuf, false); - } -} - -static void -apply_env_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *env_prefix) -{ - (void) client; - pkgconf_node_t *world_iter; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) - { - pkgconf_dependency_t *dep = world_iter->data; - pkgconf_pkg_t *pkg = dep->match; - pkgconf_node_t *tuple_iter; - - if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) - continue; - - if (dep->match == NULL) - continue; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, tuple_iter) - { - pkgconf_tuple_t *tuple = tuple_iter->data; - char havebuf[PKGCONF_ITEM_SIZE]; - char *p; - - if (want_variable != NULL && strcmp(want_variable, tuple->key)) - continue; - - snprintf(havebuf, sizeof havebuf, "%s_%s", env_prefix, tuple->key); - - for (p = havebuf; *p; p++) - { - switch (*p) - { - case ' ': - case '-': - *p = '_'; - break; - - default: - *p = toupper((unsigned char) *p); - } - } - - printf("%s='%s'\n", havebuf, tuple->value); - } - } -} - -static bool -apply_env(pkgconf_client_t *client, pkgconf_pkg_t *world, void *env_prefix_p, int maxdepth) -{ - const char *want_env_prefix = env_prefix_p, *it; - char workbuf[PKGCONF_ITEM_SIZE]; - - for (it = want_env_prefix; *it != '\0'; it++) - if (!isalpha((unsigned char)*it) && - !isdigit((unsigned char)*it)) - return false; - - snprintf(workbuf, sizeof workbuf, "%s_CFLAGS", want_env_prefix); - if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_cflags, filter_cflags, maybe_add_module_definitions)) - return false; - - snprintf(workbuf, sizeof workbuf, "%s_LIBS", want_env_prefix); - if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_libs, filter_libs, NULL)) - return false; - - if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES || want_variable != NULL) - apply_env_variables(client, world, want_env_prefix); - - return true; -} - -static bool -apply_cflags(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; - int eflag; - char *render_buf; - (void) unused; - - eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_cflags, NULL); - maybe_add_module_definitions(client, world, &filtered_list); - - if (filtered_list.head == NULL) - goto out; - - render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); - printf("%s", render_buf); - free(render_buf); - -out: - pkgconf_fragment_free(&unfiltered_list); - pkgconf_fragment_free(&filtered_list); - - return true; -} - -static bool -apply_libs(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; - int eflag; - char *render_buf; - (void) unused; - - eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_libs, NULL); - - if (filtered_list.head == NULL) - goto out; - - render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); - printf("%s", render_buf); - free(render_buf); - -out: - pkgconf_fragment_free(&unfiltered_list); - pkgconf_fragment_free(&filtered_list); - - return true; -} - -static bool -apply_requires(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_requires(pkg); - } - - return true; -} - -static bool -apply_requires_private(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) -{ - pkgconf_node_t *iter; - (void) client; - (void) unused; - (void) maxdepth; - - PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) - { - pkgconf_dependency_t *dep = iter->data; - pkgconf_pkg_t *pkg = dep->match; - - print_requires_private(pkg); - } - return true; -} - -static void -check_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) -{ - int *retval = data; - (void) client; - - if (pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED) - *retval = EXIT_SUCCESS; -} - -static bool -apply_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, check_uninstalled, data, maxdepth, 0); - - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - return true; } #ifndef PKGCONF_LITE -static void -print_graph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) -{ - pkgconf_node_t *n; - - (void) client; - (void) data; - - printf("node '%s' {\n", pkg->id); - - if (pkg->version != NULL) - printf(" version = '%s';\n", pkg->version); - - PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, n) - { - pkgconf_dependency_t *dep = n->data; - printf(" dependency '%s'", dep->package); - if (dep->compare != PKGCONF_CMP_ANY) - { - printf(" {\n"); - printf(" comparator = '%s';\n", pkgconf_pkg_get_comparator(dep)); - printf(" version = '%s';\n", dep->version); - printf(" };\n"); - } - else - printf(";\n"); - } - - printf("};\n"); -} - -static bool -apply_simulate(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +static pkgconf_cross_personality_t * +deduce_personality(char *argv[]) { - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, print_graph_node, data, maxdepth, 0); + const char *argv0 = argv[0]; + char *i, *prefix; + pkgconf_cross_personality_t *out; - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; + i = strrchr(argv0, '/'); + if (i != NULL) + argv0 = i + 1; - return true; -} +#if defined(_WIN32) || defined(_WIN64) + i = strrchr(argv0, '\\'); + if (i != NULL) + argv0 = i + 1; #endif -static void -print_fragment_tree_branch(pkgconf_list_t *fragment_list, int indent) -{ - pkgconf_node_t *iter; - - PKGCONF_FOREACH_LIST_ENTRY(fragment_list->head, iter) - { - pkgconf_fragment_t *frag = iter->data; - - if (frag->type) - printf("%*s'-%c%s' [type %c]\n", indent, "", frag->type, frag->data, frag->type); - else - printf("%*s'%s' [untyped]\n", indent, "", frag->data); + i = strstr(argv0, "-pkg"); + if (i == NULL) + return pkgconf_cross_personality_default(); - print_fragment_tree_branch(&frag->children, indent + 2); - } + prefix = pkgconf_strndup(argv0, i - argv0); + out = pkgconf_cross_personality_find(prefix); + free(prefix); + if (out == NULL) + return pkgconf_cross_personality_default(); - if (fragment_list->head != NULL) - printf("\n"); + return out; } +#endif -static bool -apply_fragment_tree(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; - int eflag; - - (void) data; - - eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - print_fragment_tree_branch(&unfiltered_list, 0); - pkgconf_fragment_free(&unfiltered_list); - - return true; -} +#ifdef _WIN32 +static UINT original_console_cp; +static UINT original_console_out_cp; static void -print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +restore_console_code_page(void) { - (void) client; - (void) data; - - if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) - return; - - /* NOASSERTION is the default when the license is unknown, per SPDX spec § 3.15 */ - printf("%s: %s\n", pkg->id, pkg->license != NULL ? pkg->license : "NOASSERTION"); -} - -static bool -apply_license(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) -{ - int eflag; - - eflag = pkgconf_pkg_traverse(client, world, print_license, data, maxdepth, 0); - - if (eflag != PKGCONF_PKG_ERRF_OK) - return false; - - return true; + SetConsoleCP(original_console_cp); + SetConsoleOutputCP(original_console_out_cp); } +#endif static void version(void) @@ -843,8 +124,7 @@ static void about(void) { printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); - printf("Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021\n"); - printf(" pkgconf authors (see AUTHORS in documentation directory).\n\n"); + printf("Copyright (c) 2011-2026 pkgconf authors (see AUTHORS in documentation directory)\n\n"); printf("Permission to use, copy, modify, and/or distribute this software for any\n"); printf("purpose with or without fee is hereby granted, provided that the above\n"); printf("copyright notice and this permission notice appear in all copies.\n\n"); @@ -885,7 +165,6 @@ usage(void) printf(" --dont-define-prefix do not override the prefix variable under any circumstances\n"); printf(" --prefix-variable=varname sets the name of the variable that pkgconf considers\n"); printf(" to be the package prefix\n"); - printf(" --relocate=path relocates a path and exits (mostly for testsuite)\n"); printf(" --dont-relocate-paths disables path relocation support\n"); #ifndef PKGCONF_LITE @@ -903,7 +182,7 @@ usage(void) printf(" --uninstalled check whether or not an uninstalled module will be used\n"); printf(" --no-uninstalled never use uninstalled modules when satisfying dependencies\n"); printf(" --no-provides do not use 'provides' rules to resolve dependencies\n"); - printf(" --maximum-traverse-depth maximum allowed depth for dependency graph\n"); + printf(" --maximum-traverse-depth maximum allowed depth for dependency graph (-1 for unlimited)\n"); printf(" --static be more aggressive when computing dependency graph\n"); printf(" (for static linking)\n"); printf(" --shared use a simplified dependency graph (usually default)\n"); @@ -931,6 +210,7 @@ usage(void) printf(" --print-variables print all known variables in module to stdout\n"); #ifndef PKGCONF_LITE printf(" --digraph print entire dependency graph in graphviz 'dot' format\n"); + printf(" --print-digraph-query-nodes also print query nodes in 'dot' format\n"); printf(" --solution print dependency graph solution in a simple format\n"); #endif printf(" --keep-system-cflags keep -I%s entries in cflags output\n", SYSTEM_INCLUDEDIR); @@ -938,131 +218,57 @@ usage(void) printf(" --path show the exact filenames for any matching .pc files\n"); printf(" --modversion print the specified module's version to stdout\n"); printf(" --internal-cflags do not filter 'internal' cflags from output\n"); - printf(" --license print the specified module's license to stdout if known\n"); - printf(" --exists-cflags add -DHAVE_FOO fragments to cflags for each found module\n"); - - printf("\nfiltering output:\n\n"); -#ifndef PKGCONF_LITE - printf(" --msvc-syntax print translatable fragments in MSVC syntax\n"); -#endif - printf(" --fragment-filter=types filter output fragments to the specified types\n"); - printf(" --env=prefix print output as shell-compatible environmental variables\n"); - printf(" --fragment-tree visualize printed CFLAGS/LIBS fragments as a tree\n"); - - printf("\nreport bugs to <%s>.\n", PACKAGE_BUGREPORT); -} - -static void -relocate_path(const char *path) -{ - char buf[PKGCONF_BUFSIZE]; - - pkgconf_strlcpy(buf, path, sizeof buf); - pkgconf_path_relocate(buf, sizeof buf); - - printf("%s\n", buf); -} - -#ifndef PKGCONF_LITE -static void -dump_personality(const pkgconf_cross_personality_t *p) -{ - pkgconf_node_t *n; - - printf("Triplet: %s\n", p->name); - - if (p->sysroot_dir) - printf("SysrootDir: %s\n", p->sysroot_dir); - - printf("DefaultSearchPaths: "); - PKGCONF_FOREACH_LIST_ENTRY(p->dir_list.head, n) - { - pkgconf_path_t *pn = n->data; - printf("%s ", pn->path); - } - - printf("\n"); - printf("SystemIncludePaths: "); - PKGCONF_FOREACH_LIST_ENTRY(p->filter_includedirs.head, n) - { - pkgconf_path_t *pn = n->data; - printf("%s ", pn->path); - } - - printf("\n"); - printf("SystemLibraryPaths: "); - PKGCONF_FOREACH_LIST_ENTRY(p->filter_libdirs.head, n) - { - pkgconf_path_t *pn = n->data; - printf("%s ", pn->path); - } - - printf("\n"); -} - -static pkgconf_cross_personality_t * -deduce_personality(char *argv[]) -{ - const char *argv0 = argv[0]; - char *i, *prefix; - pkgconf_cross_personality_t *out; - - i = strrchr(argv0, '/'); - if (i != NULL) - argv0 = i + 1; - -#if defined(_WIN32) || defined(_WIN64) - i = strrchr(argv0, '\\'); - if (i != NULL) - argv0 = i + 1; -#endif - - i = strstr(argv0, "-pkg"); - if (i == NULL) - return pkgconf_cross_personality_default(); + printf(" --license print the specified module's license to stdout if known\n"); + printf(" --link-abi print the link ABIs (e.g. c, c++) the module must be linked against\n"); + printf(" --source print the specified module's source code location to stdout if known\n"); + printf(" --exists-cflags add -DHAVE_FOO fragments to cflags for each found module\n"); - prefix = pkgconf_strndup(argv0, i - argv0); - out = pkgconf_cross_personality_find(prefix); - free(prefix); - if (out == NULL) - return pkgconf_cross_personality_default(); + printf("\nfiltering output:\n\n"); +#ifndef PKGCONF_LITE + printf(" --msvc-syntax print translatable fragments in MSVC syntax\n"); +#endif + printf(" --fragment-filter=types filter output fragments to the specified types\n"); + printf(" --env=prefix print output as shell-compatible environmental variables\n"); + printf(" --fragment-tree visualize printed CFLAGS/LIBS fragments as a tree\n"); + printf(" --newlines use newlines for whitespace between fragments\n"); - return out; + printf("\nreport bugs to <%s>.\n", PACKAGE_BUGREPORT); } -#endif int main(int argc, char *argv[]) { +#ifdef _WIN32 + // When activeCodePage is set to UTF-8 in the application manifest (requires Windows 1903+), + // GetACP() returns CP_UTF8 but the console code pages are not automatically updated to match. + // Detect this condition via GetACP() == CP_UTF8 and align the console code pages accordingly. + // Restoring on exit is safe: if ACP is already CP_UTF8, resetting to the saved values is a no-op. + if (GetACP() == CP_UTF8) + { + original_console_cp = GetConsoleCP(); + original_console_out_cp = GetConsoleOutputCP(); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + atexit(restore_console_code_page); + } +#endif + int ret; - pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + pkgconf_cli_state_t state = { + .want_flags = 0, + .maximum_traverse_depth = 256, + }; pkgconf_list_t dir_list = PKGCONF_LIST_INITIALIZER; - char *builddir; - char *sysroot_dir; char *env_traverse_depth; - char *required_pkgconfig_version = NULL; - char *required_exact_module_version = NULL; - char *required_max_module_version = NULL; - char *required_module_version = NULL; char *logfile_arg = NULL; - char *want_env_prefix = NULL; - unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE; pkgconf_cross_personality_t *personality = NULL; - bool opened_error_msgout = false; - pkgconf_pkg_t world = { - .id = "virtual:world", - .realname = "virtual world package", - .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, - }; - if (pkgconf_pledge("stdio rpath wpath cpath", NULL) == -1) + if (pkgconf_pledge("stdio rpath wpath cpath unveil", NULL) == -1) { - fprintf(stderr, "pkgconf: pledge failed: %s\n", strerror(errno)); + pkgconf_output_file_fmt(stderr, "pkgconf: pledge failed: %s\n", strerror(errno)); return EXIT_FAILURE; } - want_flags = 0; - #ifdef _WIN32 /* When running regression tests in cygwin, and building native * executable, tests fail unless native executable outputs unix @@ -1074,85 +280,91 @@ main(int argc, char *argv[]) #endif struct pkg_option options[] = { - { "version", no_argument, &want_flags, PKG_VERSION|PKG_PRINT_ERRORS, }, - { "about", no_argument, &want_flags, PKG_ABOUT|PKG_PRINT_ERRORS, }, + { "version", no_argument, &state.want_flags, PKG_VERSION|PKG_PRINT_ERRORS, }, + { "about", no_argument, &state.want_flags, PKG_ABOUT|PKG_PRINT_ERRORS, }, { "atleast-version", required_argument, NULL, 2, }, { "atleast-pkgconfig-version", required_argument, NULL, 3, }, - { "libs", no_argument, &want_flags, PKG_LIBS|PKG_PRINT_ERRORS, }, - { "cflags", no_argument, &want_flags, PKG_CFLAGS|PKG_PRINT_ERRORS, }, - { "modversion", no_argument, &want_flags, PKG_MODVERSION|PKG_PRINT_ERRORS, }, + { "libs", no_argument, &state.want_flags, PKG_LIBS|PKG_PRINT_ERRORS, }, + { "cflags", no_argument, &state.want_flags, PKG_CFLAGS|PKG_PRINT_ERRORS, }, + { "modversion", no_argument, &state.want_flags, PKG_MODVERSION|PKG_PRINT_ERRORS, }, { "variable", required_argument, NULL, 7, }, - { "exists", no_argument, &want_flags, PKG_EXISTS, }, - { "print-errors", no_argument, &want_flags, PKG_PRINT_ERRORS, }, - { "short-errors", no_argument, &want_flags, PKG_SHORT_ERRORS, }, + { "exists", no_argument, &state.want_flags, PKG_EXISTS, }, + { "print-errors", no_argument, &state.want_flags, PKG_PRINT_ERRORS, }, + { "short-errors", no_argument, &state.want_flags, PKG_SHORT_ERRORS, }, { "maximum-traverse-depth", required_argument, NULL, 11, }, - { "static", no_argument, &want_flags, PKG_STATIC, }, - { "shared", no_argument, &want_flags, PKG_SHARED, }, - { "pure", no_argument, &want_flags, PKG_PURE, }, - { "print-requires", no_argument, &want_flags, PKG_REQUIRES, }, - { "print-variables", no_argument, &want_flags, PKG_VARIABLES|PKG_PRINT_ERRORS, }, -#ifndef PKGCONF_LITE - { "digraph", no_argument, &want_flags, PKG_DIGRAPH, }, - { "solution", no_argument, &want_flags, PKG_SOLUTION, }, -#endif - { "help", no_argument, &want_flags, PKG_HELP, }, - { "env-only", no_argument, &want_flags, PKG_ENV_ONLY, }, - { "print-requires-private", no_argument, &want_flags, PKG_REQUIRES_PRIVATE, }, - { "cflags-only-I", no_argument, &want_flags, PKG_CFLAGS_ONLY_I|PKG_PRINT_ERRORS, }, - { "cflags-only-other", no_argument, &want_flags, PKG_CFLAGS_ONLY_OTHER|PKG_PRINT_ERRORS, }, - { "libs-only-L", no_argument, &want_flags, PKG_LIBS_ONLY_LDPATH|PKG_PRINT_ERRORS, }, - { "libs-only-l", no_argument, &want_flags, PKG_LIBS_ONLY_LIBNAME|PKG_PRINT_ERRORS, }, - { "libs-only-other", no_argument, &want_flags, PKG_LIBS_ONLY_OTHER|PKG_PRINT_ERRORS, }, - { "uninstalled", no_argument, &want_flags, PKG_UNINSTALLED, }, - { "no-uninstalled", no_argument, &want_flags, PKG_NO_UNINSTALLED, }, - { "keep-system-cflags", no_argument, &want_flags, PKG_KEEP_SYSTEM_CFLAGS, }, - { "keep-system-libs", no_argument, &want_flags, PKG_KEEP_SYSTEM_LIBS, }, + { "static", no_argument, &state.want_flags, PKG_STATIC, }, + { "shared", no_argument, &state.want_flags, PKG_SHARED, }, + { "pure", no_argument, &state.want_flags, PKG_PURE, }, + { "print-requires", no_argument, &state.want_flags, PKG_REQUIRES, }, + { "print-variables", no_argument, &state.want_flags, PKG_VARIABLES|PKG_PRINT_ERRORS, }, +#ifndef PKGCONF_LITE + { "digraph", no_argument, &state.want_flags, PKG_DIGRAPH, }, + { "solution", no_argument, &state.want_flags, PKG_SOLUTION, }, +#endif + { "help", no_argument, &state.want_flags, PKG_HELP, }, + { "env-only", no_argument, &state.want_flags, PKG_ENV_ONLY, }, + { "print-requires-private", no_argument, &state.want_flags, PKG_REQUIRES_PRIVATE, }, + { "cflags-only-I", no_argument, &state.want_flags, PKG_CFLAGS_ONLY_I|PKG_PRINT_ERRORS, }, + { "cflags-only-other", no_argument, &state.want_flags, PKG_CFLAGS_ONLY_OTHER|PKG_PRINT_ERRORS, }, + { "libs-only-L", no_argument, &state.want_flags, PKG_LIBS_ONLY_LDPATH|PKG_PRINT_ERRORS, }, + { "libs-only-l", no_argument, &state.want_flags, PKG_LIBS_ONLY_LIBNAME|PKG_PRINT_ERRORS, }, + { "libs-only-other", no_argument, &state.want_flags, PKG_LIBS_ONLY_OTHER|PKG_PRINT_ERRORS, }, + { "uninstalled", no_argument, &state.want_flags, PKG_UNINSTALLED, }, + { "no-uninstalled", no_argument, &state.want_flags, PKG_NO_UNINSTALLED, }, + { "keep-system-cflags", no_argument, &state.want_flags, PKG_KEEP_SYSTEM_CFLAGS, }, + { "keep-system-libs", no_argument, &state.want_flags, PKG_KEEP_SYSTEM_LIBS, }, { "define-variable", required_argument, NULL, 27, }, { "exact-version", required_argument, NULL, 28, }, { "max-version", required_argument, NULL, 29, }, - { "ignore-conflicts", no_argument, &want_flags, PKG_IGNORE_CONFLICTS, }, - { "errors-to-stdout", no_argument, &want_flags, PKG_ERRORS_ON_STDOUT, }, - { "silence-errors", no_argument, &want_flags, PKG_SILENCE_ERRORS, }, - { "list-all", no_argument, &want_flags, PKG_LIST|PKG_PRINT_ERRORS, }, - { "list-package-names", no_argument, &want_flags, PKG_LIST_PACKAGE_NAMES|PKG_PRINT_ERRORS, }, -#ifndef PKGCONF_LITE - { "simulate", no_argument, &want_flags, PKG_SIMULATE, }, -#endif - { "no-cache", no_argument, &want_flags, PKG_NO_CACHE, }, - { "print-provides", no_argument, &want_flags, PKG_PROVIDES, }, - { "no-provides", no_argument, &want_flags, PKG_NO_PROVIDES, }, - { "debug", no_argument, &want_flags, PKG_DEBUG|PKG_PRINT_ERRORS, }, - { "validate", no_argument, &want_flags, PKG_VALIDATE|PKG_PRINT_ERRORS|PKG_ERRORS_ON_STDOUT }, + { "ignore-conflicts", no_argument, &state.want_flags, PKG_IGNORE_CONFLICTS, }, + { "errors-to-stdout", no_argument, &state.want_flags, PKG_ERRORS_ON_STDOUT, }, + { "silence-errors", no_argument, &state.want_flags, PKG_SILENCE_ERRORS, }, + { "list-all", no_argument, &state.want_flags, PKG_LIST|PKG_PRINT_ERRORS, }, + { "list-package-names", no_argument, &state.want_flags, PKG_LIST_PACKAGE_NAMES|PKG_PRINT_ERRORS, }, +#ifndef PKGCONF_LITE + { "simulate", no_argument, &state.want_flags, PKG_SIMULATE, }, +#endif + { "no-cache", no_argument, &state.want_flags, PKG_NO_CACHE, }, + { "print-provides", no_argument, &state.want_flags, PKG_PROVIDES, }, + { "no-provides", no_argument, &state.want_flags, PKG_NO_PROVIDES, }, + { "debug", no_argument, &state.want_flags, PKG_DEBUG|PKG_PRINT_ERRORS, }, + { "validate", no_argument, &state.want_flags, PKG_VALIDATE|PKG_PRINT_ERRORS|PKG_ERRORS_ON_STDOUT }, { "log-file", required_argument, NULL, 40 }, - { "path", no_argument, &want_flags, PKG_PATH }, + { "path", no_argument, &state.want_flags, PKG_PATH }, { "with-path", required_argument, NULL, 42 }, { "prefix-variable", required_argument, NULL, 43 }, - { "define-prefix", no_argument, &want_flags, PKG_DEFINE_PREFIX }, - { "relocate", required_argument, NULL, 45 }, - { "dont-define-prefix", no_argument, &want_flags, PKG_DONT_DEFINE_PREFIX }, - { "dont-relocate-paths", no_argument, &want_flags, PKG_DONT_RELOCATE_PATHS }, + { "define-prefix", no_argument, &state.want_flags, PKG_DEFINE_PREFIX }, + { "dont-define-prefix", no_argument, &state.want_flags, PKG_DONT_DEFINE_PREFIX }, + { "dont-relocate-paths", no_argument, &state.want_flags, PKG_DONT_RELOCATE_PATHS }, { "env", required_argument, NULL, 48 }, #ifndef PKGCONF_LITE - { "msvc-syntax", no_argument, &want_flags, PKG_MSVC_SYNTAX }, + { "msvc-syntax", no_argument, &state.want_flags, PKG_MSVC_SYNTAX }, #endif { "fragment-filter", required_argument, NULL, 50 }, - { "internal-cflags", no_argument, &want_flags, PKG_INTERNAL_CFLAGS }, + { "internal-cflags", no_argument, &state.want_flags, PKG_INTERNAL_CFLAGS }, #ifndef PKGCONF_LITE - { "dump-personality", no_argument, &want_flags, PKG_DUMP_PERSONALITY }, + { "dump-personality", no_argument, &state.want_flags, PKG_DUMP_PERSONALITY }, { "personality", required_argument, NULL, 53 }, #endif - { "license", no_argument, &want_flags, PKG_DUMP_LICENSE }, + { "license", no_argument, &state.want_flags, PKG_DUMP_LICENSE }, + { "license-file", no_argument, &state.want_flags, PKG_DUMP_LICENSE_FILE }, + { "link-abi", no_argument, &state.want_flags, PKG_LINK_ABI }, { "verbose", no_argument, NULL, 55 }, - { "exists-cflags", no_argument, &want_flags, PKG_EXISTS_CFLAGS }, - { "fragment-tree", no_argument, &want_flags, PKG_FRAGMENT_TREE }, + { "exists-cflags", no_argument, &state.want_flags, PKG_EXISTS_CFLAGS }, + { "fragment-tree", no_argument, &state.want_flags, PKG_FRAGMENT_TREE }, + { "source", no_argument, &state.want_flags, PKG_DUMP_SOURCE }, + { "newlines", no_argument, &state.want_flags, PKG_NEWLINES }, +#ifndef PKGCONF_LITE + { "print-digraph-query-nodes", no_argument, &state.want_flags, PKG_PRINT_DIGRAPH_QUERY_NODES }, +#endif { NULL, 0, NULL, 0 } }; #ifndef PKGCONF_LITE if (getenv("PKG_CONFIG_EARLY_TRACE")) { - error_msgout = stderr; - pkgconf_client_set_trace_handler(&pkg_client, error_handler, NULL); + state.error_msgout = stderr; + pkgconf_client_set_trace_handler(&state.pkg_client, error_handler, NULL); } #endif @@ -1161,25 +373,30 @@ main(int argc, char *argv[]) switch (ret) { case 2: - required_module_version = pkg_optarg; + state.required_module_version = pkg_optarg; break; case 3: - required_pkgconfig_version = pkg_optarg; + state.required_pkgconfig_version = pkg_optarg; break; case 7: - want_variable = pkg_optarg; + state.want_variable = pkg_optarg; break; case 11: - maximum_traverse_depth = atoi(pkg_optarg); + if (!parse_maximum_traverse_depth(pkg_optarg, &state.maximum_traverse_depth)) + { + pkgconf_output_file_fmt(stderr, "pkgconf: invalid maximum traverse depth: %s\n", pkg_optarg); + ret = EXIT_FAILURE; + goto out; + } break; case 27: - pkgconf_tuple_define_global(&pkg_client, pkg_optarg); + pkgconf_tuple_define_global(&state.pkg_client, pkg_optarg); break; case 28: - required_exact_module_version = pkg_optarg; + state.required_exact_module_version = pkg_optarg; break; case 29: - required_max_module_version = pkg_optarg; + state.required_max_module_version = pkg_optarg; break; case 40: logfile_arg = pkg_optarg; @@ -1188,16 +405,13 @@ main(int argc, char *argv[]) pkgconf_path_prepend(pkg_optarg, &dir_list, true); break; case 43: - pkgconf_client_set_prefix_varname(&pkg_client, pkg_optarg); + pkgconf_client_set_prefix_varname(&state.pkg_client, pkg_optarg); break; - case 45: - relocate_path(pkg_optarg); - return EXIT_SUCCESS; case 48: - want_env_prefix = pkg_optarg; + state.want_env_prefix = pkg_optarg; break; case 50: - want_fragment_filter = pkg_optarg; + state.want_fragment_filter = pkg_optarg; break; #ifndef PKGCONF_LITE case 53: @@ -1205,7 +419,7 @@ main(int argc, char *argv[]) break; #endif case 55: - verbosity++; + state.verbosity++; break; case '?': case ':': @@ -1224,48 +438,53 @@ main(int argc, char *argv[]) #endif } - pkgconf_path_copy_list(&personality->dir_list, &dir_list); - pkgconf_path_free(&dir_list); - -#ifndef PKGCONF_LITE - if ((want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY) - { - dump_personality(personality); - return EXIT_SUCCESS; - } -#endif - /* now, bring up the client. settings are preserved since the client is prealloced */ - pkgconf_client_init(&pkg_client, error_handler, NULL, personality); + pkgconf_client_options_t client_options = { + .error_handler = error_handler, + .error_handler_data = &state, + .personality = personality, + .client_data = &state, + .environ_lookup_handler = environ_lookup_handler, + .unveil_handler = unveil_handler, + }; + pkgconf_client_init_with_options(&state.pkg_client, &client_options); #ifndef PKGCONF_LITE - if ((want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX || getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL) - want_render_ops = msvc_renderer_get(); + if (getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL) + state.want_flags |= PKG_MSVC_SYNTAX; #endif - if ((env_traverse_depth = getenv("PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH")) != NULL) - maximum_traverse_depth = atoi(env_traverse_depth); + if ((env_traverse_depth = getenv("PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH")) != NULL && + !parse_maximum_traverse_depth(env_traverse_depth, &state.maximum_traverse_depth)) + { + pkgconf_output_file_fmt(stderr, "pkgconf: invalid maximum traverse depth: %s\n", env_traverse_depth); + ret = EXIT_FAILURE; + goto out; + } - if ((want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS) - want_flags |= (PKG_SILENCE_ERRORS); + if ((state.want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS) + state.want_flags |= (PKG_SILENCE_ERRORS); - if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW")) - want_flags |= (PKG_SILENCE_ERRORS); + if ((state.want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW")) + state.want_flags |= (PKG_SILENCE_ERRORS); else - want_flags &= ~(PKG_SILENCE_ERRORS); + state.want_flags &= ~(PKG_SILENCE_ERRORS); if (getenv("PKG_CONFIG_DONT_RELOCATE_PATHS")) - want_flags |= (PKG_DONT_RELOCATE_PATHS); + state.want_flags |= (PKG_DONT_RELOCATE_PATHS); - if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE || (want_flags & PKG_DEBUG) == PKG_DEBUG) - pkgconf_client_set_warn_handler(&pkg_client, error_handler, NULL); + if ((state.want_flags & PKG_VALIDATE) == PKG_VALIDATE || (state.want_flags & PKG_DEBUG) == PKG_DEBUG) + pkgconf_client_set_warn_handler(&state.pkg_client, error_handler, NULL); #ifndef PKGCONF_LITE - if ((want_flags & PKG_DEBUG) == PKG_DEBUG) - pkgconf_client_set_trace_handler(&pkg_client, error_handler, NULL); + if ((state.want_flags & PKG_DEBUG) == PKG_DEBUG) + pkgconf_client_set_trace_handler(&state.pkg_client, error_handler, NULL); #endif - if ((want_flags & PKG_ABOUT) == PKG_ABOUT) + pkgconf_path_prepend_list(&state.pkg_client.dir_list, &dir_list); + pkgconf_path_free(&dir_list); + + if ((state.want_flags & PKG_ABOUT) == PKG_ABOUT) { about(); @@ -1273,7 +492,7 @@ main(int argc, char *argv[]) goto out; } - if ((want_flags & PKG_VERSION) == PKG_VERSION) + if ((state.want_flags & PKG_VERSION) == PKG_VERSION) { version(); @@ -1281,7 +500,7 @@ main(int argc, char *argv[]) goto out; } - if ((want_flags & PKG_HELP) == PKG_HELP) + if ((state.want_flags & PKG_HELP) == PKG_HELP) { usage(); @@ -1289,471 +508,30 @@ main(int argc, char *argv[]) goto out; } - if (getenv("PKG_CONFIG_FDO_SYSROOT_RULES")) - want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; - - if (getenv("PKG_CONFIG_PKGCONF1_SYSROOT_RULES")) - want_client_flags |= PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES; - - if ((want_flags & PKG_SHORT_ERRORS) == PKG_SHORT_ERRORS) - want_client_flags |= PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS; - - if ((want_flags & PKG_DONT_RELOCATE_PATHS) == PKG_DONT_RELOCATE_PATHS) - want_client_flags |= PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS; - - error_msgout = stderr; - if ((want_flags & PKG_ERRORS_ON_STDOUT) == PKG_ERRORS_ON_STDOUT) - error_msgout = stdout; - if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS) { - error_msgout = fopen(PATH_DEV_NULL, "w"); - opened_error_msgout = true; - } - - if ((want_flags & PKG_IGNORE_CONFLICTS) == PKG_IGNORE_CONFLICTS || getenv("PKG_CONFIG_IGNORE_CONFLICTS") != NULL) - want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; - - if ((want_flags & PKG_STATIC) == PKG_STATIC || personality->want_default_static) - want_client_flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); - - if ((want_flags & PKG_SHARED) == PKG_SHARED) - want_client_flags &= ~(PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); - - /* if --static and --pure are both specified, then disable merge-back. - * this allows for a --static which searches private modules, but has the same fragment behaviour as if - * --static were disabled. see for rationale. - */ - if ((want_flags & PKG_PURE) == PKG_PURE || getenv("PKG_CONFIG_PURE_DEPGRAPH") != NULL || personality->want_default_pure) - want_client_flags &= ~PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS; - - if ((want_flags & PKG_ENV_ONLY) == PKG_ENV_ONLY) - want_client_flags |= PKGCONF_PKG_PKGF_ENV_ONLY; - - if ((want_flags & PKG_NO_CACHE) == PKG_NO_CACHE) - want_client_flags |= PKGCONF_PKG_PKGF_NO_CACHE; - -/* On Windows we want to always redefine the prefix by default - * but allow that behavior to be manually disabled */ -#if !defined(_WIN32) && !defined(_WIN64) - if ((want_flags & PKG_DEFINE_PREFIX) == PKG_DEFINE_PREFIX || getenv("PKG_CONFIG_RELOCATE_PATHS") != NULL) -#endif - want_client_flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX; - - if ((want_flags & PKG_NO_UNINSTALLED) == PKG_NO_UNINSTALLED || getenv("PKG_CONFIG_DISABLE_UNINSTALLED") != NULL) - want_client_flags |= PKGCONF_PKG_PKGF_NO_UNINSTALLED; - - if ((want_flags & PKG_NO_PROVIDES) == PKG_NO_PROVIDES) - want_client_flags |= PKGCONF_PKG_PKGF_SKIP_PROVIDES; - - if ((want_flags & PKG_DONT_DEFINE_PREFIX) == PKG_DONT_DEFINE_PREFIX || getenv("PKG_CONFIG_DONT_DEFINE_PREFIX") != NULL) - want_client_flags &= ~PKGCONF_PKG_PKGF_REDEFINE_PREFIX; - - if ((want_flags & PKG_INTERNAL_CFLAGS) == PKG_INTERNAL_CFLAGS) - want_client_flags |= PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS; - - /* if these selectors are used, it means that we are querying metadata. - * so signal to libpkgconf that we only want to walk the flattened dependency set. - */ - if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION || - (want_flags & PKG_REQUIRES) == PKG_REQUIRES || - (want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || - (want_flags & PKG_PROVIDES) == PKG_PROVIDES || - (want_flags & PKG_VARIABLES) == PKG_VARIABLES || - (want_flags & PKG_PATH) == PKG_PATH || - want_variable != NULL) - maximum_traverse_depth = 1; - - /* if we are asking for a variable, path or list of variables, this only makes sense - * for a single package. - */ - if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES || - (want_flags & PKG_PATH) == PKG_PATH || - want_variable != NULL) - maximum_package_count = 1; - - if (getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") != NULL) - want_flags |= PKG_KEEP_SYSTEM_CFLAGS; - - if (getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS") != NULL) - want_flags |= PKG_KEEP_SYSTEM_LIBS; - - if ((builddir = getenv("PKG_CONFIG_TOP_BUILD_DIR")) != NULL) - pkgconf_client_set_buildroot_dir(&pkg_client, builddir); - - if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || - (want_flags & PKG_CFLAGS)) - { - want_client_flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE; - } - - if ((sysroot_dir = getenv("PKG_CONFIG_SYSROOT_DIR")) != NULL) - { - const char *destdir; - - pkgconf_client_set_sysroot_dir(&pkg_client, sysroot_dir); - - if ((destdir = getenv("DESTDIR")) != NULL) - { - if (!strcmp(destdir, sysroot_dir)) - want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; - } - } - - /* we have determined what features we want most likely. in some cases, we override later. */ - pkgconf_client_set_flags(&pkg_client, want_client_flags); - - /* at this point, want_client_flags should be set, so build the dir list */ - pkgconf_client_dir_list_build(&pkg_client, personality); - - if (required_pkgconfig_version != NULL) - { - if (pkgconf_compare_version(PACKAGE_VERSION, required_pkgconfig_version) >= 0) - ret = EXIT_SUCCESS; - else - ret = EXIT_FAILURE; - - goto out; - } - - if ((want_flags & PKG_LIST) == PKG_LIST) - { - pkgconf_scan_all(&pkg_client, NULL, print_list_entry); - ret = EXIT_SUCCESS; - goto out; - } - - if ((want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES) - { - pkgconf_scan_all(&pkg_client, NULL, print_package_entry); - ret = EXIT_SUCCESS; - goto out; - } - if (logfile_arg == NULL) logfile_arg = getenv("PKG_CONFIG_LOG"); if (logfile_arg != NULL) { - logfile_out = fopen(logfile_arg, "w"); - pkgconf_audit_set_log(&pkg_client, logfile_out); - } - - if (required_module_version != NULL) - { - pkgconf_pkg_t *pkg = NULL; - pkgconf_node_t *node; - pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; - - while (argv[pkg_optind]) - { - pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); - pkg_optind++; - } - - PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) - { - pkgconf_dependency_t *pkgiter = node->data; - - pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); - if (pkg == NULL) - { - if (want_flags & PKG_PRINT_ERRORS) - pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); - - ret = EXIT_FAILURE; - goto cleanup; - } - - if (pkgconf_compare_version(pkg->version, required_module_version) >= 0) - { - ret = EXIT_SUCCESS; - goto cleanup; - } - } - - ret = EXIT_FAILURE; -cleanup: - if (pkg != NULL) - pkgconf_pkg_unref(&pkg_client, pkg); - pkgconf_dependency_free(&deplist); - goto out; - } - else if (required_exact_module_version != NULL) - { - pkgconf_pkg_t *pkg = NULL; - pkgconf_node_t *node; - pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; - - while (argv[pkg_optind]) - { - pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); - pkg_optind++; - } - - PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) - { - pkgconf_dependency_t *pkgiter = node->data; - - pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); - if (pkg == NULL) - { - if (want_flags & PKG_PRINT_ERRORS) - pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); - - ret = EXIT_FAILURE; - goto cleanup2; - } - - if (pkgconf_compare_version(pkg->version, required_exact_module_version) == 0) - { - ret = EXIT_SUCCESS; - goto cleanup2; - } - } - - ret = EXIT_FAILURE; -cleanup2: - if (pkg != NULL) - pkgconf_pkg_unref(&pkg_client, pkg); - pkgconf_dependency_free(&deplist); - goto out; - } - else if (required_max_module_version != NULL) - { - pkgconf_pkg_t *pkg = NULL; - pkgconf_node_t *node; - pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; - - while (argv[pkg_optind]) - { - pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); - pkg_optind++; - } - - PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) - { - pkgconf_dependency_t *pkgiter = node->data; - - pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); - if (pkg == NULL) - { - if (want_flags & PKG_PRINT_ERRORS) - pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); - - ret = EXIT_FAILURE; - goto cleanup3; - } - - if (pkgconf_compare_version(pkg->version, required_max_module_version) <= 0) - { - ret = EXIT_SUCCESS; - goto cleanup3; - } - } - - ret = EXIT_FAILURE; -cleanup3: - if (pkg != NULL) - pkgconf_pkg_unref(&pkg_client, pkg); - pkgconf_dependency_free(&deplist); - goto out; - } - - while (1) - { - char *package = argv[pkg_optind]; - char *end; - - if (package == NULL) - break; - - /* check if there is a limit to the number of packages allowed to be included, if so and we have hit - * the limit, stop adding packages to the queue. - */ - if (maximum_package_count > 0 && pkgq.length >= maximum_package_count) - break; - - while (isspace((unsigned char)package[0])) - package++; - - /* skip empty packages */ - if (package[0] == '\0') { - pkg_optind++; - continue; - } - - end = package + strlen(package) - 1; - while(end > package && isspace((unsigned char)end[0])) end--; - end[1] = '\0'; - - if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) - { - pkgconf_queue_push(&pkgq, package); - pkg_optind++; - } - else if (argv[pkg_optind + 2] == NULL) - { - char packagebuf[PKGCONF_BUFSIZE]; - - snprintf(packagebuf, sizeof packagebuf, "%s %s", package, argv[pkg_optind + 1]); - pkg_optind += 2; - - pkgconf_queue_push(&pkgq, packagebuf); - } - else + if (pkgconf_unveil(logfile_arg, "rwc") == -1) { - char packagebuf[PKGCONF_BUFSIZE]; - - snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); - pkg_optind += 3; - - pkgconf_queue_push(&pkgq, packagebuf); + pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + return EXIT_FAILURE; } - } - - if (pkgq.head == NULL) - { - fprintf(stderr, "Please specify at least one package name on the command line.\n"); - ret = EXIT_FAILURE; - goto out; - } - - ret = EXIT_SUCCESS; - - if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) - { - ret = EXIT_FAILURE; - goto out; - } -#ifndef PKGCONF_LITE - if ((want_flags & PKG_SIMULATE) == PKG_SIMULATE) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS); - apply_simulate(&pkg_client, &world, NULL, -1); - } -#endif - - if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE) - goto out; - - if ((want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE) - { - apply_license(&pkg_client, &world, &ret, 2); - goto out; - } - - if ((want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED) - { - ret = EXIT_FAILURE; - apply_uninstalled(&pkg_client, &world, &ret, 2); - goto out; - } - - if (want_env_prefix != NULL) - { - apply_env(&pkg_client, &world, want_env_prefix, 2); - goto out; - } - - if ((want_flags & PKG_PROVIDES) == PKG_PROVIDES) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_provides(&pkg_client, &world, NULL, 2); - } - -#ifndef PKGCONF_LITE - if ((want_flags & PKG_DIGRAPH) == PKG_DIGRAPH) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_digraph(&pkg_client, &world, &pkgq, 2); - } - - if ((want_flags & PKG_SOLUTION) == PKG_SOLUTION) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_print_solution(&pkg_client, &world, NULL, 2); - } -#endif - - if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_modversion(&pkg_client, &world, &pkgq, 2); - } - - if ((want_flags & PKG_PATH) == PKG_PATH) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); - apply_path(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_variables(&pkg_client, &world, NULL, 2); - } - - if (want_variable) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); - apply_variable(&pkg_client, &world, want_variable, 2); - } - - if ((want_flags & PKG_REQUIRES) == PKG_REQUIRES) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - apply_requires(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - apply_requires_private(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_FRAGMENT_TREE)) - { - want_flags &= ~(PKG_CFLAGS|PKG_LIBS); - - apply_fragment_tree(&pkg_client, &world, NULL, 2); - } - - if ((want_flags & PKG_CFLAGS)) - { - apply_cflags(&pkg_client, &world, NULL, 2); + state.logfile_out = fopen(logfile_arg, "a"); + pkgconf_audit_set_log(&state.pkg_client, state.logfile_out); } - if ((want_flags & PKG_LIBS)) - { - if (want_flags & PKG_CFLAGS) - printf(" "); - - if (!(want_flags & PKG_STATIC)) - pkgconf_client_set_flags(&pkg_client, pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + if (getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") != NULL) + state.want_flags |= PKG_KEEP_SYSTEM_CFLAGS; - apply_libs(&pkg_client, &world, NULL, 2); - } + if (getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS") != NULL) + state.want_flags |= PKG_KEEP_SYSTEM_LIBS; - if (want_flags & (PKG_CFLAGS|PKG_LIBS)) - printf("\n"); + return pkgconf_cli_run(&state, argc, argv, pkg_optind); out: - pkgconf_solution_free(&pkg_client, &world); - pkgconf_queue_free(&pkgq); - pkgconf_cross_personality_deinit(personality); - pkgconf_client_deinit(&pkg_client); - - if (logfile_out != NULL) - fclose(logfile_out); - if (opened_error_msgout) - fclose(error_msgout); - + pkgconf_cli_state_reset(&state); return ret; } Index: cli/tuple.c =================================================================== RCS file: cli/tuple.c diff -N cli/tuple.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cli/tuple.c 4 Aug 2026 16:20:16 -0000 @@ -0,0 +1,351 @@ +/* + * tuple.c + * management of key->value tuples + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011, 2012 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `tuple` module + * ========================= + * + * The `tuple` module provides key-value mappings backed by a linked list. The key-value + * mapping is mainly used for variable substitution when parsing .pc files. + * + * There are two sets of mappings: a ``pkgconf_pkg_t`` specific mapping, and a `global` mapping. + * The `tuple` module provides convenience wrappers for managing the `global` mapping, which is + * attached to a given client object. + */ + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value) + * + * Defines a global variable, replacing the previous declaration if one was set. + * + * :param pkgconf_client_t* client: The pkgconf client object to modify. + * :param char* key: The key for the mapping (variable name). + * :param char* value: The value for the mapped entry. + * :return: nothing + */ +void +pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value) +{ + pkgconf_tuple_add(client, &client->global_vars, key, value, false, 0); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key) + * + * Looks up a global variable. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param char* key: The key or variable name to look up. + * :return: the contents of the variable or ``NULL`` + * :rtype: char * + */ +const char * +pkgconf_tuple_find_global(pkgconf_client_t *client, const char *key) +{ + pkgconf_variable_t *v; + bool saw_sysroot = false; + + if (client == NULL || key == NULL) + return NULL; + + v = pkgconf_variable_find(&client->global_vars, key); + + pkgconf_buffer_reset(&client->_scratch_buffer); + (void) pkgconf_variable_eval(client, &client->global_vars, v, &client->_scratch_buffer, &saw_sysroot); + + return pkgconf_buffer_str_or_empty(&client->_scratch_buffer); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_free_global(pkgconf_client_t *client) + * + * Delete all global variables associated with a pkgconf client object. + * + * :param pkgconf_client_t* client: The pkgconf client object to modify. + * :return: nothing + */ +void +pkgconf_tuple_free_global(pkgconf_client_t *client) +{ + pkgconf_tuple_free(&client->global_vars); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv) + * + * Parse and define a global variable. + * + * :param pkgconf_client_t* client: The pkgconf client object to modify. + * :param char* kv: The variable in the form of ``key=value``. + * :return: nothing + */ +void +pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv) +{ + char *workbuf = strdup(kv); + char *value; + pkgconf_tuple_t *tuple; + + if (workbuf == NULL) + goto out; + + value = strchr(workbuf, '='); + if (value == NULL) + goto out; + + *value++ = '\0'; + + tuple = pkgconf_tuple_add(client, &client->global_vars, workbuf, value, false, 0); + if (tuple != NULL) + tuple->flags = PKGCONF_PKG_TUPLEF_OVERRIDE; + +out: + free(workbuf); +} + +static char * +dequote(const char *value) +{ + char *buf = calloc(1, (strlen(value) + 1) * 2); + char *bptr = buf; + const char *i; + char quote = 0; + + if (buf == NULL) + return NULL; + + if (*value == '\'' || *value == '"') + quote = *value; + + for (i = value; *i != '\0'; i++) + { + /* An escaped whitespace denotes a literal whitespace, e.g. a path + * component containing a space produced by `printf %q`. Store the + * canonical (unescaped) text so it is not left escaped in memory: + * otherwise it would be escaped a second time when rendered into a + * fragment ("a\ dir" would come out as "a\\\ dir"). Other backslash + * sequences are preserved verbatim so variable-expansion escapes such + * as "\${var}" keep working. + * + * See . + */ + if (*i == '\\' && isspace((unsigned char) *(i + 1))) + { + i++; + *bptr++ = *i; + } + else if (*i == '\\' && quote && *(i + 1) == quote) + { + i++; + *bptr++ = *i; + } + else if (*i != quote) + *bptr++ = *i; + } + + return buf; +} + +/* + * !doc + * + * .. c:function:: pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse) + * + * Wrapper around pkgconf_variable_get_or_create(list, key) and bytecode compiler. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param pkgconf_list_t* list: The variable list to add the new variable to. + * :param char* key: The name of the variable being added. + * :param char* value: The value of the variable being added. + * :param bool parse: Whether or not to parse the value for variable substitution. + * :return: a variable object + * :rtype: pkgconf_tuple_t * + */ +pkgconf_tuple_t * +pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse, unsigned int flags) +{ + char *dequote_value; + pkgconf_buffer_t rhs_bcbuf = PKGCONF_BUFFER_INITIALIZER; + + (void) client; + + if (list == NULL || key == NULL || value == NULL) + return NULL; + + dequote_value = dequote(value); + if (dequote_value == NULL) + return NULL; + + pkgconf_variable_t *v = pkgconf_variable_get_or_create(list, key); + if (v == NULL) + { + free(dequote_value); + return NULL; + } + + if (!parse) + { + if (!pkgconf_bytecode_emit_text(&rhs_bcbuf, dequote_value, strlen(dequote_value))) + { + pkgconf_buffer_finalize(&rhs_bcbuf); + free(dequote_value); + return NULL; + } + + free(dequote_value); + } + else + { + if (!pkgconf_bytecode_compile(&rhs_bcbuf, dequote_value)) + { + pkgconf_buffer_finalize(&rhs_bcbuf); + free(dequote_value); + return NULL; + } + + free(dequote_value); + } + + /* ugh, we are doing var=${var}/foo stuff */ + if (parse && pkgconf_bytecode_references_var(&rhs_bcbuf, key)) + { + pkgconf_buffer_t old_bcbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t new_bcbuf = PKGCONF_BUFFER_INITIALIZER; + + /* preserve the old bytecode */ + if (!pkgconf_buffer_copy(&v->bcbuf, &old_bcbuf)) + { + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&rhs_bcbuf); + return NULL; + } + + /* splice the selfrefs, using the old bytecode instead of ${var} */ + if (!pkgconf_bytecode_rewrite_selfrefs(&new_bcbuf, &rhs_bcbuf, key, &old_bcbuf)) + { + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&new_bcbuf); + pkgconf_buffer_finalize(&rhs_bcbuf); + + return NULL; + } + + /* copy the spliced bytecode back to &rhs_bcbuf, replacing its contents */ + if (!pkgconf_buffer_copy(&new_bcbuf, &rhs_bcbuf)) + { + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&new_bcbuf); + pkgconf_buffer_finalize(&rhs_bcbuf); + return NULL; + } + + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&new_bcbuf); + } + + pkgconf_buffer_finalize(&v->bcbuf); + v->bcbuf = rhs_bcbuf; + v->flags = flags; + pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf); + + return (pkgconf_tuple_t *) v; +} + +/* + * !doc + * + * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) + * + * Look up a variable in a variable list. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param pkgconf_list_t* list: The variable list to search. + * :param char* key: The variable name to search for. + * :return: the value of the variable or ``NULL`` + * :rtype: char * + */ +const char * +pkgconf_tuple_find(pkgconf_client_t *client, pkgconf_list_t *list, const char *key) +{ + pkgconf_variable_t *v; + + if (client == NULL || list == NULL || key == NULL) + return NULL; + + v = pkgconf_variable_find(list, key); + if (v == NULL) + v = pkgconf_variable_find(&client->global_vars, key); + + pkgconf_buffer_reset(&client->_scratch_buffer); + + (void) pkgconf_variable_eval(client, list, v, &client->_scratch_buffer, NULL); + + return pkgconf_buffer_str_or_empty(&client->_scratch_buffer); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list) + * + * Deletes a variable object, removing it from any variable lists and releasing any memory associated + * with it. + * + * :param pkgconf_tuple_t* tuple: The variable object to release. + * :param pkgconf_list_t* list: The variable list the variable object is attached to. + * :return: nothing + */ +void +pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list) +{ + pkgconf_node_delete(&tuple->iter, list); + pkgconf_variable_free(tuple); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_free(pkgconf_list_t *list) + * + * Deletes a variable list and any variables attached to it. + * + * :param pkgconf_list_t* list: The variable list to delete. + * :return: nothing + */ +void +pkgconf_tuple_free(pkgconf_list_t *list) +{ + pkgconf_node_t *node, *next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) + pkgconf_tuple_free_entry(node->data, list); + + pkgconf_list_zero(list); +} Index: libpkgconf/argvsplit.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/argvsplit.c,v diff -u -p -r1.1.1.1 argvsplit.c --- libpkgconf/argvsplit.c 3 May 2025 15:09:38 -0000 1.1.1.1 +++ libpkgconf/argvsplit.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * argvsplit.c * argv_split() routine * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2012, 2017 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -39,27 +41,28 @@ void pkgconf_argv_free(char **argv) { + if (argv == NULL) + return; + free(argv[0]); free(argv); } /* - * !doc - * - * .. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv) - * - * Splits a string into an argument vector. + * Walk `src`, breaking it at whitespace which is neither quoted nor escaped. * - * :param char* src: The string to split. - * :param int* argc: A pointer to an integer to store the argument count. - * :param char*** argv: A pointer to a pointer for an argument vector. - * :return: 0 on success, -1 on error. - * :rtype: int + * When `raw` is set, the quoting and escaping is only read to find those breaks: + * the quotes and backslashes are copied into the argument along with everything + * else, leaving it spelled exactly as it was written. Otherwise they are + * consumed, as a shell would consume them. */ -int -pkgconf_argv_split(const char *src, int *argc, char ***argv) +static int +argv_split(const char *src, int *argc, char ***argv, bool raw) { - char *buf = malloc(strlen(src) + 1); + char *buf = calloc(1, strlen(src) + 1); + if (buf == NULL) + return -1; + const char *src_iter; char *dst_iter; int argc_count = 0; @@ -70,9 +73,13 @@ pkgconf_argv_split(const char *src, int src_iter = src; dst_iter = buf; - memset(buf, 0, strlen(src) + 1); - *argv = calloc(argv_size, sizeof (void *)); + if (*argv == NULL) + { + free(buf); + return -1; + } + (*argv)[argc_count] = dst_iter; while (*src_iter) @@ -80,7 +87,7 @@ pkgconf_argv_split(const char *src, int if (escaped) { /* POSIX: only \CHAR is special inside a double quote if CHAR is {$, `, ", \, newline}. */ - if (quote == '"') + if (quote == '"' && !raw) { if (!(*src_iter == '$' || *src_iter == '`' || *src_iter == '"' || *src_iter == '\\')) *dst_iter++ = '\\'; @@ -97,9 +104,19 @@ pkgconf_argv_split(const char *src, int else if (quote) { if (*src_iter == quote) + { quote = 0; + + if (raw) + *dst_iter++ = *src_iter; + } else if (*src_iter == '\\' && quote != '\'') + { escaped = true; + + if (raw) + *dst_iter++ = *src_iter; + } else *dst_iter++ = *src_iter; } @@ -111,8 +128,19 @@ pkgconf_argv_split(const char *src, int if (argc_count == argv_size) { + char **new_argv; + argv_size += 5; - *argv = realloc(*argv, sizeof(void *) * argv_size); + new_argv = pkgconf_reallocarray(*argv, argv_size, sizeof(void *)); + if (new_argv == NULL) + { + free(*argv); + free(buf); + *argv = NULL; + return -1; + } + + *argv = new_argv; } (*argv)[argc_count] = dst_iter; @@ -122,11 +150,17 @@ pkgconf_argv_split(const char *src, int { case '\\': escaped = true; + + if (raw) + *dst_iter++ = *src_iter; break; case '\"': case '\'': quote = *src_iter; + + if (raw) + *dst_iter++ = *src_iter; break; default: @@ -141,6 +175,7 @@ pkgconf_argv_split(const char *src, int { free(*argv); free(buf); + *argv = NULL; return -1; } @@ -151,4 +186,49 @@ pkgconf_argv_split(const char *src, int *argc = argc_count; return 0; +} + +/* + * !doc + * + * .. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv) + * + * Splits a string into an argument vector, consuming shell quoting and + * backslash escapes the way a shell would. + * + * :param char* src: The string to split. + * :param int* argc: A pointer to an integer to store the argument count. + * :param char*** argv: A pointer to a pointer for an argument vector. + * :return: 0 on success, -1 on error. + * :rtype: int + */ +int +pkgconf_argv_split(const char *src, int *argc, char ***argv) +{ + return argv_split(src, argc, argv, false); +} + +/* + * !doc + * + * .. c:function:: int pkgconf_argv_split_raw(const char *src, int *argc, char ***argv) + * + * Splits a string into an argument vector without interpreting it: the + * quoting and escaping says where the arguments end, but is left in the text. + * + * This is for input which still has to be expanded. Consuming the quoting + * here would consume only what the input itself spells, leaving whatever a + * variable substitutes into it unconsumed, so it is left for a second pass + * over the finished text. + * + * :param char* src: The string to split. + * :param int* argc: A pointer to an integer to store the argument count. + * :param char*** argv: A pointer to a pointer for an argument vector. + * :return: 0 on success, -1 on error. + * :rtype: int + */ +int +pkgconf_argv_split_raw(const char *src, int *argc, char ***argv) +{ + return argv_split(src, argc, argv, true); } Index: libpkgconf/audit.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/audit.c,v diff -u -p -r1.1.1.1 audit.c --- libpkgconf/audit.c 3 May 2025 15:09:38 -0000 1.1.1.1 +++ libpkgconf/audit.c 4 Aug 2026 16:20:54 -0000 @@ -2,6 +2,8 @@ * audit.c * package audit log functions * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2016 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -14,6 +16,7 @@ */ #include +#include /* * !doc @@ -66,7 +69,11 @@ pkgconf_audit_log(pkgconf_client_t *clie return; va_start(va, format); - vfprintf(client->auditf, format, va); + if (!pkgconf_output_file_vfmt(client->auditf, format, va)) + { + // TODO: should probably return here + pkgconf_error(client, "Failed to output to file: %s", strerror(errno)); + } va_end(va); } @@ -88,11 +95,24 @@ pkgconf_audit_log_dependency(pkgconf_cli if (client->auditf == NULL) return; - fprintf(client->auditf, "%s ", dep->id); + if (!pkgconf_output_file_fmt(client->auditf, "%s ", dep->id)) + { + // TODO: should probably return here + pkgconf_error(client, "Failed to output to file: %s", strerror(errno)); + } + if (depnode->version != NULL && depnode->compare != PKGCONF_CMP_ANY) { - fprintf(client->auditf, "%s %s ", pkgconf_pkg_get_comparator(depnode), depnode->version); + if (!pkgconf_output_file_fmt(client->auditf, "%s %s ", pkgconf_pkg_get_comparator(depnode), depnode->version)) + { + // TODO: should probably return here + pkgconf_error(client, "Failed to output to file: %s", strerror(errno)); + } } - fprintf(client->auditf, "[%s]\n", dep->version); + if (!pkgconf_output_file_fmt(client->auditf, "[%s]\n", dep->version)) + { + // TODO: should probably return here + pkgconf_error(client, "Failed to output to file: %s", strerror(errno)); + } } Index: libpkgconf/bsdstubs.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/bsdstubs.c,v diff -u -p -r1.2 bsdstubs.c --- libpkgconf/bsdstubs.c 9 May 2025 06:07:51 -0000 1.2 +++ libpkgconf/bsdstubs.c 4 Aug 2026 16:20:54 -0000 @@ -1,103 +1,7 @@ -/* $OpenBSD: bsdstubs.c,v 1.2 2025/05/09 06:07:51 tb Exp $ */ -/* $OpenBSD: bsdstubs.c,v 1.2 2025/05/09 06:07:51 tb Exp $ */ - /* - * Copyright (c) 1998 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. + * SPDX-License-Identifier: pkgconf * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include -#include - -#if !HAVE_DECL_STRLCPY -/* - * Copy src to string dst of size siz. At most siz-1 characters - * will be copied. Always NUL terminates (unless siz == 0). - * Returns strlen(src); if retval >= siz, truncation occurred. - */ -static inline size_t -strlcpy(char *dst, const char *src, size_t siz) -{ - char *d = dst; - const char *s = src; - size_t n = siz; - - /* Copy as many bytes as will fit */ - if (n != 0) { - while (--n != 0) { - if ((*d++ = *s++) == '\0') - break; - } - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (siz != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) - ; - } - - return(s - src - 1); /* count does not include NUL */ -} -#endif - -#if !HAVE_DECL_STRLCAT -/* - * Appends src to string dst of size siz (unlike strncat, siz is the - * full size of dst, not space left). At most siz-1 characters - * will be copied. Always NUL terminates (unless siz <= strlen(dst)). - * Returns strlen(src) + MIN(siz, strlen(initial dst)). - * If retval >= siz, truncation occurred. - */ -static inline size_t -strlcat(char *dst, const char *src, size_t siz) -{ - char *d = dst; - const char *s = src; - size_t n = siz; - size_t dlen; - - /* Find the end of dst and adjust bytes left but don't go past end */ - while (n-- != 0 && *d != '\0') - d++; - dlen = d - dst; - n = siz - dlen; - - if (n == 0) - return(dlen + strlen(s)); - while (*s != '\0') { - if (n != 1) { - *d++ = *s; - n--; - } - s++; - } - *d = '\0'; - - return(dlen + (s - src)); /* count does not include NUL */ -} -#endif - -/* - * Copyright (c) 2012 William Pitcock . + * Copyright (c) 2012 Ariadne Conill * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -108,23 +12,39 @@ strlcat(char *dst, const char *src, size * from the use of this software. */ -#if !HAVE_DECL_STRNDUP +#include +#include +#include + +#if HAVE_DECL_STRNDUP +# define pkgconf_strndup_impl strndup +#else /* * Creates a memory buffer and copies at most 'len' characters to it. - * If 'len' is less than the length of the source string, truncation occured. + * If 'len' is less than the length of the source string, truncation occurred. */ static inline char * -strndup(const char *src, size_t len) +pkgconf_strndup_impl(const char *src, size_t len) { - char *out = malloc(len + 1); - pkgconf_strlcpy(out, src, len + 1); + const char *end = memchr(src, '\0', len); + size_t n = end != NULL ? (size_t)(end - src) : len; + char *out = malloc(n + 1); + + if (out == NULL) + return NULL; + + memcpy(out, src, n); + out[n] = '\0'; + return out; } #endif -#if !HAVE_DECL_PLEDGE +#if HAVE_DECL_PLEDGE +# define pkgconf_pledge_impl pledge +#else static inline int -pledge(const char *promises, const char *execpromises) +pkgconf_pledge_impl(const char *promises, const char *execpromises) { (void) promises; (void) execpromises; @@ -133,9 +53,11 @@ pledge(const char *promises, const char } #endif -#if !HAVE_DECL_UNVEIL +#if HAVE_DECL_UNVEIL +# define pkgconf_unveil_impl unveil +#else static inline int -unveil(const char *path, const char *permissions) +pkgconf_unveil_impl(const char *path, const char *permissions) { (void) path; (void) permissions; @@ -144,27 +66,11 @@ unveil(const char *path, const char *per } #endif -size_t -pkgconf_strlcpy(char *dst, const char *src, size_t siz) -{ - return strlcpy(dst, src, siz); -} - -size_t -pkgconf_strlcat(char *dst, const char *src, size_t siz) -{ - return strlcat(dst, src, siz); -} - -char * -pkgconf_strndup(const char *src, size_t len) -{ - return strndup(src, len); -} - -#if !HAVE_DECL_REALLOCARRAY -void * -reallocarray(void *ptr, size_t m, size_t n) +#if HAVE_DECL_REALLOCARRAY +# define pkgconf_reallocarray_impl reallocarray +#else +static inline void * +pkgconf_reallocarray_impl(void *ptr, size_t m, size_t n) { if (n && m > -1 / n) { @@ -176,20 +82,26 @@ reallocarray(void *ptr, size_t m, size_t } #endif +char * +pkgconf_strndup(const char *src, size_t len) +{ + return pkgconf_strndup_impl(src, len); +} + void * pkgconf_reallocarray(void *ptr, size_t m, size_t n) { - return reallocarray(ptr, m, n); + return pkgconf_reallocarray_impl(ptr, m, n); } int pkgconf_pledge(const char *promises, const char *execpromises) { - return pledge(promises, execpromises); + return pkgconf_pledge_impl(promises, execpromises); } int pkgconf_unveil(const char *path, const char *permissions) { - return unveil(path, permissions); + return pkgconf_unveil_impl(path, permissions); } Index: libpkgconf/bsdstubs.h =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/bsdstubs.h,v diff -u -p -r1.2 bsdstubs.h --- libpkgconf/bsdstubs.h 9 May 2025 06:07:51 -0000 1.2 +++ libpkgconf/bsdstubs.h 4 Aug 2026 16:20:54 -0000 @@ -2,6 +2,8 @@ * bsdstubs.h * Header for stub BSD function prototypes if unavailable on a specific platform. * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2012 William Pitcock . * * Permission to use, copy, modify, and/or distribute this software for any @@ -15,6 +17,8 @@ #ifndef LIBPKGCONF_BSDSTUBS_H #define LIBPKGCONF_BSDSTUBS_H + +#include #include Index: libpkgconf/buffer.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/buffer.c,v diff -u -p -r1.1.1.1 buffer.c --- libpkgconf/buffer.c 3 May 2025 15:09:38 -0000 1.1.1.1 +++ libpkgconf/buffer.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * buffer.c * dynamically-managed buffers * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2024 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -26,62 +28,701 @@ * dynamically-allocated buffers. */ -static inline size_t -target_allocation_size(size_t target_size) +/* buffers below BUFFER_LINEAR_GROWTH_MAX increment by 128 bytes each time */ +#define BUFFER_LINEAR_GROWTH_MAX 4096 + +static inline bool +target_allocation_size(size_t target_size, size_t *allocation_size) { - return 4096 + (4096 * (target_size / 4096)); + if (target_size < BUFFER_LINEAR_GROWTH_MAX) + { + *allocation_size = target_size + (128 - (target_size % 128)); + return true; + } + + size_t cap = BUFFER_LINEAR_GROWTH_MAX; + while (cap <= target_size) + { + if (cap > SIZE_MAX / 2) + return false; + + cap *= 2; + } + + *allocation_size = cap; + return true; } -void -pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text) +static inline bool +buffer_reserve(pkgconf_buffer_t *buffer, size_t content_len) { - size_t needed = strlen(text) + 1; - size_t newsize = pkgconf_buffer_len(buffer) + needed; + size_t new_cap, old_cap; + char *newbase; - char *newbase = realloc(buffer->base, target_allocation_size(newsize)); + if (!target_allocation_size(content_len, &new_cap)) + return false; - /* XXX: silently failing here is antisocial */ - if (newbase == NULL) - return; + if (buffer->base != NULL && target_allocation_size(pkgconf_buffer_len(buffer), &old_cap) && old_cap == new_cap) + return true; - char *newend = newbase + pkgconf_buffer_len(buffer); - pkgconf_strlcpy(newend, text, needed); + newbase = realloc(buffer->base, new_cap); + if (newbase == NULL) + return false; buffer->base = newbase; - buffer->end = newend + needed; + return true; } -void +static bool +buffer_storage_overlaps(const pkgconf_buffer_t *buffer, const void *source, size_t source_size) +{ + if (buffer->base == NULL || source == NULL || source_size == 0) + return false; + + size_t buffer_size = pkgconf_buffer_len(buffer) + 1; + uintptr_t buffer_start = (uintptr_t) buffer->base; + uintptr_t source_start = (uintptr_t) source; + + if (buffer_start > UINTPTR_MAX - (buffer_size - 1) || + source_start > UINTPTR_MAX - (source_size - 1)) + return true; + + uintptr_t buffer_end = buffer_start + buffer_size - 1; + uintptr_t source_end = source_start + source_size - 1; + + return buffer_start <= source_end && source_start <= buffer_end; +} + +#if 0 +static void +buffer_debug(pkgconf_buffer_t *buffer) +{ + for (char *c = buffer->base; c <= buffer->end; c++) + { + fprintf(stderr, "%02x ", (unsigned char) *c); + } + + pkgconf_output_file_fmt(stderr, "\n"); +} +#endif + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text) + * + * Append a null-terminated string to the buffer, reallocating as necessary. + * The storage occupied by *text* must not overlap the buffer. + * + * :param pkgconf_buffer_t *buffer: The buffer to append to. + * :param char *text: The null-terminated string to append. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text) +{ + size_t len = pkgconf_buffer_len(buffer); + size_t text_len = strlen(text); + + if (text_len == SIZE_MAX) + return false; + + if (buffer_storage_overlaps(buffer, text, text_len + 1)) + return false; + + if (len > SIZE_MAX - text_len) + return false; + + size_t new_len = len + text_len; + if (!buffer_reserve(buffer, new_len)) + return false; + + memcpy(buffer->base + len, text, text_len); + + buffer->end = buffer->base + new_len; + *buffer->end = '\0'; + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n) + * + * Append a slice of *n* bytes to the buffer. Does nothing if *n* is zero. + * The source slice must not overlap the buffer. + * + * :param pkgconf_buffer_t *buf: The buffer to append to. + * :param char *p: Pointer to the byte sequence to append. + * :param size_t n: Number of bytes to append. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n) +{ + size_t len = pkgconf_buffer_len(buf); + + if (n == 0) + return true; + + if (buffer_storage_overlaps(buf, p, n)) + return false; + + if (len > SIZE_MAX - n) + return false; + + size_t new_len = len + n; + if (!buffer_reserve(buf, new_len)) + return false; + + memcpy(buf->base + len, p, n); + + buf->end = buf->base + new_len; + *buf->end = '\0'; + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list src_va) + * + * Append a formatted string to the buffer using a :code:`va_list`. + * + * :param pkgconf_buffer_t *buffer: The buffer to append to. + * :param char *fmt: A printf-style format string. + * :param va_list src_va: The variadic argument list for the format string. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list src_va) +{ + char stackbuf[256]; + va_list va; + int formatted_len; + + va_copy(va, src_va); + formatted_len = vsnprintf(stackbuf, sizeof stackbuf, fmt, va); + va_end(va); + + if (formatted_len < 0) + return false; + + if ((size_t) formatted_len < sizeof stackbuf) + return pkgconf_buffer_append_slice(buffer, stackbuf, (size_t) formatted_len); + + size_t len = pkgconf_buffer_len(buffer); + + if (!buffer_reserve(buffer, len + (size_t) formatted_len)) + return false; + + va_copy(va, src_va); + vsnprintf(buffer->base + len, (size_t) formatted_len + 1, fmt, va); + va_end(va); + + buffer->end = buffer->base + len + (size_t) formatted_len; + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...) + * + * Append a formatted string to the buffer using variadic arguments. + * + * :param pkgconf_buffer_t *buffer: The buffer to append to. + * :param char *fmt: A printf-style format string. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + bool ret = pkgconf_buffer_append_vfmt(buffer, fmt, va); + va_end(va); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text) + * + * Prepend a null-terminated string to the beginning of the buffer. + * If *text* is NULL, the buffer contents are unchanged. + * The storage occupied by *text* must not overlap the buffer. + * + * :param pkgconf_buffer_t *buffer: The buffer to prepend to. + * :param char *text: The null-terminated string to prepend, or NULL. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text) +{ + pkgconf_buffer_t tmpbuf = PKGCONF_BUFFER_INITIALIZER; + bool ret = true; + + if (text != NULL) + { + size_t text_len = strlen(text); + + if (text_len == SIZE_MAX || + buffer_storage_overlaps(buffer, text, text_len + 1)) + return false; + } + + if (text != NULL && !pkgconf_buffer_append(&tmpbuf, text)) + return false; + + if (!pkgconf_buffer_append(&tmpbuf, pkgconf_buffer_str_or_empty(buffer))) + { + pkgconf_buffer_finalize(&tmpbuf); + return false; + } + + if (pkgconf_buffer_len(&tmpbuf)) + ret = pkgconf_buffer_copy(&tmpbuf, buffer); + + pkgconf_buffer_finalize(&tmpbuf); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte) + * + * Append a single byte to the buffer, reallocating as necessary. + * + * :param pkgconf_buffer_t *buffer: The buffer to append to. + * :param char byte: The byte to append. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte) { - size_t newsize = pkgconf_buffer_len(buffer) + 1; - char *newbase = realloc(buffer->base, target_allocation_size(newsize)); + size_t len = pkgconf_buffer_len(buffer); - /* XXX: silently failing here remains antisocial */ - if (newbase == NULL) - return; + if (len == SIZE_MAX) + return false; - char *newend = newbase + newsize; - *(newend - 1) = byte; - *newend = '\0'; + size_t new_len = len + 1; + if (!buffer_reserve(buffer, new_len)) + return false; - buffer->base = newbase; - buffer->end = newend; + buffer->base[len] = byte; + + buffer->end = buffer->base + new_len; + *buffer->end = '\0'; + + return true; } -void +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer) + * + * Remove the last byte from the buffer. The buffer must be non-empty. + * + * :param pkgconf_buffer_t *buffer: The buffer to trim. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer) { - size_t newsize = pkgconf_buffer_len(buffer) - 1; - char *newbase = realloc(buffer->base, target_allocation_size(newsize)); + size_t len = pkgconf_buffer_len(buffer); + if (len == 0) + return false; + + size_t new_len = len - 1; + if (!buffer_reserve(buffer, new_len)) + return false; - buffer->base = newbase; - buffer->end = newbase + newsize; + buffer->end = buffer->base + new_len; *(buffer->end) = '\0'; + + return true; } +/* + * !doc + * + * .. c:function:: void pkgconf_buffer_finalize(pkgconf_buffer_t *buffer) + * + * Free all memory owned by the buffer and reset it to an empty state. + * + * :param pkgconf_buffer_t *buffer: The buffer to finalize. + * :return: nothing + */ void pkgconf_buffer_finalize(pkgconf_buffer_t *buffer) { free(buffer->base); + buffer->base = buffer->end = NULL; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out) + * + * Write the buffer contents followed by a newline to a file stream. + * If the buffer is empty, only a newline is written. + * + * :param pkgconf_buffer_t *buffer: The buffer to write. + * :param FILE *out: The output file stream. + * :return: :code:`true` on success, :code:`false` on I/O error. + * :code:`errno` will be set by fputs/fputc. + */ +bool +pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out) +{ + if (pkgconf_buffer_len(buffer) != 0) + { + if (fputs(pkgconf_buffer_str(buffer), out) == EOF) + return false; + } + + if (fputc('\n', out) == EOF) + return false; + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list src_va) + * + * Join a NULL-terminated list of strings into the buffer, separated by *delim*. + * Uses a :code:`va_list` for the string arguments. + * + * :param pkgconf_buffer_t *buffer: The buffer to join into. + * :param char delim: The delimiter byte inserted between each argument. + * :param va_list src_va: The variadic argument list of :code:`const char *` strings, terminated by NULL. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list src_va) +{ + va_list va; + const char *arg; + + va_copy(va, src_va); + + while ((arg = va_arg(va, const char *)) != NULL) + { + if (pkgconf_buffer_str(buffer) != NULL) + { + if (!pkgconf_buffer_push_byte(buffer, delim)) + { + va_end(va); + return false; + } + } + + if (!pkgconf_buffer_append(buffer, arg)) + { + va_end(va); + return false; + } + } + + va_end(va); + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...) + * + * Join a NULL-terminated list of strings into the buffer, separated by *delim*. + * The *delim* parameter is typed as :code:`int` due to C variadic promotion rules. + * + * :param pkgconf_buffer_t *buffer: The buffer to join into. + * :param int delim: The delimiter byte inserted between each argument (cast to :code:`char` internally). + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...) +{ + va_list va; + + va_start(va, delim); + bool ret = pkgconf_buffer_vjoin(buffer, (char)delim, va); + va_end(va); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix) + * + * Test whether the buffer begins with the contents of *prefix*. + * + * :param pkgconf_buffer_t *haystack: The buffer to search in. + * :param pkgconf_buffer_t *prefix: The prefix to test for. + * :return: :code:`true` if *haystack* starts with *prefix*, :code:`false` otherwise. + */ +bool +pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix) +{ + const char *haystack_str = pkgconf_buffer_str_or_empty(haystack); + const char *prefix_str = pkgconf_buffer_str_or_empty(prefix); + + return strncmp(haystack_str, prefix_str, strlen(prefix_str)) == 0; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle) + * + * Test whether the buffer contains the contents of *needle* as a substring. + * + * :param pkgconf_buffer_t *haystack: The buffer to search in. + * :param pkgconf_buffer_t *needle: The substring to search for. + * :return: :code:`true` if *needle* is found, :code:`false` otherwise. + */ +bool +pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle) +{ + const char *haystack_str = pkgconf_buffer_str_or_empty(haystack); + const char *needle_str = pkgconf_buffer_str_or_empty(needle); + + return strstr(haystack_str, needle_str) != NULL; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle) + * + * Test whether the buffer contains a given byte. + * + * :param pkgconf_buffer_t *haystack: The buffer to search in. + * :param char needle: The byte to search for. + * :return: :code:`true` if *needle* is found, :code:`false` otherwise. + */ + +bool +pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle) +{ + const char *haystack_str = pkgconf_buffer_str_or_empty(haystack); + return strchr(haystack_str, needle) != NULL; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle) + * + * Test whether two buffers have identical contents. + * + * :param pkgconf_buffer_t *haystack: The first buffer. + * :param pkgconf_buffer_t *needle: The second buffer. + * :return: :code:`true` if the buffers have the same length and contents, :code:`false` otherwise. + */ +bool +pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle) +{ + const char *haystack_str = pkgconf_buffer_str_or_empty(haystack); + const char *needle_str = pkgconf_buffer_str_or_empty(needle); + + if (pkgconf_buffer_len(haystack) != pkgconf_buffer_len(needle)) + return false; + + return memcmp(haystack_str, needle_str, pkgconf_buffer_len(haystack)) == 0; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value) + * + * Copy *src* into *dest*, replacing all occurrences of *pattern* with *value*. + * If *pattern* is empty, *src* is appended to *dest* unmodified. + * Neither *src*, *pattern* nor *value* may overlap *dest*. + * + * :param pkgconf_buffer_t *dest: The destination buffer. + * :param pkgconf_buffer_t *src: The source buffer. + * :param char *pattern: The pattern string to search for. + * :param char *value: The replacement string. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value) +{ + const char *iter = src->base; + + if (dest == src || + buffer_storage_overlaps(dest, src->base, pkgconf_buffer_len(src) + 1)) + return false; + + if (pattern == NULL) + pattern = ""; + + if (value == NULL) + value = ""; + + size_t pattern_len = strlen(pattern); + if (buffer_storage_overlaps(dest, pattern, pattern_len + 1) || + buffer_storage_overlaps(dest, value, strlen(value) + 1)) + return false; + + if (!pkgconf_buffer_len(src)) + return true; + + if (!pattern_len) + return pkgconf_buffer_append(dest, pkgconf_buffer_str(src)); + + while (iter < src->end) + { + if ((size_t)(src->end - iter) >= pattern_len && !memcmp(iter, pattern, pattern_len)) + { + if (!pkgconf_buffer_append(dest, value)) + return false; + + iter += pattern_len; + } + else + { + if (!pkgconf_buffer_push_byte(dest, *iter++)) + return false; + } + } + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans) + * + * Copy *src* into *dest*, inserting a backslash before any byte that falls + * within the provided character spans. + * The source and destination buffers must not overlap. + * + * :param pkgconf_buffer_t *dest: The destination buffer. + * :param pkgconf_buffer_t *src: The source buffer. + * :param pkgconf_span_t *spans: Array of character spans to escape. + * :param size_t nspans: Number of entries in the *spans* array. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans) +{ + pkgconf_charset_t charset; + + pkgconf_charset_from_spans(&charset, spans, nspans); + + return pkgconf_buffer_escape_charset(dest, src, &charset); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_charset_from_spans(pkgconf_charset_t *charset, const pkgconf_span_t *spans, size_t nspans) + * + * Build a byte-set from a list of character spans, so that membership tests over it are a + * single lookup. Callers which test many bytes against the same spans should build the set + * once and pass it to :c:func:`pkgconf_buffer_escape_charset`. + * + * :param pkgconf_charset_t *charset: The byte-set to populate. + * :param pkgconf_span_t *spans: Array of character spans to include. + * :param size_t nspans: Number of entries in the *spans* array. + * :return: nothing + */ +void +pkgconf_charset_from_spans(pkgconf_charset_t *charset, const pkgconf_span_t *spans, size_t nspans) +{ + memset(charset, 0, sizeof(*charset)); + + for (size_t i = 0; i < nspans; i++) + { + unsigned int lo = spans[i].lo, hi = spans[i].hi; + + if (lo > hi) + continue; + + size_t wlo = lo >> 6, whi = hi >> 6; + + /* bits at or above lo, and bits at or below hi, within their words */ + uint64_t mlo = ~UINT64_C(0) << (lo & 63); + uint64_t mhi = (hi & 63) == 63 ? ~UINT64_C(0) : (UINT64_C(1) << ((hi & 63) + 1)) - 1; + + if (wlo == whi) + { + charset->words[wlo] |= mlo & mhi; + continue; + } + + charset->words[wlo] |= mlo; + + for (size_t w = wlo + 1; w < whi; w++) + charset->words[w] = ~UINT64_C(0); + + charset->words[whi] |= mhi; + } +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_buffer_escape_charset(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_charset_t *charset) + * + * Copy *src* into *dest*, inserting a backslash before any byte which is a member of + * *charset*. Like :c:func:`pkgconf_buffer_escape`, but takes a prebuilt byte-set. + * The source and destination buffers must not overlap. + * + * :param pkgconf_buffer_t *dest: The destination buffer. + * :param pkgconf_buffer_t *src: The source buffer. + * :param pkgconf_charset_t *charset: The set of bytes to escape. + * :return: :code:`true` on success, :code:`false` on allocation failure. + */ +bool +pkgconf_buffer_escape_charset(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_charset_t *charset) +{ + const char *p = pkgconf_buffer_str(src); + const char *run = p; + + if (dest == src || + buffer_storage_overlaps(dest, src->base, pkgconf_buffer_len(src) + 1)) + return false; + + if (!pkgconf_buffer_len(src)) + return true; + + for (; *p; p++) + { + if (!pkgconf_charset_contains(charset, (unsigned char) *p)) + continue; + + if (p > run && !pkgconf_buffer_append_slice(dest, run, (size_t) (p - run))) + return false; + + if (!pkgconf_buffer_push_byte(dest, '\\')) + return false; + + run = p; + } + + if (p > run) + return pkgconf_buffer_append_slice(dest, run, (size_t) (p - run)); + + return true; } Index: libpkgconf/bufferset.c =================================================================== RCS file: libpkgconf/bufferset.c diff -N libpkgconf/bufferset.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/bufferset.c 4 Aug 2026 16:20:54 -0000 @@ -0,0 +1,65 @@ +/* + * bufferset.c + * dynamically-managed buffer sets + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `bufferset` module + * ============================= + * + * The libpkgconf `bufferset` module contains the functions related to managing + * dynamically-allocated sets of buffers. + */ + +pkgconf_bufferset_t * +pkgconf_bufferset_extend(pkgconf_list_t *list, pkgconf_buffer_t *buffer) +{ + pkgconf_bufferset_t *set = calloc(1, sizeof(*set)); + if (set == NULL) + return NULL; + + if (pkgconf_buffer_len(buffer)) + { + if (!pkgconf_buffer_append(&set->buffer, pkgconf_buffer_str(buffer))) + { + free(set); + return NULL; + } + } + + pkgconf_node_insert_tail(&set->node, set, list); + return set; +} + +void +pkgconf_bufferset_free(pkgconf_list_t *list) +{ + pkgconf_node_t *iter, *iter_next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, iter_next, iter) + { + pkgconf_bufferset_t *set = iter->data; + + pkgconf_buffer_finalize(&set->buffer); + pkgconf_node_delete(&set->node, list); + + free(set); + } +} Index: libpkgconf/bytecode.c =================================================================== RCS file: libpkgconf/bytecode.c diff -N libpkgconf/bytecode.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/bytecode.c 4 Aug 2026 16:20:54 -0000 @@ -0,0 +1,553 @@ +/* + * bytecode.c + * variable expansion bytecode evaluator + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `bytecode` module + * ============================ + * + * The libpkgconf `bytecode` module contains the functions related to + * evaluating variable expansion bytecode. + */ + +#define PKGCONF_EVAL_MAX_OUTPUT (PKGCONF_BUFSIZE - 1) +#define PKGCONF_EVAL_MAX_ITERATIONS (512) + +static bool +pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *p, size_t n) +{ + size_t cur = pkgconf_buffer_len(out); + if (cur >= PKGCONF_EVAL_MAX_OUTPUT) + return false; + + if (n > PKGCONF_EVAL_MAX_OUTPUT - cur) + { + pkgconf_warn(ctx->client, "warning: truncating very long variable to 64KB\n"); + + n = PKGCONF_EVAL_MAX_OUTPUT - cur; + if (!pkgconf_buffer_append_slice(out, p, n)) + pkgconf_error(ctx->client, "pkgconf_bytecode_eval_append_slice: failed to append to slice"); + + return false; + } + + return pkgconf_buffer_append_slice(out, p, n); +} + +static bool +pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *s) +{ + if (s == NULL || *s == '\0') + return true; + + return pkgconf_bytecode_eval_append_slice(ctx, out, s, strlen(s)); +} + +static bool +pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot); + +static bool +pkgconf_bytecode_read_op(const uint8_t *p, size_t remaining, + const pkgconf_bytecode_op_t **op, size_t *op_size) +{ + if (p == NULL || remaining < sizeof(**op)) + return false; + + *op = (const pkgconf_bytecode_op_t *) p; + if ((size_t) (*op)->size > remaining - sizeof(**op)) + return false; + + *op_size = sizeof(**op) + (size_t) (*op)->size; + return true; +} + +static pkgconf_variable_t * +pkgconf_bytecode_eval_scan(const pkgconf_list_t *vars, const char *name, size_t nlen, unsigned int require_flags, unsigned int forbid_flags) +{ + const pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(vars->head, node) + { + pkgconf_variable_t *v = node->data; + + if ((v->flags & require_flags) != require_flags) + continue; + + if ((v->flags & forbid_flags) != 0) + continue; + + if (pkgconf_str_eq_slice(v->key, name, nlen)) + return v; + } + + return NULL; +} + +pkgconf_variable_t * +pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen) +{ + pkgconf_variable_t *v; + + if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, PKGCONF_VARIABLEF_OVERRIDE, 0)) != NULL) + return v; + + if (ctx->vars != NULL && (v = pkgconf_bytecode_eval_scan(ctx->vars, name, nlen, 0, 0)) != NULL) + return v; + + if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, 0, PKGCONF_VARIABLEF_OVERRIDE)) != NULL) + return v; + + return NULL; +} + +static bool +pkgconf_bytecode_eval_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen, pkgconf_buffer_t *out, bool *saw_sysroot) +{ + pkgconf_variable_t *v; + + v = pkgconf_bytecode_eval_lookup_var(ctx, name, nlen); + if (v == NULL) + return true; + + if (v->expanding) + return false; + + v->expanding = true; + + bool inner_saw = false; + bool ok = pkgconf_bytecode_eval_internal(ctx, &v->bc, out, &inner_saw); + + v->expanding = false; + + if (!ok) + return false; + + if (saw_sysroot != NULL) + *saw_sysroot |= inner_saw; + + return true; +} + +static bool +pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot) +{ + (void) ctx; + + if (bc == NULL || out == NULL) + return false; + + if (bc->len != 0 && bc->base == NULL) + return false; + + const uint8_t *p = bc->base; + size_t remaining = bc->len; + + if (++ctx->expansions > PKGCONF_EVAL_MAX_ITERATIONS) + { + pkgconf_warn(ctx->client, + "warning: bytecode program exceeds iteration limit (" SIZE_FMT_SPECIFIER ")\n", + ctx->expansions - 1); + return false; + } + + while (remaining != 0) + { + const pkgconf_bytecode_op_t *op; + size_t op_size; + + if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size)) + return false; + + switch (op->tag) + { + case PKGCONF_BYTECODE_OP_TEXT: + /* this only fails due to truncation */ + if (!pkgconf_bytecode_eval_append_slice(ctx, out, op->data, op->size)) + return false; + break; + + case PKGCONF_BYTECODE_OP_VAR: + if (!pkgconf_bytecode_eval_var(ctx, op->data, op->size, out, saw_sysroot)) + return false; + break; + + case PKGCONF_BYTECODE_OP_SYSROOT: + if (saw_sysroot != NULL) + *saw_sysroot = true; + if (!pkgconf_bytecode_eval_append(ctx, out, pkgconf_buffer_str_or_empty(&ctx->sysroot))) + return false; + break; + + default: + /* reserved/unimplemented */ + return false; + } + + p += op_size; + remaining -= op_size; + } + + return true; +} + +static bool +pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_client_t *client, const pkgconf_list_t *vars) +{ + memset(ctx, 0, sizeof(*ctx)); + + ctx->client = client; + ctx->vars = vars; + + const char *raw = pkgconf_client_get_sysroot_dir(client); + + /* disabled sysroot cases */ + if (raw == NULL || *raw == '\0') + return true; + + if (raw[0] == '.' && raw[1] == '\0') + return true; + + if (raw[0] == '/' && raw[1] == '\0') + return true; + + if (!pkgconf_buffer_append(&ctx->sysroot, raw)) + return false; + + while (pkgconf_buffer_len(&ctx->sysroot) > 1 && ctx->sysroot.end[-1] == '/') + { + if (!pkgconf_buffer_trim_byte(&ctx->sysroot)) + return false; + } + + /* if normalization yields "/", disable by making buffer empty */ + if (pkgconf_buffer_len(&ctx->sysroot) == 1 && ctx->sysroot.base[0] == '/') + { + if (!pkgconf_buffer_trim_byte(&ctx->sysroot)) + return false; + } + + return true; +} + +bool +pkgconf_bytecode_eval(const pkgconf_client_t *client, const pkgconf_list_t *vars, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot) +{ + bool ret; + + if (client == NULL || bc == NULL || out == NULL) + return false; + + pkgconf_bytecode_eval_ctx_t ctx; + if (!pkgconf_bytecode_eval_ctx_init(&ctx, client, vars)) + return false; + + if (saw_sysroot != NULL) + *saw_sysroot = false; + + ret = pkgconf_bytecode_eval_internal(&ctx, bc, out, saw_sysroot); + + pkgconf_buffer_finalize(&ctx.sysroot); + + return ret; +} + +bool +pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size) +{ + pkgconf_bytecode_op_t op = { + .tag = tag, + .size = size, + }; + + if (!pkgconf_buffer_append_slice(buf, (const char *) &op, sizeof(op))) + return false; + + if (size != 0) + { + if (!pkgconf_buffer_append_slice(buf, (const char *) data, (size_t) size)) + return false; + } + + return true; +} + +bool +pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n) +{ + if (p == NULL || n == 0) + return true; + + return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_TEXT, p, (uint32_t) n); +} + +bool +pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen) +{ + if (name == NULL || nlen == 0) + return true; + + return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_VAR, name, (uint32_t) nlen); +} + +bool +pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf) +{ + return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_SYSROOT, NULL, 0); +} + +void +pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf) +{ + bc->base = (const uint8_t *)buf->base; + bc->len = buf->base != NULL ? (size_t)(buf->end - buf->base) : 0; +} + +bool +pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value) +{ + const char *p, *text_start; + + if (out == NULL || value == NULL) + return false; + + p = value; + text_start = value; + + for (; *p != '\0'; p++) + { + const char *name, *q; + + if (*p != '$') + continue; + + /* $$ escapes to a literal $ */ + if (p[1] == '$') + { + if (p > text_start) + { + if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start))) + return false; + } + + if (!pkgconf_bytecode_emit_text(out, "$", 1)) + return false; + + p++; + text_start = p + 1; + continue; + } + + if (p[1] != '{') + continue; + + if (p > text_start) + { + if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start))) + return false; + } + + name = p + 2; + q = name; + + for (; *q != '\0' && *q != '}'; q++) + ; + + /* make sure a variable expansion ends with } */ + if (*q != '}') + { + text_start = p; + continue; + } + + /* if this is not a valid variable, emit it as text */ + size_t nlen = (size_t)(q - name); + if (nlen == 0 || nlen >= PKGCONF_ITEM_SIZE) + { + if (!pkgconf_bytecode_emit_text(out, p, (size_t)((q + 1) - p))) + return false; + + p = q; + text_start = p + 1; + continue; + } + + /* we need to special-case ${pc_sysrootdir} and emit OP_SYSROOT instead... */ + if (nlen == strlen("pc_sysrootdir") && !memcmp(name, "pc_sysrootdir", nlen)) + { + if (!pkgconf_bytecode_emit_sysroot(out)) + return false; + } + else + { + if (!pkgconf_bytecode_emit_var(out, name, nlen)) + return false; + } + + p = q; + text_start = p + 1; + } + + if (p > text_start) + { + if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start))) + return false; + } + + return true; +} + +bool +pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot, pkgconf_buffer_t *out) +{ + pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_bytecode_t bc; + bool ret = false; + + if (!pkgconf_bytecode_compile(&bcbuf, input)) + { + pkgconf_buffer_finalize(&bcbuf); + return false; + } + + pkgconf_bytecode_from_buffer(&bc, &bcbuf); + + ret = pkgconf_bytecode_eval(client, vars, &bc, out, saw_sysroot); + + pkgconf_buffer_finalize(&bcbuf); + + return ret; +} + +char * +pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot) +{ + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + + if (!pkgconf_bytecode_eval_str_to_buf(client, vars, input, saw_sysroot, &out)) + { + if (pkgconf_buffer_len(&out) > 0) + return pkgconf_buffer_freeze(&out); + + pkgconf_buffer_finalize(&out); + return NULL; + } + + if (pkgconf_buffer_len(&out) == 0) + { + pkgconf_buffer_finalize(&out); + return strdup(""); + } + + return pkgconf_buffer_freeze(&out); +} + +bool +pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key) +{ + const uint8_t *p; + size_t remaining; + size_t klen; + + if (buf == NULL || key == NULL) + return false; + + klen = strlen(key); + p = (const uint8_t *) buf->base; + remaining = pkgconf_buffer_len(buf); + + while (remaining != 0) + { + const pkgconf_bytecode_op_t *op; + size_t op_size; + + if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size)) + return false; + + if (op->tag == PKGCONF_BYTECODE_OP_VAR) + { + if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0) + return true; + } + + p += op_size; + remaining -= op_size; + } + + return false; +} + +static bool +pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t *op, const char *key) +{ + const size_t klen = strlen(key); + + if (op->tag != PKGCONF_BYTECODE_OP_VAR) + return false; + + if (op->size != (uint32_t) klen) + return false; + + return memcmp(op->data, key, klen) == 0; +} + +static bool +pkgconf_bytecode_append_stream(pkgconf_buffer_t *dst, const pkgconf_buffer_t *bcbuf) +{ + if (dst == NULL || bcbuf == NULL || pkgconf_buffer_str(bcbuf) == NULL) + return true; + + return pkgconf_buffer_append_slice(dst, pkgconf_buffer_str(bcbuf), pkgconf_buffer_len(bcbuf)); +} + +bool +pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev) +{ + if (out == NULL || rhs == NULL || key == NULL || prev == NULL) + return false; + + const uint8_t *p = (const uint8_t *) rhs->base; + size_t remaining = pkgconf_buffer_len(rhs); + + while (remaining != 0) + { + const pkgconf_bytecode_op_t *op; + size_t op_size; + + if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size)) + return false; + + if (pkgconf_bytecode_op_is_selfref(op, key)) + { + if (!pkgconf_bytecode_append_stream(out, prev)) + return false; + } + else + { + if (!pkgconf_buffer_append_slice(out, (const char *) op, op_size)) + return false; + } + + p += op_size; + remaining -= op_size; + } + + return true; +} Index: libpkgconf/cache.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/cache.c,v diff -u -p -r1.1.1.1 cache.c --- libpkgconf/cache.c 3 May 2025 15:09:38 -0000 1.1.1.1 +++ libpkgconf/cache.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * cache.c * package object cache * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2013 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -40,37 +42,6 @@ cache_member_cmp(const void *a, const vo return strcmp(key, pkg->id); } -static int -cache_member_sort_cmp(const void *a, const void *b) -{ - const pkgconf_pkg_t *pkgA = *(void **) a; - const pkgconf_pkg_t *pkgB = *(void **) b; - - if (pkgA == NULL) - return 1; - - if (pkgB == NULL) - return -1; - - return strcmp(pkgA->id, pkgB->id); -} - -static void -cache_dump(const pkgconf_client_t *client) -{ - size_t i; - - PKGCONF_TRACE(client, "dumping package cache contents"); - - for (i = 0; i < client->cache_count; i++) - { - const pkgconf_pkg_t *pkg = client->cache_table[i]; - - PKGCONF_TRACE(client, SIZE_FMT_SPECIFIER": %p(%s)", - i, pkg, pkg == NULL ? "NULL" : pkg->id); - } -} - /* * !doc * @@ -125,20 +96,51 @@ pkgconf_cache_add(pkgconf_client_t *clie if (pkg == NULL) return; + pkgconf_pkg_t *cached_pkg = pkgconf_cache_lookup(client, pkg->id); + if (cached_pkg != NULL) + { + pkgconf_pkg_unref(client, cached_pkg); + return; + } + pkgconf_pkg_ref(client, pkg); - PKGCONF_TRACE(client, "added @%p to cache", pkg); + pkgconf_pkg_t **new_table; /* mark package as cached */ pkg->flags |= PKGCONF_PKG_PROPF_CACHED; ++client->cache_count; - client->cache_table = pkgconf_reallocarray(client->cache_table, + new_table = pkgconf_reallocarray(client->cache_table, client->cache_count, sizeof (void *)); - client->cache_table[client->cache_count - 1] = pkg; - qsort(client->cache_table, client->cache_count, - sizeof(void *), cache_member_sort_cmp); + /* if we are out of memory, roll back adding to cache and bail */ + if (new_table == NULL) + { + --client->cache_count; + pkg->flags &= ~PKGCONF_PKG_PROPF_CACHED; + pkgconf_pkg_unref(client, pkg); + return; + } + + client->cache_table = new_table; + + size_t lo = 0, hi = client->cache_count - 1; + while (lo < hi) + { + size_t mid = lo + (hi - lo) / 2; + + if (strcmp(client->cache_table[mid]->id, pkg->id) < 0) + lo = mid + 1; + else + hi = mid; + } + + memmove(&client->cache_table[lo + 1], &client->cache_table[lo], + (client->cache_count - 1 - lo) * sizeof(void *)); + client->cache_table[lo] = pkg; + + PKGCONF_TRACE(client, "added @%p to cache", pkg); } /* @@ -177,24 +179,19 @@ pkgconf_cache_remove(pkgconf_client_t *c (*slot)->flags &= ~PKGCONF_PKG_PROPF_CACHED; pkgconf_pkg_unref(client, *slot); - *slot = NULL; - qsort(client->cache_table, client->cache_count, - sizeof(void *), cache_member_sort_cmp); - - if (client->cache_table[client->cache_count - 1] != NULL) - { - PKGCONF_TRACE(client, "end of cache table refers to %p, not NULL", - client->cache_table[client->cache_count - 1]); - cache_dump(client); - abort(); - } + size_t idx = (size_t)(slot - client->cache_table); + memmove(&client->cache_table[idx], &client->cache_table[idx + 1], + (client->cache_count - idx - 1) * sizeof(void *)); client->cache_count--; if (client->cache_count > 0) { - client->cache_table = pkgconf_reallocarray(client->cache_table, + pkgconf_pkg_t **new_table = pkgconf_reallocarray(client->cache_table, client->cache_count, sizeof(void *)); + + if (new_table != NULL) + client->cache_table = new_table; } else { Index: libpkgconf/client.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/client.c,v diff -u -p -r1.1.1.1 client.c --- libpkgconf/client.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/client.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * client.c * libpkgconf consumer lifecycle management * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2016 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -60,17 +62,26 @@ trace_path_list(const pkgconf_client_t * void pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) { - pkgconf_path_build_from_environ("PKG_CONFIG_PATH", NULL, &client->dir_list, true); + pkgconf_path_build_from_environ(client, "PKG_CONFIG_PATH", NULL, &client->dir_list, true); if (!(client->flags & PKGCONF_PKG_PKGF_ENV_ONLY)) { pkgconf_list_t dir_list = PKGCONF_LIST_INITIALIZER; const pkgconf_list_t *prepend_list = &personality->dir_list; - if (getenv("PKG_CONFIG_LIBDIR") != NULL) +#ifdef _WIN32 + /* + * NOTE: setting the pkgconf path from the registry is deprecated + * and will be removed in pkgconf 3.1. + */ + (void) pkgconf_path_build_from_registry(client, HKEY_CURRENT_USER, &client->dir_list, true); + (void) pkgconf_path_build_from_registry(client, HKEY_LOCAL_MACHINE, &client->dir_list, true); +#endif + + if (pkgconf_client_getenv(client, "PKG_CONFIG_LIBDIR") != NULL) { /* PKG_CONFIG_LIBDIR= should empty the search path entirely. */ - (void) pkgconf_path_build_from_environ("PKG_CONFIG_LIBDIR", NULL, &dir_list, true); + (void) pkgconf_path_build_from_environ(client, "PKG_CONFIG_LIBDIR", NULL, &dir_list, true); prepend_list = &dir_list; } @@ -82,7 +93,7 @@ pkgconf_client_dir_list_build(pkgconf_cl /* * !doc * - * .. c:function:: void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) + * .. c:function:: void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler) * * Initialise a pkgconf client object. * @@ -90,13 +101,45 @@ pkgconf_client_dir_list_build(pkgconf_cl * :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors. * :param void* error_handler_data: user data passed to optional error handler * :param pkgconf_cross_personality_t* personality: the cross-compile personality to use for defaults + * :param void* client_data: user data associated with the client + * :param pkgconf_environ_lookup_handler_func_t environ_lookup_handler: the lookup handler to use for environment variables * :return: nothing */ void -pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) +pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler) { - client->error_handler_data = error_handler_data; - client->error_handler = error_handler; + pkgconf_client_options_t options = { + .error_handler = error_handler, + .error_handler_data = error_handler_data, + .personality = personality, + .client_data = client_data, + .environ_lookup_handler = environ_lookup_handler, + }; + + pkgconf_client_init_with_options(client, &options); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_init_with_options(pkgconf_client_t *client, const pkgconf_client_options_t *options) + * + * Initialise a pkgconf client object using an options structure. + * + * :param pkgconf_client_t* client: The client to initialise. + * :param pkgconf_client_options_t* options: The options used to configure the client. + * :return: nothing + */ +void +pkgconf_client_init_with_options(pkgconf_client_t *client, const pkgconf_client_options_t *options) +{ + const pkgconf_cross_personality_t *personality = options->personality; + + client->personality = personality; + client->client_data = options->client_data; + client->environ_lookup_handler = options->environ_lookup_handler; + client->error_handler_data = options->error_handler_data; + client->error_handler = options->error_handler; client->auditf = NULL; client->cache_table = NULL; client->cache_count = 0; @@ -106,43 +149,50 @@ pkgconf_client_init(pkgconf_client_t *cl pkgconf_client_set_trace_handler(client, NULL, NULL); #endif - pkgconf_client_set_error_handler(client, error_handler, error_handler_data); + if (options->unveil_handler != NULL) + pkgconf_client_set_unveil_handler(client, options->unveil_handler); + else if (client->unveil_handler == NULL) + pkgconf_client_set_unveil_handler(client, NULL); + + pkgconf_client_set_error_handler(client, options->error_handler, options->error_handler_data); pkgconf_client_set_warn_handler(client, NULL, NULL); pkgconf_client_set_sysroot_dir(client, personality->sysroot_dir); pkgconf_client_set_buildroot_dir(client, NULL); pkgconf_client_set_prefix_varname(client, NULL); - if(getenv("PKG_CONFIG_SYSTEM_LIBRARY_PATH") == NULL) + if(pkgconf_client_getenv(client, "PKG_CONFIG_SYSTEM_LIBRARY_PATH") == NULL) pkgconf_path_copy_list(&client->filter_libdirs, &personality->filter_libdirs); else - pkgconf_path_build_from_environ("PKG_CONFIG_SYSTEM_LIBRARY_PATH", NULL, &client->filter_libdirs, false); + pkgconf_path_build_from_environ(client, "PKG_CONFIG_SYSTEM_LIBRARY_PATH", NULL, &client->filter_libdirs, false); - if(getenv("PKG_CONFIG_SYSTEM_INCLUDE_PATH") == NULL) + if(pkgconf_client_getenv(client, "PKG_CONFIG_SYSTEM_INCLUDE_PATH") == NULL) pkgconf_path_copy_list(&client->filter_includedirs, &personality->filter_includedirs); else - pkgconf_path_build_from_environ("PKG_CONFIG_SYSTEM_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ(client, "PKG_CONFIG_SYSTEM_INCLUDE_PATH", NULL, &client->filter_includedirs, false); /* GCC uses these environment variables to define system include paths, so we should check them. */ #ifdef __HAIKU__ - pkgconf_path_build_from_environ("BELIBRARIES", NULL, &client->filter_libdirs, false); + pkgconf_path_build_from_environ(client, "BELIBRARIES", NULL, &client->filter_libdirs, false); #else - pkgconf_path_build_from_environ("LIBRARY_PATH", NULL, &client->filter_libdirs, false); + pkgconf_path_build_from_environ(client, "LIBRARY_PATH", NULL, &client->filter_libdirs, false); #endif - pkgconf_path_build_from_environ("CPATH", NULL, &client->filter_includedirs, false); - pkgconf_path_build_from_environ("C_INCLUDE_PATH", NULL, &client->filter_includedirs, false); - pkgconf_path_build_from_environ("CPLUS_INCLUDE_PATH", NULL, &client->filter_includedirs, false); - pkgconf_path_build_from_environ("OBJC_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ(client, "CPATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ(client, "C_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ(client, "CPLUS_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ(client, "OBJC_INCLUDE_PATH", NULL, &client->filter_includedirs, false); #ifdef _WIN32 /* also use the path lists that MSVC uses on windows */ - pkgconf_path_build_from_environ("INCLUDE", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ(client, "INCLUDE", NULL, &client->filter_includedirs, false); #endif PKGCONF_TRACE(client, "initialized client @%p", client); trace_path_list(client, "filtered library paths", &client->filter_libdirs); trace_path_list(client, "filtered include paths", &client->filter_includedirs); + + client->output = pkgconf_output_default(); } /* @@ -155,17 +205,59 @@ pkgconf_client_init(pkgconf_client_t *cl * :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors. * :param void* error_handler_data: user data passed to optional error handler * :param pkgconf_cross_personality_t* personality: cross-compile personality to use + * :param void* client_data: user data associated with the client + * :param pkgconf_environ_lookup_handler_func_t environ_lookup_handler: the lookup handler to use for environment variables * :return: A pkgconf client object. * :rtype: pkgconf_client_t* */ pkgconf_client_t * -pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) +pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler) +{ + pkgconf_client_options_t options = { + .error_handler = error_handler, + .error_handler_data = error_handler_data, + .personality = personality, + .client_data = client_data, + .environ_lookup_handler = environ_lookup_handler, + }; + + return pkgconf_client_new_with_options(&options); +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_t* pkgconf_client_new_with_options(const pkgconf_client_options_t *options) + * + * Allocate and initialise a pkgconf client object using an options structure. + * + * :param pkgconf_client_options_t* options: The options used to configure the client. + * :return: A pkgconf client object. + * :rtype: pkgconf_client_t* + */ +pkgconf_client_t * +pkgconf_client_new_with_options(const pkgconf_client_options_t *options) { pkgconf_client_t *out = calloc(1, sizeof(pkgconf_client_t)); - pkgconf_client_init(out, error_handler, error_handler_data, personality); + if (out == NULL) + return NULL; + + pkgconf_client_init_with_options(out, options); return out; } +static void +unref_preload_list(pkgconf_client_t *client) +{ + pkgconf_node_t *n, *tn; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(client->preloaded_pkgs.head, tn, n) + { + pkgconf_pkg_t *pkg = n->data; + pkgconf_pkg_unref(client, pkg); + } +} + /* * !doc * @@ -181,6 +273,8 @@ pkgconf_client_deinit(pkgconf_client_t * { PKGCONF_TRACE(client, "deinit @%p", client); + unref_preload_list(client); + if (client->prefix_varname != NULL) free(client->prefix_varname); @@ -196,6 +290,10 @@ pkgconf_client_deinit(pkgconf_client_t * pkgconf_tuple_free_global(client); pkgconf_path_free(&client->dir_list); pkgconf_cache_free(client); + + pkgconf_buffer_finalize(&client->_scratch_buffer); + + memset(client, '\0', sizeof(*client)); } /* @@ -249,14 +347,25 @@ pkgconf_client_get_sysroot_dir(const pkg void pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir) { + if (sysroot_dir != NULL && (!strcmp(sysroot_dir, "/") || !strcmp(sysroot_dir, "."))) + { + pkgconf_warn(client, "ignoring bogus sysroot_dir: %s", sysroot_dir); + sysroot_dir = NULL; + } + if (client->sysroot_dir != NULL) free(client->sysroot_dir); client->sysroot_dir = sysroot_dir != NULL ? strdup(sysroot_dir) : NULL; + /* normalize separators so the sysroot compares equal to the (already + * canonicalized) paths it is matched against during injection */ + if (client->sysroot_dir != NULL) + pkgconf_path_normalize_separators(client->sysroot_dir); + PKGCONF_TRACE(client, "set sysroot_dir to: %s", client->sysroot_dir != NULL ? client->sysroot_dir : ""); - pkgconf_tuple_add_global(client, "pc_sysrootdir", client->sysroot_dir != NULL ? client->sysroot_dir : "/"); + pkgconf_tuple_add_global(client, "pc_sysrootdir", client->sysroot_dir != NULL ? client->sysroot_dir : ""); } /* @@ -298,6 +407,9 @@ pkgconf_client_set_buildroot_dir(pkgconf client->buildroot_dir = buildroot_dir != NULL ? strdup(buildroot_dir) : NULL; + if (client->buildroot_dir != NULL) + pkgconf_path_normalize_separators(client->buildroot_dir); + PKGCONF_TRACE(client, "set buildroot_dir to: %s", client->buildroot_dir != NULL ? client->buildroot_dir : ""); pkgconf_tuple_add_global(client, "pc_top_builddir", client->buildroot_dir != NULL ? client->buildroot_dir : "$(top_builddir)"); @@ -318,14 +430,32 @@ pkgconf_client_set_buildroot_dir(pkgconf bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) { - char errbuf[PKGCONF_BUFSIZE]; + char *errbuf; + ssize_t msgsize = 0; + bool ret; va_list va; va_start(va, format); - vsnprintf(errbuf, sizeof errbuf, format, va); + msgsize = vsnprintf(NULL, 0, format, va); + va_end(va); + + if (msgsize < 0) + return false; + + msgsize++; + + errbuf = calloc(1, msgsize); + if (errbuf == NULL) + return false; + + va_start(va, format); + vsnprintf(errbuf, msgsize, format, va); va_end(va); - return client->error_handler(errbuf, client, client->error_handler_data); + ret = client->error_handler(errbuf, client, client->error_handler_data); + free(errbuf); + + return ret; } /* @@ -343,14 +473,32 @@ pkgconf_error(const pkgconf_client_t *cl bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) { - char errbuf[PKGCONF_BUFSIZE]; + char *errbuf; + ssize_t msgsize = 0; + bool ret; va_list va; va_start(va, format); - vsnprintf(errbuf, sizeof errbuf, format, va); + msgsize = vsnprintf(NULL, 0, format, va); + va_end(va); + + if (msgsize < 0) + return false; + + msgsize++; + + errbuf = calloc(1, msgsize); + if (errbuf == NULL) + return false; + + va_start(va, format); + vsnprintf(errbuf, msgsize, format, va); va_end(va); - return client->warn_handler(errbuf, client, client->warn_handler_data); + ret = client->warn_handler(errbuf, client, client->warn_handler_data); + free(errbuf); + + return ret; } /* @@ -371,22 +519,56 @@ pkgconf_warn(const pkgconf_client_t *cli bool pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t lineno, const char *funcname, const char *format, ...) { - char errbuf[PKGCONF_BUFSIZE]; - size_t len; + char prefix[PKGCONF_ITEM_SIZE]; + char *errbuf = NULL; + ssize_t errlen; + char *finalbuf = NULL; + ssize_t finallen; + bool ret; va_list va; if (client == NULL || client->trace_handler == NULL) return false; - len = snprintf(errbuf, sizeof errbuf, "%s:" SIZE_FMT_SPECIFIER " [%s]: ", filename, lineno, funcname); + snprintf(prefix, sizeof prefix, "%s:" SIZE_FMT_SPECIFIER " [%s]:", filename, lineno, funcname); va_start(va, format); - vsnprintf(errbuf + len, sizeof(errbuf) - len, format, va); + errlen = vsnprintf(NULL, 0, format, va); va_end(va); - pkgconf_strlcat(errbuf, "\n", sizeof errbuf); + if (errlen < 0) + return false; + + errlen++; + errbuf = calloc(1, errlen); + if (errbuf == NULL) + return false; + + va_start(va, format); + vsnprintf(errbuf, errlen, format, va); + va_end(va); - return client->trace_handler(errbuf, client, client->trace_handler_data); + finallen = snprintf(NULL, 0, "%s %s\n", prefix, errbuf); + if (finallen < 0) + { + free(errbuf); + return false; + } + + finallen++; + finalbuf = calloc(1, finallen); + if (finalbuf == NULL) + { + free(errbuf); + return false; + } + + snprintf(finalbuf, finallen, "%s %s\n", prefix, errbuf); + ret = client->trace_handler(finalbuf, client, client->trace_handler_data); + free(errbuf); + free(finalbuf); + + return ret; } /* @@ -412,6 +594,14 @@ pkgconf_default_error_handler(const char return true; } +static void +default_unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions) +{ + (void) client; + (void) path; + (void) permissions; +} + /* * !doc * @@ -571,6 +761,47 @@ pkgconf_client_set_error_handler(pkgconf } } +/* + * !doc + * + * .. c:function:: pkgconf_client_get_unveil_handler(const pkgconf_client_t *client) + * + * Returns the unveil handler if one is set, else ``NULL``. + * + * :param pkgconf_client_t* client: The client object to get the unveil handler from. + * :return: a function pointer to the error handler or ``NULL`` + */ +pkgconf_unveil_handler_func_t +pkgconf_client_get_unveil_handler(const pkgconf_client_t *client) +{ + return client->unveil_handler; +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_set_unveil_handler(pkgconf_client_t *client, pkgconf_unveil_handler_func_t unveil_handler) + * + * Sets an unveil handler on a client object or uninstalls one if set to ``NULL``. + * + * :param pkgconf_client_t* client: The client object to set the error handler on. + * :param pkgconf_unveil_handler_func_t unveil_handler: The unveil handler to set. + * :return: nothing + */ +void +pkgconf_client_set_unveil_handler(pkgconf_client_t *client, pkgconf_unveil_handler_func_t unveil_handler) +{ + client->unveil_handler = unveil_handler; + + if (client->unveil_handler == NULL) + { + PKGCONF_TRACE(client, "installing default unveil handler"); + client->unveil_handler = default_unveil_handler; + } + else + PKGCONF_TRACE(client, "installing custom unveil handler"); +} + #ifndef PKGCONF_LITE /* * !doc @@ -605,11 +836,136 @@ pkgconf_client_set_trace_handler(pkgconf { client->trace_handler = trace_handler; client->trace_handler_data = trace_handler_data; +} +#endif - if (client->trace_handler == NULL) +/* + * !doc + * + * .. c:function:: bool pkgconf_client_preload_one(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + * + * Adds a package to the preloaded packages set. + * + * :param pkgconf_client_t* client: The client object for preloading. + * :param pkgconf_pkg_t* pkg: The package to preload. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_client_preload_one(pkgconf_client_t *client, pkgconf_pkg_t *pkg) +{ + PKGCONF_TRACE(client, "preloading pkg %s@%p", pkg->id, pkg); + + pkg->flags |= PKGCONF_PKG_PROPF_PRELOADED; + + pkgconf_pkg_ref(client, pkg); + pkgconf_node_insert_tail(&pkg->preload_node, pkg, &client->preloaded_pkgs); + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_client_preload_path(pkgconf_client_t *client, const char *path) + * + * Loads a pkg-config file into the preloaded packages set. + * + * :param pkgconf_client_t* client: The client object for preloading. + * :param char* path: The path to the pkg-config file to preload. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_client_preload_path(pkgconf_client_t *client, const char *path) +{ + pkgconf_pkg_t *pkg = pkgconf_pkg_new_from_path(client, path, PKGCONF_PKG_PROPF_PRELOADED); + if (pkg == NULL) + return false; + + bool ret = pkgconf_client_preload_one(client, pkg); + pkgconf_pkg_unref(client, pkg); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env) + * + * Loads zero or more pkg-config files specified in the given environmental + * variable. + * + * :param pkgconf_client_t* client: The client object for preloading. + * :param char* environ: The environment variable to use for preloading. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env) +{ + const char *data; + pkgconf_list_t pathlist = PKGCONF_LIST_INITIALIZER; + pkgconf_node_t *n; + bool ret = true; + + data = pkgconf_client_getenv(client, env); + if (data == NULL) + return true; + + pkgconf_path_split(data, &pathlist, true); + + PKGCONF_FOREACH_LIST_ENTRY(pathlist.head, n) { - client->trace_handler = pkgconf_default_error_handler; - PKGCONF_TRACE(client, "installing default trace handler"); + pkgconf_path_t *pn = n->data; + + ret = pkgconf_client_preload_path(client, pn->path); + if (!ret) + break; } + + pkgconf_path_free(&pathlist); + + return ret; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_set_output(pkgconf_client_t *client, pkgconf_output_t *output) + * + * Sets the client's output object. This is mainly a convenience function for clients + * to use. + * + * :param pkgconf_client_t* client: The client object to set the output object for. + * :param pkgconf_output_t* output: The output object to use. + * :return: nothing + */ +void +pkgconf_client_set_output(pkgconf_client_t *client, pkgconf_output_t *output) +{ + client->output = output; +} + +/* + * !doc + * + * .. c:function:: const char *pkgconf_client_getenv(const pkgconf_client_t *client, const char *key) + * + * Looks up an environmental variable which may be mocked, otherwise fetches + * from the main environment. + * + * :param pkgconf_client_t* client: the client object to use for looking up environmental variables. + * :param char* key: the environmental variable to look up. + * :return: the environmental variable contents else NULL + * :rtype: const char* + */ +const char * +pkgconf_client_getenv(const pkgconf_client_t *client, const char *key) +{ + if (client != NULL && client->environ_lookup_handler != NULL) + return client->environ_lookup_handler(client, key); + + return getenv(key); } -#endif Index: libpkgconf/config.h =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/config.h,v diff -u -p -r1.3 config.h --- libpkgconf/config.h 6 May 2026 00:07:46 -0000 1.3 +++ libpkgconf/config.h 4 Aug 2026 16:46:06 -0000 @@ -1,6 +1,6 @@ #define PACKAGE_NAME "pkgconf-lite" -#define PACKAGE_BUGREPORT "https://git.dereferenced.org/pkgconf/pkgconf/issues" -#define PACKAGE_VERSION "2.4.3" +#define PACKAGE_BUGREPORT "https://github.com/pkgconf/pkgconf/issues" +#define PACKAGE_VERSION "3.0.5" #define PACKAGE PACKAGE_NAME " " PACKAGE_VERSION #define HAVE_DECL_STRLCPY 1 #define HAVE_STRLCAT @@ -8,4 +8,5 @@ #define HAVE_DECL_STRNDUP 1 #define HAVE_DECL_PLEDGE 1 #define HAVE_DECL_UNVEIL 1 -#define HAVE_DECL_REALLOCARRAY 1 +#define HAVE_DECL_PLEDGE 1 +#define HAVE_DECL_UNVEIL 1 Index: libpkgconf/dependency.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/dependency.c,v diff -u -p -r1.1.1.1 dependency.c --- libpkgconf/dependency.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/dependency.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * dependency.c * dependency parsing and management * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -37,19 +39,18 @@ typedef enum { #define DEBUG_PARSE 0 -static const char * -dependency_to_str(const pkgconf_dependency_t *dep, char *buf, size_t buflen) +static inline const char * +dependency_to_buf(const pkgconf_dependency_t *dep, pkgconf_buffer_t *buf) { - pkgconf_strlcpy(buf, dep->package, buflen); - if (dep->version != NULL) - { - pkgconf_strlcat(buf, " ", buflen); - pkgconf_strlcat(buf, pkgconf_pkg_get_comparator(dep), buflen); - pkgconf_strlcat(buf, " ", buflen); - pkgconf_strlcat(buf, dep->version, buflen); - } + pkgconf_buffer_reset(buf); + if (!pkgconf_buffer_append(buf, dep->package)) + return NULL; + + if (dep->version != NULL && + !pkgconf_buffer_append_fmt(buf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version)) + return NULL; - return buf; + return pkgconf_buffer_str(buf); } /* find a colliding dependency that is coloured differently */ @@ -72,32 +73,52 @@ find_colliding_dependency(const pkgconf_ return NULL; } +static inline const char * +dependency_trace_str(const pkgconf_client_t *client, const pkgconf_dependency_t *dep, pkgconf_buffer_t *buf) +{ +#ifndef PKGCONF_LITE + const char *str; + + if (pkgconf_client_get_trace_handler(client) == NULL) + return dep->package; + + str = dependency_to_buf(dep, buf); + return str != NULL ? str : dep->package; +#else + return dep->package; +#endif +} + static inline pkgconf_dependency_t * add_or_replace_dependency_node(pkgconf_client_t *client, pkgconf_dependency_t *dep, pkgconf_list_t *list) { - char depbuf[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t depbuf = PKGCONF_BUFFER_INITIALIZER; pkgconf_dependency_t *dep2 = find_colliding_dependency(dep, list); + const char *depstr = dependency_trace_str(client, dep, &depbuf); /* there is already a node in the graph which describes this dependency */ if (dep2 != NULL) { - char depbuf2[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t depbuf2 = PKGCONF_BUFFER_INITIALIZER; + const char *depstr2 = dependency_trace_str(client, dep2, &depbuf2); PKGCONF_TRACE(client, "dependency collision: [%s/%x] -- [%s/%x]", - dependency_to_str(dep, depbuf, sizeof depbuf), dep->flags, - dependency_to_str(dep2, depbuf2, sizeof depbuf2), dep2->flags); + depstr, dep->flags, depstr2, dep2->flags); /* prefer the uncoloured node, either dep or dep2 */ if (dep->flags && dep2->flags == 0) { - PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depbuf, dep); + PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depstr, dep); + pkgconf_buffer_finalize(&depbuf); + pkgconf_buffer_finalize(&depbuf2); pkgconf_dependency_unref(dep->owner, dep); + return NULL; } else if (dep2->flags && dep->flags == 0) { - PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depbuf2, dep2); + PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depstr2, dep2); pkgconf_node_delete(&dep2->iter, list); pkgconf_dependency_unref(dep2->owner, dep2); @@ -110,11 +131,15 @@ add_or_replace_dependency_node(pkgconf_c * fragment deduplication will handle the excessive fragments. */ PKGCONF_TRACE(client, "keeping both dependencies (harmless)"); + + pkgconf_buffer_finalize(&depbuf2); } - PKGCONF_TRACE(client, "added dependency [%s] to list @%p; flags=%x", dependency_to_str(dep, depbuf, sizeof depbuf), list, dep->flags); + PKGCONF_TRACE(client, "added dependency [%s] to list @%p; flags=%x", depstr, list, dep->flags); pkgconf_node_insert_tail(&dep->iter, pkgconf_dependency_ref(dep->owner, dep), list); + pkgconf_buffer_finalize(&depbuf); + /* This dependency is intentionally unowned. * * Internally we have no use for the returned type, and usually just @@ -131,10 +156,25 @@ pkgconf_dependency_addraw(pkgconf_client pkgconf_dependency_t *dep; dep = calloc(1, sizeof(pkgconf_dependency_t)); + if (dep == NULL) + return NULL; + dep->package = pkgconf_strndup(package, package_sz); + if (dep->package == NULL) + { + pkgconf_dependency_free_one(dep); + return NULL; + } if (version_sz != 0) + { dep->version = pkgconf_strndup(version, version_sz); + if (dep->version == NULL) + { + pkgconf_dependency_free_one(dep); + return NULL; + } + } dep->compare = compare; dep->flags = flags; @@ -166,6 +206,8 @@ pkgconf_dependency_add(pkgconf_client_t pkgconf_dependency_t *dep; dep = pkgconf_dependency_addraw(client, list, package, strlen(package), version, version != NULL ? strlen(version) : 0, compare, flags); + if (dep == NULL) + return NULL; return pkgconf_dependency_ref(dep->owner, dep); } @@ -208,6 +250,9 @@ pkgconf_dependency_free_one(pkgconf_depe if (dep->version != NULL) free(dep->version); + if (dep->why != NULL) + free(dep->why); + free(dep); } @@ -303,20 +348,25 @@ pkgconf_dependency_parse_str(pkgconf_cli { parse_state_t state = OUTSIDE_MODULE; pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY; - char cmpname[PKGCONF_ITEM_SIZE]; - char buf[PKGCONF_BUFSIZE]; + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t cmpname = PKGCONF_BUFFER_INITIALIZER; size_t package_sz = 0, version_sz = 0; - char *start = buf; - char *ptr = buf; + char *start = NULL; + char *ptr = NULL; char *vstart = NULL; char *package = NULL, *version = NULL; - char *cnameptr = cmpname; - char *cnameend = cmpname + PKGCONF_ITEM_SIZE - 1; + char *opstart = NULL; - memset(cmpname, '\0', sizeof cmpname); + if (depends == NULL || *depends == '\0') + return; - pkgconf_strlcpy(buf, depends, sizeof buf); - pkgconf_strlcat(buf, " ", sizeof buf); + if (!pkgconf_buffer_append(&buf, depends)) + goto out; + + if (!pkgconf_buffer_append(&buf, " ")) + goto out; + + start = ptr = buf.base; while (*ptr) { @@ -378,23 +428,22 @@ pkgconf_dependency_parse_str(pkgconf_cli case BEFORE_OPERATOR: if (PKGCONF_IS_OPERATOR_CHAR(*ptr)) { + opstart = ptr; state = INSIDE_OPERATOR; - if (cnameptr < cnameend) - *cnameptr++ = *ptr; } break; case INSIDE_OPERATOR: if (PKGCONF_IS_OPERATOR_CHAR(*ptr)) - { - if (cnameptr < cnameend) - *cnameptr++ = *ptr; break; - } + pkgconf_buffer_rewind(&cmpname); + if (!pkgconf_buffer_append_slice(&cmpname, opstart, ptr - opstart)) + goto out; + + compare = pkgconf_pkg_comparator_lookup_by_name(pkgconf_buffer_str(&cmpname)); state = AFTER_OPERATOR; - compare = pkgconf_pkg_comparator_lookup_by_name(cmpname); // fallthrough case AFTER_OPERATOR: @@ -415,9 +464,9 @@ pkgconf_dependency_parse_str(pkgconf_cli pkgconf_dependency_addraw(client, deplist_head, package, package_sz, version, version_sz, compare, flags); compare = PKGCONF_CMP_ANY; - cnameptr = cmpname; - memset(cmpname, 0, sizeof cmpname); package_sz = 0; + opstart = NULL; + pkgconf_buffer_rewind(&cmpname); } if (state == OUTSIDE_MODULE) @@ -427,6 +476,10 @@ pkgconf_dependency_parse_str(pkgconf_cli ptr++; } + +out: + pkgconf_buffer_finalize(&cmpname); + pkgconf_buffer_finalize(&buf); } /* @@ -448,7 +501,7 @@ pkgconf_dependency_parse_str(pkgconf_cli void pkgconf_dependency_parse(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends, unsigned int flags) { - char *kvdepends = pkgconf_tuple_parse(client, &pkg->vars, depends, pkg->flags); + char *kvdepends = pkgconf_bytecode_eval_str(client, &pkg->vars, depends, NULL); pkgconf_dependency_parse_str(client, deplist, kvdepends, flags); free(kvdepends); @@ -471,10 +524,25 @@ pkgconf_dependency_copy(pkgconf_client_t pkgconf_dependency_t *new_dep; new_dep = calloc(1, sizeof(pkgconf_dependency_t)); + if (new_dep == NULL) + return NULL; + new_dep->package = strdup(dep->package); + if (new_dep->package == NULL) + { + pkgconf_dependency_free_one(new_dep); + return NULL; + } if (dep->version != NULL) + { new_dep->version = strdup(dep->version); + if (new_dep->version == NULL) + { + pkgconf_dependency_free_one(new_dep); + return NULL; + } + } new_dep->compare = dep->compare; new_dep->flags = dep->flags; Index: libpkgconf/fileio.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/fileio.c,v diff -u -p -r1.1.1.1 fileio.c --- libpkgconf/fileio.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/fileio.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * fileio.c * File reading utilities * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2012, 2025 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -13,75 +15,52 @@ * from the use of this software. */ +#include #include #include +#if HAVE_DECL_GETC_UNLOCKED +# define pkgconf_getc(stream) getc_unlocked(stream) +#else +# define pkgconf_getc(stream) getc(stream) +#endif + bool pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream) { bool quoted = false; - int c = '\0', c2; + bool got_data = false; + char run[256]; + size_t runlen = 0; + int c; - while ((c = getc(stream)) != EOF) + while ((c = pkgconf_getc(stream)) != EOF) { + got_data = true; + if (c == '\\' && !quoted) { quoted = true; continue; } - else if (c == '#') - { - if (!quoted) { - /* Skip the rest of the line */ - do { - c = getc(stream); - } while (c != '\n' && c != EOF); - pkgconf_buffer_push_byte(buffer, c); - break; - } - else - pkgconf_buffer_push_byte(buffer, c); - quoted = false; - continue; - } - else if (c == '\n') + if (c == '\n') { if (quoted) { - /* Trim spaces */ - do { - c2 = getc(stream); - } while (c2 == '\t' || c2 == ' '); - - ungetc(c2, stream); - quoted = false; continue; } - else - { - pkgconf_buffer_push_byte(buffer, c); - } break; } - else if (c == '\r') - { - pkgconf_buffer_push_byte(buffer, '\n'); - if ((c2 = getc(stream)) == '\n') - { - if (quoted) - { - quoted = false; - continue; - } - - break; - } + if (c == '\r') + { + int next = pkgconf_getc(stream); - ungetc(c2, stream); + if (next != '\n' && next != EOF && ungetc(next, stream) == EOF) + return false; if (quoted) { @@ -91,23 +70,26 @@ pkgconf_fgetline(pkgconf_buffer_t *buffe break; } - else + + if (runlen > sizeof run - 2) { - if (quoted) { - pkgconf_buffer_push_byte(buffer, '\\'); - quoted = false; - } - pkgconf_buffer_push_byte(buffer, c); + if (!pkgconf_buffer_append_slice(buffer, run, runlen)) + return false; + + runlen = 0; } - } + if (quoted) + { + run[runlen++] = '\\'; + quoted = false; + } - /* Remove newline character. */ - if (pkgconf_buffer_lastc(buffer) == '\n') - pkgconf_buffer_trim_byte(buffer); + run[runlen++] = (char) c; + } - if (pkgconf_buffer_lastc(buffer) == '\r') - pkgconf_buffer_trim_byte(buffer); + if (runlen != 0 && !pkgconf_buffer_append_slice(buffer, run, runlen)) + return false; - return !(c == EOF || ferror(stream)); + return got_data && !ferror(stream); } Index: libpkgconf/fragment.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/fragment.c,v diff -u -p -r1.1.1.1 fragment.c --- libpkgconf/fragment.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/fragment.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * fragment.c * Management of fragment lists. * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2012, 2013, 2014 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -13,9 +15,19 @@ * from the use of this software. */ +#include #include #include +#ifndef _WIN32 +#include +#include +#endif + +#ifdef __APPLE__ +#include +#endif + /* * !doc * @@ -28,11 +40,107 @@ */ struct pkgconf_fragment_check { - char *token; + const char *token; size_t len; }; static inline bool +pkgconf_fragment_is_greedy(const char *string) +{ + static const struct pkgconf_fragment_check check_fragments[] = { + {"-F", 2}, + {"-I", 2}, + {"-L", 2}, + {"-D", 2}, + {"-l", 2}, + }; + + if (*string != '-') + return false; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + { + /* if it is the bare flag, then we want the next token to be the data */ + if (!*(string + check_fragments[i].len)) + return true; + } + + return false; +} + +/* + * Length of the flag introducing a path which sysroot may be injected into, or + * 0 if this is not such a flag. The length matters as well as the answer: the + * path starts there, and it is the path which has to be judged, not the whole + * fragment. + */ +static inline size_t +pkgconf_fragment_sysroot_path_offset(const char *string) +{ + static const struct pkgconf_fragment_check check_fragments[] = { + {"-F", 2}, + {"-I", 2}, + {"-L", 2}, + {"-isystem", 8}, + {"-idirafter", 10}, + }; + + if (*string != '-') + return 0; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + return check_fragments[i].len; + + return 0; +} + +/* + * Length of a flag which names its path as a separate argument, or 0 if this is + * not one of those. + */ +static inline size_t +pkgconf_fragment_separate_path_offset(const char *string) +{ + static const struct pkgconf_fragment_check check_fragments[] = { + {"-isystem", 8}, + {"-idirafter", 10}, + }; + + if (*string != '-') + return 0; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + return check_fragments[i].len; + + return 0; +} + +/* + * Whether `path` already lies under the sysroot, in which case injecting it + * again would only produce a doubled prefix. + * + * This is read off the path rather than remembered from the expansion which + * produced it. A fragment carries everything the question needs, so the answer + * holds per fragment however the .pc file arrived at the path -- whether the + * sysroot came from ${pc_sysrootdir}, from a variable which happens to sit under + * it, or was written out longhand -- and survives the path being split out of a + * larger expansion, where provenance would not. + */ +static inline bool +pkgconf_fragment_under_sysroot(const pkgconf_client_t *client, const char *path) +{ + size_t len = strlen(client->sysroot_dir); + + if (strncmp(path, client->sysroot_dir, len)) + return false; + + return path[len] == '/' || path[len] == '\0'; +} + +static inline bool pkgconf_fragment_is_unmergeable(const char *string) { static const struct pkgconf_fragment_check check_fragments[] = { @@ -70,13 +178,18 @@ pkgconf_fragment_is_unmergeable(const ch } static inline bool -pkgconf_fragment_should_munge(const char *string, const char *sysroot_dir) +pkgconf_fragment_only_group_one(const char *string) { - if (*string != '/') - return false; + static const struct pkgconf_fragment_check check_fragments[] = { + {"-framework", 10}, + {"-isystem", 8}, + {"-idirafter", 10}, + {"-include", 8}, + }; - if (sysroot_dir != NULL && strncmp(sysroot_dir, string, strlen(sysroot_dir))) - return true; + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + return true; return false; } @@ -86,12 +199,11 @@ pkgconf_fragment_is_groupable(const char { static const struct pkgconf_fragment_check check_fragments[] = { {"-Wl,--start-group", 17}, - {"-framework", 10}, - {"-isystem", 8}, - {"-idirafter", 10}, - {"-include", 8}, }; + if (pkgconf_fragment_only_group_one(string)) + return true; + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) return true; @@ -100,12 +212,15 @@ pkgconf_fragment_is_groupable(const char } static inline bool -pkgconf_fragment_is_terminus(const char *string) +pkgconf_fragment_is_terminus(const char *parent, const char *string) { static const struct pkgconf_fragment_check check_fragments[] = { {"-Wl,--end-group", 15}, }; + if (pkgconf_fragment_only_group_one(parent)) + return true; + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) return true; @@ -125,32 +240,24 @@ pkgconf_fragment_is_special(const char * return pkgconf_fragment_is_unmergeable(string); } -static inline void -pkgconf_fragment_munge(const pkgconf_client_t *client, char *buf, size_t buflen, const char *source, const char *sysroot_dir, unsigned int flags) +static pkgconf_fragment_t * +fragment_new(char type, const char *data) { - *buf = '\0'; - - if (!(flags & PKGCONF_PKG_PROPF_UNINSTALLED) || (client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES)) - { - if (sysroot_dir == NULL) - sysroot_dir = pkgconf_tuple_find_global(client, "pc_sysrootdir"); + size_t datalen = data != NULL ? strlen(data) : 0; + pkgconf_fragment_t *frag = calloc(1, sizeof(*frag) + (data != NULL ? datalen + 1 : 0)); - if (sysroot_dir != NULL && pkgconf_fragment_should_munge(source, sysroot_dir)) - pkgconf_strlcat(buf, sysroot_dir, buflen); - } + if (frag == NULL) + return NULL; - pkgconf_strlcat(buf, source, buflen); + frag->type = type; - if (*buf == '/' && !(client->flags & PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS)) - pkgconf_path_relocate(buf, buflen); -} + if (data != NULL) + { + frag->data = (char *)(frag + 1); + memcpy(frag->data, data, datalen + 1); + } -static inline char * -pkgconf_fragment_copy_munged(const pkgconf_client_t *client, const char *source, unsigned int flags) -{ - char mungebuf[PKGCONF_ITEM_SIZE]; - pkgconf_fragment_munge(client, mungebuf, sizeof mungebuf, source, client->sysroot_dir, flags); - return strdup(mungebuf); + return frag; } /* @@ -168,13 +275,15 @@ pkgconf_fragment_copy_munged(const pkgco * :return: nothing */ void -pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail) +pkgconf_fragment_insert(pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail) { + (void) client; + pkgconf_fragment_t *frag; - frag = calloc(1, sizeof(pkgconf_fragment_t)); - frag->type = type; - frag->data = pkgconf_fragment_copy_munged(client, data, 0); + frag = fragment_new(type, data); + if (frag == NULL) + return; if (tail) { @@ -185,68 +294,301 @@ pkgconf_fragment_insert(const pkgconf_cl pkgconf_node_insert(&frag->iter, frag, list); } +static bool +should_inject_sysroot(const pkgconf_client_t *client, const char *string, unsigned int flags) +{ + size_t offset; + + if (client->flags & PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION) + return false; + + /* we never automatically inject sysroot on -uninstalled packages */ + if (flags & PKGCONF_PKG_PROPF_UNINSTALLED) + { + /* ... unless we are emulating pkgconf 1.x */ + if (!(client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES)) + return false; + } + + if (client->sysroot_dir == NULL) + return false; + + offset = pkgconf_fragment_sysroot_path_offset(string); + if (offset == 0) + return false; + + return !pkgconf_fragment_under_sysroot(client, string + offset); +} + +static bool +should_inject_sysroot_child(const pkgconf_client_t *client, const pkgconf_fragment_t *last, const char *string, unsigned int flags) +{ + if (client->flags & PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION) + return false; + + /* we never automatically inject sysroot on -uninstalled packages */ + if (flags & PKGCONF_PKG_PROPF_UNINSTALLED) + { + /* ... unless we are emulating pkgconf 1.x */ + if (!(client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES)) + return false; + } + + if (last->type) + return false; + + if (last->data == NULL) + return false; + + if (client->sysroot_dir == NULL) + return false; + + /* the flag is the preceding fragment; this one is the bare path it takes. + * a flag which has already taken its argument takes no further ones, so + * what follows is a fragment in its own right and not a path at all. */ + if (last->flags & PKGCONF_PKG_FRAGF_TERMINATED) + return false; + + if (pkgconf_fragment_sysroot_path_offset(last->data) == 0) + return false; + + return !pkgconf_fragment_under_sysroot(client, string); +} + /* - * !doc - * - * .. c:function:: void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) - * - * Adds a `fragment` of text to a `fragment list`, possibly modifying the fragment if a sysroot is set. - * - * :param pkgconf_client_t* client: The pkgconf client being accessed. - * :param pkgconf_list_t* list: The fragment list. - * :param char* string: The string of text to add as a fragment to the fragment list. - * :param uint flags: Parsing-related flags for the package. - * :return: nothing + * Insert an already-expanded fragment string into the list. No variable + * substitution is performed here: `string` is taken verbatim, so this must + * only be called with input that has already been through the bytecode + * evaluator. Whether sysroot injection is still wanted is read off `string` + * itself; see pkgconf_fragment_under_sysroot. */ -void -pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) +static bool +fragment_insert_evaluated(pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) { pkgconf_list_t *target = list; + pkgconf_fragment_t *terminate_parent = NULL; pkgconf_fragment_t *frag; + size_t separate; - if (*string == '\0') - return; + if (string == NULL || *string == '\0') + return true; + + /* A .pc file may join one of these flags to its path. That names the same + * thing as writing them apart, so split it back apart and let there be one + * spelling of it in a fragment list: the flag, and the path it takes. */ + separate = pkgconf_fragment_separate_path_offset(string); + if (separate != 0 && string[separate] != '\0') + { + pkgconf_buffer_t flagbuf = PKGCONF_BUFFER_INITIALIZER; + bool ok = pkgconf_buffer_append_slice(&flagbuf, string, separate) && + fragment_insert_evaluated(client, list, pkgconf_buffer_str(&flagbuf), flags) && + fragment_insert_evaluated(client, list, string + separate, flags); + + pkgconf_buffer_finalize(&flagbuf); + return ok; + } if (list->tail != NULL && list->tail->data != NULL && - !(client->flags & PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS)) + !(client->flags & PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS)) { pkgconf_fragment_t *parent = list->tail->data; /* only attempt to merge 'special' fragments together */ if (!parent->type && parent->data != NULL && - pkgconf_fragment_is_unmergeable(parent->data) && - !(parent->flags & PKGCONF_PKG_FRAGF_TERMINATED)) + pkgconf_fragment_is_unmergeable(parent->data) && + !(parent->flags & PKGCONF_PKG_FRAGF_TERMINATED)) { if (pkgconf_fragment_is_groupable(parent->data)) target = &parent->children; - if (pkgconf_fragment_is_terminus(string)) - parent->flags |= PKGCONF_PKG_FRAGF_TERMINATED; + if (pkgconf_fragment_is_terminus(parent->data, string)) + terminate_parent = parent; PKGCONF_TRACE(client, "adding fragment as child to list @%p", target); } } - if (strlen(string) > 1 && !pkgconf_fragment_is_special(string)) - { - frag = calloc(1, sizeof(pkgconf_fragment_t)); + /* Compute the final data string first (borrowing sysroot_buf when we have to + * prepend the sysroot), then hand it to fragment_new(), which copies it + * inline. data == NULL here means an allocation/append failure. */ + { + char type = 0; + const char *data = NULL; + pkgconf_buffer_t sysroot_buf = PKGCONF_BUFFER_INITIALIZER; + + if (strlen(string) > 1 && !pkgconf_fragment_is_special(string)) + { + type = *(string + 1); - frag->type = *(string + 1); - frag->data = pkgconf_fragment_copy_munged(client, string + 2, flags); + if (should_inject_sysroot(client, string, flags)) + { + if (pkgconf_buffer_append(&sysroot_buf, client->sysroot_dir) && + pkgconf_buffer_append(&sysroot_buf, string + 2)) + data = pkgconf_buffer_str(&sysroot_buf); + } + else + data = string + 2; + } + else + { + type = 0; - PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list); + if (client->sysroot_dir != NULL && list->tail != NULL && list->tail->data != NULL && + should_inject_sysroot_child(client, list->tail->data, string, flags)) + { + if (pkgconf_buffer_append(&sysroot_buf, client->sysroot_dir) && + pkgconf_buffer_append(&sysroot_buf, string)) + data = pkgconf_buffer_str(&sysroot_buf); + } + else + data = string; + } + + frag = data != NULL ? fragment_new(type, data) : NULL; + pkgconf_buffer_finalize(&sysroot_buf); } + + if (frag == NULL) + { + PKGCONF_TRACE(client, "failed to add new fragment due to allocation failure to list @%p", target); + return false; + } + + if (frag->type) + PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list); else + PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target); + + pkgconf_node_insert_tail(&frag->iter, frag, target); + if (terminate_parent != NULL) + terminate_parent->flags |= PKGCONF_PKG_FRAGF_TERMINATED; + + return true; +} + +/* + * Split a string into whitespace-delimited fragments, honouring shell quoting + * and greedy flags (e.g. "-I /usr/include" -> "-I/usr/include"). + * + * When `evaluate` is true, `value` is a property as the .pc file spells it and + * each token is handed to pkgconf_fragment_add to be expanded. Splitting it + * only settles where the tokens end, so the quoting is left in place for the + * pass over the expansion to consume. + * + * When `evaluate` is false, `value` is the expansion of one such token. This is + * where the quoting is consumed, once, over text which is otherwise finished, + * and the tokens are inserted as they stand. It must not evaluate again, lest + * an expansion yielding a literal "${...}" (via a "$$" escape) recurse forever. + */ +static bool +fragment_split(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags, bool evaluate) +{ + int i, ret, argc; + char **argv; + + ret = evaluate + ? pkgconf_argv_split_raw(value, &argc, &argv) + : pkgconf_argv_split(value, &argc, &argv); + if (ret < 0) { - frag = calloc(1, sizeof(pkgconf_fragment_t)); + PKGCONF_TRACE(client, "unable to parse fragment string [%s]", value); + return false; + } - frag->type = 0; - frag->data = pkgconf_fragment_copy_munged(client, string, flags); + for (i = 0; i < argc; i++) + { + const char *token; + pkgconf_buffer_t greedybuf = PKGCONF_BUFFER_INITIALIZER; + bool ok; - PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target); + if (argv[i] == NULL) + { + PKGCONF_TRACE(client, "parsed fragment string is inconsistent: argc = %d while argv[%d] == NULL", argc, i); + pkgconf_argv_free(argv); + return false; + } + + token = argv[i]; + + PKGCONF_TRACE(client, "processing [%s]", argv[i]); + + if (pkgconf_fragment_is_greedy(argv[i]) && i + 1 < argc) + { + if (!pkgconf_buffer_append(&greedybuf, argv[i]) || + !pkgconf_buffer_append(&greedybuf, argv[i + 1])) + { + pkgconf_buffer_finalize(&greedybuf); + pkgconf_argv_free(argv); + return false; + } + + token = pkgconf_buffer_str(&greedybuf); + + /* skip over next arg as we combined them */ + i++; + } + + if (evaluate) + ok = pkgconf_fragment_add(client, list, vars, token, flags); + else + ok = fragment_insert_evaluated(client, list, token, flags); + + pkgconf_buffer_finalize(&greedybuf); + + if (!ok) + { + pkgconf_argv_free(argv); + return false; + } } - pkgconf_node_insert_tail(&frag->iter, frag, target); + pkgconf_argv_free(argv); + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) + * + * Adds a `fragment` of text to a `fragment list`, possibly modifying the fragment if a sysroot is set. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The fragment list. + * :param char* string: The string of text to add as a fragment to the fragment list. + * :param uint flags: Parsing-related flags for the package. + * :return: true on success, false on parse error or allocation failure + */ +bool +pkgconf_fragment_add(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags) +{ + pkgconf_buffer_t evalbuf = PKGCONF_BUFFER_INITIALIZER; + bool ret; + + if (!pkgconf_bytecode_eval_str_to_buf(client, vars, value, NULL, &evalbuf)) + { + pkgconf_buffer_finalize(&evalbuf); + return false; + } + + if (pkgconf_buffer_len(&evalbuf) == 0) + { + pkgconf_buffer_finalize(&evalbuf); + return true; + } + + /* fragment_split() only reads the expanded string, so hand it the buffer + * contents directly rather than freezing (copying) them out. + * + * The expansion is split rather than taken whole: the quoting it carries is + * consumed there, and a value may in any case expand to several + * whitespace-separated fragments. + */ + ret = fragment_split(client, list, vars, pkgconf_buffer_str(&evalbuf), flags, false); + + pkgconf_buffer_finalize(&evalbuf); + return ret; } static inline pkgconf_fragment_t * @@ -258,14 +600,115 @@ pkgconf_fragment_lookup(pkgconf_list_t * { pkgconf_fragment_t *frag = node->data; - if (base->type != frag->type) - continue; + if (base->type != frag->type) + continue; + + if (base->data == NULL || frag->data == NULL) + { + if (base->data == frag->data) + return frag; + + continue; + } + + if (!strcmp(base->data, frag->data)) + return frag; + } + + return NULL; +} + +/* Order fragments by (type, data) so that the cursor index can be searched + * with bsearch(). NULL data sorts before any non-NULL data of the same type. */ +static int +fragment_index_cmp(const void *a, const void *b) +{ + const pkgconf_fragment_t *fa = *(pkgconf_fragment_t * const *) a; + const pkgconf_fragment_t *fb = *(pkgconf_fragment_t * const *) b; + + if (fa->type != fb->type) + return (unsigned char) fa->type < (unsigned char) fb->type ? -1 : 1; + + if (fa->data == NULL || fb->data == NULL) + { + if (fa->data == fb->data) + return 0; + + return fa->data == NULL ? -1 : 1; + } + + return strcmp(fa->data, fb->data); +} + +static pkgconf_fragment_t * +fragment_index_lookup(const pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base) +{ + pkgconf_fragment_t **found; + + if (cursor->count == 0) + return NULL; + + found = bsearch(&base, cursor->index, cursor->count, sizeof(void *), fragment_index_cmp); + return found != NULL ? *found : NULL; +} + +static bool +fragment_index_insert(pkgconf_fragment_cursor_t *cursor, pkgconf_fragment_t *frag) +{ + size_t lo = 0, hi = cursor->count; + + if (cursor->count == cursor->alloc) + { + size_t newalloc = cursor->alloc != 0 ? cursor->alloc * 2 : 16; + pkgconf_fragment_t **newindex = pkgconf_reallocarray(cursor->index, newalloc, sizeof(void *)); + + if (newindex == NULL) + return false; + + cursor->index = newindex; + cursor->alloc = newalloc; + } + + while (lo < hi) + { + size_t mid = lo + (hi - lo) / 2; + + if (fragment_index_cmp(&cursor->index[mid], &frag) < 0) + lo = mid + 1; + else + hi = mid; + } + + memmove(&cursor->index[lo + 1], &cursor->index[lo], (cursor->count - lo) * sizeof(void *)); + cursor->index[lo] = frag; + cursor->count++; + + return true; +} + +static void +fragment_index_remove(pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *frag) +{ + pkgconf_fragment_t **found = bsearch(&frag, cursor->index, cursor->count, sizeof(void *), fragment_index_cmp); + size_t idx; + + if (found == NULL) + return; - if (!strcmp(base->data, frag->data)) - return frag; - } + idx = (size_t) (found - cursor->index); + memmove(&cursor->index[idx], &cursor->index[idx + 1], (cursor->count - idx - 1) * sizeof(void *)); + cursor->count--; +} - return NULL; +/* Look up an existing fragment matching `base`: via the cursor's sorted index + * when one is provided, otherwise a linear scan of the list. */ +static inline pkgconf_fragment_t * +fragment_lookup(pkgconf_list_t *list, const pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base) +{ + if (cursor != NULL) + return fragment_index_lookup(cursor, base); + + return pkgconf_fragment_lookup(list, base); } static inline bool @@ -302,11 +745,14 @@ pkgconf_fragment_can_merge(const pkgconf if (base->children.head != NULL) return false; + if (base->data == NULL) + return false; + return pkgconf_fragment_is_unmergeable(base->data); } static inline pkgconf_fragment_t * -pkgconf_fragment_exists(pkgconf_list_t *list, const pkgconf_fragment_t *base, unsigned int flags, bool is_private) +pkgconf_fragment_exists(pkgconf_list_t *list, const pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, unsigned int flags, bool is_private) { if (!pkgconf_fragment_can_merge_back(base, flags, is_private)) return NULL; @@ -314,7 +760,7 @@ pkgconf_fragment_exists(pkgconf_list_t * if (!pkgconf_fragment_can_merge(base, flags, is_private)) return NULL; - return pkgconf_fragment_lookup(list, base); + return fragment_lookup(list, cursor, base); } static inline bool @@ -375,6 +821,68 @@ pkgconf_fragment_has_system_dir(const pk return pkgconf_path_match_list(frag->data, check_paths); } +static bool +pkgconf_fragment_copy_node(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private); + +static bool +pkgconf_fragment_copy_list_node(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(base->head, node) + { + pkgconf_fragment_t *frag = node->data; + + /* children are copied into their own (small) list, which is never + * indexed, so no cursor is threaded down here. */ + if (!pkgconf_fragment_copy_node(client, list, NULL, frag, true)) + return false; + } + + return true; +} + +static bool +pkgconf_fragment_copy_node(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private) +{ + pkgconf_fragment_t *old_frag = NULL; + pkgconf_fragment_t *frag; + + if ((old_frag = pkgconf_fragment_exists(list, cursor, base, client->flags, is_private)) != NULL) + { + if (!pkgconf_fragment_should_merge(old_frag)) + old_frag = NULL; + } + else if (!is_private && !pkgconf_fragment_can_merge_back(base, client->flags, is_private) && (fragment_lookup(list, cursor, base) != NULL)) + return true; + + frag = fragment_new(base->type, base->data); + if (frag == NULL) + return false; + + if (!pkgconf_fragment_copy_list_node(client, &frag->children, &base->children)) + { + pkgconf_fragment_free(&frag->children); + free(frag); + return false; + } + + if (old_frag != NULL) + { + if (cursor != NULL) + fragment_index_remove(cursor, old_frag); + + pkgconf_fragment_delete(list, old_frag); + } + + pkgconf_node_insert_tail(&frag->iter, frag, list); + + if (cursor != NULL && !fragment_index_insert(cursor, frag)) + return false; + + return true; +} + /* * !doc * @@ -392,24 +900,76 @@ pkgconf_fragment_has_system_dir(const pk void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private) { - pkgconf_fragment_t *frag; + (void) pkgconf_fragment_copy_node(client, list, NULL, base, is_private); +} - if ((frag = pkgconf_fragment_exists(list, base, client->flags, is_private)) != NULL) - { - if (pkgconf_fragment_should_merge(frag)) - pkgconf_fragment_delete(list, frag); - } - else if (!is_private && !pkgconf_fragment_can_merge_back(base, client->flags, is_private) && (pkgconf_fragment_lookup(list, base) != NULL)) - return; +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_cursor_init(pkgconf_fragment_cursor_t *cursor, pkgconf_list_t *list) + * + * Initialises a `fragment cursor` bound to a (typically empty) destination list. While the + * cursor is in use, fragments must be added to the list only via ``pkgconf_fragment_copy_cursor()`` + * so that the cursor's index stays in sync. + * + * :param pkgconf_fragment_cursor_t* cursor: The cursor to initialise. + * :param pkgconf_list_t* list: The destination fragment list. + * :return: nothing + */ +void +pkgconf_fragment_cursor_init(pkgconf_fragment_cursor_t *cursor, pkgconf_list_t *list) +{ + pkgconf_node_t *node; - frag = calloc(1, sizeof(pkgconf_fragment_t)); + cursor->list = list; + cursor->index = NULL; + cursor->count = 0; + cursor->alloc = 0; - frag->type = base->type; - pkgconf_fragment_copy_list(client, &frag->children, &base->children); - if (base->data != NULL) - frag->data = strdup(base->data); + /* seed the index with anything already present, so dedup against pre-existing + * fragments behaves exactly as the linear scan would. */ + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + (void) fragment_index_insert(cursor, node->data); +} - pkgconf_node_insert_tail(&frag->iter, frag, list); +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_cursor_deinit(pkgconf_fragment_cursor_t *cursor) + * + * Releases the index held by a `fragment cursor`. The destination list and its fragments are + * not affected. + * + * :param pkgconf_fragment_cursor_t* cursor: The cursor to release. + * :return: nothing + */ +void +pkgconf_fragment_cursor_deinit(pkgconf_fragment_cursor_t *cursor) +{ + free(cursor->index); + cursor->index = NULL; + cursor->count = 0; + cursor->alloc = 0; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_copy_cursor(const pkgconf_client_t *client, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private) + * + * Like ``pkgconf_fragment_copy()``, but uses the cursor's sorted index for the mergeback lookup, + * turning what would be a linear scan of the destination list into a bsearch. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_fragment_cursor_t* cursor: The cursor bound to the destination list. + * :param pkgconf_fragment_t* base: The fragment being copied. + * :param bool is_private: Whether the fragment list is a `private` fragment list (static linking). + * :return: nothing + */ +void +pkgconf_fragment_copy_cursor(const pkgconf_client_t *client, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private) +{ + (void) pkgconf_fragment_copy_node(client, cursor->list, cursor, base, is_private); } /* @@ -428,14 +988,7 @@ pkgconf_fragment_copy(const pkgconf_clie void pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) { - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(base->head, node) - { - pkgconf_fragment_t *frag = node->data; - - pkgconf_fragment_copy(client, list, frag, true); - } + (void) pkgconf_fragment_copy_list_node(client, list, base); } /* @@ -462,231 +1015,227 @@ pkgconf_fragment_filter(const pkgconf_cl pkgconf_fragment_t *frag = node->data; if (filter_func(client, frag, data)) - pkgconf_fragment_copy(client, dest, frag, true); + (void) pkgconf_fragment_copy_node(client, dest, NULL, frag, true); } } -static inline char * -fragment_quote(const pkgconf_fragment_t *frag) +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_filter_splice(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data) + * + * Like ``pkgconf_fragment_filter()``, but moves the matching `fragments` to `dest` instead of + * copying them. Fragments which do not match are left in `src` for the caller to release. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* dest: The destination list. + * :param pkgconf_list_t* src: The source list. + * :param pkgconf_fragment_filter_func_t filter_func: The filter function to use. + * :param void* data: Optional data to pass to the filter function. + * :return: nothing + */ +void +pkgconf_fragment_filter_splice(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data) { - const char *src = frag->data; - ssize_t outlen = strlen(src) + 10; - char *out, *dst; - - if (frag->data == NULL) - return NULL; + pkgconf_node_t *node, *next; - out = dst = calloc(1, outlen); + if (dest == src) + return; - for (; *src; src++) + PKGCONF_FOREACH_LIST_ENTRY_SAFE(src->head, next, node) { - if (((*src < ' ') || - (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') || - (*src > '$' && *src < '(') || - (*src > ')' && *src < '+') || - (*src > ':' && *src < '=') || - (*src > '=' && *src < '@') || - (*src > 'Z' && *src < '\\') || -#ifndef _WIN32 - (*src == '\\') || -#endif - (*src > '\\' && *src < '^') || - (*src == '`') || - (*src > 'z' && *src < '~') || - (*src > '~'))) - *dst++ = '\\'; - - *dst++ = *src; - - if ((ptrdiff_t)(dst - out) + 2 > outlen) - { - ptrdiff_t offset = dst - out; - outlen *= 2; - out = realloc(out, outlen); - dst = out + offset; - } - } - - *dst = 0; - return out; -} - -static inline size_t -pkgconf_fragment_len(const pkgconf_fragment_t *frag) -{ - size_t len = 1; + pkgconf_fragment_t *frag = node->data; - if (frag->type) - len += 2; + if (!filter_func(client, frag, data)) + continue; - if (frag->data != NULL) - { - pkgconf_node_t *iter; + pkgconf_node_delete(node, src); - char *quoted = fragment_quote(frag); - len += strlen(quoted); - free(quoted); + /* pkgconf_node_delete() leaves the node's links pointing into the + * source list, and pkgconf_node_insert_tail() only overwrites prev. */ + node->prev = node->next = NULL; - PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter) - { - const pkgconf_fragment_t *child_frag = iter->data; - len += pkgconf_fragment_len(child_frag) + 1; - } + pkgconf_node_insert_tail(node, frag, dest); } +} - return len; +/* + * !doc + * + * .. c:function:: bool pkgconf_is_locale_utf8(void) + * + * Check whether text is expected to be UTF-8 encoded in the current environment. + * + * :return: :code:`true` if text is expected to be UTF-8 encoded, :code:`false` otherwise. + */ +#ifndef _WIN32 +static bool +codeset_is_utf8(const char *codeset) +{ + return codeset != NULL && (!strcasecmp(codeset, "UTF-8") || !strcasecmp(codeset, "UTF8")); } +#endif -static size_t -fragment_render_len(const pkgconf_list_t *list, bool escape) +bool +pkgconf_is_locale_utf8(void) { - (void) escape; +#ifdef _WIN32 + return GetACP() == CP_UTF8; +#else + static int cached = -1; - size_t out = 1; /* trailing nul */ - pkgconf_node_t *node; + if (cached >= 0) + return cached; - PKGCONF_FOREACH_LIST_ENTRY(list->head, node) +#if HAVE_DECL_NL_LANGINFO_L + locale_t loc = newlocale(LC_CTYPE_MASK, "", (locale_t)0); + + if (loc != (locale_t)0) { - const pkgconf_fragment_t *frag = node->data; - out += pkgconf_fragment_len(frag); + cached = codeset_is_utf8(nl_langinfo_l(CODESET, loc)); + freelocale(loc); } + else + cached = 0; +#else + const char *prev_locale = setlocale(LC_CTYPE, NULL); + char *saved_locale = prev_locale != NULL ? strdup(prev_locale) : NULL; + + setlocale(LC_CTYPE, ""); + cached = codeset_is_utf8(nl_langinfo(CODESET)); + setlocale(LC_CTYPE, saved_locale != NULL ? saved_locale : "C"); + free(saved_locale); +#endif - return out; + return cached; +#endif } -static inline size_t -fragment_render_item(const pkgconf_fragment_t *frag, char *bptr, size_t bufremain) -{ - char *base = bptr; - char *quoted = fragment_quote(frag); - const pkgconf_node_t *iter; +static const pkgconf_span_t quote_spans[] = { + { 0x00, 0x1f }, + { (unsigned char)' ', (unsigned char)'#' }, + { (unsigned char)'%', (unsigned char)'\'' }, + { (unsigned char)'*', (unsigned char)'*' }, + { (unsigned char)';', (unsigned char)'<' }, + { (unsigned char)'>', (unsigned char)'?' }, + { (unsigned char)'[', (unsigned char)']' }, + { (unsigned char)'`', (unsigned char)'`' }, + { (unsigned char)'{', (unsigned char)'}' }, + { 0x7f, 0xff }, +}; - if (strlen(quoted) > bufremain) - { - free(quoted); - return 0; - } +/* If the locale is UTF-8 we must not split character over 0x7f because it would add "\" between each bytes. + So only DEL (0x7f) needs escaping */ +static const pkgconf_span_t quote_spans_utf8[] = { + { 0x00, 0x1f }, + { (unsigned char)' ', (unsigned char)'#' }, + { (unsigned char)'%', (unsigned char)'\'' }, + { (unsigned char)'*', (unsigned char)'*' }, + { (unsigned char)';', (unsigned char)'<' }, + { (unsigned char)'>', (unsigned char)'?' }, + { (unsigned char)'[', (unsigned char)']' }, + { (unsigned char)'`', (unsigned char)'`' }, + { (unsigned char)'{', (unsigned char)'}' }, + { 0x7f, 0x7f }, +}; - if (frag->type) - { - *bptr++ = '-'; - *bptr++ = frag->type; - } +/* + * The set of bytes needing a backslash is fixed for the life of the process + * (pkgconf_is_locale_utf8() is itself cached), so resolve the spans into a + * byte-set once instead of walking them for every byte of every fragment. + */ +static const pkgconf_charset_t * +fragment_quote_charset(void) +{ + static pkgconf_charset_t charset; + static bool have_charset = false; - if (quoted != NULL) + if (!have_charset) { - bptr += pkgconf_strlcpy(bptr, quoted, bufremain - (bptr - base)); - free(quoted); + if (pkgconf_is_locale_utf8()) + pkgconf_charset_from_spans(&charset, quote_spans_utf8, PKGCONF_ARRAY_SIZE(quote_spans_utf8)); + else + pkgconf_charset_from_spans(&charset, quote_spans, PKGCONF_ARRAY_SIZE(quote_spans)); + + have_charset = true; } - PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter) - { - const pkgconf_fragment_t *child_frag = iter->data; + return &charset; +} - *bptr++ = ' '; - bptr += fragment_render_item(child_frag, bptr, bufremain - (bptr - base)); - } +static bool +fragment_quote(pkgconf_buffer_t *out, const pkgconf_fragment_t *frag) +{ + if (frag->data == NULL) + return true; - return bptr - base; + return pkgconf_buffer_escape_charset(out, PKGCONF_BUFFER_FROM_STR(frag->data), fragment_quote_charset()); } -static void -fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape) +static bool +fragment_render(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf) { - (void) escape; + const pkgconf_node_t *iter; - pkgconf_node_t *node; - char *bptr = buf; + if (frag->type && + (!pkgconf_buffer_push_byte(buf, '-') || !pkgconf_buffer_push_byte(buf, frag->type))) + return false; - memset(buf, 0, buflen); + if (!fragment_quote(buf, frag)) + return false; - PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter) { - const pkgconf_fragment_t *frag = node->data; - size_t buf_remaining = buflen - (bptr - buf); - size_t written = fragment_render_item(frag, bptr, buf_remaining); - - bptr += written; + const pkgconf_fragment_t *child_frag = iter->data; - if (node->next != NULL) - *bptr++ = ' '; + if (!pkgconf_buffer_push_byte(buf, ctx->delim) || + !fragment_render(ctx, child_frag, buf)) + return false; } + + return true; } static const pkgconf_fragment_render_ops_t default_render_ops = { - .render_len = fragment_render_len, - .render_buf = fragment_render_buf + .render = fragment_render }; /* * !doc * - * .. c:function:: size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) - * - * Calculates the required memory to store a `fragment list` when rendered as a string. - * - * :param pkgconf_list_t* list: The `fragment list` being rendered. - * :param bool escape: Whether or not to escape special shell characters (deprecated). - * :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. - * :return: the amount of bytes required to represent the `fragment list` when rendered - * :rtype: size_t - */ -size_t -pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) -{ - (void) escape; - - ops = ops != NULL ? ops : &default_render_ops; - return ops->render_len(list, true); -} - -/* - * !doc - * - * .. c:function:: void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops) + * .. c:function:: void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops, char delim) * * Renders a `fragment list` into a buffer. * * :param pkgconf_list_t* list: The `fragment list` being rendered. - * :param char* buf: The buffer to render the fragment list into. - * :param size_t buflen: The length of the buffer. + * :param pkgconf_buffer_t* buf: The buffer to render the fragment list into. * :param bool escape: Whether or not to escape special shell characters (deprecated). * :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + * :param char delim: The delimiter to use between fragments. * :return: nothing */ -void -pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops) +bool +pkgconf_fragment_render_buf(const pkgconf_list_t *list, pkgconf_buffer_t *buf, bool escape, const pkgconf_fragment_render_ops_t *ops, char delim) { - (void) escape; + pkgconf_node_t *node; + pkgconf_fragment_render_ctx_t ctx = { + .escape = escape, + .delim = delim, + }; ops = ops != NULL ? ops : &default_render_ops; - ops->render_buf(list, buf, buflen, true); -} - -/* - * !doc - * - * .. c:function:: char *pkgconf_fragment_render(const pkgconf_list_t *list) - * - * Allocate memory and render a `fragment list` into it. - * - * :param pkgconf_list_t* list: The `fragment list` being rendered. - * :param bool escape: Whether or not to escape special shell characters (deprecated). - * :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. - * :return: An allocated string containing the rendered `fragment list`. - * :rtype: char * - */ -char * -pkgconf_fragment_render(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) -{ - (void) escape; - size_t buflen = pkgconf_fragment_render_len(list, true, ops); - char *buf = calloc(1, buflen); + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + const pkgconf_fragment_t *frag = node->data; + if (!ops->render(&ctx, frag, buf)) + return false; - pkgconf_fragment_render_buf(list, buf, buflen, true, ops); + if (node->next != NULL && !pkgconf_buffer_push_byte(buf, ctx.delim)) + return false; + } - return buf; + return true; } /* @@ -705,7 +1254,7 @@ pkgconf_fragment_delete(pkgconf_list_t * { pkgconf_node_delete(&node->iter, list); - free(node->data); + pkgconf_fragment_free(&node->children); free(node); } @@ -729,9 +1278,10 @@ pkgconf_fragment_free(pkgconf_list_t *li pkgconf_fragment_t *frag = node->data; pkgconf_fragment_free(&frag->children); - free(frag->data); free(frag); } + + pkgconf_list_zero(list); } /* @@ -749,39 +1299,7 @@ pkgconf_fragment_free(pkgconf_list_t *li * :return: true on success, false on parse error */ bool -pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags) +pkgconf_fragment_parse(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags) { - int i, ret, argc; - char **argv; - char *repstr = pkgconf_tuple_parse(client, vars, value, flags); - - PKGCONF_TRACE(client, "post-subst: [%s] -> [%s]", value, repstr); - - ret = pkgconf_argv_split(repstr, &argc, &argv); - if (ret < 0) - { - PKGCONF_TRACE(client, "unable to parse fragment string [%s]", repstr); - free(repstr); - return false; - } - - for (i = 0; i < argc; i++) - { - PKGCONF_TRACE(client, "processing %s", argv[i]); - - if (argv[i] == NULL) - { - PKGCONF_TRACE(client, "parsed fragment string is inconsistent: argc = %d while argv[%d] == NULL", argc, i); - pkgconf_argv_free(argv); - free(repstr); - return false; - } - - pkgconf_fragment_add(client, list, argv[i], flags); - } - - pkgconf_argv_free(argv); - free(repstr); - - return true; + return fragment_split(client, list, vars, value, flags, true); } Index: libpkgconf/iter.h =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/iter.h,v diff -u -p -r1.1.1.1 iter.h --- libpkgconf/iter.h 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/iter.h 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * iter.h * Linked lists and iterators. * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2013 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -16,6 +18,8 @@ #ifndef LIBPKGCONF_ITER_H #define LIBPKGCONF_ITER_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -40,6 +44,26 @@ pkgconf_list_zero(pkgconf_list_t *list) list->head = NULL; list->tail = NULL; list->length = 0; +} + +static inline void +pkgconf_list_splice(pkgconf_list_t *dest, pkgconf_list_t *src) +{ + if (dest == src || src->head == NULL) + return; + + if (dest->head == NULL) + dest->head = src->head; + else + { + dest->tail->next = src->head; + src->head->prev = dest->tail; + } + + dest->tail = src->tail; + dest->length += src->length; + + pkgconf_list_zero(src); } static inline void Index: libpkgconf/libpkgconf.h =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/libpkgconf.h,v diff -u -p -r1.1.1.1 libpkgconf.h --- libpkgconf/libpkgconf.h 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/libpkgconf.h 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * libpkgconf.h * Global include file for everything in libpkgconf. * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2011, 2015 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -30,23 +33,6 @@ extern "C" { #endif -/* pkg-config uses ';' on win32 as ':' is part of path */ -#ifdef _WIN32 -#define PKG_CONFIG_PATH_SEP_S ";" -#else -#define PKG_CONFIG_PATH_SEP_S ":" -#endif - -#ifdef _WIN32 -#define PKG_DIR_SEP_S '\\' -#else -#define PKG_DIR_SEP_S '/' -#endif - -#ifdef _WIN32 -#define realpath(N,R) _fullpath((R),(N),_MAX_PATH) -#endif - #define PKGCONF_BUFSIZE (65535) typedef enum { @@ -63,12 +49,16 @@ typedef enum { typedef struct pkgconf_pkg_ pkgconf_pkg_t; typedef struct pkgconf_dependency_ pkgconf_dependency_t; -typedef struct pkgconf_tuple_ pkgconf_tuple_t; +typedef struct pkgconf_buffer_ pkgconf_buffer_t; +typedef struct pkgconf_bufferset_ pkgconf_bufferset_t; +typedef struct pkgconf_span_ pkgconf_span_t; typedef struct pkgconf_fragment_ pkgconf_fragment_t; typedef struct pkgconf_path_ pkgconf_path_t; typedef struct pkgconf_client_ pkgconf_client_t; typedef struct pkgconf_cross_personality_ pkgconf_cross_personality_t; typedef struct pkgconf_queue_ pkgconf_queue_t; +typedef struct pkgconf_output_ pkgconf_output_t; +typedef struct pkgconf_license_ pkgconf_license_t; #define PKGCONF_ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) @@ -81,8 +71,8 @@ typedef struct pkgconf_queue_ pkgconf_qu #define PKGCONF_FOREACH_LIST_ENTRY_REVERSE(tail, value) \ for ((value) = (tail); (value) != NULL; (value) = (value)->prev) -#define LIBPKGCONF_VERSION 20403 -#define LIBPKGCONF_VERSION_STR "2.4.3" +#define LIBPKGCONF_VERSION 30005 +#define LIBPKGCONF_VERSION_STR "3.0.5" struct pkgconf_queue_ { pkgconf_node_t iter; @@ -116,8 +106,62 @@ struct pkgconf_dependency_ { int refcount; pkgconf_client_t *owner; + + char *why; +}; + +struct pkgconf_buffer_ { + char *base; + char *end; +}; + +struct pkgconf_bufferset_ { + pkgconf_node_t node; + pkgconf_buffer_t buffer; +}; + +#if defined(_MSC_VER) && !defined(__clang__) +# define PKGCONF_PACKED_STRUCT(name) __pragma(pack(push, 1)) struct name __pragma(pack(pop)) +#else +# define PKGCONF_PACKED_STRUCT(name) struct __attribute__((__packed__)) name +#endif + +enum pkgconf_bytecode_op { + PKGCONF_BYTECODE_OP_TEXT = 1, + PKGCONF_BYTECODE_OP_VAR = 2, + PKGCONF_BYTECODE_OP_SYSROOT = 3, }; +typedef PKGCONF_PACKED_STRUCT(pkgconf_bytecode_op_) { + enum pkgconf_bytecode_op tag; + uint32_t size; + char data[]; +} pkgconf_bytecode_op_t; + +typedef struct { + const uint8_t *base; + size_t len; +} pkgconf_bytecode_t; + +typedef struct pkgconf_variable_ { + pkgconf_node_t iter; + + char *key; + + pkgconf_buffer_t bcbuf; + pkgconf_bytecode_t bc; + + unsigned int flags; + + bool expanding; +} pkgconf_variable_t; + +#define PKGCONF_VARIABLEF_OVERRIDE 0x1 + +typedef pkgconf_variable_t pkgconf_tuple_t; + +#define PKGCONF_PKG_TUPLEF_OVERRIDE PKGCONF_VARIABLEF_OVERRIDE + struct pkgconf_tuple_ { pkgconf_node_t iter; @@ -127,8 +171,6 @@ struct pkgconf_tuple_ { unsigned int flags; }; -#define PKGCONF_PKG_TUPLEF_OVERRIDE 0x1 - struct pkgconf_path_ { pkgconf_node_t lnode; @@ -139,6 +181,23 @@ struct pkgconf_path_ { unsigned int flags; }; +typedef enum { + PKGCONF_LICENSE_UNKNOWN = 0, + PKGCONF_LICENSE_EXPRESSION = 1, + PKGCONF_LICENSE_AND = 10, + PKGCONF_LICENSE_OR = 11, + PKGCONF_LICENSE_WITH = 12, + PKGCONF_LICENSE_BRACKET_OPEN = 20, + PKGCONF_LICENSE_BRACKET_CLOSE = 21, +} pkgconf_license_types_t; + +struct pkgconf_license_ { + pkgconf_node_t iter; + + unsigned char type; + char *data; +}; + #define PKGCONF_PKG_PROPF_NONE 0x00 #define PKGCONF_PKG_PROPF_STATIC 0x01 #define PKGCONF_PKG_PROPF_CACHED 0x02 @@ -146,6 +205,7 @@ struct pkgconf_path_ { #define PKGCONF_PKG_PROPF_VIRTUAL 0x10 #define PKGCONF_PKG_PROPF_ANCESTOR 0x20 #define PKGCONF_PKG_PROPF_VISITED_PRIVATE 0x40 +#define PKGCONF_PKG_PROPF_PRELOADED 0x80 struct pkgconf_pkg_ { int refcount; @@ -156,18 +216,27 @@ struct pkgconf_pkg_ { char *description; char *url; char *pc_filedir; - char *license; char *maintainer; - char *copyright; + char *source; + char *license_file; char *why; + pkgconf_list_t copyright; + + pkgconf_list_t license; + + pkgconf_list_t link_abi; + pkgconf_list_t libs; pkgconf_list_t libs_private; + pkgconf_list_t libs_shared; pkgconf_list_t cflags; pkgconf_list_t cflags_private; + pkgconf_list_t cflags_shared; pkgconf_list_t required; /* this used to be requires but that is now a reserved keyword */ pkgconf_list_t requires_private; + pkgconf_list_t requires_shared; pkgconf_list_t conflicts; pkgconf_list_t provides; @@ -177,20 +246,35 @@ struct pkgconf_pkg_ { pkgconf_client_t *owner; - /* these resources are owned by the package and do not need special management, - * under no circumstance attempt to allocate or free objects belonging to these pointers - */ - pkgconf_tuple_t *orig_prefix; - pkgconf_tuple_t *prefix; + pkgconf_buffer_t orig_prefix; + pkgconf_buffer_t calculated_prefix; uint64_t serial; uint64_t identifier; + + pkgconf_node_t preload_node; }; typedef bool (*pkgconf_pkg_iteration_func_t)(const pkgconf_pkg_t *pkg, void *data); -typedef void (*pkgconf_pkg_traverse_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data); + +/* iteration state threaded through pkgconf_pkg_traverse() to its callback */ +#define PKGCONF_PKG_ITERF_NONE 0x0 +#define PKGCONF_PKG_ITERF_PRIVATE 0x1 + +typedef void (*pkgconf_pkg_traverse_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags); typedef bool (*pkgconf_queue_apply_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth); typedef bool (*pkgconf_error_handler_func_t)(const char *msg, const pkgconf_client_t *client, void *data); +typedef void (*pkgconf_unveil_handler_func_t)(const pkgconf_client_t *client, const char *path, const char *permissions); +typedef const char *(*pkgconf_environ_lookup_handler_func_t)(const pkgconf_client_t *client, const char *variable); + +typedef struct pkgconf_client_options_ { + pkgconf_error_handler_func_t error_handler; + void *error_handler_data; + const pkgconf_cross_personality_t *personality; + void *client_data; + pkgconf_environ_lookup_handler_func_t environ_lookup_handler; + pkgconf_unveil_handler_func_t unveil_handler; +} pkgconf_client_options_t; struct pkgconf_client_ { pkgconf_list_t dir_list; @@ -200,6 +284,7 @@ struct pkgconf_client_ { pkgconf_list_t global_vars; + void *client_data; void *error_handler_data; void *warn_handler_data; void *trace_handler_data; @@ -208,6 +293,8 @@ struct pkgconf_client_ { pkgconf_error_handler_func_t warn_handler; pkgconf_error_handler_func_t trace_handler; + pkgconf_environ_lookup_handler_func_t environ_lookup_handler; + FILE *auditf; char *sysroot_dir; @@ -224,10 +311,20 @@ struct pkgconf_client_ { pkgconf_pkg_t **cache_table; size_t cache_count; + + pkgconf_unveil_handler_func_t unveil_handler; + + pkgconf_list_t preloaded_pkgs; + + pkgconf_output_t *output; + + const pkgconf_cross_personality_t *personality; + + pkgconf_buffer_t _scratch_buffer; }; struct pkgconf_cross_personality_ { - const char *name; + char *name; pkgconf_list_t dir_list; @@ -240,9 +337,52 @@ struct pkgconf_cross_personality_ { bool want_default_pure; }; +/* bytecode.c */ +static inline const pkgconf_bytecode_op_t * +pkgconf_bytecode_op_next(const pkgconf_bytecode_op_t *op) +{ + return (const pkgconf_bytecode_op_t *) + ((const uint8_t *)op + sizeof(*op) + op->size); +} + +typedef struct pkgconf_bytecode_eval_ctx_ { + const pkgconf_client_t *client; + const pkgconf_list_t *vars; + + pkgconf_buffer_t sysroot; + + size_t expansions; +} pkgconf_bytecode_eval_ctx_t; + +PKGCONF_API bool pkgconf_bytecode_eval(const pkgconf_client_t *client, const pkgconf_list_t *tuples, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot); +PKGCONF_API bool pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size); +PKGCONF_API bool pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n); +PKGCONF_API bool pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen); +PKGCONF_API bool pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf); +PKGCONF_API void pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf); +PKGCONF_API bool pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value); +PKGCONF_API bool pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot, pkgconf_buffer_t *out); +PKGCONF_API char *pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot); +PKGCONF_API pkgconf_variable_t *pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen); +PKGCONF_API bool pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key); +PKGCONF_API bool pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev); + +/* variable.c */ +PKGCONF_API pkgconf_variable_t *pkgconf_variable_new(const char *key); +PKGCONF_API void pkgconf_variable_free(pkgconf_variable_t *v); +PKGCONF_API pkgconf_variable_t *pkgconf_variable_find(const pkgconf_list_t *vars, const char *key); +PKGCONF_API pkgconf_variable_t *pkgconf_variable_get_or_create(pkgconf_list_t *vars, const char *key); +PKGCONF_API void pkgconf_variable_delete(pkgconf_list_t *vars, pkgconf_variable_t *v); +PKGCONF_API void pkgconf_variable_list_free(pkgconf_list_t *vars); +PKGCONF_API bool pkgconf_variable_eval(pkgconf_client_t *client, const pkgconf_list_t *tuples, const pkgconf_variable_t *v, pkgconf_buffer_t *out, bool *saw_sysroot); +PKGCONF_API char *pkgconf_variable_eval_str(pkgconf_client_t *client, const pkgconf_list_t *tuples, const pkgconf_variable_t *v, bool *saw_sysroot); +PKGCONF_API char *pkgconf_variable_eval_name(pkgconf_client_t *client, const pkgconf_list_t *vars, const char *varname); + /* client.c */ -PKGCONF_API void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality); -PKGCONF_API pkgconf_client_t * pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality); +PKGCONF_API void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler); +PKGCONF_API void pkgconf_client_init_with_options(pkgconf_client_t *client, const pkgconf_client_options_t *options); +PKGCONF_API pkgconf_client_t * pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler); +PKGCONF_API pkgconf_client_t * pkgconf_client_new_with_options(const pkgconf_client_options_t *options); PKGCONF_API void pkgconf_client_deinit(pkgconf_client_t *client); PKGCONF_API void pkgconf_client_free(pkgconf_client_t *client); PKGCONF_API const char *pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client); @@ -259,7 +399,14 @@ PKGCONF_API pkgconf_error_handler_func_t PKGCONF_API void pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data); PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_trace_handler(const pkgconf_client_t *client); PKGCONF_API void pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data); +PKGCONF_API pkgconf_unveil_handler_func_t pkgconf_client_get_unveil_handler(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_unveil_handler(pkgconf_client_t *client, pkgconf_unveil_handler_func_t unveil_handler); PKGCONF_API void pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality); +PKGCONF_API bool pkgconf_client_preload_one(pkgconf_client_t *client, pkgconf_pkg_t *pkg); +PKGCONF_API bool pkgconf_client_preload_path(pkgconf_client_t *client, const char *path); +PKGCONF_API bool pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env); +PKGCONF_API void pkgconf_client_set_output(pkgconf_client_t *client, pkgconf_output_t *output); +PKGCONF_API const char *pkgconf_client_getenv(const pkgconf_client_t *client, const char *key); /* personality.c */ PKGCONF_API pkgconf_cross_personality_t *pkgconf_cross_personality_default(void); @@ -278,7 +425,7 @@ PKGCONF_API void pkgconf_cross_personali #define PKGCONF_PKG_PKGF_SKIP_CONFLICTS 0x0020 #define PKGCONF_PKG_PKGF_NO_CACHE 0x0040 #define PKGCONF_PKG_PKGF_SKIP_ERRORS 0x0080 -#define PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE 0x0100 +/* 0x0100 reserved (was PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; now threaded as a traversal parameter) */ #define PKGCONF_PKG_PKGF_SKIP_PROVIDES 0x0200 #define PKGCONF_PKG_PKGF_REDEFINE_PREFIX 0x0400 #define PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS 0x0800 @@ -287,10 +434,13 @@ PKGCONF_API void pkgconf_cross_personali #define PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS 0x4000 #define PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES 0x8000 #define PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES 0x10000 +#define PKGCONF_PKG_PKGF_REQUIRE_INTERNAL 0x20000 +#define PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION 0x40000 #define PKGCONF_PKG_DEPF_INTERNAL 0x1 #define PKGCONF_PKG_DEPF_PRIVATE 0x2 #define PKGCONF_PKG_DEPF_QUERY 0x4 +#define PKGCONF_PKG_DEPF_SHARED 0x8 #define PKGCONF_PKG_ERRF_OK 0x0 #define PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND 0x1 @@ -298,21 +448,23 @@ PKGCONF_API void pkgconf_cross_personali #define PKGCONF_PKG_ERRF_PACKAGE_CONFLICT 0x4 #define PKGCONF_PKG_ERRF_DEPGRAPH_BREAK 0x8 -#if defined(__GNUC__) || defined(__INTEL_COMPILER) -#define PRINTFLIKE(fmtarg, firstvararg) \ - __attribute__((__format__ (__printf__, fmtarg, firstvararg))) -#define DEPRECATED \ - __attribute__((deprecated)) +#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +# define PRINTFLIKE(fmtarg, firstvararg) \ + __attribute__((__format__ (gnu_printf, fmtarg, firstvararg))) +#elif defined(__clang__) || defined(__INTEL_COMPILER) || __GNUC__ > 2 || (_GNUC__ == 2 && __GNUC_MINOR__ >= 5) +# define PRINTFLIKE(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #else -#define PRINTFLIKE(fmtarg, firstvararg) -#define DEPRECATED -#endif /* defined(__INTEL_COMPILER) || defined(__GNUC__) */ - -/* parser.c */ -typedef void (*pkgconf_parser_operand_func_t)(void *data, const size_t lineno, const char *key, const char *value); -typedef void (*pkgconf_parser_warn_func_t)(void *data, const char *fmt, ...); +# define PRINTFLIKE(fmtarg, firstvararg) +#endif -PKGCONF_API void pkgconf_parser_parse(FILE *f, void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, const char *filename); +#if defined(__clang__) || defined(__INTEL_COMPILER) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +# define DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define DEPRECATED __declspec(deprecated) +#else +# define DEPRECATED +#endif /* pkg.c */ PKGCONF_API bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3); @@ -323,11 +475,15 @@ PKGCONF_API bool pkgconf_default_error_h #ifndef PKGCONF_LITE #if defined(__GNUC__) || defined(__INTEL_COMPILER) #define PKGCONF_TRACE(client, ...) do { \ - pkgconf_trace(client, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__); \ + const pkgconf_client_t *pkgconf_trace_client_ = (client); \ + if (pkgconf_trace_client_ != NULL && pkgconf_trace_client_->trace_handler != NULL) \ + pkgconf_trace(pkgconf_trace_client_, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__); \ } while (0) #else #define PKGCONF_TRACE(client, ...) do { \ - pkgconf_trace(client, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + const pkgconf_client_t *pkgconf_trace_client_ = (client); \ + if (pkgconf_trace_client_ != NULL && pkgconf_trace_client_->trace_handler != NULL) \ + pkgconf_trace(pkgconf_trace_client_, __FILE__, __LINE__, __func__, __VA_ARGS__); \ } while (0) #endif #else @@ -339,19 +495,20 @@ PKGCONF_API void pkgconf_pkg_unref(pkgco PKGCONF_API void pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg); PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_find(pkgconf_client_t *client, const char *name); PKGCONF_API unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags); +PKGCONF_API unsigned int pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *deplist); PKGCONF_API unsigned int pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth); PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags); PKGCONF_API const char *pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep); PKGCONF_API unsigned int pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth); PKGCONF_API unsigned int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth); +PKGCONF_API unsigned int pkgconf_pkg_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth); PKGCONF_API pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name); -PKGCONF_API pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name); PKGCONF_API int pkgconf_compare_version(const char *a, const char *b); PKGCONF_API pkgconf_pkg_t *pkgconf_scan_all(pkgconf_client_t *client, void *ptr, pkgconf_pkg_iteration_func_t func); /* parse.c */ -PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *path, FILE *f, unsigned int flags); +PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *path, unsigned int flags); PKGCONF_API void pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags); PKGCONF_API void pkgconf_dependency_parse(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags); PKGCONF_API void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail); @@ -364,41 +521,70 @@ PKGCONF_API pkgconf_dependency_t *pkgcon /* argvsplit.c */ PKGCONF_API int pkgconf_argv_split(const char *src, int *argc, char ***argv); +PKGCONF_API int pkgconf_argv_split_raw(const char *src, int *argc, char ***argv); PKGCONF_API void pkgconf_argv_free(char **argv); /* fragment.c */ +typedef struct pkgconf_fragment_render_ctx_ { + const bool escape; + const char delim; +} pkgconf_fragment_render_ctx_t; + typedef struct pkgconf_fragment_render_ops_ { - size_t (*render_len)(const pkgconf_list_t *list, bool escape); - void (*render_buf)(const pkgconf_list_t *list, char *buf, size_t len, bool escape); + bool (*render)(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf); } pkgconf_fragment_render_ops_t; typedef bool (*pkgconf_fragment_filter_func_t)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data); -PKGCONF_API bool pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags); -PKGCONF_API void pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail); -PKGCONF_API void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags); + +/* A cursor accelerates repeated pkgconf_fragment_copy() calls into the same + * destination list by maintaining a sorted index of the fragments already + * present, so that the deduplication lookup is a bsearch() rather than a linear + * scan of the (potentially large) accumulator. + */ +typedef struct pkgconf_fragment_cursor_ { + pkgconf_list_t *list; + pkgconf_fragment_t **index; + size_t count; + size_t alloc; +} pkgconf_fragment_cursor_t; + +PKGCONF_API bool pkgconf_fragment_parse(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags); +PKGCONF_API void pkgconf_fragment_insert(pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail); +PKGCONF_API bool pkgconf_fragment_add(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *string, unsigned int flags); PKGCONF_API void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private); +PKGCONF_API void pkgconf_fragment_cursor_init(pkgconf_fragment_cursor_t *cursor, pkgconf_list_t *list); +PKGCONF_API void pkgconf_fragment_cursor_deinit(pkgconf_fragment_cursor_t *cursor); +PKGCONF_API void pkgconf_fragment_copy_cursor(const pkgconf_client_t *client, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private); PKGCONF_API void pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base); PKGCONF_API void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node); PKGCONF_API void pkgconf_fragment_free(pkgconf_list_t *list); PKGCONF_API void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data); -PKGCONF_API size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops); -PKGCONF_API void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t len, bool escape, const pkgconf_fragment_render_ops_t *ops); -PKGCONF_API char *pkgconf_fragment_render(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops); +PKGCONF_API void pkgconf_fragment_filter_splice(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data); +PKGCONF_API bool pkgconf_fragment_render_buf(const pkgconf_list_t *list, pkgconf_buffer_t *buf, bool escape, const pkgconf_fragment_render_ops_t *ops, char delim); PKGCONF_API bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag); +PKGCONF_API bool pkgconf_is_locale_utf8(void); + +/* license.c */ +PKGCONF_API bool pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base); +PKGCONF_API bool pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *expression, unsigned int flags); +PKGCONF_API bool pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends, unsigned int flags); +PKGCONF_API void pkgconf_license_free(pkgconf_list_t *list); +PKGCONF_API bool pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data); +PKGCONF_API bool pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkgconf_buffer_t *buf); /* tuple.c */ PKGCONF_API pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *parent, const char *key, const char *value, bool parse, unsigned int flags); -PKGCONF_API char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key); -PKGCONF_API char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *list, const char *value, unsigned int flags); +PKGCONF_API const char *pkgconf_tuple_find(pkgconf_client_t *client, pkgconf_list_t *list, const char *key); PKGCONF_API void pkgconf_tuple_free(pkgconf_list_t *list); PKGCONF_API void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list); PKGCONF_API void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value); -PKGCONF_API char *pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key); +PKGCONF_API const char *pkgconf_tuple_find_global(pkgconf_client_t *client, const char *key); PKGCONF_API void pkgconf_tuple_free_global(pkgconf_client_t *client); PKGCONF_API void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv); /* queue.c */ PKGCONF_API void pkgconf_queue_push(pkgconf_list_t *list, const char *package); +PKGCONF_API void pkgconf_queue_push_dependency(pkgconf_list_t *list, const pkgconf_dependency_t *dep); PKGCONF_API bool pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list); PKGCONF_API bool pkgconf_queue_solve(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_pkg_t *world, int maxdepth); PKGCONF_API void pkgconf_queue_free(pkgconf_list_t *list); @@ -421,31 +607,126 @@ PKGCONF_API void pkgconf_audit_log_depen PKGCONF_API void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter); PKGCONF_API void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist, bool filter); PKGCONF_API size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter); -PKGCONF_API size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter); +PKGCONF_API size_t pkgconf_path_build_from_environ(const pkgconf_client_t *client, const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter); +#ifdef _WIN32 +PKGCONF_API size_t pkgconf_path_build_from_registry(pkgconf_client_t *client, /* HKEY -> HANDLE -> PVOID */ void *hKey, pkgconf_list_t *dirlist, bool filter); +#endif PKGCONF_API bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist); PKGCONF_API void pkgconf_path_free(pkgconf_list_t *dirlist); -PKGCONF_API bool pkgconf_path_relocate(char *buf, size_t buflen); +PKGCONF_API bool pkgconf_path_relocate(pkgconf_buffer_t *buf); +PKGCONF_API void pkgconf_path_normalize_separators(char *path); PKGCONF_API void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src); +PKGCONF_API void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src); +PKGCONF_API bool pkgconf_path_is_plausible(const pkgconf_buffer_t *buf); /* buffer.c */ -typedef struct pkgconf_buffer_ { - char *base; - char *end; -} pkgconf_buffer_t; +struct pkgconf_span_ { + unsigned char lo; + unsigned char hi; /* inclusive */ +}; + +static inline bool pkgconf_span_contains(unsigned char c, const pkgconf_span_t *spans, size_t nspans) { + for (size_t i = 0; i < nspans; i++) + if (c >= spans[i].lo && c <= spans[i].hi) + return true; + + return false; +} + +/* A set of byte values as a 256-bit map, so that membership is a single test + * rather than a walk over a span list. Build one with + * pkgconf_charset_from_spans() and keep it: the point is to hoist the span + * walk out of any per-byte loop. */ +typedef struct pkgconf_charset_ { + uint64_t words[4]; +} pkgconf_charset_t; + +static inline bool pkgconf_charset_contains(const pkgconf_charset_t *charset, unsigned char c) +{ + return (charset->words[c >> 6] >> (c & 63)) & 1; +} + +PKGCONF_API void pkgconf_charset_from_spans(pkgconf_charset_t *charset, const pkgconf_span_t *spans, size_t nspans); -PKGCONF_API void pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text); -PKGCONF_API void pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte); -PKGCONF_API void pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer); +PKGCONF_API bool pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text); +PKGCONF_API bool pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n); +PKGCONF_API bool pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...) PRINTFLIKE(2, 3); +PKGCONF_API bool pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list va) PRINTFLIKE(2, 0); +PKGCONF_API bool pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text); +PKGCONF_API bool pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte); +PKGCONF_API bool pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer); PKGCONF_API void pkgconf_buffer_finalize(pkgconf_buffer_t *buffer); -static inline const char *pkgconf_buffer_str(const pkgconf_buffer_t *buffer) { +PKGCONF_API bool pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out); +PKGCONF_API bool pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list va); +PKGCONF_API bool pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...); +PKGCONF_API bool pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix); +PKGCONF_API bool pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle); +PKGCONF_API bool pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle); +PKGCONF_API bool pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle); +PKGCONF_API bool pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value); +PKGCONF_API bool pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans); +PKGCONF_API bool pkgconf_buffer_escape_charset(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_charset_t *charset); + +/* + * !doc + * + * .. c:function:: static inline const char *pkgconf_buffer_str(const pkgconf_buffer_t *buffer) + * + * Get the underlying string from the buffer. This may return :code:`NULL`. + * + * :param const pkgconf_buffer_t *buffer: The buffer to get the string from. + * :return: The underlying string. + */ +static inline const char *pkgconf_buffer_str(const pkgconf_buffer_t *buffer) +{ return buffer->base; } -static inline size_t pkgconf_buffer_len(const pkgconf_buffer_t *buffer) { +/* + * !doc + * + * .. c:function:: static inline const char *pkgconf_buffer_str_or_empty(const pkgconf_buffer_t *buffer) + * + * Get the underlying string from the buffer, or the empty string if :code:`NULL`. + * + * :param const pkgconf_buffer_t *buffer: The buffer to get the string from. + * :return: The underlying string, or the empty string. + */ +static inline const char *pkgconf_buffer_str_or_empty(const pkgconf_buffer_t *buffer) +{ + return buffer->base != NULL ? buffer->base : ""; +} + +/* + * !doc + * + * .. c:function:: static inline size_t *pkgconf_buffer_len(const pkgconf_buffer_t *buffer) + * + * Get the underlying raw buffer length. + * + * :param const pkgconf_buffer_t *buffer: The buffer to check the length of. + * :return: The size of the underlying buffer. + */ +static inline size_t pkgconf_buffer_len(const pkgconf_buffer_t *buffer) +{ + if (buffer->base == NULL) + return 0; + return (size_t)(ptrdiff_t)(buffer->end - buffer->base); } -static inline char pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer) { +/* + * !doc + * + * .. c:function:: static inline const char *pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer) + * + * Get the last character from the buffer. If the buffer is empty, return :code:`'\0'`. + * + * :param pkgconf_buffer_t *buffer: The buffer to get the last character from. + * :return: The last character, or '\0' if the string is empty. + */ +static inline char pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer) +{ if (buffer->base == buffer->end) return '\0'; @@ -453,14 +734,140 @@ static inline char pkgconf_buffer_lastc( } #define PKGCONF_BUFFER_INITIALIZER { NULL, NULL } +#define PKGCONF_BUFFER_FROM_STR(str) &(const pkgconf_buffer_t){ .base = str, .end = ((str) ? &(str)[strlen(str)] : (str)) } +#define PKGCONF_BUFFER_FROM_STR_NONNULL(str) &(const pkgconf_buffer_t){ .base = str, .end = &(str)[strlen(str)] } -static inline void pkgconf_buffer_reset(pkgconf_buffer_t *buffer) { +/* + * !doc + * + * .. c:function:: static inline void pkgconf_buffer_reset(const pkgconf_buffer_t *buffer) + * + * Reset the underlying buffer, freeing any existing string. + * + * :param pkgconf_buffer_t *buffer: The buffer to reset. + * :return: nothing + */ +static inline void pkgconf_buffer_reset(pkgconf_buffer_t *buffer) +{ pkgconf_buffer_finalize(buffer); buffer->base = buffer->end = NULL; } +/* + * !doc + * + * .. c:function:: static inline void pkgconf_buffer_rewind(pkgconf_buffer_t *buffer) + * + * Truncate the buffer to empty while retaining its allocation, so it can be + * refilled without reallocating. Unlike pkgconf_buffer_reset(), the backing + * storage is kept. + * + * :param pkgconf_buffer_t *buffer: The buffer to rewind. + * :return: nothing + */ +static inline void pkgconf_buffer_rewind(pkgconf_buffer_t *buffer) +{ + if (buffer->base != NULL) + { + buffer->end = buffer->base; + *buffer->base = '\0'; + } +} + +/* + * !doc + * + * .. c:function:: static inline char *pkgconf_buffer_freeze(pkgconf_buffer_t *buffer) + * + * Hand ownership of the underlying storage to the caller as a string and + * empty the buffer. The string must be freed by the caller. + * + * :param pkgconf_buffer_t *buffer: The buffer to freeze. + * :return: The underlying string. + */ +static inline char *pkgconf_buffer_freeze(pkgconf_buffer_t *buffer) +{ + char *out = buffer->base; + + buffer->base = buffer->end = NULL; + + return out; +} + +/* + * !doc + * + * .. c:function:: static inline bool pkgconf_buffer_copy(pkgconf_buffer_t *buffer, pkgconf_buffer_t *newptr) + * + * Copy the contents of one buffer to another, erasing the contents of the buffer in :code:`newptr`. + * The source and destination buffers must not overlap. + * + * :param pkgconf_buffer_t *buffer: The buffer to copy from. + * :param pkgconf_buffer_t *buffer: The buffer to copy to. + * :return: :code:`true` on success, :code:`false` on failure. + */ +static inline bool pkgconf_buffer_copy(pkgconf_buffer_t *buffer, pkgconf_buffer_t *newptr) +{ + if (buffer == newptr || (buffer->base != NULL && buffer->base == newptr->base)) + return false; + + pkgconf_buffer_reset(newptr); + return pkgconf_buffer_append_slice(newptr, pkgconf_buffer_str(buffer), pkgconf_buffer_len(buffer)); +} + +/* + * !doc + * + * .. c:function:: static inline bool pkgconf_str_eq_slice(const char *s, const char *p, size_t n) + * + * Compare :code:`s` to :code:`p`, which must be :code:`n` size long. + * + * :param const char *s: string to search + * :param const char *p: string to search for + * :param size_t n: length of :code:`s` + * :return: :code:`true` if equal, :code:`false` if not. + */ +static inline bool pkgconf_str_eq_slice(const char *s, const char *p, size_t n) +{ + return s != NULL && + strncmp(s, p, n) == 0 && + s[n] == '\0'; +} + +/* bufferset.c */ +PKGCONF_API pkgconf_bufferset_t *pkgconf_bufferset_extend(pkgconf_list_t *list, pkgconf_buffer_t *buffer); +PKGCONF_API void pkgconf_bufferset_free(pkgconf_list_t *list); + /* fileio.c */ PKGCONF_API bool pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream); + +/* parser.c */ +typedef void (*pkgconf_parser_operand_func_t)(void *data, const char *warnprefix, const char *key, const char *value); +typedef void (*pkgconf_parser_warn_func_t)(void *data, const char *fmt, ...); + +PKGCONF_API void pkgconf_parser_parse_buffer(void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, pkgconf_buffer_t *buffer, const char *warnprefix); +PKGCONF_API void pkgconf_parser_parse(FILE *f, void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, const char *filename); + +/* output.c */ +typedef enum { + PKGCONF_OUTPUT_STDOUT, + PKGCONF_OUTPUT_STDERR, +} pkgconf_output_stream_t; + +struct pkgconf_output_ { + void *privdata; + + bool (*write)(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer); +}; + +PKGCONF_API bool pkgconf_output_putbuf(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer, bool newline); +PKGCONF_API bool pkgconf_output_puts(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *str); +PKGCONF_API bool pkgconf_output_fmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, ...) PRINTFLIKE(3,4); +PKGCONF_API bool pkgconf_output_vfmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, va_list va_src) PRINTFLIKE(3,0); +PKGCONF_API pkgconf_output_t *pkgconf_output_default(void); + +PKGCONF_API bool pkgconf_output_file_vfmt(FILE *f, const char *fmt, va_list va) PRINTFLIKE(2,0); +PKGCONF_API bool pkgconf_output_file_fmt(FILE *f, const char *fmt, ...) PRINTFLIKE(2,3); #ifdef __cplusplus } Index: libpkgconf/license.c =================================================================== RCS file: libpkgconf/license.c diff -N libpkgconf/license.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/license.c 4 Aug 2026 16:20:54 -0000 @@ -0,0 +1,373 @@ +/* + * license.c + * Evaluate SPDX license expressions + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +static bool license_sanitize_string(const char *s, pkgconf_buffer_t *buf) +{ + unsigned int i = 0; + /* + * Allowed chars are: + * - a-z + * - 0-9 + * - chars '-', '+', '.', '(' and ')' + */ + for (i = 0; i < strlen(s); i ++) + { + if (isalnum((unsigned char) s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '(' || s[i] == ')' || s[i] == '.' || s[i] == ':') + { + if (!pkgconf_buffer_push_byte(buf, s[i])) + return false; + } + } + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) + * + * Copies a `license list` to another `license list` + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The list the fragments are being added to. + * :param pkgconf_list_t* base: The list the fragments are being copied from. + * :return: true on success, false on allocation failure + */ +bool +pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) +{ + pkgconf_node_t *node; + (void) client; + + PKGCONF_FOREACH_LIST_ENTRY(base->head, node) + { + pkgconf_license_t *license = node->data; + pkgconf_license_t *cpy_license = calloc(1, sizeof(pkgconf_license_t)); + + if (cpy_license == NULL) + return false; + + cpy_license->type = license->type; + + if (license->data != NULL) + { + cpy_license->data = strdup(license->data); + if (cpy_license->data == NULL) + { + free(cpy_license); + return false; + } + } + + pkgconf_node_insert_tail(&cpy_license->iter, cpy_license, list); + } + + return true; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_license_free(pkgconf_list_t *list) + * + * Delete an entire `license list`. + * + * :param pkgconf_list_t* list: The `license list` to delete. + * :return: nothing + */ +void +pkgconf_license_free(pkgconf_list_t *list) +{ + if (!list) + return; + + pkgconf_node_t *node, *next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) + { + pkgconf_license_t *license = node->data; + free(license->data); + free(license); + } + + pkgconf_list_zero(list); +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data) + * + * Adds a `license` of text to a `license list` directly without interpreting it. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The fragment list. + * :param char type: The type of the license. + * :param char* data: The data of the license + * :return: true on success, false on allocation failure + */ +bool +pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data) +{ + pkgconf_license_t *license; + + if (data == NULL) + return false; + + license = calloc(1, sizeof(pkgconf_license_t)); + if (!license) + { + pkgconf_error(client, "pkgconf_license_insert: out of memory"); + return false; + } + license->type = type; + license->data = strdup(data); + if (license->data == NULL) + { + pkgconf_error(client, "pkgconf_license_insert: out of memory"); + free(license); + return false; + } + + pkgconf_node_insert_tail(&license->iter, license, list); + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags) + * + * Evaluates SPDX expression strings like: + * - BSD-3-Clause + * - LGPL-2.1-only OR MIT + * - LGPL-2.1-only OR MIT OR BSD-3-Clause + * - ISC AND (BSD-3-Clause AND BSD-2-Clause) + * + * Function parses and sanitizes license strings. Also adding multiple + * 'License:'-keys is supported like: + * License: BSD-3-Clause + * License: BSD-2-Clause + * + * Which will evaluate to: BSD-3-Clause AND BSD-2-Clause + * + * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + * :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes. + * :param char* expression: The dependency data to parse. + * :param uint flags: Any flags to attach to the dependency nodes. + * :return: true on success, false on allocation failure + */ +bool +pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags) +{ + pkgconf_buffer_t out_buffer = PKGCONF_BUFFER_INITIALIZER; + size_t buf_size = 0; + char *cur_word = NULL; + int i, ret, argc; + char **argv; + size_t string_len = 0; + + (void)flags; + + if (expression == NULL) + return true; + + buf_size = strlen(expression) + 1; + ret = pkgconf_argv_split(expression, &argc, &argv); + if (ret) + return true; + + /* This is not the first License: + * so add AND + */ + if (license_list->head) + { + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND")) + goto fail; + } + + i = 0; + while (i < argc) + { + string_len = strnlen(argv[i], buf_size); + cur_word = argv[i]; + if (string_len >= 1) + { + if (!license_sanitize_string(cur_word, &out_buffer)) + goto fail; + + if (!pkgconf_buffer_len(&out_buffer)) + { + i ++; + pkgconf_buffer_finalize(&out_buffer); + continue; + } + + cur_word = (char *)pkgconf_buffer_str(&out_buffer); + size_t cur_len = strnlen(cur_word, buf_size); + if (!cur_len) + { + i ++; + pkgconf_buffer_finalize(&out_buffer); + continue; + } + + if (cur_word[0] == '(') + { + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_OPEN, "(")) + goto fail; + /* If there is more after '(' like '(BSD-2-Clause' + * Then append rest to fragments as license. + * This is expression like GPL-2.0-only OR (BSD-2-Clause AND ISC) + */ + if (cur_len >= 2) + { + cur_word ++; + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word)) + goto fail; + } + } + else if (cur_word[cur_len - 1] == ')') + { + if (cur_len >= 2) + { + cur_word[cur_len - 1] = 0x00; + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word)) + goto fail; + } + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_CLOSE, ")")) + goto fail; + } + else if (!strcasecmp(cur_word, "and")) + { + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND")) + goto fail; + } + else if (!strcasecmp(cur_word, "or")) + { + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_OR, "OR")) + goto fail; + } + else if (!strcasecmp(cur_word, "with")) + { + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_WITH, "WITH")) + goto fail; + } + else + { + if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word)) + goto fail; + } + pkgconf_buffer_finalize(&out_buffer); + } + i++; + } + + pkgconf_argv_free(argv); + return true; + +fail: + pkgconf_buffer_finalize(&out_buffer); + pkgconf_argv_free(argv); + return false; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags) + * + * Evaluates SPDX expression strings like: + * - BSD-3-Clause + * - LGPL-2.1-only OR MIT + * - LGPL-2.1-only OR MIT OR BSD-3-Clause + * - ISC AND (BSD-3-Clause AND BSD-2-Clause) + * + * Function parses and sanitizes license strings. Also adding multiple + * 'License:'-keys is supported like: + * License: BSD-3-Clause + * License: BSD-2-Clause + * + * Which will evaluate to: BSD-3-Clause AND BSD-2-Clause + * + * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + * :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes. + * :param char* expression: The dependency data to parse. + * :param uint flags: Any flags to attach to the dependency nodes. + * :return: true on success, false on allocation failure + */ +bool +pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags) +{ + char *license_expression = pkgconf_bytecode_eval_str(client, &pkg->vars, license_str, NULL); + if (license_expression == NULL) + return false; + + bool ret = pkgconf_license_evaluate_str(client, license_list, license_expression, flags); + free(license_expression); + return ret; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends) + * + * Renders license fragments back to SPDX expression string. Tries + * to keep output as close as possible to original input + * + * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + * :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes. + * :param char* depends: The dependency data to parse. + * :param uint flags: Any flags to attach to the dependency nodes. + * :return: nothing + */ +bool +pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkgconf_buffer_t *buf) +{ + const pkgconf_buffer_t *frag_string = NULL; + pkgconf_node_t *node; + bool is_delim = true; + + (void)client; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + pkgconf_license_t *license = node->data; + is_delim = true; + frag_string = PKGCONF_BUFFER_FROM_STR(license->data); + if (!pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(frag_string))) + return false; + + if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next->data)->type == PKGCONF_LICENSE_BRACKET_CLOSE)) + { + is_delim = false; + } + + if (node->next != NULL && is_delim) + { + if (!pkgconf_buffer_push_byte(buf, ' ')) + return false; + } + } + + return true; +} Index: libpkgconf/output.c =================================================================== RCS file: libpkgconf/output.c diff -N libpkgconf/output.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/output.c 4 Aug 2026 16:20:54 -0000 @@ -0,0 +1,163 @@ +/* + * output.c + * I/O abstraction layer + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +bool +pkgconf_output_putbuf(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer, bool newline) +{ + bool ret; + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + if (pkgconf_buffer_len(buffer) != 0 && + !pkgconf_buffer_append(&buf, pkgconf_buffer_str(buffer))) + return false; + + if (newline && !pkgconf_buffer_push_byte(&buf, '\n')) + { + pkgconf_buffer_finalize(&buf); + return false; + } + + ret = output->write(output, stream, &buf); + pkgconf_buffer_finalize(&buf); + + return ret; +} + +bool +pkgconf_output_puts(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *str) +{ + bool ret; + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + if (!pkgconf_buffer_append(&buf, str) || + !pkgconf_buffer_push_byte(&buf, '\n')) + { + pkgconf_buffer_finalize(&buf); + return false; + } + + ret = output->write(output, stream, &buf); + pkgconf_buffer_finalize(&buf); + + return ret; +} + +bool +pkgconf_output_fmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, ...) +{ + bool ret; + va_list va; + + va_start(va, fmt); + ret = pkgconf_output_vfmt(output, stream, fmt, va); + va_end(va); + + return ret; +} + +bool +pkgconf_output_vfmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, va_list src_va) +{ + va_list va; + bool ret; + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + va_copy(va, src_va); + ret = pkgconf_buffer_append_vfmt(&buf, fmt, va); + va_end(va); + + if (ret) + ret = output->write(output, stream, &buf); + pkgconf_buffer_finalize(&buf); + + return ret; +} + +static bool +pkgconf_output_stdio_write(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer) +{ + (void) output; + + FILE *target = stream == PKGCONF_OUTPUT_STDERR ? stderr : stdout; + + if (buffer != NULL) + { + const char *str = pkgconf_buffer_str(buffer); + size_t size = pkgconf_buffer_len(buffer); + + if (size > 0 && !fwrite(str, size, 1, target)) + return false; + } + + fflush(target); + return true; +} + +static pkgconf_output_t pkgconf_default_output = { + .privdata = NULL, + .write = pkgconf_output_stdio_write, +}; + +pkgconf_output_t * +pkgconf_output_default(void) +{ + return &pkgconf_default_output; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_output_file_vfmt(FILE *f, const char *fmt, va_list va) + * + * Wrapper around :code:`vfprintf` that returns a boolean. + * + * :param FILE *f: Pointer to an open `FILE` pointer. + * :param: const char *fmt: Format string. + * :param va_list va: Variable list. + * :return: :code:`true` on success, :code:`false` on failure. + */ +bool +pkgconf_output_file_vfmt(FILE *f, const char *fmt, va_list va) +{ + int ret = vfprintf(f, fmt, va); + return ret >= 0; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_output_file_fmt(FILE *f, const char *fmt, va_list va) + * + * Wrapper around :code:`fprintf` that returns a boolean. + * + * :param FILE *f: Pointer to an open `FILE` pointer. + * :param: const char *fmt: Format string. + * :return: :code:`true` on success, :code:`false` on failure. + */ +bool +pkgconf_output_file_fmt(FILE *f, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + bool ret = pkgconf_output_file_vfmt(f, fmt, va); + va_end(va); + + return ret; +} Index: libpkgconf/parser.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/parser.c,v diff -u -p -r1.1.1.1 parser.c --- libpkgconf/parser.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/parser.c 4 Aug 2026 16:30:47 -0000 @@ -2,7 +2,9 @@ * parser.c * rfc822 message parser * - * Copyright (c) 2018 pkgconf authors (see AUTHORS). + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2018, 2025 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,100 +19,134 @@ #include #include -/* - * !doc - * - * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_file(const pkgconf_client_t *client, const char *filename, FILE *f) - * - * Parse a .pc file into a pkgconf_pkg_t object structure. - * - * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. - * :param char* filename: The filename of the package file (including full path). - * :param FILE* f: The file object to read from. - * :returns: A ``pkgconf_pkg_t`` object which contains the package data. - * :rtype: pkgconf_pkg_t * - */ -void -pkgconf_parser_parse(FILE *f, void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, const char *filename) +static void +pkgconf_parser_canonicalize_line(pkgconf_buffer_t *buffer) { - pkgconf_buffer_t readbuf = PKGCONF_BUFFER_INITIALIZER; - size_t lineno = 0; - bool continue_reading = true; - - while (continue_reading) - { - char op, *p, *key, *value; - bool warned_key_whitespace = false, warned_value_whitespace = false; - - continue_reading = pkgconf_fgetline(&readbuf, f); - lineno++; + bool escaped = false; + char *buf = buffer->base; + char *src = buf, *dst = buf; - p = readbuf.base; - if (p == NULL) - continue; - while (*p && isspace((unsigned char)*p)) - p++; - if (*p && p != readbuf.base) - { - warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: whitespace encountered while parsing key section\n", - filename, lineno); - warned_key_whitespace = true; - } - key = p; - while (*p && (isalpha((unsigned char)*p) || isdigit((unsigned char)*p) || *p == '_' || *p == '.')) - p++; + if (buf == NULL) + return; - if (!isalpha((unsigned char)*key) && - !isdigit((unsigned char)*p)) + for (; *src != '\0'; src++) + { + if (*src == '\\' && !escaped) { - pkgconf_buffer_reset(&readbuf); + escaped = true; continue; } - while (*p && isspace((unsigned char)*p)) + if (*src == '#' && !escaped) + break; + + if (escaped) { - if (!warned_key_whitespace) + if (*src == '#') { - warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: whitespace encountered while parsing key section\n", - filename, lineno); - warned_key_whitespace = true; + *dst++ = '#'; + escaped = false; + continue; } - /* set to null to avoid trailing spaces in key */ - *p = '\0'; - p++; + *dst++ = '\\'; + escaped = false; } - op = *p; - if (*p != '\0') + *dst++ = *src; + } + + if (escaped) + *dst++ = '\\'; + + *dst = '\0'; + buffer->end = dst; +} + +void +pkgconf_parser_parse_buffer(void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, pkgconf_buffer_t *buffer, const char *warnprefix) +{ + char op, *p, *key, *value; + size_t vallen; + + pkgconf_parser_canonicalize_line(buffer); + + p = buffer->base; + if (p == NULL) + return; + while (*p && isspace((unsigned char)*p)) + p++; + if (*p && p != buffer->base) + { + warnfunc(data, "%s: warning: whitespace encountered while parsing key section\n", + warnprefix); + } + key = p; + while (*p && (isalpha((unsigned char)*p) || isdigit((unsigned char)*p) || *p == '_' || *p == '.')) + p++; + + if (!isalpha((unsigned char)*key) && !isdigit((unsigned char)*key)) + return; + + while (*p && isspace((unsigned char)*p)) + { + warnfunc(data, "%s: warning: whitespace encountered while parsing key section\n", + warnprefix); + + /* set to null to avoid trailing spaces in key */ + *p = '\0'; + p++; + } + + op = *p; + if (*p != '\0') + { + *p = '\0'; + p++; + } + + while (*p && isspace((unsigned char)*p)) + p++; + + value = p; + vallen = strlen(value); + if (vallen) + p = value + (vallen - 1); + + while (*p && isspace((unsigned char) *p) && p > value) + { + if (op == '=') { - *p = '\0'; - p++; + warnfunc(data, "%s: warning: trailing whitespace encountered while parsing value section\n", + warnprefix); } - while (*p && isspace((unsigned char)*p)) - p++; + *p = '\0'; + p--; + } - value = p; - p = value + (strlen(value) - 1); - while (*p && isspace((unsigned char) *p) && p > value) - { - if (!warned_value_whitespace && op == '=') - { - warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: trailing whitespace encountered while parsing value section\n", - filename, lineno); - warned_value_whitespace = true; - } + if (ops[(unsigned char) op]) + ops[(unsigned char) op](data, warnprefix, key, value); +} - *p = '\0'; - p--; - } - if (ops[(unsigned char) op]) - ops[(unsigned char) op](data, lineno, key, value); +void +pkgconf_parser_parse(FILE *f, void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, const char *filename) +{ + pkgconf_buffer_t readbuf = PKGCONF_BUFFER_INITIALIZER; + size_t lineno = 0; + bool continue_reading = true; + + while (continue_reading) + { + char warnprefix[PKGCONF_ITEM_SIZE]; + + continue_reading = pkgconf_fgetline(&readbuf, f); + lineno++; - pkgconf_buffer_reset(&readbuf); + snprintf(warnprefix, sizeof warnprefix, "%s:" SIZE_FMT_SPECIFIER, filename, lineno); + pkgconf_parser_parse_buffer(data, ops, warnfunc, &readbuf, warnprefix); + pkgconf_buffer_rewind(&readbuf); } - fclose(f); pkgconf_buffer_finalize(&readbuf); } Index: libpkgconf/path.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/path.c,v diff -u -p -r1.1.1.1 path.c --- libpkgconf/path.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/path.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * path.c * filesystem path management * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2016 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -16,6 +18,7 @@ #include #include #include +#include #if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32) # include @@ -24,9 +27,9 @@ static bool #ifdef PKGCONF_CACHE_INODES -path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st) +path_list_contains_entry(const pkgconf_buffer_t *text, pkgconf_list_t *dirlist, struct stat *st) #else -path_list_contains_entry(const char *text, pkgconf_list_t *dirlist) +path_list_contains_entry(const pkgconf_buffer_t *text, pkgconf_list_t *dirlist) #endif { pkgconf_node_t *n; @@ -40,7 +43,7 @@ path_list_contains_entry(const char *tex return true; #endif - if (!strcmp(text, pn->path)) + if (!strcmp(pkgconf_buffer_str(text), pn->path)) return true; } @@ -62,39 +65,71 @@ static pkgconf_path_t * prepare_path_node(const char *text, pkgconf_list_t *dirlist, bool filter) { pkgconf_path_t *node; - char path[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; + + if (!pkgconf_buffer_append(&pathbuf, text)) + return NULL; - pkgconf_strlcpy(path, text, sizeof path); - pkgconf_path_relocate(path, sizeof path); + if (!pkgconf_path_relocate(&pathbuf)) + { + pkgconf_buffer_finalize(&pathbuf); + return NULL; + } #ifdef PKGCONF_CACHE_INODES struct stat st; if (filter) { - if (lstat(path, &st) == -1) + if (lstat(pkgconf_buffer_str(&pathbuf), &st) == -1) + { + pkgconf_buffer_finalize(&pathbuf); return NULL; + } + if (S_ISLNK(st.st_mode)) { - char pathbuf[PKGCONF_ITEM_SIZE * 4]; - char *linkdest = realpath(path, pathbuf); + char realpathbuf[PKGCONF_ITEM_SIZE * 4]; + char *linkdest = realpath(pkgconf_buffer_str(&pathbuf), realpathbuf); if (linkdest != NULL && stat(linkdest, &st) == -1) + { + pkgconf_buffer_finalize(&pathbuf); return NULL; + } } - if (path_list_contains_entry(path, dirlist, &st)) + + if (path_list_contains_entry(&pathbuf, dirlist, &st)) + { + pkgconf_buffer_finalize(&pathbuf); return NULL; + } } #else - if (filter && path_list_contains_entry(path, dirlist)) + if (filter && path_list_contains_entry(&pathbuf, dirlist)) + { + pkgconf_buffer_finalize(&pathbuf); return NULL; + } #endif node = calloc(1, sizeof(pkgconf_path_t)); - node->path = strdup(path); + if (node == NULL) + { + pkgconf_buffer_finalize(&pathbuf); + return NULL; + } + + node->path = pkgconf_buffer_freeze(&pathbuf); + if (node->path == NULL) + { + free(node); + return NULL; + } #ifdef PKGCONF_CACHE_INODES - if (filter) { + if (filter) + { node->handle_path = (void *)(intptr_t) st.st_ino; node->handle_device = (void *)(intptr_t) st.st_dev; } @@ -170,6 +205,9 @@ pkgconf_path_split(const char *text, pkg return 0; iter = workbuf = strdup(text); + if (workbuf == NULL) + return 0; + while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL) { pkgconf_path_add(p, dirlist, filter); @@ -189,6 +227,7 @@ pkgconf_path_split(const char *text, pkg * Adds the paths specified in an environment variable to a path list. If the environment variable is not set, * an optional default set of paths is added. * + * :param pkgconf_client_t* client: The client to use for environmental variable lookup (can be NULL). * :param char* envvarname: The environment variable to look up. * :param char* fallback: The fallback paths to use if the environment variable is not set. * :param pkgconf_list_t* dirlist: The path list to add the path nodes to. @@ -197,11 +236,11 @@ pkgconf_path_split(const char *text, pkg * :rtype: size_t */ size_t -pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter) +pkgconf_path_build_from_environ(const pkgconf_client_t *client, const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter) { const char *data; - data = getenv(envvarname); + data = pkgconf_client_getenv(client, envvarname); if (data != NULL) return pkgconf_path_split(data, dirlist, filter); @@ -228,21 +267,38 @@ bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) { pkgconf_node_t *n = NULL; - char relocated[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t relocated = PKGCONF_BUFFER_INITIALIZER; const char *cpath = path; - pkgconf_strlcpy(relocated, path, sizeof relocated); - if (pkgconf_path_relocate(relocated, sizeof relocated)) - cpath = relocated; + if (path == NULL) + return false; + + if (!pkgconf_buffer_append(&relocated, path)) + return false; + + cpath = pkgconf_buffer_str(&relocated); + + if (pkgconf_path_relocate(&relocated)) + cpath = pkgconf_buffer_str(&relocated); + + if (cpath == NULL) + { + pkgconf_buffer_finalize(&relocated); + return false; + } PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) { pkgconf_path_t *pnode = n->data; if (!strcmp(pnode->path, cpath)) + { + pkgconf_buffer_finalize(&relocated); return true; + } } + pkgconf_buffer_finalize(&relocated); return false; } @@ -267,7 +323,15 @@ pkgconf_path_copy_list(pkgconf_list_t *d pkgconf_path_t *srcpath = n->data, *path; path = calloc(1, sizeof(pkgconf_path_t)); + if (path == NULL) + continue; + path->path = strdup(srcpath->path); + if (path->path == NULL) + { + free(path); + continue; + } #ifdef PKGCONF_CACHE_INODES path->handle_path = srcpath->handle_path; @@ -281,6 +345,46 @@ pkgconf_path_copy_list(pkgconf_list_t *d /* * !doc * + * .. c:function:: void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) + * + * Copies a path list to another path list. + * + * :param pkgconf_list_t* dst: The path list to copy to. + * :param pkgconf_list_t* src: The path list to copy from. + * :return: nothing + */ +void +pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(src->head, n) + { + pkgconf_path_t *srcpath = n->data, *path; + + path = calloc(1, sizeof(pkgconf_path_t)); + if (path == NULL) + continue; + + path->path = strdup(srcpath->path); + if (path->path == NULL) + { + free(path); + continue; + } + +#ifdef PKGCONF_CACHE_INODES + path->handle_path = srcpath->handle_path; + path->handle_device = srcpath->handle_device; +#endif + + pkgconf_node_insert(&path->lnode, path, dst); + } +} + +/* + * !doc + * * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist) * * Releases any path nodes attached to the given path list. @@ -304,62 +408,242 @@ pkgconf_path_free(pkgconf_list_t *dirlis pkgconf_list_zero(dirlist); } -static char * -normpath(const char *path) +/* + * !doc + * + * .. c:function:: void pkgconf_path_normalize_separators(char *path) + * + * Rewrites a real OS path into pkgconf's canonical internal form, in which + * the platform directory separator is always represented as ``/``. On POSIX + * this is a no-op (a backslash is an ordinary filename character); on Windows + * it maps ``\`` to ``/``. Runtime-injected paths (search paths, sysroot, + * build root, prefix, pcfiledir) are normalized this way so that later path + * comparisons -- sysroot injection and system-directory filtering -- are all + * made on a consistent basis. + * + * :param char* path: The path to normalize in place. + * :return: nothing + */ +void +pkgconf_path_normalize_separators(char *path) { - if (!path) - return NULL; - - char *copy = strdup(path); - if (NULL == copy) - return NULL; - char *ptr = copy; +#ifdef _WIN32 + for (; *path != '\0'; path++) + if (*path == PKG_DIR_SEP_S) + *path = '/'; +#else + (void) path; +#endif +} - for (int ii = 0; copy[ii]; ii++) +/* + * !doc + * + * .. c:function:: bool pkgconf_path_relocate(pkgconf_buffer_t *buf) + * + * Normalizes a path in place: folds the platform separator to '/' and + * collapses runs of '/'. + * + * :param pkgconf_buffer_t* buf: The path to relocate. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_path_relocate(pkgconf_buffer_t *buf) +{ + char *base = buf->base; + char *w; + const char *r; + + if (base == NULL || pkgconf_buffer_len(buf) == 0) + return true; + + /* fold the platform separator to '/' before collapsing runs, so that a + * mix of '\' and '/' (as seen on Windows) is treated uniformly. */ + pkgconf_path_normalize_separators(base); + + /* collapse runs of '/' in place; the result never grows, so w <= r always */ + w = base; + for (r = base; *r != '\0'; r++) { - *ptr++ = path[ii]; - if ('/' == path[ii]) - { - ii++; - while ('/' == path[ii]) - ii++; - ii--; - } + *w++ = *r; + if (*r == '/') + while (r[1] == '/') + r++; } - *ptr = '\0'; - return copy; + *w = '\0'; + buf->end = w; + + return true; } /* * !doc * - * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen) + * .. c:function:: bool pkgconf_path_trim_basename(pkgconf_buffer_t *buf) * - * Relocates a path, possibly calling normpath() on it. + * Trims the basename from a path. * - * :param char* buf: The path to relocate. - * :param size_t buflen: The buffer length the path is contained in. - * :return: true on success, false on error + * :param pkgconf_buffer_t* buf: The path to trim. + * :return: true if a separator was found and the path was trimmed, false otherwise * :rtype: bool */ bool -pkgconf_path_relocate(char *buf, size_t buflen) +pkgconf_path_trim_basename(pkgconf_buffer_t *buf) +{ + char *sep; + + if (!pkgconf_buffer_len(buf)) + return false; + + sep = strrchr(buf->base, PKG_DIR_SEP_S); +#ifdef _WIN32 + char *sep2 = strrchr(buf->base, '/'); + if (sep2 != NULL && (sep == NULL || sep2 > sep)) + sep = sep2; +#endif + + if (sep != NULL) + { + *sep = '\0'; + buf->end = sep; + return true; + } + + return false; +} + +/* + * !doc + * + * .. c:function:: const char *pkgconf_path_find_basename(const char *path) + * + * Finds the basename from a path. + * + * :param char* path: The path to find the basename from. + * :return: a pointer to the basename + * :rtype: const char * + */ +const char * +pkgconf_path_find_basename(const char *path) { - char *tmpbuf; + const char *sep; - if ((tmpbuf = normpath(buf)) != NULL) + sep = strrchr(path, PKG_DIR_SEP_S); +#ifdef _WIN32 + const char *sep2 = strrchr(path, '/'); + if (sep2 != NULL && (sep == NULL || sep2 > sep)) + sep = sep2; +#endif + + if (sep != NULL) + return sep + 1; + + return path; +} + +#ifdef _WIN32 +#define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH" +/* + * !doc + * + * .. c:function:: void pkgconf_path_build_from_registry(HKEY hKey, pkgconf_list_t *dir_list, bool filter) + * + * Adds paths to a directory list discovered from a given registry key. + * + * .. warning:: + * The Windows registry search path mechanism is deprecated and will be + * removed in pkgconf 3.1. Use ``PKG_CONFIG_PATH`` or configure search + * paths explicitly instead. Avoid using this function directly in new + * code. + * + * :param pkgconf_client_t* client: pkgconf client + * :param HKEY hKey: The registry key to enumerate. + * :param pkgconf_list_t* dir_list: The directory list to append enumerated paths to. + * :param bool filter: Whether duplicate paths should be filtered. + * :return: number of path nodes added to the list + * :rtype: size_t + */ +size_t +pkgconf_path_build_from_registry(pkgconf_client_t *client, void *hKey, pkgconf_list_t *dir_list, bool filter) +{ + HKEY key; + int i = 0; + size_t added = 0; + + char buf[16384]; /* per registry limits */ + DWORD bufsize = sizeof buf; + if (RegOpenKeyEx(hKey, PKG_CONFIG_REG_KEY, + 0, KEY_READ, &key) != ERROR_SUCCESS) + return 0; + + pkgconf_warn(client, + "WARNING: support for reading PKG_CONFIG_PATH from the Windows registry " + "is deprecated and will be removed in pkgconf 3.1\n"); + + while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL) + == ERROR_SUCCESS) { - size_t tmpbuflen = strlen(tmpbuf); - if (tmpbuflen > buflen) + char pathbuf[PKGCONF_ITEM_SIZE]; + DWORD type; + DWORD pathbuflen = sizeof pathbuf; + + if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen) + == ERROR_SUCCESS && type == REG_SZ) { - free(tmpbuf); - return false; + pkgconf_path_add(pathbuf, dir_list, filter); + added++; } - pkgconf_strlcpy(buf, tmpbuf, buflen); - free(tmpbuf); + bufsize = sizeof buf; } - return true; + RegCloseKey(key); + return added; +} +#endif + +bool +pkgconf_path_is_plausible(const pkgconf_buffer_t *buf) +{ + const char *s; + + if (buf == NULL) + return false; + + s = pkgconf_buffer_str(buf); + if (s == NULL) + return false; + + /* skip leading whitespace */ + while (*s != '\0' && isspace((unsigned char)*s)) + s++; + + if (*s == '\0') + return false; + + /* POSIX absolute path */ + if (*s == '/') + return true; + + /* ./ or ../ relative path */ + if (s[0] == '.' && (s[1] == '/' || s[1] == '\\')) + return true; + + if (s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == '\\')) + return true; + + /* Windows drive path: C:/... or C:\... */ + if (isalpha((unsigned char)s[0]) && s[1] == ':' && (s[2] == '/' || s[2] == '\\')) + return true; + + /* anything with a path separator seems plausible, for example "Program Files/MySDK" */ + for (const char *p = s; *p != '\0'; p++) + { + if (*p == '/' || *p == '\\') + return true; + } + + return false; } Index: libpkgconf/path.h =================================================================== RCS file: libpkgconf/path.h diff -N libpkgconf/path.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/path.h 4 Aug 2026 16:20:54 -0000 @@ -0,0 +1,9 @@ +#ifndef LIBPKGCONF_PATH_H +#define LIBPKGCONF_PATH_H + +#include + +PKGCONF_API bool pkgconf_path_trim_basename(pkgconf_buffer_t *buf); +PKGCONF_API const char *pkgconf_path_find_basename(const char *path); + +#endif Index: libpkgconf/personality.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/personality.c,v diff -u -p -r1.1.1.1 personality.c --- libpkgconf/personality.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/personality.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * personality.c * libpkgconf cross-compile personality database * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2018 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -24,10 +26,6 @@ * ========================= */ -#ifdef _WIN32 -# define strcasecmp _stricmp -#endif - /* * Increment each time the default personality is inited, decrement each time * it's deinited. Whenever it is 0, then the deinit frees the personality. In @@ -37,15 +35,6 @@ static unsigned default_personality_init static pkgconf_cross_personality_t default_personality = { .name = "default", -#ifdef _WIN32 - /* PE/COFF uses a different linking model than ELF and Mach-O, where - * all transitive dependency references must be processed by the linker - * when linking the final executable image, even if those dependencies - * are pulled in as DLLs. - * This translates to always using --static on Windows targets. - */ - .want_default_static = true, -#endif }; static inline void @@ -53,29 +42,36 @@ build_default_search_path(pkgconf_list_t { #ifdef _WIN32 char namebuf[MAX_PATH]; - char outbuf[MAX_PATH]; + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; char *p; - int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf); - char * winslash; + /* Reserve one byte for the NUL: GetModuleFileName returns the size passed + * to it (nSize) when the path is truncated, so passing sizeof namebuf could + * yield sizepath == sizeof namebuf and overflow namebuf[] by one byte below. + * + * See the GetModuleFileNameA return-value documentation: + * https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea + */ + int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf - 1); namebuf[sizepath] = '\0'; - while ((winslash = strchr (namebuf, '\\')) != NULL) - { - *winslash = '/'; - } + + pkgconf_path_normalize_separators(namebuf); + p = strrchr(namebuf, '/'); if (p == NULL) + { pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true); - + return; + } *p = '\0'; - pkgconf_strlcpy(outbuf, namebuf, sizeof outbuf); - pkgconf_strlcat(outbuf, "/", sizeof outbuf); - pkgconf_strlcat(outbuf, "../lib/pkgconfig", sizeof outbuf); - pkgconf_path_add(outbuf, dirlist, true); - pkgconf_strlcpy(outbuf, namebuf, sizeof outbuf); - pkgconf_strlcat(outbuf, "/", sizeof outbuf); - pkgconf_strlcat(outbuf, "../share/pkgconfig", sizeof outbuf); - pkgconf_path_add(outbuf, dirlist, true); + + pkgconf_buffer_append_fmt(&pathbuf, "%s/../lib/pkgconfig", namebuf); + pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, true); + pkgconf_buffer_rewind(&pathbuf); + + pkgconf_buffer_append_fmt(&pathbuf, "%s/../share/pkgconfig", namebuf); + pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, true); + pkgconf_buffer_finalize(&pathbuf); #elif __HAIKU__ char **paths; size_t count; @@ -92,7 +88,7 @@ build_default_search_path(pkgconf_list_t paths = NULL; } #else - pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true); + pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, false); #endif } @@ -130,7 +126,8 @@ pkgconf_cross_personality_default(void) * * .. c:function:: void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *) * - * Decrements the count of default cross personality instances. + * Destroys a cross personality object and/or decreases the reference count on the + * default cross personality object. * * Not thread safe. * @@ -139,11 +136,28 @@ pkgconf_cross_personality_default(void) void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *personality) { - if (--default_personality_init == 0) { - pkgconf_path_free(&personality->dir_list); - pkgconf_path_free(&personality->filter_libdirs); - pkgconf_path_free(&personality->filter_includedirs); - } + /* allow NULL parameter for API backwards compatibility */ + if (personality == NULL) + return; + + /* XXX: this hack is rather ugly, but it works for now... */ + if (personality == &default_personality && --default_personality_init > 0) + return; + + pkgconf_path_free(&personality->dir_list); + pkgconf_path_free(&personality->filter_libdirs); + pkgconf_path_free(&personality->filter_includedirs); + + if (personality->sysroot_dir != NULL) + free(personality->sysroot_dir); + + if (personality == &default_personality) + return; + + if (personality->name != NULL) + free(personality->name); + + free(personality); } #ifndef PKGCONF_LITE @@ -159,7 +173,7 @@ valid_triplet(const char *triplet) return true; } -typedef void (*personality_keyword_func_t)(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value); +typedef void (*personality_keyword_func_t)(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value); typedef struct { const char *keyword; const personality_keyword_func_t func; @@ -167,30 +181,34 @@ typedef struct { } personality_keyword_pair_t; static void -personality_bool_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value) +personality_bool_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { (void) keyword; - (void) lineno; + (void) warnprefix; bool *dest = (bool *)((char *) p + offset); - *dest = strcasecmp(value, "true") || strcasecmp(value, "yes") || *value == '1'; + *dest = strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0; } static void -personality_copy_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value) +personality_copy_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { (void) keyword; - (void) lineno; + (void) warnprefix; char **dest = (char **)((char *) p + offset); + + if (*dest != NULL) + free(*dest); + *dest = strdup(value); } static void -personality_fragment_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value) +personality_fragment_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { (void) keyword; - (void) lineno; + (void) warnprefix; pkgconf_list_t *dest = (pkgconf_list_t *)((char *) p + offset); pkgconf_path_split(value, dest, false); @@ -215,8 +233,9 @@ personality_keyword_pair_cmp(const void } static void -personality_keyword_set(pkgconf_cross_personality_t *p, const size_t lineno, const char *keyword, char *value) +personality_keyword_set(void *data, const char *warnprefix, const char *keyword, const char *value) { + pkgconf_cross_personality_t *p = data; const personality_keyword_pair_t *pair = bsearch(keyword, personality_keyword_pairs, PKGCONF_ARRAY_SIZE(personality_keyword_pairs), sizeof(personality_keyword_pair_t), personality_keyword_pair_cmp); @@ -224,7 +243,7 @@ personality_keyword_set(pkgconf_cross_pe if (pair == NULL || pair->func == NULL) return; - pair->func(p, keyword, lineno, pair->offset, value); + pair->func(p, keyword, warnprefix, pair->offset, value); } static const pkgconf_parser_operand_func_t personality_parser_ops[256] = { @@ -241,33 +260,54 @@ personality_warn_func(void *p, const cha (void) p; va_start(va, fmt); - vfprintf(stderr, fmt, va); + pkgconf_output_file_vfmt(stderr, fmt, va); va_end(va); } static pkgconf_cross_personality_t * load_personality_with_path(const char *path, const char *triplet, bool datadir) { - char pathbuf[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; FILE *f; pkgconf_cross_personality_t *p; /* if triplet is null, assume that path is a direct path to the personality file */ if (triplet == NULL) - pkgconf_strlcpy(pathbuf, path, sizeof pathbuf); + { + if (!pkgconf_buffer_append(&pathbuf, path)) + return NULL; + } else if (datadir) - snprintf(pathbuf, sizeof pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet); + { + if (!pkgconf_buffer_append_fmt(&pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet)) + return NULL; + } else - snprintf(pathbuf, sizeof pathbuf, "%s/%s.personality", path, triplet); + { + if (!pkgconf_buffer_append_fmt(&pathbuf, "%s/%s.personality", path, triplet)) + return NULL; + } - f = fopen(pathbuf, "r"); - if (f == NULL) + p = calloc(1, sizeof(pkgconf_cross_personality_t)); + if (p == NULL) + { + pkgconf_buffer_finalize(&pathbuf); return NULL; + } - p = calloc(1, sizeof(pkgconf_cross_personality_t)); if (triplet != NULL) p->name = strdup(triplet); - pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pathbuf); + + f = fopen(pkgconf_buffer_str(&pathbuf), "rb"); + if (f == NULL) + { + pkgconf_buffer_finalize(&pathbuf); + pkgconf_cross_personality_deinit(p); + return NULL; + } + + pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pkgconf_buffer_str(&pathbuf)); + pkgconf_buffer_finalize(&pathbuf); return p; } @@ -289,7 +329,6 @@ pkgconf_cross_personality_find(const cha pkgconf_node_t *n; pkgconf_cross_personality_t *out = NULL; #if ! defined(_WIN32) && ! defined(__HAIKU__) - char pathbuf[PKGCONF_ITEM_SIZE]; const char *envvar; #endif @@ -304,16 +343,20 @@ pkgconf_cross_personality_find(const cha envvar = getenv("XDG_DATA_HOME"); if (envvar != NULL) pkgconf_path_add(envvar, &plist, true); - else { + else + { envvar = getenv("HOME"); - if (envvar != NULL) { - pkgconf_strlcpy(pathbuf, envvar, sizeof pathbuf); - pkgconf_strlcat(pathbuf, "/.local/share", sizeof pathbuf); - pkgconf_path_add(pathbuf, &plist, true); + if (envvar != NULL) + { + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&pathbuf, "%s/.local/share", envvar); + pkgconf_path_add(pkgconf_buffer_str(&pathbuf), &plist, true); + pkgconf_buffer_finalize(&pathbuf); } } - pkgconf_path_build_from_environ("XDG_DATA_DIRS", "/usr/local/share" PKG_CONFIG_PATH_SEP_S "/usr/share", &plist, true); + pkgconf_path_build_from_environ(NULL, "XDG_DATA_DIRS", "/usr/local/share" PKG_CONFIG_PATH_SEP_S "/usr/share", &plist, true); PKGCONF_FOREACH_LIST_ENTRY(plist.head, n) { Index: libpkgconf/pkg.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/pkg.c,v diff -u -p -r1.1.1.1 pkg.c --- libpkgconf/pkg.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/pkg.c 4 Aug 2026 17:05:31 -0000 @@ -2,6 +2,8 @@ * pkg.c * higher-level dependency graph compilation, management and manipulation * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -16,15 +18,7 @@ #include #include #include - -#ifndef _WIN32 -#include // open -#include // basename/dirname -#include // lstat, S_ISLNK -#include // close, readlinkat - -#include -#endif +#include /* * !doc @@ -36,14 +30,6 @@ * routines. */ -#ifdef _WIN32 -# define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH" -# undef PKG_DEFAULT_PATH -# define PKG_DEFAULT_PATH "../lib/pkgconfig;../share/pkgconfig" -# define strncasecmp _strnicmp -# define strcasecmp _stricmp -#endif - #define PKG_CONFIG_EXT ".pc" static unsigned int @@ -52,11 +38,15 @@ pkgconf_pkg_traverse_main(pkgconf_client pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, - unsigned int skip_flags); + unsigned int skip_flags, + unsigned int iter_flags); static inline bool str_has_suffix(const char *str, const char *suffix) { + if (str == NULL || suffix == NULL) + return false; + size_t str_len = strlen(str); size_t suf_len = strlen(suffix); @@ -69,48 +59,58 @@ str_has_suffix(const char *str, const ch static char * pkg_get_parent_dir(pkgconf_pkg_t *pkg) { - char buf[PKGCONF_ITEM_SIZE], *pathbuf; + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; + + if (!pkgconf_buffer_append(&buf, pkg->filename)) + goto fail; - pkgconf_strlcpy(buf, pkg->filename, sizeof buf); #ifndef _WIN32 - /* - * We want to resolve symlinks, since ${pcfiledir} should point to the - * parent of the file symlinked to. - */ struct stat path_stat; - while (!lstat(buf, &path_stat) && S_ISLNK(path_stat.st_mode)) + + while (buf.base != NULL && + !lstat(buf.base, &path_stat) && + S_ISLNK(path_stat.st_mode)) { - /* - * Have to split the path into the dir + file components, - * in order to extract the directory file descriptor. - * - * The nomenclature here uses the - * - * ln - * - * model. - */ - char basenamebuf[PKGCONF_ITEM_SIZE]; - pkgconf_strlcpy(basenamebuf, buf, sizeof(basenamebuf)); - const char* targetfilename = basename(basenamebuf); - - char dirnamebuf[PKGCONF_ITEM_SIZE]; - pkgconf_strlcpy(dirnamebuf, buf, sizeof(dirnamebuf)); - const char* targetdir = dirname(dirnamebuf); + char sourcebuf[PKGCONF_ITEM_SIZE]; + char *targetfilename, *targetdir; + + pkgconf_buffer_rewind(&pathbuf); + if (!pkgconf_buffer_append(&pathbuf, buf.base)) + goto fail; + targetfilename = strrchr(pathbuf.base, '/'); + if (targetfilename != NULL) + { + *targetfilename++ = '\0'; + targetdir = pathbuf.base; + + if (*targetdir == '\0') + targetdir = "/"; + } + else + { + targetfilename = pathbuf.base; + targetdir = "."; + } + +#if HAVE_DECL_READLINKAT const int dirfd = open(targetdir, O_DIRECTORY); if (dirfd == -1) break; - char sourcebuf[PKGCONF_ITEM_SIZE]; ssize_t len = readlinkat(dirfd, targetfilename, sourcebuf, sizeof(sourcebuf) - 1); close(dirfd); +#else + ssize_t len = readlink(buf.base, sourcebuf, sizeof(sourcebuf) - 1); +#endif if (len == -1) break; sourcebuf[len] = '\0'; - memset(buf, '\0', sizeof buf); + pkgconf_buffer_rewind(&buf); + /* * The logic here can be a bit tricky, so here's a table: * @@ -121,26 +121,31 @@ pkg_get_parent_dir(pkgconf_pkg_t *pkg) * /bar (absolute) | /foo/link (absolute) | /bar (absolute) * ../bar (relative) | /foo/link (absolute) | /foo/../bar (relative) */ - if ((sourcebuf[0] != '/') /* absolute path in wins */ - && (strcmp(targetdir, "."))) /* do not prepend "." */ + if ((sourcebuf[0] != '/') && strcmp(targetdir, ".")) { - pkgconf_strlcat(buf, targetdir, sizeof buf); - pkgconf_strlcat(buf, "/", sizeof buf); + if (!pkgconf_buffer_append(&buf, targetdir) || !pkgconf_buffer_push_byte(&buf, '/')) + goto fail; } - pkgconf_strlcat(buf, sourcebuf, sizeof buf); + + if (!pkgconf_buffer_append(&buf, sourcebuf)) + goto fail; } #endif - pathbuf = strrchr(buf, PKG_DIR_SEP_S); - if (pathbuf == NULL) - pathbuf = strrchr(buf, '/'); - if (pathbuf != NULL) - pathbuf[0] = '\0'; + pkgconf_buffer_finalize(&pathbuf); + + if (pkgconf_buffer_len(&buf) > 0) + pkgconf_path_trim_basename(&buf); - return strdup(buf); + return pkgconf_buffer_freeze(&buf); + +fail: + pkgconf_buffer_finalize(&pathbuf); + pkgconf_buffer_finalize(&buf); + return NULL; } -typedef void (*pkgconf_pkg_parser_keyword_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value); +typedef void (*pkgconf_pkg_parser_keyword_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value); typedef struct { const char *keyword; const pkgconf_pkg_parser_keyword_func_t func; @@ -154,26 +159,85 @@ static int pkgconf_pkg_parser_keyword_pa } static void -pkgconf_pkg_parser_tuple_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +pkgconf_pkg_parser_tuple_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { (void) keyword; - (void) lineno; + (void) warnprefix; char **dest = (char **)((char *) pkg + offset); - *dest = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags); + + if (*dest != NULL) + free(*dest); + + *dest = pkgconf_bytecode_eval_str(client, &pkg->vars, value, NULL); } static void -pkgconf_pkg_parser_version_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +pkgconf_pkg_parser_bufferset_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) warnprefix; + + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; + + if (pkgconf_bytecode_eval_str_to_buf(client, &pkg->vars, value, NULL, &buf)) + pkgconf_bufferset_extend(dest, &buf); + + pkgconf_buffer_finalize(&buf); +} + +/* parses a comma-separated list of ABI tags, lowercasing each, into a bufferset */ +static void +pkgconf_pkg_parser_link_abi_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) warnprefix; + + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + char *expanded = pkgconf_bytecode_eval_str(client, &pkg->vars, value, NULL); + + if (expanded == NULL) + return; + + for (char *p = expanded; *p != '\0';) + { + pkgconf_buffer_t tag = PKGCONF_BUFFER_INITIALIZER; + + while (*p == ',' || isspace((unsigned char) *p)) + p++; + + while (*p != '\0' && *p != ',' && !isspace((unsigned char) *p)) + { + if (!pkgconf_buffer_push_byte(&tag, (char) tolower((unsigned char) *p++))) + { + pkgconf_buffer_finalize(&tag); + free(expanded); + return; + } + } + + if (pkgconf_buffer_len(&tag)) + pkgconf_bufferset_extend(dest, &tag); + + pkgconf_buffer_finalize(&tag); + } + + free(expanded); +} + +static void +pkgconf_pkg_parser_version_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { (void) keyword; - (void) lineno; char *p, *i; size_t len; char **dest = (char **)((char *) pkg + offset); /* cut at any detected whitespace */ - p = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags); + p = pkgconf_bytecode_eval_str(client, &pkg->vars, value, NULL); + if (p == NULL) + return; len = strcspn(p, " \t"); if (len != strlen(p)) @@ -181,88 +245,127 @@ pkgconf_pkg_parser_version_func(pkgconf_ i = p + (ptrdiff_t) len; *i = '\0'; - pkgconf_warn(client, "%s:" SIZE_FMT_SPECIFIER ": warning: malformed version field with whitespace, trimming to [%s]\n", pkg->filename, - lineno, p); + pkgconf_warn(client, "%s: warning: malformed version field with whitespace, trimming to [%s]\n", + warnprefix, p); } + if (*dest != NULL) + free(*dest); + *dest = p; } static void -pkgconf_pkg_parser_fragment_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +pkgconf_pkg_parser_fragment_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); - - /* we patch client-wide sysroot dir and then patch it back when it is overridden */ - char *sysroot_dir = client->sysroot_dir; - char *pkg_sysroot_dir = pkgconf_tuple_find(client, &pkg->vars, "pc_sysrootdir"); - if (pkg_sysroot_dir != NULL) - client->sysroot_dir = pkg_sysroot_dir; - bool ret = pkgconf_fragment_parse(client, dest, &pkg->vars, value, pkg->flags); - client->sysroot_dir = sysroot_dir; if (!ret) { - pkgconf_warn(client, "%s:" SIZE_FMT_SPECIFIER ": warning: unable to parse field '%s' into an argument vector, value [%s]\n", pkg->filename, - lineno, keyword, value); + pkgconf_warn(client, "%s: warning: unable to parse field '%s' into an argument vector, value [%s]\n", + warnprefix, keyword, value); } } static void -pkgconf_pkg_parser_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +pkgconf_pkg_parser_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { - (void) keyword; - (void) lineno; - pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + + if (dest->tail != NULL) + { + pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n", + warnprefix, keyword); + } + pkgconf_dependency_parse(client, pkg, dest, value, 0); } /* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as an "internal" dependency. */ static void -pkgconf_pkg_parser_internal_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +pkgconf_pkg_parser_internal_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { - (void) keyword; - (void) lineno; - pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + + if (dest->tail != NULL) + { + pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n", + warnprefix, keyword); + } + pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_INTERNAL); } /* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as a "private" dependency. */ static void -pkgconf_pkg_parser_private_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +pkgconf_pkg_parser_private_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) { - (void) keyword; - (void) lineno; - pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + + if (dest->tail != NULL) + { + pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n", + warnprefix, keyword); + } + pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_PRIVATE); } +/* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as a "shared" dependency. */ +static void +pkgconf_pkg_parser_shared_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) +{ + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + + if (dest->tail != NULL) + { + pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n", + warnprefix, keyword); + } + + pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_SHARED); +} + +/* Evaluates SPDX expression or parses comma separated list of licenses */ +static void +pkgconf_pkg_evaluate_license_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) +{ + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + + if (!pkgconf_license_evaluate(client, pkg, dest, value, 0)) + pkgconf_warn(client, "%s: warning: license field '%s' could not be fully evaluated\n", + warnprefix, keyword); +} + /* keep this in alphabetical order */ static const pkgconf_pkg_parser_keyword_pair_t pkgconf_pkg_parser_keyword_funcs[] = { {"CFLAGS", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags)}, {"CFLAGS.private", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags_private)}, + {"CFLAGS.shared", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags_shared)}, {"Conflicts", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, conflicts)}, - {"Copyright", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, copyright)}, + {"Copyright", pkgconf_pkg_parser_bufferset_func, offsetof(pkgconf_pkg_t, copyright)}, {"Description", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, description)}, {"LIBS", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs)}, {"LIBS.private", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs_private)}, - {"License", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, license)}, + {"LIBS.shared", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs_shared)}, + {"License", pkgconf_pkg_evaluate_license_func, offsetof(pkgconf_pkg_t, license)}, + {"License.file", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, license_file)}, + {"Link.ABI", pkgconf_pkg_parser_link_abi_func, offsetof(pkgconf_pkg_t, link_abi)}, {"Maintainer", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, maintainer)}, {"Name", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, realname)}, {"Provides", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, provides)}, {"Requires", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, required)}, {"Requires.internal", pkgconf_pkg_parser_internal_dependency_func, offsetof(pkgconf_pkg_t, requires_private)}, {"Requires.private", pkgconf_pkg_parser_private_dependency_func, offsetof(pkgconf_pkg_t, requires_private)}, + {"Requires.shared", pkgconf_pkg_parser_shared_dependency_func, offsetof(pkgconf_pkg_t, requires_shared)}, + {"Source", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, source)}, {"URL", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, url)}, {"Version", pkgconf_pkg_parser_version_func, offsetof(pkgconf_pkg_t, version)}, }; static void -pkgconf_pkg_parser_keyword_set(void *opaque, const size_t lineno, const char *keyword, const char *value) +pkgconf_pkg_parser_keyword_set(void *opaque, const char *warnprefix, const char *keyword, const char *value) { pkgconf_pkg_t *pkg = opaque; @@ -273,44 +376,29 @@ pkgconf_pkg_parser_keyword_set(void *opa if (pair == NULL || pair->func == NULL) return; - pair->func(pkg->owner, pkg, keyword, lineno, pair->offset, value); + pair->func(pkg->owner, pkg, keyword, warnprefix, pair->offset, value); } -static const char * -determine_prefix(const pkgconf_pkg_t *pkg, char *buf, size_t buflen) +static bool +determine_prefix(const pkgconf_pkg_t *pkg, pkgconf_buffer_t *pathbuf) { - char *pathiter; - - pkgconf_strlcpy(buf, pkg->filename, buflen); - pkgconf_path_relocate(buf, buflen); + if (!pkgconf_buffer_append(pathbuf, pkg->filename)) + return false; - pathiter = strrchr(buf, PKG_DIR_SEP_S); - if (pathiter == NULL) - pathiter = strrchr(buf, '/'); - if (pathiter != NULL) - pathiter[0] = '\0'; + pkgconf_path_relocate(pathbuf); - pathiter = strrchr(buf, PKG_DIR_SEP_S); - if (pathiter == NULL) - pathiter = strrchr(buf, '/'); - if (pathiter == NULL) - return NULL; + pkgconf_path_trim_basename(pathbuf); - /* parent dir is not pkgconfig, can't relocate then */ - if (strcmp(pathiter + 1, "pkgconfig")) - return NULL; + if (strcmp(pkgconf_path_find_basename(pkgconf_buffer_str_or_empty(pathbuf)), "pkgconfig")) + return false; - /* okay, work backwards and do it again. */ - pathiter[0] = '\0'; - pathiter = strrchr(buf, PKG_DIR_SEP_S); - if (pathiter == NULL) - pathiter = strrchr(buf, '/'); - if (pathiter == NULL) - return NULL; + if (!pkgconf_path_trim_basename(pathbuf)) + return false; - pathiter[0] = '\0'; + if (!pkgconf_path_trim_basename(pathbuf)) + return false; - return buf; + return true; } /* @@ -326,22 +414,33 @@ determine_prefix(const pkgconf_pkg_t *pk static char * convert_path_to_value(const char *path) { - char *buf = calloc(1, (strlen(path) + 1) * 2); - char *bptr = buf; + pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; const char *i; for (i = path; *i != '\0'; i++) { - if (*i == PKG_DIR_SEP_S) - *bptr++ = '/'; - else if (*i == ' ') { - *bptr++ = '\\'; - *bptr++ = *i; - } else - *bptr++ = *i; + char c = *i; + +#ifdef _WIN32 + if (c == PKG_DIR_SEP_S) + c = '/'; +#endif + + if (c == ' ') + { + if (!pkgconf_buffer_push_byte(&buf, '\\') || + !pkgconf_buffer_push_byte(&buf, ' ')) + goto fail; + } + else if (!pkgconf_buffer_push_byte(&buf, c)) + goto fail; } - return buf; + return pkgconf_buffer_freeze(&buf); + +fail: + pkgconf_buffer_finalize(&buf); + return NULL; } static void @@ -381,47 +480,142 @@ is_path_prefix_equal(const char *path1, #endif } +static inline bool +is_path_separator(char c) +{ +#ifdef _WIN32 + return c == '/' || c == '\\'; +#else + return c == '/'; +#endif +} + +static inline const char * +lookup_val_from_env(const pkgconf_client_t *client, const char *pkg_id, const char *keyword) +{ + pkgconf_buffer_t env_var = PKGCONF_BUFFER_INITIALIZER; + const char *result; + char *c; + + if (!pkgconf_buffer_join(&env_var, '_', "PKG_CONFIG", pkg_id, keyword, NULL)) + { + pkgconf_buffer_finalize(&env_var); + return NULL; + } + + for (c = env_var.base; *c != '\0'; c++) + { + *c = (char) toupper((unsigned char) *c); + + if (!isalnum((unsigned char) *c)) + *c = '_'; + } + + result = pkgconf_client_getenv(client, pkgconf_buffer_str(&env_var)); + + pkgconf_buffer_finalize(&env_var); + + return result; +} + static void -pkgconf_pkg_parser_value_set(void *opaque, const size_t lineno, const char *keyword, const char *value) +pkgconf_pkg_parser_value_set(void *opaque, const char *warnprefix, const char *keyword, const char *value) { - char canonicalized_value[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t canonicalized_value = PKGCONF_BUFFER_INITIALIZER; pkgconf_pkg_t *pkg = opaque; + const char *env_content; - (void) lineno; + (void) warnprefix; - pkgconf_strlcpy(canonicalized_value, value, sizeof canonicalized_value); - canonicalize_path(canonicalized_value); + env_content = lookup_val_from_env(pkg->owner, pkg->id, keyword); + if (env_content != NULL) + { + PKGCONF_TRACE(pkg->owner, "overriding %s from environment", keyword); + value = env_content; + } + + if (!(pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX)) + { + pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); + return; + } + + if (!pkgconf_buffer_append(&canonicalized_value, value)) + goto out; + + canonicalize_path(canonicalized_value.base); /* Some pc files will use absolute paths for all of their directories * which is broken when redefining the prefix. We try to outsmart the * file and rewrite any directory that starts with the same prefix. */ - if (pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX && pkg->orig_prefix - && is_path_prefix_equal(canonicalized_value, pkg->orig_prefix->value, strlen(pkg->orig_prefix->value))) + if (strcmp(keyword, pkg->owner->prefix_varname)) { - char newvalue[PKGCONF_ITEM_SIZE]; + if (pkgconf_buffer_len(&pkg->orig_prefix) != 0) + { + const char *op = pkgconf_buffer_str_or_empty(&pkg->orig_prefix); + const char *cv = pkgconf_buffer_str(&canonicalized_value); + size_t oplen = pkgconf_buffer_len(&pkg->orig_prefix); + + /* Match orig_prefix as a directory path: disregard any trailing + * separator so oplen describes the directory itself, and require + * the value to continue on a component boundary. This leaves the + * separator at cv[oplen] as the tail's leading character, which + * joins it to calculated_prefix (which never has a trailing one). + */ + while (oplen > 1 && is_path_separator(op[oplen - 1])) + oplen--; + + if (is_path_prefix_equal(cv, op, oplen) && + (cv[oplen] == '\0' || is_path_separator(cv[oplen]))) + { + pkgconf_buffer_t newvalue = PKGCONF_BUFFER_INITIALIZER; + + if (!pkgconf_buffer_append(&newvalue, pkgconf_buffer_str_or_empty(&pkg->calculated_prefix)) || + !pkgconf_buffer_append(&newvalue, cv + oplen)) + { + pkgconf_buffer_finalize(&newvalue); + goto out; + } + + pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, pkgconf_buffer_str(&newvalue), false, pkg->flags); + pkgconf_buffer_finalize(&newvalue); + + goto out; + } + } - pkgconf_strlcpy(newvalue, pkg->prefix->value, sizeof newvalue); - pkgconf_strlcat(newvalue, canonicalized_value + strlen(pkg->orig_prefix->value), sizeof newvalue); - pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, newvalue, false, pkg->flags); - } - else if (strcmp(keyword, pkg->owner->prefix_varname) || !(pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX)) pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); + } else { - char pathbuf[PKGCONF_ITEM_SIZE]; - const char *relvalue = determine_prefix(pkg, pathbuf, sizeof pathbuf); + pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; - if (relvalue != NULL) + if (determine_prefix(pkg, &pathbuf)) { + const char *relvalue = pkgconf_buffer_str(&pathbuf); char *prefix_value = convert_path_to_value(relvalue); - pkg->orig_prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, "orig_prefix", canonicalized_value, true, pkg->flags); - pkg->prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, prefix_value, false, pkg->flags); + + if (prefix_value == NULL || + !pkgconf_buffer_append(&pkg->orig_prefix, pkgconf_buffer_str(&canonicalized_value)) || + !pkgconf_buffer_append(&pkg->calculated_prefix, prefix_value)) + { + free(prefix_value); + pkgconf_buffer_finalize(&pathbuf); + goto out; + } + + pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, prefix_value, false, pkg->flags); free(prefix_value); } else pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); + + pkgconf_buffer_finalize(&pathbuf); } + +out: + pkgconf_buffer_finalize(&canonicalized_value); } typedef struct { @@ -440,11 +634,12 @@ static const pkgconf_parser_operand_func ['='] = pkgconf_pkg_parser_value_set }; -static void pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...) PRINTFLIKE(2, 3); +static void pkg_warn_func(void *pkg_p, const char *fmt, ...) PRINTFLIKE(2, 3); static void -pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...) +pkg_warn_func(void *pkg_p, const char *fmt, ...) { + pkgconf_pkg_t *pkg = pkg_p; char buf[PKGCONF_ITEM_SIZE]; va_list va; @@ -475,10 +670,78 @@ pkgconf_pkg_validate(const pkgconf_clien return valid; } +static void +pkg_free_object(pkgconf_pkg_t *pkg) +{ + if (pkg->flags & PKGCONF_PKG_PROPF_PRELOADED) + pkgconf_node_delete(&pkg->preload_node, &pkg->owner->preloaded_pkgs); + + if (pkg->id != NULL) + free(pkg->id); + + if (pkg->filename != NULL) + free(pkg->filename); + + if (pkg->realname != NULL) + free(pkg->realname); + + if (pkg->version != NULL) + free(pkg->version); + + if (pkg->description != NULL) + free(pkg->description); + + if (pkg->url != NULL) + free(pkg->url); + + if (pkg->pc_filedir != NULL) + free(pkg->pc_filedir); + + if (pkg->license_file != NULL) + free(pkg->license_file); + + if (pkg->maintainer != NULL) + free(pkg->maintainer); + + if (pkg->why != NULL) + free(pkg->why); + + if (pkg->source != NULL) + free(pkg->source); + + pkgconf_buffer_finalize(&pkg->orig_prefix); + pkgconf_buffer_finalize(&pkg->calculated_prefix); + + free(pkg); +} + +static void +pkg_free_lists(pkgconf_pkg_t *pkg) +{ + pkgconf_bufferset_free(&pkg->copyright); + pkgconf_bufferset_free(&pkg->link_abi); + + pkgconf_dependency_free(&pkg->required); + pkgconf_dependency_free(&pkg->requires_private); + pkgconf_dependency_free(&pkg->requires_shared); + pkgconf_dependency_free(&pkg->conflicts); + pkgconf_dependency_free(&pkg->provides); + + pkgconf_fragment_free(&pkg->cflags); + pkgconf_fragment_free(&pkg->cflags_private); + pkgconf_fragment_free(&pkg->cflags_shared); + pkgconf_license_free(&pkg->license); + pkgconf_fragment_free(&pkg->libs); + pkgconf_fragment_free(&pkg->libs_private); + pkgconf_fragment_free(&pkg->libs_shared); + + pkgconf_tuple_free(&pkg->vars); +} + /* * !doc * - * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_file(const pkgconf_client_t *client, const char *filename, FILE *f, unsigned int flags) + * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_path(const pkgconf_client_t *client, const char *filename, unsigned int flags) * * Parse a .pc file into a pkgconf_pkg_t object structure. * @@ -490,16 +753,49 @@ pkgconf_pkg_validate(const pkgconf_clien * :rtype: pkgconf_pkg_t * */ pkgconf_pkg_t * -pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *filename, FILE *f, unsigned int flags) +pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *filename, unsigned int flags) { pkgconf_pkg_t *pkg; char *idptr; + FILE *f; + + /* make sure we only load .pc files */ + if (!str_has_suffix(filename, PKG_CONFIG_EXT)) + return NULL; + + f = fopen(filename, "rb"); + if (f == NULL) + return NULL; pkg = calloc(1, sizeof(pkgconf_pkg_t)); + if (pkg == NULL) + { + fclose(f); + return NULL; + } + pkg->owner = client; + pkg->flags = flags; + pkg->filename = strdup(filename); + if (pkg->filename == NULL) + { + fclose(f); + pkg_free_object(pkg); + return NULL; + } + pkg->pc_filedir = pkg_get_parent_dir(pkg); - pkg->flags = flags; + if (pkg->pc_filedir == NULL) + { + fclose(f); + pkg_free_object(pkg); + return NULL; + } + + /* keep pc_filedir in canonical (forward-slash) form so it compares + * consistently against sysroot_dir and the search-path entries below */ + pkgconf_path_normalize_separators(pkg->pc_filedir); char *pc_filedir_value = convert_path_to_value(pkg->pc_filedir); pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pc_filedir_value, true, pkg->flags); @@ -509,26 +805,20 @@ pkgconf_pkg_new_from_file(pkgconf_client * package. * See https://github.com/pkgconf/pkgconf/issues/213 */ - if (client->sysroot_dir && strncmp(pkg->pc_filedir, client->sysroot_dir, strlen(client->sysroot_dir))) + if (client->sysroot_dir != NULL && strncmp(pkg->pc_filedir, client->sysroot_dir, strlen(client->sysroot_dir)) && + !(client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES)) pkgconf_tuple_add(client, &pkg->vars, "pc_sysrootdir", "", false, pkg->flags); /* make module id */ - if ((idptr = strrchr(pkg->filename, PKG_DIR_SEP_S)) != NULL) - idptr++; - else - idptr = pkg->filename; - -#ifdef _WIN32 - /* On Windows, both \ and / are allowed in paths, so we have to chop both. - * strrchr() took us to the last \ in that case, so we just have to see if - * it is followed by a /. If so, lop it off. - */ - char *mungeptr; - if ((mungeptr = strrchr(idptr, '/')) != NULL) - idptr = ++mungeptr; -#endif + pkg->id = strdup(pkgconf_path_find_basename(pkg->filename)); + if (pkg->id == NULL) + { + fclose(f); + pkg_free_lists(pkg); + pkg_free_object(pkg); + return NULL; + } - pkg->id = strdup(idptr); idptr = strrchr(pkg->id, '.'); if (idptr) *idptr = '\0'; @@ -540,7 +830,8 @@ pkgconf_pkg_new_from_file(pkgconf_client *idptr = '\0'; } - pkgconf_parser_parse(f, pkg, pkg_parser_funcs, (pkgconf_parser_warn_func_t) pkg_warn_func, pkg->filename); + pkgconf_parser_parse(f, pkg, pkg_parser_funcs, pkg_warn_func, pkg->filename); + fclose(f); if (!pkgconf_pkg_validate(client, pkg)) { @@ -549,7 +840,30 @@ pkgconf_pkg_new_from_file(pkgconf_client return NULL; } + /* a package that does not declare a Link.ABI defaults to the C ABI; a + * declared Link.ABI replaces this default rather than adding to it. + */ + if (pkg->link_abi.head == NULL) + { + pkgconf_buffer_t abibuf = PKGCONF_BUFFER_INITIALIZER; + + if (!pkgconf_buffer_append(&abibuf, "c")) + { + pkgconf_pkg_free(client, pkg); + return NULL; + } + + pkgconf_bufferset_extend(&pkg->link_abi, &abibuf); + pkgconf_buffer_finalize(&abibuf); + } + pkgconf_dependency_t *dep = pkgconf_dependency_add(client, &pkg->provides, pkg->id, pkg->version, PKGCONF_CMP_EQUAL, 0); + if (dep == NULL) + { + pkgconf_pkg_free(client, pkg); + return NULL; + } + pkgconf_dependency_unref(dep->owner, dep); return pkgconf_pkg_ref(client, pkg); @@ -577,55 +891,12 @@ pkgconf_pkg_free(pkgconf_client_t *clien pkgconf_cache_remove(client, pkg); - pkgconf_dependency_free(&pkg->required); - pkgconf_dependency_free(&pkg->requires_private); - pkgconf_dependency_free(&pkg->conflicts); - pkgconf_dependency_free(&pkg->provides); - - pkgconf_fragment_free(&pkg->cflags); - pkgconf_fragment_free(&pkg->cflags_private); - pkgconf_fragment_free(&pkg->libs); - pkgconf_fragment_free(&pkg->libs_private); - - pkgconf_tuple_free(&pkg->vars); + pkg_free_lists(pkg); if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) return; - if (pkg->id != NULL) - free(pkg->id); - - if (pkg->filename != NULL) - free(pkg->filename); - - if (pkg->realname != NULL) - free(pkg->realname); - - if (pkg->version != NULL) - free(pkg->version); - - if (pkg->description != NULL) - free(pkg->description); - - if (pkg->url != NULL) - free(pkg->url); - - if (pkg->pc_filedir != NULL) - free(pkg->pc_filedir); - - if (pkg->license != NULL) - free(pkg->license); - - if (pkg->maintainer != NULL) - free(pkg->maintainer); - - if (pkg->copyright != NULL) - free(pkg->copyright); - - if (pkg->why != NULL) - free(pkg->why); - - free(pkg); + pkg_free_object(pkg); } /* @@ -666,6 +937,11 @@ pkgconf_pkg_ref(pkgconf_client_t *client void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg) { + if (pkg == NULL) { + PKGCONF_TRACE(client, "WTF: client %p unrefs a NULL package", client); + return; + } + if (pkg->owner != NULL && pkg->owner != client) PKGCONF_TRACE(client, "WTF: client %p unrefs package %p owned by other client %p", client, pkg, pkg->owner); @@ -680,26 +956,36 @@ static inline pkgconf_pkg_t * pkgconf_pkg_try_specific_path(pkgconf_client_t *client, const char *path, const char *name) { pkgconf_pkg_t *pkg = NULL; - FILE *f; - char locbuf[PKGCONF_ITEM_SIZE]; - char uninst_locbuf[PKGCONF_ITEM_SIZE]; + pkgconf_buffer_t locbuf = PKGCONF_BUFFER_INITIALIZER; PKGCONF_TRACE(client, "trying path: %s for %s", path, name); - snprintf(locbuf, sizeof locbuf, "%s%c%s" PKG_CONFIG_EXT, path, PKG_DIR_SEP_S, name); - snprintf(uninst_locbuf, sizeof uninst_locbuf, "%s%c%s-uninstalled" PKG_CONFIG_EXT, path, PKG_DIR_SEP_S, name); - - if (!(client->flags & PKGCONF_PKG_PKGF_NO_UNINSTALLED) && (f = fopen(uninst_locbuf, "r")) != NULL) + if (!(client->flags & PKGCONF_PKG_PKGF_NO_UNINSTALLED)) { - PKGCONF_TRACE(client, "found (uninstalled): %s", uninst_locbuf); - pkg = pkgconf_pkg_new_from_file(client, uninst_locbuf, f, PKGCONF_PKG_PROPF_UNINSTALLED); + pkgconf_buffer_append(&locbuf, path); + pkgconf_buffer_push_byte(&locbuf, PKG_DIR_SEP_S); + pkgconf_buffer_append(&locbuf, name); + pkgconf_buffer_append(&locbuf, "-uninstalled" PKG_CONFIG_EXT); + + pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&locbuf), PKGCONF_PKG_PROPF_UNINSTALLED); } - else if ((f = fopen(locbuf, "r")) != NULL) + + if (pkg == NULL) { - PKGCONF_TRACE(client, "found: %s", locbuf); - pkg = pkgconf_pkg_new_from_file(client, locbuf, f, 0); + pkgconf_buffer_rewind(&locbuf); + pkgconf_buffer_append(&locbuf, path); + pkgconf_buffer_push_byte(&locbuf, PKG_DIR_SEP_S); + pkgconf_buffer_append(&locbuf, name); + pkgconf_buffer_append(&locbuf, PKG_CONFIG_EXT); + + pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&locbuf), 0); } + if (pkg != NULL) + PKGCONF_TRACE(client, "found%s: %s", pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED ? " (uninstalled)" : "", pkgconf_buffer_str(&locbuf)); + + pkgconf_buffer_finalize(&locbuf); + return pkg; } @@ -718,26 +1004,49 @@ pkgconf_pkg_scan_dir(pkgconf_client_t *c for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) { - char filebuf[PKGCONF_ITEM_SIZE]; - pkgconf_pkg_t *pkg; - FILE *f; - - pkgconf_strlcpy(filebuf, path, sizeof filebuf); - pkgconf_strlcat(filebuf, "/", sizeof filebuf); - pkgconf_strlcat(filebuf, dirent->d_name, sizeof filebuf); + pkgconf_buffer_t filebuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_pkg_t *pkg = NULL; - if (!str_has_suffix(filebuf, PKG_CONFIG_EXT)) + if (!pkgconf_buffer_join(&filebuf, '/', path, dirent->d_name, NULL)) + { + closedir(dir); + return NULL; + } + + if (!str_has_suffix(pkgconf_buffer_str(&filebuf), PKG_CONFIG_EXT)) + { + pkgconf_buffer_finalize(&filebuf); continue; + } - PKGCONF_TRACE(client, "trying file [%s]", filebuf); + if (!(client->flags & PKGCONF_PKG_PKGF_NO_CACHE)) + { + pkgconf_buffer_t idbuf = PKGCONF_BUFFER_INITIALIZER; - f = fopen(filebuf, "r"); - if (f == NULL) - continue; + if (pkgconf_buffer_append(&idbuf, pkgconf_path_find_basename(pkgconf_buffer_str(&filebuf)))) + { + char *idptr = strrchr(pkgconf_buffer_str(&idbuf), '.'); + + if (idptr != NULL) + *idptr = '\0'; + + pkg = pkgconf_cache_lookup(client, pkgconf_buffer_str(&idbuf)); + } + + pkgconf_buffer_finalize(&idbuf); + } + + PKGCONF_TRACE(client, "trying file [%s]", pkgconf_buffer_str(&filebuf)); + + if (pkg == NULL) + pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&filebuf), 0); + pkgconf_buffer_finalize(&filebuf); - pkg = pkgconf_pkg_new_from_file(client, filebuf, f, 0); if (pkg != NULL) { + if (!(pkg->flags & PKGCONF_PKG_PROPF_CACHED) && !(client->flags & PKGCONF_PKG_PKGF_NO_CACHE)) + pkgconf_cache_add(client, pkg); + if (func(pkg, data)) { outpkg = pkg; @@ -774,6 +1083,22 @@ pkgconf_scan_all(pkgconf_client_t *clien pkgconf_node_t *n; pkgconf_pkg_t *pkg; + PKGCONF_TRACE(client, "scanning preloaded list"); + PKGCONF_FOREACH_LIST_ENTRY(client->preloaded_pkgs.head, n) + { + pkg = n->data; + + /* add an additional reference to ensure preloaded packages have the same + * object ownership semantics as non-preloaded packages + */ + pkgconf_pkg_ref(client, pkg); + + if (func(pkg, data)) + return pkg; + + pkgconf_pkg_unref(client, pkg); + } + PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) { pkgconf_path_t *pnode = n->data; @@ -787,43 +1112,24 @@ pkgconf_scan_all(pkgconf_client_t *clien return NULL; } -#ifdef _WIN32 static pkgconf_pkg_t * -pkgconf_pkg_find_in_registry_key(pkgconf_client_t *client, HKEY hkey, const char *name) +search_preload_list(pkgconf_client_t *client, const char *name) { - pkgconf_pkg_t *pkg = NULL; - - HKEY key; - int i = 0; - - char buf[16384]; /* per registry limits */ - DWORD bufsize = sizeof buf; - if (RegOpenKeyEx(hkey, PKG_CONFIG_REG_KEY, - 0, KEY_READ, &key) != ERROR_SUCCESS) - return NULL; + pkgconf_node_t *n; - while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL) - == ERROR_SUCCESS) + PKGCONF_FOREACH_LIST_ENTRY(client->preloaded_pkgs.head, n) { - char pathbuf[PKGCONF_ITEM_SIZE]; - DWORD type; - DWORD pathbuflen = sizeof pathbuf; + pkgconf_pkg_t *pkg = n->data; - if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen) - == ERROR_SUCCESS && type == REG_SZ) + if (!strcmp(pkg->id, name)) { - pkg = pkgconf_pkg_try_specific_path(client, pathbuf, name); - if (pkg != NULL) - break; + pkgconf_pkg_ref(client, pkg); + return pkg; } - - bufsize = sizeof buf; } - RegCloseKey(key); - return pkg; + return NULL; } -#endif /* * !doc @@ -842,33 +1148,22 @@ pkgconf_pkg_find(pkgconf_client_t *clien { pkgconf_pkg_t *pkg = NULL; pkgconf_node_t *n; - FILE *f; PKGCONF_TRACE(client, "looking for: %s", name); /* name might actually be a filename. */ if (str_has_suffix(name, PKG_CONFIG_EXT)) { - if ((f = fopen(name, "r")) != NULL) + pkg = pkgconf_pkg_new_from_path(client, name, 0); + if (pkg != NULL) { PKGCONF_TRACE(client, "%s is a file", name); - pkg = pkgconf_pkg_new_from_file(client, name, f, 0); - if (pkg != NULL) - { - pkgconf_path_add(pkg->pc_filedir, &client->dir_list, true); - goto out; - } + pkgconf_path_add(pkg->pc_filedir, &client->dir_list, true); + goto out; } } - /* check builtins */ - if ((pkg = pkgconf_builtin_pkg_get(name)) != NULL) - { - PKGCONF_TRACE(client, "%s is a builtin", name); - return pkg; - } - /* check cache */ if (!(client->flags & PKGCONF_PKG_PKGF_NO_CACHE)) { @@ -879,6 +1174,13 @@ pkgconf_pkg_find(pkgconf_client_t *clien } } + /* check preload list */ + if ((pkg = search_preload_list(client, name)) != NULL) + { + PKGCONF_TRACE(client, "%s is preloaded", name); + return pkg; + } + PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) { pkgconf_path_t *pnode = n->data; @@ -888,253 +1190,12 @@ pkgconf_pkg_find(pkgconf_client_t *clien goto out; } -#ifdef _WIN32 - /* support getting PKG_CONFIG_PATH from registry */ - pkg = pkgconf_pkg_find_in_registry_key(client, HKEY_CURRENT_USER, name); - if (!pkg) - pkg = pkgconf_pkg_find_in_registry_key(client, HKEY_LOCAL_MACHINE, name); -#endif - out: pkgconf_cache_add(client, pkg); return pkg; } -/* - * !doc - * - * .. c:function:: int pkgconf_compare_version(const char *a, const char *b) - * - * Compare versions using RPM version comparison rules as described in the LSB. - * - * :param char* a: The first version to compare in the pair. - * :param char* b: The second version to compare in the pair. - * :return: -1 if the first version is less than, 0 if both versions are equal, 1 if the second version is less than. - * :rtype: int - */ -int -pkgconf_compare_version(const char *a, const char *b) -{ - char oldch1, oldch2; - char buf1[PKGCONF_ITEM_SIZE], buf2[PKGCONF_ITEM_SIZE]; - char *str1, *str2; - char *one, *two; - int ret; - bool isnum; - - /* optimization: if version matches then it's the same version. */ - if (a == NULL) - return -1; - - if (b == NULL) - return 1; - - if (!strcasecmp(a, b)) - return 0; - - pkgconf_strlcpy(buf1, a, sizeof buf1); - pkgconf_strlcpy(buf2, b, sizeof buf2); - - one = buf1; - two = buf2; - - while (*one || *two) - { - while (*one && !isalnum((unsigned char)*one) && *one != '~') - one++; - while (*two && !isalnum((unsigned char)*two) && *two != '~') - two++; - - if (*one == '~' || *two == '~') - { - if (*one != '~') - return 1; - if (*two != '~') - return -1; - - one++; - two++; - continue; - } - - if (!(*one && *two)) - break; - - str1 = one; - str2 = two; - - if (isdigit((unsigned char)*str1)) - { - while (*str1 && isdigit((unsigned char)*str1)) - str1++; - - while (*str2 && isdigit((unsigned char)*str2)) - str2++; - - isnum = true; - } - else - { - while (*str1 && isalpha((unsigned char)*str1)) - str1++; - - while (*str2 && isalpha((unsigned char)*str2)) - str2++; - - isnum = false; - } - - oldch1 = *str1; - oldch2 = *str2; - - *str1 = '\0'; - *str2 = '\0'; - - if (one == str1) - return -1; - - if (two == str2) - return (isnum ? 1 : -1); - - if (isnum) - { - int onelen, twolen; - - while (*one == '0') - one++; - - while (*two == '0') - two++; - - onelen = strlen(one); - twolen = strlen(two); - - if (onelen > twolen) - return 1; - else if (twolen > onelen) - return -1; - } - - ret = strcmp(one, two); - if (ret != 0) - return ret < 0 ? -1 : 1; - - *str1 = oldch1; - *str2 = oldch2; - - one = str1; - two = str2; - } - - if ((!*one) && (!*two)) - return 0; - - if (!*one) - return -1; - - return 1; -} - -static pkgconf_pkg_t pkg_config_virtual = { - .id = "pkg-config", - .realname = "pkg-config", - .description = "virtual package defining pkg-config API version supported", - .url = PACKAGE_BUGREPORT, - .version = PACKAGE_VERSION, - .flags = PKGCONF_PKG_PROPF_STATIC, - .vars = { - .head = &(pkgconf_node_t){ - .next = &(pkgconf_node_t){ - .next = &(pkgconf_node_t){ - .data = &(pkgconf_tuple_t){ - .key = "pc_system_libdirs", - .value = SYSTEM_LIBDIR, - } - }, - .data = &(pkgconf_tuple_t){ - .key = "pc_system_includedirs", - .value = SYSTEM_INCLUDEDIR, - } - }, - .data = &(pkgconf_tuple_t){ - .key = "pc_path", - .value = PKG_DEFAULT_PATH, - }, - }, - .tail = NULL, - } -}; - -static pkgconf_pkg_t pkgconf_virtual = { - .id = "pkgconf", - .realname = "pkgconf", - .description = "virtual package defining pkgconf API version supported", - .url = PACKAGE_BUGREPORT, - .version = PACKAGE_VERSION, - .license = "ISC", - .flags = PKGCONF_PKG_PROPF_STATIC, - .vars = { - .head = &(pkgconf_node_t){ - .next = &(pkgconf_node_t){ - .next = &(pkgconf_node_t){ - .data = &(pkgconf_tuple_t){ - .key = "pc_system_libdirs", - .value = SYSTEM_LIBDIR, - } - }, - .data = &(pkgconf_tuple_t){ - .key = "pc_system_includedirs", - .value = SYSTEM_INCLUDEDIR, - } - }, - .data = &(pkgconf_tuple_t){ - .key = "pc_path", - .value = PKG_DEFAULT_PATH, - }, - }, - .tail = NULL, - }, -}; - -typedef struct { - const char *name; - pkgconf_pkg_t *pkg; -} pkgconf_builtin_pkg_pair_t; - -/* keep these in alphabetical order */ -static const pkgconf_builtin_pkg_pair_t pkgconf_builtin_pkg_pair_set[] = { - {"pkg-config", &pkg_config_virtual}, - {"pkgconf", &pkgconf_virtual}, -}; - -static int pkgconf_builtin_pkg_pair_cmp(const void *key, const void *ptr) -{ - const pkgconf_builtin_pkg_pair_t *pair = ptr; - return strcasecmp(key, pair->name); -} - -/* - * !doc - * - * .. c:function:: pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name) - * - * Looks up a built-in package. The package should not be freed or dereferenced. - * - * :param char* name: An atom corresponding to a built-in package to search for. - * :return: the built-in package if present, else ``NULL``. - * :rtype: pkgconf_pkg_t * - */ -pkgconf_pkg_t * -pkgconf_builtin_pkg_get(const char *name) -{ - const pkgconf_builtin_pkg_pair_t *pair = bsearch(name, pkgconf_builtin_pkg_pair_set, - PKGCONF_ARRAY_SIZE(pkgconf_builtin_pkg_pair_set), sizeof(pkgconf_builtin_pkg_pair_t), - pkgconf_builtin_pkg_pair_cmp); - - return (pair != NULL) ? pair->pkg : NULL; -} - typedef bool (*pkgconf_vercmp_res_func_t)(const char *a, const char *b); typedef struct { @@ -1249,6 +1310,9 @@ pkgconf_pkg_get_comparator(const pkgconf pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name) { + if (name == NULL) + return PKGCONF_CMP_ANY; + const pkgconf_pkg_comparator_pair_t *p = bsearch(name, pkgconf_pkg_comparator_names, PKGCONF_ARRAY_SIZE(pkgconf_pkg_comparator_names), sizeof(pkgconf_pkg_comparator_pair_t), pkgconf_pkg_comparator_pair_namecmp); @@ -1269,10 +1333,10 @@ static const pkgconf_pkg_provides_vermat [PKGCONF_CMP_ANY] = { .rulecmp = { [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, - }, + }, .depcmp = { [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, - }, + }, }, [PKGCONF_CMP_LESS_THAN] = { .rulecmp = { @@ -1346,7 +1410,7 @@ static const pkgconf_pkg_provides_vermat }, .depcmp = { [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, - }, + }, }, [PKGCONF_CMP_NOT_EQUAL] = { .rulecmp = { @@ -1360,7 +1424,7 @@ static const pkgconf_pkg_provides_vermat }, .depcmp = { [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, - }, + }, }, }; @@ -1377,11 +1441,11 @@ pkgconf_pkg_scan_provides_vercmp(const p const pkgconf_pkg_provides_vermatch_rule_t *rule = &pkgconf_pkg_provides_vermatch_rules[pkgdep->compare]; if (rule->depcmp[provider->compare] != NULL && - !rule->depcmp[provider->compare](provider->version, pkgdep->version)) + !rule->depcmp[provider->compare](provider->version, pkgdep->version)) return false; if (rule->rulecmp[provider->compare] != NULL && - !rule->rulecmp[provider->compare](pkgdep->version, provider->version)) + !rule->rulecmp[provider->compare](pkgdep->version, provider->version)) return false; return true; @@ -1393,8 +1457,9 @@ pkgconf_pkg_scan_provides_vercmp(const p * attempt to match a single package's Provides rules against the requested dependency node. */ static bool -pkgconf_pkg_scan_provides_entry(const pkgconf_pkg_t *pkg, const pkgconf_pkg_scan_providers_ctx_t *ctx) +pkgconf_pkg_scan_provides_entry(const pkgconf_pkg_t *pkg, void *data) { + const pkgconf_pkg_scan_providers_ctx_t *ctx = data; const pkgconf_dependency_t *pkgdep = ctx->pkgdep; pkgconf_node_t *node; @@ -1421,7 +1486,7 @@ pkgconf_pkg_scan_providers(pkgconf_clien .pkgdep = pkgdep, }; - pkg = pkgconf_scan_all(client, &ctx, (pkgconf_pkg_iteration_func_t) pkgconf_pkg_scan_provides_entry); + pkg = pkgconf_scan_all(client, &ctx, pkgconf_pkg_scan_provides_entry); if (pkg != NULL) { pkgdep->match = pkgconf_pkg_ref(client, pkg); @@ -1522,7 +1587,7 @@ pkgconf_pkg_report_graph_error(pkgconf_c { if (eflags & PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND) { - if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS) & !client->already_sent_notice) + if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS) && !client->already_sent_notice) { pkgconf_error(client, "Package %s was not found in the pkg-config search path.\n", node->package); pkgconf_error(client, "Perhaps you should add the directory containing `%s.pc'\n", node->package); @@ -1553,6 +1618,18 @@ pkgconf_pkg_report_graph_error(pkgconf_c return eflags; } +static inline bool +missing_node_is_tolerable(const pkgconf_client_t *client, const pkgconf_dependency_t *dep) +{ + if (!(dep->flags & PKGCONF_PKG_DEPF_INTERNAL)) + return false; + + if ((client->flags & PKGCONF_PKG_PKGF_REQUIRE_INTERNAL)) + return false; + + return true; +} + static inline unsigned int pkgconf_pkg_walk_list(pkgconf_client_t *client, pkgconf_pkg_t *parent, @@ -1560,7 +1637,8 @@ pkgconf_pkg_walk_list(pkgconf_client_t * pkgconf_pkg_traverse_func_t func, void *data, int depth, - unsigned int skip_flags) + unsigned int skip_flags, + unsigned int iter_flags) { unsigned int eflags = PKGCONF_PKG_ERRF_OK; pkgconf_node_t *node, *next; @@ -1577,20 +1655,22 @@ pkgconf_pkg_walk_list(pkgconf_client_t * continue; pkgdep = pkgconf_pkg_verify_dependency(client, depnode, &eflags_local); - - eflags |= eflags_local; - if (eflags_local != PKGCONF_PKG_ERRF_OK && !(client->flags & PKGCONF_PKG_PKGF_SKIP_ERRORS)) + if (eflags_local != PKGCONF_PKG_ERRF_OK) { - pkgconf_pkg_report_graph_error(client, parent, pkgdep, depnode, eflags_local); + if (missing_node_is_tolerable(client, depnode)) + continue; + + if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_ERRORS)) + pkgconf_pkg_report_graph_error(client, parent, pkgdep, depnode, eflags_local); + + eflags |= eflags_local; continue; } - if (pkgdep == NULL) - continue; if((pkgdep->flags & PKGCONF_PKG_PROPF_ANCESTOR) != 0) { /* In this case we have a circular reference. - * We break that by deleteing the circular node from the + * We break that by deleting the circular node from the * the list, so that we dont create a situation where * memory is leaked due to circular ownership. * i.e: A owns B owns A @@ -1599,10 +1679,11 @@ pkgconf_pkg_walk_list(pkgconf_client_t * * lists causes problems. Find a way to refactor the Requires.private list out. */ if (!(depnode->flags & PKGCONF_PKG_DEPF_PRIVATE) && - !(parent->flags & PKGCONF_PKG_PROPF_VIRTUAL)) + !(depnode->flags & PKGCONF_PKG_DEPF_SHARED) && + !(parent->flags & PKGCONF_PKG_PROPF_VIRTUAL)) { pkgconf_warn(client, "%s: breaking circular reference (%s -> %s -> %s)\n", - parent->id, parent->id, pkgdep->id, parent->id); + parent->id, parent->id, pkgdep->id, parent->id); pkgconf_node_delete(node, deplist); pkgconf_dependency_unref(client, depnode); @@ -1616,7 +1697,7 @@ pkgconf_pkg_walk_list(pkgconf_client_t * pkgconf_audit_log_dependency(client, pkgdep, depnode); - eflags |= pkgconf_pkg_traverse_main(client, pkgdep, func, data, depth - 1, skip_flags); + eflags |= pkgconf_pkg_traverse_main(client, pkgdep, func, data, depth - 1, skip_flags, iter_flags); next: pkgconf_pkg_unref(client, pkgdep); } @@ -1626,7 +1707,7 @@ next: return eflags; } -static inline unsigned int +unsigned int pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *deplist) { @@ -1652,7 +1733,7 @@ pkgconf_pkg_walk_conflicts_list(pkgconf_ if (eflags == PKGCONF_PKG_ERRF_OK) { pkgconf_error(client, "Version '%s' of '%s' conflicts with '%s' due to satisfying conflict rule '%s %s%s%s'.\n", - pkgdep->version, pkgdep->realname, root->realname, parentnode->package, pkgconf_pkg_get_comparator(parentnode), + pkgdep->version, pkgdep->id, parentnode->why, parentnode->package, pkgconf_pkg_get_comparator(parentnode), parentnode->version != NULL ? " " : "", parentnode->version != NULL ? parentnode->version : ""); if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS)) @@ -1676,7 +1757,7 @@ pkgconf_pkg_walk_conflicts_list(pkgconf_ /* * !doc * - * .. c:function:: unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags) + * .. c:function:: unsigned int pkgconf_pkg_traverse_main(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags) * * Walk and resolve the dependency graph up to `maxdepth` levels. * @@ -1695,7 +1776,8 @@ pkgconf_pkg_traverse_main(pkgconf_client pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, - unsigned int skip_flags) + unsigned int skip_flags, + unsigned int iter_flags) { unsigned int eflags = PKGCONF_PKG_ERRF_OK; @@ -1712,12 +1794,12 @@ pkgconf_pkg_traverse_main(pkgconf_client if (root->identifier == 0) root->identifier = ++client->identifier; - PKGCONF_TRACE(client, "%s: level %d, serial %"PRIu64, root->id, maxdepth, client->serial); + PKGCONF_TRACE(client, "%s: level %d, serial %llu", root->id, maxdepth, (unsigned long long) client->serial); if ((root->flags & PKGCONF_PKG_PROPF_VIRTUAL) != PKGCONF_PKG_PROPF_VIRTUAL || (client->flags & PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL) != PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL) { if (func != NULL) - func(client, root, data); + func(client, root, data, iter_flags); } if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS) && root->conflicts.head != NULL) @@ -1730,17 +1812,22 @@ pkgconf_pkg_traverse_main(pkgconf_client } PKGCONF_TRACE(client, "%s: walking 'Requires' list", root->id); - eflags = pkgconf_pkg_walk_list(client, root, &root->required, func, data, maxdepth, skip_flags); + eflags = pkgconf_pkg_walk_list(client, root, &root->required, func, data, maxdepth, skip_flags, iter_flags); if (eflags != PKGCONF_PKG_ERRF_OK) return eflags; - PKGCONF_TRACE(client, "%s: walking 'Requires.private' list", root->id); + if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)) + { + PKGCONF_TRACE(client, "%s: walking 'Requires.shared' list", root->id); + + eflags = pkgconf_pkg_walk_list(client, root, &root->requires_shared, func, data, maxdepth, skip_flags, iter_flags); + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + } - /* XXX: ugly */ - client->flags |= PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; - eflags = pkgconf_pkg_walk_list(client, root, &root->requires_private, func, data, maxdepth, skip_flags); - client->flags &= ~PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; + PKGCONF_TRACE(client, "%s: walking 'Requires.private' list", root->id); + eflags = pkgconf_pkg_walk_list(client, root, &root->requires_private, func, data, maxdepth, skip_flags, iter_flags | PKGCONF_PKG_ITERF_PRIVATE); if (eflags != PKGCONF_PKG_ERRF_OK) return eflags; @@ -1755,38 +1842,62 @@ pkgconf_pkg_traverse(pkgconf_client_t *c int maxdepth, unsigned int skip_flags) { - if (root->flags & PKGCONF_PKG_PROPF_VIRTUAL) - client->serial++; + client->serial++; if ((client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) == 0) + { skip_flags |= PKGCONF_PKG_DEPF_PRIVATE; + } + + if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) + // Skip shared deps in static mode + skip_flags |= PKGCONF_PKG_DEPF_SHARED; - return pkgconf_pkg_traverse_main(client, root, func, data, maxdepth, skip_flags); + return pkgconf_pkg_traverse_main(client, root, func, data, maxdepth, skip_flags, PKGCONF_PKG_ITERF_NONE); } static void -pkgconf_pkg_cflags_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +pkgconf_pkg_cflags_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) { - pkgconf_list_t *list = data; + pkgconf_fragment_cursor_t *cursor = data; pkgconf_node_t *node; + (void) iter_flags; + PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags.head, node) { pkgconf_fragment_t *frag = node->data; - pkgconf_fragment_copy(client, list, frag, false); + pkgconf_fragment_copy_cursor(client, cursor, frag, false); } } static void -pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) { - pkgconf_list_t *list = data; + pkgconf_fragment_cursor_t *cursor = data; pkgconf_node_t *node; + (void) iter_flags; + PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_private.head, node) { pkgconf_fragment_t *frag = node->data; - pkgconf_fragment_copy(client, list, frag, true); + pkgconf_fragment_copy_cursor(client, cursor, frag, true); + } +} + +static void +pkgconf_pkg_cflags_shared_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + pkgconf_fragment_cursor_t *cursor = data; + pkgconf_node_t *node; + + (void) iter_flags; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_shared.head, node) + { + pkgconf_fragment_t *frag = node->data; + pkgconf_fragment_copy_cursor(client, cursor, frag, true); } } @@ -1810,11 +1921,25 @@ pkgconf_pkg_cflags(pkgconf_client_t *cli unsigned int eflag; unsigned int skip_flags = (client->flags & PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS) == 0 ? PKGCONF_PKG_DEPF_INTERNAL : 0; pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + pkgconf_fragment_cursor_t cursor; + + pkgconf_fragment_cursor_init(&cursor, &frags); + + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &cursor, maxdepth, skip_flags); - eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &frags, maxdepth, skip_flags); + if (eflag == PKGCONF_PKG_ERRF_OK) + { + if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) + { + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_private_collect, &cursor, maxdepth, skip_flags); + } + else + { + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_shared_collect, &cursor, maxdepth, skip_flags); + } + } - if (eflag == PKGCONF_PKG_ERRF_OK && client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) - eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_private_collect, &frags, maxdepth, skip_flags); + pkgconf_fragment_cursor_deinit(&cursor); if (eflag != PKGCONF_PKG_ERRF_OK) { @@ -1822,16 +1947,15 @@ pkgconf_pkg_cflags(pkgconf_client_t *cli return eflag; } - pkgconf_fragment_copy_list(client, list, &frags); - pkgconf_fragment_free(&frags); + pkgconf_list_splice(list, &frags); return eflag; } static void -pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) { - pkgconf_list_t *list = data; + pkgconf_fragment_cursor_t *cursor = data; pkgconf_node_t *node; if (!(client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) && pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) @@ -1840,7 +1964,7 @@ pkgconf_pkg_libs_collect(pkgconf_client_ PKGCONF_FOREACH_LIST_ENTRY(pkg->libs.head, node) { pkgconf_fragment_t *frag = node->data; - pkgconf_fragment_copy(client, list, frag, (client->flags & PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE) != 0); + pkgconf_fragment_copy_cursor(client, cursor, frag, (iter_flags & PKGCONF_PKG_ITERF_PRIVATE) != 0); } if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) @@ -1848,7 +1972,15 @@ pkgconf_pkg_libs_collect(pkgconf_client_ PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_private.head, node) { pkgconf_fragment_t *frag = node->data; - pkgconf_fragment_copy(client, list, frag, true); + pkgconf_fragment_copy_cursor(client, cursor, frag, true); + } + } + else + { + PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_shared.head, node) + { + pkgconf_fragment_t *frag = node->data; + pkgconf_fragment_copy_cursor(client, cursor, frag, true); } } } @@ -1871,12 +2003,85 @@ unsigned int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) { unsigned int eflag; + pkgconf_fragment_cursor_t cursor; - eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_libs_collect, list, maxdepth, 0); + pkgconf_fragment_cursor_init(&cursor, list); + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_libs_collect, &cursor, maxdepth, 0); + pkgconf_fragment_cursor_deinit(&cursor); if (eflag != PKGCONF_PKG_ERRF_OK) { pkgconf_fragment_free(list); + return eflag; + } + + return eflag; +} + +static void +pkgconf_pkg_link_abi_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) +{ + pkgconf_list_t *list = data; + pkgconf_node_t *node; + + (void) iter_flags; + + if (!(client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) && pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) + return; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->link_abi.head, node) + { + pkgconf_bufferset_t *tag = node->data; + pkgconf_node_t *iter; + bool seen = false; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + pkgconf_bufferset_t *existing = iter->data; + + if (pkgconf_buffer_match(&existing->buffer, &tag->buffer)) + { + seen = true; + break; + } + } + + if (!seen) + pkgconf_bufferset_extend(list, &tag->buffer); + } +} + +/* + * !doc + * + * .. c:function:: int pkgconf_pkg_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) + * + * Walks a dependency graph and collects the union of ``Link.ABI`` tags. + * + * The tags describe the ABI a consumer must link the module against. They + * are gathered over the same closure as ``LIBS``: the module's own tags and + * those of its public ``Requires`` always contribute, while ``Requires.private`` + * tags contribute only when private dependencies are being linked (i.e. a + * static link). Unlike a runtime library load, ABI compatibility of the + * exposed interface applies equally to shared and static linking. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* root: The root of the dependency graph. + * :param pkgconf_list_t* list: The bufferset list to add the collected ``Link.ABI`` tags to. + * :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion. + * :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code. + * :rtype: unsigned int + */ +unsigned int +pkgconf_pkg_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) +{ + unsigned int eflag; + + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_link_abi_collect, list, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + { + pkgconf_bufferset_free(list); return eflag; } Index: libpkgconf/queue.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/queue.c,v diff -u -p -r1.1.1.1 queue.c --- libpkgconf/queue.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/queue.c 4 Aug 2026 16:20:54 -0000 @@ -2,6 +2,8 @@ * queue.c * compilation of a list of packages into a world dependency set * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2012 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -32,6 +34,46 @@ /* * !doc * + * .. c:function:: void pkgconf_queue_push_dependency(pkgconf_list_t *list, const pkgconf_dependency_t *dep) + * + * Pushes a requested dependency onto the dependency resolver's queue which is described by + * a pkgconf_dependency_t node. + * + * :param pkgconf_list_t* list: the dependency resolution queue to add the package request to. + * :param pkgconf_dependency_t* dep: the dependency requested + * :return: nothing + */ +void +pkgconf_queue_push_dependency(pkgconf_list_t *list, const pkgconf_dependency_t *dep) +{ + pkgconf_buffer_t depbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_queue_t *pkgq = calloc(1, sizeof(pkgconf_queue_t)); + + if (pkgq == NULL) + return; + + if (!pkgconf_buffer_append(&depbuf, dep->package) || + (dep->version != NULL && + !pkgconf_buffer_append_fmt(&depbuf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version))) + { + pkgconf_buffer_finalize(&depbuf); + free(pkgq); + return; + } + + pkgq->package = pkgconf_buffer_freeze(&depbuf); + if (pkgq->package == NULL) + { + free(pkgq); + return; + } + + pkgconf_node_insert_tail(&pkgq->iter, pkgq, list); +} + +/* + * !doc + * * .. c:function:: void pkgconf_queue_push(pkgconf_list_t *list, const char *package) * * Pushes a requested dependency onto the dependency resolver's queue. @@ -45,7 +87,16 @@ pkgconf_queue_push(pkgconf_list_t *list, { pkgconf_queue_t *pkgq = calloc(1, sizeof(pkgconf_queue_t)); + if (pkgq == NULL) + return; + pkgq->package = strdup(package); + if (pkgq->package == NULL) + { + free(pkgq); + return; + } + pkgconf_node_insert_tail(&pkgq->iter, pkgq, list); } @@ -103,8 +154,10 @@ pkgconf_queue_free(pkgconf_list_t *list) } static void -pkgconf_queue_mark_public(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +pkgconf_queue_mark_public(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags) { + (void) iter_flags; + if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) { pkgconf_list_t *list = data; @@ -127,13 +180,15 @@ static unsigned int pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client, pkgconf_pkg_t *root, void *data, - int maxdepth); + int maxdepth, + unsigned int iter_flags); static inline unsigned int pkgconf_queue_collect_dependencies_walk(pkgconf_client_t *client, pkgconf_list_t *deplist, void *data, - int depth) + int depth, + unsigned int iter_flags) { unsigned int eflags = PKGCONF_PKG_ERRF_OK; pkgconf_node_t *node; @@ -157,14 +212,20 @@ pkgconf_queue_collect_dependencies_walk( if (pkg->serial == client->serial) continue; - if (client->flags & PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE) + if (iter_flags & PKGCONF_PKG_ITERF_PRIVATE) pkg->flags |= PKGCONF_PKG_PROPF_VISITED_PRIVATE; else pkg->flags &= ~PKGCONF_PKG_PROPF_VISITED_PRIVATE; - eflags |= pkgconf_queue_collect_dependencies_main(client, pkg, data, depth - 1); + eflags |= pkgconf_queue_collect_dependencies_main(client, pkg, data, depth - 1, iter_flags); flattened_dep = pkgconf_dependency_copy(client, dep); + if (flattened_dep == NULL) + { + eflags |= PKGCONF_PKG_ERRF_DEPGRAPH_BREAK; + continue; + } + pkgconf_node_insert(&flattened_dep->iter, flattened_dep, &world->required); } @@ -175,7 +236,8 @@ static unsigned int pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client, pkgconf_pkg_t *root, void *data, - int maxdepth) + int maxdepth, + unsigned int iter_flags) { unsigned int eflags = PKGCONF_PKG_ERRF_OK; @@ -189,19 +251,24 @@ pkgconf_queue_collect_dependencies_main( root->serial = client->serial; + if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)) + { + PKGCONF_TRACE(client, "%s: collecting shared dependencies, level %d", root->id, maxdepth); + + eflags = pkgconf_queue_collect_dependencies_walk(client, &root->requires_shared, data, maxdepth, iter_flags); + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + } + PKGCONF_TRACE(client, "%s: collecting private dependencies, level %d", root->id, maxdepth); - /* XXX: ugly */ - const unsigned int saved_flags = client->flags; - client->flags |= PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; - eflags = pkgconf_queue_collect_dependencies_walk(client, &root->requires_private, data, maxdepth); - client->flags = saved_flags; + eflags = pkgconf_queue_collect_dependencies_walk(client, &root->requires_private, data, maxdepth, iter_flags | PKGCONF_PKG_ITERF_PRIVATE); if (eflags != PKGCONF_PKG_ERRF_OK) return eflags; PKGCONF_TRACE(client, "%s: collecting public dependencies, level %d", root->id, maxdepth); - eflags = pkgconf_queue_collect_dependencies_walk(client, &root->required, data, maxdepth); + eflags = pkgconf_queue_collect_dependencies_walk(client, &root->required, data, maxdepth, iter_flags); if (eflags != PKGCONF_PKG_ERRF_OK) return eflags; @@ -217,7 +284,58 @@ pkgconf_queue_collect_dependencies(pkgco int maxdepth) { ++client->serial; - return pkgconf_queue_collect_dependencies_main(client, root, data, maxdepth); + return pkgconf_queue_collect_dependencies_main(client, root, data, maxdepth, PKGCONF_PKG_ITERF_NONE); +} + +static inline unsigned int +pkgconf_queue_collect_conflicts(pkgconf_client_t *client, + pkgconf_pkg_t *root, + pkgconf_pkg_t *world, + int maxdepth) +{ + unsigned int eflags = PKGCONF_PKG_ERRF_OK; + pkgconf_node_t *node; + + PKGCONF_TRACE(client, "%s: collecting conflicts, level %d", root->id, maxdepth); + + PKGCONF_FOREACH_LIST_ENTRY(root->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *pkg = dep->match; + pkgconf_node_t *cnode; + + if (*dep->package == '\0') + continue; + + if (pkg == NULL) + { + PKGCONF_TRACE(client, "WTF: unmatched dependency %p <%s>", dep, dep->package); + continue; + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->conflicts.head, cnode) + { + pkgconf_dependency_t *conflict = cnode->data; + pkgconf_dependency_t *flattened_conflict = pkgconf_dependency_copy(client, conflict); + if (flattened_conflict == NULL) + { + eflags |= PKGCONF_PKG_ERRF_DEPGRAPH_BREAK; + continue; + } + + flattened_conflict->why = strdup(pkg->id); + if (flattened_conflict->why == NULL) + { + eflags |= PKGCONF_PKG_ERRF_DEPGRAPH_BREAK; + pkgconf_dependency_unref(client, flattened_conflict); + continue; + } + + pkgconf_node_insert(&flattened_conflict->iter, flattened_conflict, &world->conflicts); + } + } + + return eflags; } static inline unsigned int @@ -253,6 +371,13 @@ pkgconf_queue_verify(pkgconf_client_t *c return result; } + result = pkgconf_queue_collect_conflicts(client, world, world, maxdepth); + if (result != PKGCONF_PKG_ERRF_OK) + { + pkgconf_solution_free(client, &initial_world); + return result; + } + if (client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) { PKGCONF_TRACE(client, "marking public deps"); @@ -267,6 +392,18 @@ pkgconf_queue_verify(pkgconf_client_t *c } } + if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS)) + { + PKGCONF_TRACE(client, "checking for conflicts"); + + result = pkgconf_pkg_walk_conflicts_list(client, world, &world->conflicts); + if (result != PKGCONF_PKG_ERRF_OK) + { + pkgconf_solution_free(client, &initial_world); + return result; + } + } + /* free the initial solution */ pkgconf_solution_free(client, &initial_world); @@ -293,6 +430,7 @@ pkgconf_solution_free(pkgconf_client_t * { pkgconf_dependency_free(&world->required); pkgconf_dependency_free(&world->requires_private); + pkgconf_dependency_free(&world->conflicts); } } Index: libpkgconf/stdinc.h =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/stdinc.h,v diff -u -p -r1.2 stdinc.h --- libpkgconf/stdinc.h 9 May 2025 06:07:51 -0000 1.2 +++ libpkgconf/stdinc.h 4 Aug 2026 16:20:54 -0000 @@ -2,6 +2,8 @@ * stdinc.h * pull in standard headers (including portability hacks) * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2012 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -16,6 +18,11 @@ #ifndef LIBPKGCONF_STDINC_H #define LIBPKGCONF_STDINC_H +/* make POSIX/BSD declarations (e.g. strdup) visible even under a strict -std= */ +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif + #include #include #include @@ -26,16 +33,35 @@ #include #include #include +#include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include # include +# include /* for _setmode() */ +# include # define PATH_DEV_NULL "nul" -# ifdef _WIN64 -# define SIZE_FMT_SPECIFIER "%I64u" +# ifdef _MSC_VER +# if _MSC_VER >= 1900 +# define SIZE_FMT_SPECIFIER "%zu" +# else +# ifdef _WIN64 +# define SIZE_FMT_SPECIFIER "%I64u" +# else +# define SIZE_FMT_SPECIFIER "%u" +# endif +# endif # else -# define SIZE_FMT_SPECIFIER "%u" +# ifdef _WIN64 +# ifndef __MINGW32__ +# define SIZE_FMT_SPECIFIER "%I64u" +# else +# define SIZE_FMT_SPECIFIER "%llu" +# endif +# else +# define SIZE_FMT_SPECIFIER "%u" +# endif # endif # ifndef ssize_t # ifndef __MINGW32__ @@ -48,9 +74,14 @@ # ifndef __MINGW32__ # include "win-dirent.h" # else -# include +# include # endif # define PKGCONF_ITEM_SIZE (_MAX_PATH + 1024) +# define PKG_CONFIG_PATH_SEP_S ";" +# define PKG_DIR_SEP_S '\\' +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# define realpath(N,R) _fullpath((R),(N),_MAX_PATH) #else # define PATH_DEV_NULL "/dev/null" # define SIZE_FMT_SPECIFIER "%zu" @@ -59,13 +90,19 @@ # endif # include # include -# include # include +# include // open +# include // basename/dirname +# include // lstat, S_ISLNK +# include // close, readlinkat +# include # ifdef PATH_MAX # define PKGCONF_ITEM_SIZE (PATH_MAX + 1024) # else # define PKGCONF_ITEM_SIZE (4096 + 1024) # endif +# define PKG_CONFIG_PATH_SEP_S ":" +# define PKG_DIR_SEP_S '/' #endif #endif Index: libpkgconf/tuple.c =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/tuple.c,v diff -u -p -r1.1.1.1 tuple.c --- libpkgconf/tuple.c 3 May 2025 15:09:39 -0000 1.1.1.1 +++ libpkgconf/tuple.c 4 Aug 2026 16:30:47 -0000 @@ -2,6 +2,8 @@ * tuple.c * management of key->value tuples * + * SPDX-License-Identifier: pkgconf + * * Copyright (c) 2011, 2012 pkgconf authors (see AUTHORS). * * Permission to use, copy, modify, and/or distribute this software for any @@ -48,22 +50,6 @@ pkgconf_tuple_add_global(pkgconf_client_ pkgconf_tuple_add(client, &client->global_vars, key, value, false, 0); } -static pkgconf_tuple_t * -lookup_global_tuple(const pkgconf_client_t *client, const char *key) -{ - pkgconf_node_t *node; - - PKGCONF_FOREACH_LIST_ENTRY(client->global_vars.head, node) - { - pkgconf_tuple_t *tuple = node->data; - - if (!strcmp(tuple->key, key)) - return tuple; - } - - return NULL; -} - /* * !doc * @@ -76,16 +62,21 @@ lookup_global_tuple(const pkgconf_client * :return: the contents of the variable or ``NULL`` * :rtype: char * */ -char * -pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key) +const char * +pkgconf_tuple_find_global(pkgconf_client_t *client, const char *key) { - pkgconf_tuple_t *tuple; + pkgconf_variable_t *v; + bool saw_sysroot = false; - tuple = lookup_global_tuple(client, key); - if (tuple == NULL) + if (client == NULL || key == NULL) return NULL; - return tuple->value; + v = pkgconf_variable_find(&client->global_vars, key); + + pkgconf_buffer_rewind(&client->_scratch_buffer); + (void) pkgconf_variable_eval(client, &client->global_vars, v, &client->_scratch_buffer, &saw_sysroot); + + return pkgconf_buffer_str_or_empty(&client->_scratch_buffer); } /* @@ -122,6 +113,9 @@ pkgconf_tuple_define_global(pkgconf_clie char *value; pkgconf_tuple_t *tuple; + if (workbuf == NULL) + goto out; + value = strchr(workbuf, '='); if (value == NULL) goto out; @@ -136,23 +130,6 @@ out: free(workbuf); } -static void -pkgconf_tuple_find_delete(pkgconf_list_t *list, const char *key) -{ - pkgconf_node_t *node, *next; - - PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) - { - pkgconf_tuple_t *tuple = node->data; - - if (!strcmp(tuple->key, key)) - { - pkgconf_tuple_free_entry(tuple, list); - return; - } - } -} - static char * dequote(const char *value) { @@ -161,6 +138,9 @@ dequote(const char *value) const char *i; char quote = 0; + if (buf == NULL) + return NULL; + if (*value == '\'' || *value == '"') quote = *value; @@ -178,51 +158,12 @@ dequote(const char *value) return buf; } -static const char * -find_sysroot(const pkgconf_client_t *client, pkgconf_list_t *vars) -{ - const char *sysroot_dir; - - sysroot_dir = pkgconf_tuple_find(client, vars, "pc_sysrootdir"); - if (sysroot_dir == NULL) - sysroot_dir = client->sysroot_dir; - - return sysroot_dir; -} - -static bool -should_rewrite_sysroot(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *buf, unsigned int flags) -{ - const char *sysroot_dir; - - if (flags & PKGCONF_PKG_PROPF_UNINSTALLED && !(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES)) - return false; - - sysroot_dir = find_sysroot(client, vars); - if (sysroot_dir == NULL) - return false; - - if (*buf != '/') - return false; - - if (!strcmp(sysroot_dir, "/")) - return false; - - if (strlen(buf) <= strlen(sysroot_dir)) - return false; - - if (strstr(buf + strlen(sysroot_dir), sysroot_dir) == NULL) - return false; - - return true; -} - /* * !doc * * .. c:function:: pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse) * - * Optionally parse and then define a variable. + * Wrapper around pkgconf_variable_get_or_create(list, key) and bytecode compiler. * * :param pkgconf_client_t* client: The pkgconf client object to access. * :param pkgconf_list_t* list: The variable list to add the new variable to. @@ -236,200 +177,122 @@ pkgconf_tuple_t * pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse, unsigned int flags) { char *dequote_value; - pkgconf_tuple_t *tuple = calloc(1, sizeof(pkgconf_tuple_t)); + pkgconf_buffer_t rhs_bcbuf = PKGCONF_BUFFER_INITIALIZER; - pkgconf_tuple_find_delete(list, key); + (void) client; + + if (list == NULL || key == NULL || value == NULL) + return NULL; dequote_value = dequote(value); + if (dequote_value == NULL) + return NULL; - tuple->key = strdup(key); - if (parse) - tuple->value = pkgconf_tuple_parse(client, list, dequote_value, flags); - else - tuple->value = strdup(dequote_value); + pkgconf_variable_t *v = pkgconf_variable_get_or_create(list, key); + if (v == NULL) + { + free(dequote_value); + return NULL; + } + + if (!parse) + { + if (!pkgconf_bytecode_emit_text(&rhs_bcbuf, dequote_value, strlen(dequote_value))) + { + pkgconf_buffer_finalize(&rhs_bcbuf); + free(dequote_value); + return NULL; + } - PKGCONF_TRACE(client, "adding tuple to @%p: %s => %s (parsed? %d)", list, key, tuple->value, parse); + free(dequote_value); + } + else + { + if (!pkgconf_bytecode_compile(&rhs_bcbuf, dequote_value)) + { + pkgconf_buffer_finalize(&rhs_bcbuf); + free(dequote_value); + return NULL; + } - pkgconf_node_insert(&tuple->iter, tuple, list); + free(dequote_value); + } - free(dequote_value); + /* ugh, we are doing var=${var}/foo stuff */ + if (parse && pkgconf_bytecode_references_var(&rhs_bcbuf, key)) + { + pkgconf_buffer_t old_bcbuf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_t new_bcbuf = PKGCONF_BUFFER_INITIALIZER; - return tuple; -} + /* preserve the old bytecode */ + if (!pkgconf_buffer_copy(&v->bcbuf, &old_bcbuf)) + { + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&rhs_bcbuf); + return NULL; + } -/* - * !doc - * - * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) - * - * Look up a variable in a variable list. - * - * :param pkgconf_client_t* client: The pkgconf client object to access. - * :param pkgconf_list_t* list: The variable list to search. - * :param char* key: The variable name to search for. - * :return: the value of the variable or ``NULL`` - * :rtype: char * - */ -char * -pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) -{ - pkgconf_node_t *node; - pkgconf_tuple_t *global_tuple; + /* splice the selfrefs, using the old bytecode instead of ${var} */ + if (!pkgconf_bytecode_rewrite_selfrefs(&new_bcbuf, &rhs_bcbuf, key, &old_bcbuf)) + { + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&new_bcbuf); + pkgconf_buffer_finalize(&rhs_bcbuf); - global_tuple = lookup_global_tuple(client, key); - if (global_tuple != NULL && global_tuple->flags & PKGCONF_PKG_TUPLEF_OVERRIDE) - return global_tuple->value; + return NULL; + } - PKGCONF_FOREACH_LIST_ENTRY(list->head, node) - { - pkgconf_tuple_t *tuple = node->data; + /* copy the spliced bytecode back to &rhs_bcbuf, replacing its contents */ + if (!pkgconf_buffer_copy(&new_bcbuf, &rhs_bcbuf)) + { + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&new_bcbuf); + pkgconf_buffer_finalize(&rhs_bcbuf); + return NULL; + } - if (!strcmp(tuple->key, key)) - return tuple->value; + pkgconf_buffer_finalize(&old_bcbuf); + pkgconf_buffer_finalize(&new_bcbuf); } - if (global_tuple != NULL) - return global_tuple->value; + pkgconf_buffer_finalize(&v->bcbuf); + v->bcbuf = rhs_bcbuf; + v->flags = flags; + pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf); - return NULL; + return (pkgconf_tuple_t *) v; } /* * !doc * - * .. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags) + * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) * - * Parse an expression for variable substitution. + * Look up a variable in a variable list. * * :param pkgconf_client_t* client: The pkgconf client object to access. - * :param pkgconf_list_t* list: The variable list to search for variables (along side the global variable list). - * :param char* value: The ``key=value`` string to parse. - * :param uint flags: Any flags to consider while parsing. - * :return: the variable data with any variables substituted + * :param pkgconf_list_t* list: The variable list to search. + * :param char* key: The variable name to search for. + * :return: the value of the variable or ``NULL`` * :rtype: char * */ -char * -pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags) +const char * +pkgconf_tuple_find(pkgconf_client_t *client, pkgconf_list_t *list, const char *key) { - char buf[PKGCONF_BUFSIZE]; - const char *ptr; - char *bptr = buf; + pkgconf_variable_t *v; - if (!(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES) && - (!(flags & PKGCONF_PKG_PROPF_UNINSTALLED) || (client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES))) - { - if (*value == '/' && client->sysroot_dir != NULL && strncmp(value, client->sysroot_dir, strlen(client->sysroot_dir))) - bptr += pkgconf_strlcpy(buf, client->sysroot_dir, sizeof buf); - } - - for (ptr = value; *ptr != '\0' && bptr - buf < PKGCONF_BUFSIZE; ptr++) - { - if (*ptr != '$' || (*ptr == '$' && *(ptr + 1) != '{')) - *bptr++ = *ptr; - else if (*(ptr + 1) == '{') - { - char varname[PKGCONF_ITEM_SIZE]; - char *vend = varname + PKGCONF_ITEM_SIZE - 1; - char *vptr = varname; - const char *pptr; - char *kv, *parsekv; - - *vptr = '\0'; - - for (pptr = ptr + 2; *pptr != '\0'; pptr++) - { - if (*pptr != '}') - { - if (vptr < vend) - *vptr++ = *pptr; - else - { - *vptr = '\0'; - break; - } - } - else - { - *vptr = '\0'; - break; - } - } - - PKGCONF_TRACE(client, "lookup tuple %s", varname); - - size_t remain = PKGCONF_BUFSIZE - (bptr - buf); - ptr += (pptr - ptr); - kv = pkgconf_tuple_find_global(client, varname); - if (kv != NULL) - { - size_t nlen = pkgconf_strlcpy(bptr, kv, remain); - if (nlen > remain) - { - pkgconf_warn(client, "warning: truncating very long variable to 64KB\n"); - - bptr = buf + (PKGCONF_BUFSIZE - 1); - break; - } - - bptr += nlen; - } - else - { - kv = pkgconf_tuple_find(client, vars, varname); - - if (kv != NULL) - { - size_t nlen; - - parsekv = pkgconf_tuple_parse(client, vars, kv, flags); - nlen = pkgconf_strlcpy(bptr, parsekv, remain); - free(parsekv); - - if (nlen > remain) - { - pkgconf_warn(client, "warning: truncating very long variable to 64KB\n"); - - bptr = buf + (PKGCONF_BUFSIZE - 1); - break; - } - - bptr += nlen; - } - } - } - } - - *bptr = '\0'; + if (client == NULL || list == NULL || key == NULL) + return NULL; - /* - * Sigh. Somebody actually attempted to use freedesktop.org pkg-config's broken sysroot support, - * which was written by somebody who did not understand how sysroots are supposed to work. This - * results in an incorrect path being built as the sysroot will be prepended twice, once explicitly, - * and once by variable expansion (the pkgconf approach). We could simply make ${pc_sysrootdir} blank, - * but sometimes it is necessary to know the explicit sysroot path for other reasons, so we can't really - * do that. - * - * As a result, we check to see if ${pc_sysrootdir} is prepended as a duplicate, and if so, remove the - * prepend. This allows us to handle both our approach and the broken freedesktop.org implementation's - * approach. Because a path can be shorter than ${pc_sysrootdir}, we do some checks first to ensure it's - * safe to skip ahead in the string to scan for our sysroot dir. - * - * Finally, we call pkgconf_path_relocate() to clean the path of spurious elements. - * - * New in 1.9: Only attempt to rewrite the sysroot if we are not processing an uninstalled package. - */ - if (should_rewrite_sysroot(client, vars, buf, flags)) - { - char cleanpath[PKGCONF_ITEM_SIZE]; - const char *sysroot_dir = find_sysroot(client, vars); + v = pkgconf_variable_find(list, key); + if (v == NULL) + v = pkgconf_variable_find(&client->global_vars, key); - pkgconf_strlcpy(cleanpath, buf + strlen(sysroot_dir), sizeof cleanpath); - pkgconf_path_relocate(cleanpath, sizeof cleanpath); + pkgconf_buffer_rewind(&client->_scratch_buffer); - return strdup(cleanpath); - } + (void) pkgconf_variable_eval(client, list, v, &client->_scratch_buffer, NULL); - return strdup(buf); + return pkgconf_buffer_str_or_empty(&client->_scratch_buffer); } /* @@ -448,10 +311,7 @@ void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list) { pkgconf_node_delete(&tuple->iter, list); - - free(tuple->key); - free(tuple->value); - free(tuple); + pkgconf_variable_free(tuple); } /* Index: libpkgconf/variable.c =================================================================== RCS file: libpkgconf/variable.c diff -N libpkgconf/variable.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/variable.c 4 Aug 2026 16:30:47 -0000 @@ -0,0 +1,188 @@ +/* + * variable.c + * variable management + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `variable` module + * ============================ + * + * The libpkgconf `variable` module contains the functions related to + * managing variables. It replaces the old `tuple` module. + */ + +pkgconf_variable_t * +pkgconf_variable_new(const char *key) +{ + pkgconf_variable_t *v; + size_t keylen; + + if (key == NULL) + return NULL; + + keylen = strlen(key); + v = calloc(1, sizeof(*v) + keylen + 1); + if (v == NULL) + return NULL; + + v->key = (char *)(v + 1); + memcpy(v->key, key, keylen + 1); + + return v; +} + +void +pkgconf_variable_free(pkgconf_variable_t *v) +{ + if (v == NULL) + return; + + pkgconf_buffer_finalize(&v->bcbuf); + free(v); +} + +pkgconf_variable_t * +pkgconf_variable_find(const pkgconf_list_t *vars, const char *key) +{ + const pkgconf_node_t *n; + + if (vars == NULL || key == NULL) + return NULL; + + PKGCONF_FOREACH_LIST_ENTRY(vars->head, n) + { + pkgconf_variable_t *v = n->data; + + if (!strcmp(v->key, key)) + return v; + } + + return NULL; +} + +pkgconf_variable_t * +pkgconf_variable_get_or_create(pkgconf_list_t *vars, const char *key) +{ + pkgconf_variable_t *v; + + if (vars == NULL || key == NULL) + return NULL; + + v = pkgconf_variable_find(vars, key); + if (v != NULL) + return v; + + v = pkgconf_variable_new(key); + if (v == NULL) + return NULL; + + pkgconf_node_insert_tail(&v->iter, v, vars); + + return v; +} + +void +pkgconf_variable_delete(pkgconf_list_t *vars, pkgconf_variable_t *v) +{ + if (vars == NULL || v == NULL) + return; + + pkgconf_node_delete(&v->iter, vars); + pkgconf_variable_free(v); +} + +void +pkgconf_variable_list_free(pkgconf_list_t *vars) +{ + pkgconf_node_t *node, *tmp; + + if (vars == NULL) + return; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(vars->head, tmp, node) + { + pkgconf_variable_t *v = node->data; + + pkgconf_node_delete(node, vars); + pkgconf_variable_free(v); + } +} + +bool +pkgconf_variable_eval(pkgconf_client_t *client, + const pkgconf_list_t *tuples, + const pkgconf_variable_t *v, + pkgconf_buffer_t *out, + bool *saw_sysroot) +{ + if (client == NULL || tuples == NULL || v == NULL || out == NULL) + return false; + + return pkgconf_bytecode_eval(client, tuples, &v->bc, out, saw_sysroot); +} + +char * +pkgconf_variable_eval_str(pkgconf_client_t *client, + const pkgconf_list_t *tuples, + const pkgconf_variable_t *v, + bool *saw_sysroot) +{ + pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER; + + if (client == NULL || tuples == NULL || v == NULL) + return NULL; + + if (!pkgconf_variable_eval(client, tuples, v, &out, saw_sysroot)) + { + pkgconf_buffer_finalize(&out); + return NULL; + } + + return pkgconf_buffer_freeze(&out); +} + +char * +pkgconf_variable_eval_name(pkgconf_client_t *client, + const pkgconf_list_t *vars, + const char *varname) +{ + pkgconf_buffer_t varbuf = PKGCONF_BUFFER_INITIALIZER; + const pkgconf_buffer_t *sysroot_dir = PKGCONF_BUFFER_FROM_STR(client->sysroot_dir); + bool saw_sysroot = false; + pkgconf_variable_t *v; + + pkgconf_bytecode_eval_ctx_t ctx = { + .client = client, + .vars = vars, + }; + + v = pkgconf_bytecode_eval_lookup_var(&ctx, varname, strlen(varname)); + (void) pkgconf_variable_eval(client, vars, v, &varbuf, &saw_sysroot); + + if (!(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES) && + !saw_sysroot && pkgconf_path_is_plausible(&varbuf)) + { + /* if sysroot is set, and value does not already begin with sysroot */ + if (!pkgconf_buffer_has_prefix(&varbuf, sysroot_dir)) + pkgconf_buffer_prepend(&varbuf, pkgconf_buffer_str_or_empty(sysroot_dir)); + } + + return pkgconf_buffer_freeze(&varbuf); +} Index: libpkgconf/version.c =================================================================== RCS file: libpkgconf/version.c diff -N libpkgconf/version.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libpkgconf/version.c 4 Aug 2026 16:20:54 -0000 @@ -0,0 +1,238 @@ +/* + * version.c + * version comparison + * + * SPDX-License-Identifier: pkgconf + * + * Copyright (c) 2011-2026 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +typedef enum { + PKGCONF_VERSION_TOKEN_END = 0, + PKGCONF_VERSION_TOKEN_TILDE, + PKGCONF_VERSION_TOKEN_NUMERIC, + PKGCONF_VERSION_TOKEN_ALPHA +} pkgconf_version_token_kind_t; + +typedef struct { + pkgconf_version_token_kind_t kind; + const char *start; + const char *end; +} pkgconf_version_token_t; + +typedef struct { + const char *cur; +} pkgconf_version_iter_t; + +static inline bool +pkgconf_version_is_separator(unsigned char ch) +{ + return !isalnum(ch) && ch != '~'; +} + +static const char * +pkgconf_version_skip_separators(const char *s) +{ + while (*s && pkgconf_version_is_separator((unsigned char)*s)) + s++; + + return s; +} + +static pkgconf_version_token_t +pkgconf_version_next_token(pkgconf_version_iter_t *it) +{ + pkgconf_version_token_t tok; + const char *s = pkgconf_version_skip_separators(it->cur); + + tok.start = s; + tok.end = s; + tok.kind = PKGCONF_VERSION_TOKEN_END; + + if (*s == '\0') + { + it->cur = s; + return tok; + } + + if (*s == '~') + { + tok.kind = PKGCONF_VERSION_TOKEN_TILDE; + tok.end = s + 1; + it->cur = tok.end; + return tok; + } + + if (isdigit((unsigned char)*s)) + { + tok.kind = PKGCONF_VERSION_TOKEN_NUMERIC; + while (*tok.end && isdigit((unsigned char)*tok.end)) + tok.end++; + it->cur = tok.end; + return tok; + } + + /* + * Having skipped separators and ruled out end-of-string, tilde and + * digits, the only remaining possibility is alpha: isalnum(c) is by + * definition isalpha(c) || isdigit(c). + */ + tok.kind = PKGCONF_VERSION_TOKEN_ALPHA; + while (*tok.end && isalpha((unsigned char)*tok.end)) + tok.end++; + it->cur = tok.end; + + return tok; +} + +static int +pkgconf_version_compare_numeric(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b) +{ + const char *ap = a->start; + const char *bp = b->start; + size_t alen, blen; + int ret; + + while (ap < a->end && *ap == '0') + ap++; + + while (bp < b->end && *bp == '0') + bp++; + + alen = (size_t)(a->end - ap); + blen = (size_t)(b->end - bp); + + if (alen > blen) + return 1; + if (alen < blen) + return -1; + + if (alen == 0) + return 0; + + ret = strncmp(ap, bp, alen); + if (ret < 0) + return -1; + if (ret > 0) + return 1; + + return 0; +} + +static int +pkgconf_version_compare_alpha(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b) +{ + size_t alen = (size_t)(a->end - a->start); + size_t blen = (size_t)(b->end - b->start); + size_t len = alen < blen ? alen : blen; + int ret; + + ret = strncmp(a->start, b->start, len); + if (ret < 0) + return -1; + if (ret > 0) + return 1; + + if (alen < blen) + return -1; + if (alen > blen) + return 1; + + return 0; +} + +static int +pkgconf_version_compare_token(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b) +{ + if (a->kind == PKGCONF_VERSION_TOKEN_TILDE || b->kind == PKGCONF_VERSION_TOKEN_TILDE) + { + if (a->kind != PKGCONF_VERSION_TOKEN_TILDE) + return 1; + if (b->kind != PKGCONF_VERSION_TOKEN_TILDE) + return -1; + + return 0; + } + + if (a->kind == PKGCONF_VERSION_TOKEN_END || b->kind == PKGCONF_VERSION_TOKEN_END) + { + if (a->kind == PKGCONF_VERSION_TOKEN_END && b->kind == PKGCONF_VERSION_TOKEN_END) + return 0; + if (a->kind == PKGCONF_VERSION_TOKEN_END) + return -1; + + return 1; + } + + /* left-side is numeric, beats any right-side non-numeric */ + if (a->kind == PKGCONF_VERSION_TOKEN_NUMERIC) + { + if (b->kind != PKGCONF_VERSION_TOKEN_NUMERIC) + return 1; + + return pkgconf_version_compare_numeric(a, b); + } + + /* left-side is alpha, any right-side non-alpha wins */ + if (b->kind != PKGCONF_VERSION_TOKEN_ALPHA) + return -1; + + return pkgconf_version_compare_alpha(a, b); +} + +/* + * !doc + * + * .. c:function:: int pkgconf_compare_version(const char *a, const char *b) + * + * Compare versions using RPM version comparison rules as described in the LSB. + * + * :param char* a: The first version to compare in the pair. + * :param char* b: The second version to compare in the pair. + * :return: -1 if the first version is less than, 0 if both versions are equal, 1 if the second version is less than. + * :rtype: int + */ +int +pkgconf_compare_version(const char *a, const char *b) +{ + pkgconf_version_iter_t ia, ib; + + if (a == NULL) + return -1; + if (b == NULL) + return 1; + + if (!strcasecmp(a, b)) + return 0; + + ia.cur = a; + ib.cur = b; + + for (;;) + { + pkgconf_version_token_t ta = pkgconf_version_next_token(&ia); + pkgconf_version_token_t tb = pkgconf_version_next_token(&ib); + int ret = pkgconf_version_compare_token(&ta, &tb); + + if (ret != 0) + return ret; + + if (ta.kind == PKGCONF_VERSION_TOKEN_END && + tb.kind == PKGCONF_VERSION_TOKEN_END) + { + return 0; + } + } +} Index: man/pkgconf.1 =================================================================== RCS file: /cvs/src/usr.bin/pkgconf/man/pkgconf.1,v diff -u -p -r1.12 pkgconf.1 --- man/pkgconf.1 7 Jun 2025 14:14:25 -0000 1.12 +++ man/pkgconf.1 4 Aug 2026 16:30:47 -0000 @@ -32,6 +32,22 @@ to build software that uses the librarie .Ar module arguments. .Pp +When printing compiler and linker flags, +.Nm +escapes shell metacharacters +.Pq such as quotes, whitespace, brackets, braces and glob characters +in each fragment with a backslash. +The output is intended to be re-tokenised by the consumer using POSIX shell +word-splitting rules, as build systems generally do; under those rules the +protective backslashes are removed and the original values are recovered. +Note that pasting the output directly into an unquoted command substitution, +for example +.Ql cc $(pkg-config \-\-libs foo) , +performs word-splitting and pathname expansion but does +.Em not +remove the backslashes, so a value containing such characters may reach the +toolchain with the escaping still present. +.Pp The .Xr pc 5 files are searched for along a path constructed from the @@ -57,7 +73,6 @@ These options are, ordered by descending .Bl -enum .It No-module options: -.Fl -relocate , .Fl -dump-personality , .Fl -about , .Fl -version , @@ -79,6 +94,9 @@ command line and do not look at dependen Limited-output options: .Fl -validate , .Fl -license , +.Fl -license-file , +.Fl -link-abi , +.Fl -source , .Fl -uninstalled , and .Fl -env : @@ -120,7 +138,8 @@ General output options: .Fl -simulate , .Fl -digraph , .Fl -solution , -.Fl -fragment-tree : +.Fl -fragment-tree , +.Fl -newlines : These options do not limit dependency resolution. .El .Pp @@ -269,6 +288,19 @@ Most other options and all command line Ignore .Sq Conflicts rules in modules. +This option is implied by the options which merely report metadata about the +requested modules rather than combining them into a build, namely +.Fl -license , +.Fl -license-file , +.Fl -modversion , +.Fl -path , +.Fl -print-provides , +.Fl -print-requires , +.Fl -print-requires-private , +.Fl -print-variables , +.Fl -source , +and +.Fl -variable . .It Fl -keep-system-cflags , Fl -keep-system-libs Keep CFLAGS or linker flag fragments that would be filtered due to being included by default in the compiler. @@ -283,6 +315,29 @@ flags, or only the linker flags that are nor library flags, respectively. These options imply .Fl -print-errors . +.It Fl -link-abi +Print the set of link ABI tags +.Pq for example Sy c No or Sy c++ +that the +.Ar module +must be linked against, derived from the +.Sy Link.ABI +properties found across its dependency closure. +The tags from a package's own +.Sy Link.ABI +property and those of its public +.Sy Requires +always apply, while +.Sy Requires.private +tags apply only when linking statically +.Pq Fl -static . +A module with no link ABI requirements prints +.Sy c . +See +.Xr pc 5 +for details of the +.Sy Link.ABI +property. .It Fl -list-all Walk the module search path in the order of descending priority. For each @@ -342,6 +397,12 @@ is greater than the requested number. .It Fl -maximum-traverse-depth Ns = Ns Ar depth Impose a limit on the allowed depth in the dependency graph. +The default +.Ar depth +is 256. +Set +.Ar depth +to \-1 to allow unlimited traversal. For example, a .Ar depth of 2 restricts the resolver from acting on child @@ -385,6 +446,9 @@ output. This option is only available if the preprocessor macro .Dv PKGCONF_LITE was not defined during compilation. +.It Fl -newlines +Use newlines to separate individual CFLAGS or linker flag fragments +instead of spaces. .It Fl -no-cache Skip caching packages when they are loaded into the internal resolver. This may result in an alternate dependency graph being computed. @@ -475,10 +539,6 @@ This is mainly intended for use with the flag and has no effect if .Fl -shared is also specified. -.It Fl -relocate Ns = Ns Ar path -Relocates a path using the pkgconf_path_relocate API. -This is mainly used by the testsuite to provide a guaranteed interface -to the system's path relocation backend. .It Fl -shared Compute a simple dependency graph that is only suitable for shared linking. This option overrides @@ -500,7 +560,9 @@ environment variable is set. .It Fl -simulate Simulates resolving a dependency graph based on the requested modules on the command line. -Dumps a series of trees denoting pkgconf's resolver state. +Dumps a series of trees denoting +.Nm Ns 's +resolver state. This option is only available if the preprocessor macro .Dv PKGCONF_LITE was not defined during compilation. @@ -580,9 +642,15 @@ in the same way as .It Ev DESTDIR If set to the same value as .Ev PKG_CONFIG_SYSROOT_DIR , -behave in the same way as if +follow .Ev PKG_CONFIG_FDO_SYSROOT_RULES -is set. +for variable lookups, but disable automatic sysroot injection into +.Fl I +and +.Fl L +fragments to avoid prefixing paths with +.Ev DESTDIR +twice. If .Ev PKG_CONFIG_SYSROOT_DIR is not set or set to a different value, @@ -652,6 +720,7 @@ For more details, see the command line option, which overrides this variable. .It Ev PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH Impose a limit on the allowed depth in the dependency graph. +Set this variable to \-1 to allow unlimited traversal. This variable overrides the .Fl -maximum-traverse-depth option, but is overridden by the other options mentioned there. @@ -680,6 +749,13 @@ if any, and unless is specified, by appending either .Ev PKG_CONFIG_LIBDIR or the compiled-in default directories with lower priority. +.It Ev PKG_CONFIG_PRELOADED_FILES +Colon-separated list of +.Xr pc 5 +files which are loaded before any other pkg-config files. +These packages are given highest priority over any other +.Xr pc 5 +files that would otherwise provide a given package. .It Ev PKG_CONFIG_PURE_DEPGRAPH If set, enables the same behaviour as the .Fl -pure @@ -710,7 +786,6 @@ If not defined, the default list compile program from the .Dv SYSTEM_INCLUDEDIR preprocessor macro is used instead. -This variable is a pkgconf-specific extension. Any directories listed in the environment variables .Ev CPATH , .Ev C_INCLUDE_PATH , @@ -730,7 +805,6 @@ If not defined, the default list compile program from the .Dv SYSTEM_LIBDIR preprocessor macro is used instead. -This variable is a pkgconf-specific extension. .It Ev PKG_CONFIG_TOP_BUILD_DIR The value of the .Va pc_top_builddir