pkgconf 3.0.5
Theo Buehler <[email protected]> Thu, 6 Aug 2026 07:56:44 +0200
| Newsgroups | gmane.os.openbsd.tech |
|---|---|
| Message-ID | <[email protected]> |
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 ✈ <[email protected]>
A. Wilcox <[email protected]>
Alexander Tsoy <[email protected]>
Alexpux <[email protected]>
Alon Bar-Lev <[email protected]>
Alyx <[email protected]>
-Ariadne Conill <[email protected]>
+Andrea Pappacoda <[email protected]>
+Andrej Shadura <[email protected]>
+Antonin Décimo <[email protected]>
+Ariadne Conill <[email protected]>
Baptiste Daroussin <[email protected]>
-Baptiste Daroussin <[email protected]>
+Ben <[email protected]>
Bryan Drewery <[email protected]>
+Christoph Reiter <[email protected]>
+Colin Gillespie <[email protected]>
Dag-Erling Smørgrav <[email protected]>
Dan Kegel <[email protected]>
-Dan Kegel <[email protected]>
Dan Nicholson <[email protected]>
+data-man <[email protected]>
David Michael <[email protected]>
+David Seifert <[email protected]>
+Doug Freed <[email protected]>
+Dylan Baker <[email protected]>
+Eli Schwartz <[email protected]>
+Elizabeth Kiara Regina Ashford <[email protected]>
Emil Renner Berthing <[email protected]>
Fabian Groffen <[email protected]>
+Filipe Laíns <[email protected]>
Graham Ollis <[email protected]>
Gregor Richards <[email protected]>
+h30032433 <[email protected]>
+Harmen Stoppels <[email protected]>
+huyubiao <[email protected]>
Ignacio Casal Quinteiro <[email protected]>
Igor Gnatenko <[email protected]>
+Ingo Schwarze <[email protected]>
+Ismael Luceno <[email protected]>
Issam Maghni <[email protected]>
-JD Horelick <[email protected]>
Jason Dusek <[email protected]>
Javier Viguera <[email protected]>
+JD Horelick <[email protected]>
Jean-Sébastien Pédron <[email protected]>
+Jeff Moguillansky <[email protected]>
John Hein <[email protected]>
+Jonathan Gray <[email protected]>
+Joshua Watt <[email protected]>
Jussi Pakkanen <[email protected]>
+Kai Pastor <[email protected]>
+L. E. Segovia <[email protected]>
Leorize <[email protected]>
Luca Barbato <[email protected]>
+Marc-André Lureau <[email protected]>
Marcin Wojdyr <[email protected]>
+Mattias Hansson <[email protected]>
Maxin B. John <[email protected]>
Michał Górny <[email protected]>
+midipix <[email protected]>
Mike Frysinger <[email protected]>
+Mike L <[email protected]>
+moi15moi <[email protected]>
+mrgrouse <[email protected]>
+Neal Gompa <[email protected]>
+Nicolas Braud-Santoni <[email protected]>
+Olaf Hering <[email protected]>
+olf <[email protected]>
+orbea <[email protected]>
+Peter Kokot <[email protected]>
+Petr Písař <[email protected]>
+Pierce <[email protected]>
+Pierre Pronchery <[email protected]>
+psykose <[email protected]>
+rentianyue-jk <[email protected]>
+Ross Burton <[email protected]>
+Ryan Scott <[email protected]>
+Sam James <[email protected]>
+Sandro Mani <[email protected]>
Seungha Yang <[email protected]>
+Shubham Chakraborty <[email protected]>
+Simon Josefsson <[email protected]>
+Stefan Weil <[email protected]>
+Stéphane Rochoy <[email protected]>
+Stone Tickle <[email protected]>
+Taylor R Campbell <[email protected]>
+Timo Röhling <[email protected]>
TingPing <[email protected]>
Tobias Kortkamp <[email protected]>
+Tobias Stoeckmann <[email protected]>
Tony Theodore <[email protected]>
+Tuukka Pasanen <[email protected]>
+Undefine <[email protected]>
+Victor Westerhuis <[email protected]>
+Vincent Torri <[email protected]>
Volker Braun <[email protected]>
+wi24rd <[email protected]>
+Xi Ruoyao <[email protected]>
Yu Kobayashi <[email protected]>
-orbea <[email protected]>
-✈ Graham ✈ <[email protected]>
+Ziemowit Łąski <[email protected]>
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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#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 <https://github.com/pkgconf/pkgconf/issues/83> 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 <https://github.com/pkgconf/pkgconf/issues/580>.
+ */
+ 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 <[email protected]>
*
* 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 <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
#include "getopt_long.h"
-#ifndef PKGCONF_LITE
-#include "renderer-msvc.h"
-#endif
-#ifdef _WIN32
-#include <io.h> /* for _setmode() */
-#include <fcntl.h>
-#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 <https://github.com/pkgconf/pkgconf/issues/83> 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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !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 <https://github.com/pkgconf/pkgconf/issues/575>.
+ */
+ 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 <libpkgconf/libpkgconf.h>
+#include <errno.h>
/*
* !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 <[email protected]>
- *
- * 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 <stdlib.h>
-#include <sys/types.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-
-#include <libpkgconf/bsdstubs.h>
-#include <libpkgconf/config.h>
-
-#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 <[email protected]>.
+ * Copyright (c) 2012 Ariadne Conill <[email protected]>
*
* 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 <libpkgconf/stdinc.h>
+#include <libpkgconf/bsdstubs.h>
+#include <libpkgconf/config.h>
+
+#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 <[email protected]>.
*
* 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 <stddef.h>
#include <libpkgconf/libpkgconf-api.h>
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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !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 : "<default>");
- 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 : "<default>");
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 <libpkgconf/config.h>
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
+#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 <libpkgconf/config.h>
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
+#ifndef _WIN32
+#include <locale.h>
+#include <langinfo.h>
+#endif
+
+#ifdef __APPLE__
+#include <xlocale.h>
+#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 <stddef.h>
+
#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 <stddef.h>
#include <stdbool.h>
#include <stdint.h>
+#include <string.h>
#include <libpkgconf/libpkgconf-api.h>
#include <libpkgconf/iter.h>
#include <libpkgconf/bsdstubs.h>
@@ -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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+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 <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
-/*
- * !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 <libpkgconf/config.h>
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/path.h>
#if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32)
# include <sys/stat.h>
@@ -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 <libpkgconf/libpkgconf.h>
+
+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 <libpkgconf/config.h>
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
-
-#ifndef _WIN32
-#include <fcntl.h> // open
-#include <libgen.h> // basename/dirname
-#include <sys/stat.h> // lstat, S_ISLNK
-#include <unistd.h> // close, readlinkat
-
-#include <string.h>
-#endif
+#include <libpkgconf/path.h>
/*
* !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 <source> <target>
- *
- * 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 <source> 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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
@@ -26,16 +33,35 @@
#include <sys/types.h>
#include <stdint.h>
#include <errno.h>
+#include <limits.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <malloc.h>
+# include <io.h> /* for _setmode() */
+# include <fcntl.h>
# 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 <dirent.h>
+# include <dirent.h>
# 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 <dirent.h>
# include <unistd.h>
-# include <limits.h>
# include <strings.h>
+# include <fcntl.h> // open
+# include <libgen.h> // basename/dirname
+# include <sys/stat.h> // lstat, S_ISLNK
+# include <unistd.h> // close, readlinkat
+# include <string.h>
# 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 <libpkgconf/libpkgconf.h>
+#include <libpkgconf/stdinc.h>
+
+/*
+ * !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 <libpkgconf/config.h>
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+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