summaryrefslogtreecommitdiff
path: root/libpkgconf
diff options
context:
space:
mode:
Diffstat (limited to 'libpkgconf')
-rw-r--r--libpkgconf/argvsplit.c19
-rw-r--r--libpkgconf/audit.c28
-rw-r--r--libpkgconf/bsdstubs.c174
-rw-r--r--libpkgconf/bsdstubs.h4
-rw-r--r--libpkgconf/buffer.c483
-rw-r--r--libpkgconf/bufferset.c65
-rw-r--r--libpkgconf/bytecode.c535
-rw-r--r--libpkgconf/cache.c24
-rw-r--r--libpkgconf/client.c141
-rw-r--r--libpkgconf/config.h.meson9
-rw-r--r--libpkgconf/dependency.c102
-rw-r--r--libpkgconf/fileio.c130
-rw-r--r--libpkgconf/fragment.c608
-rw-r--r--libpkgconf/iter.h4
-rw-r--r--libpkgconf/libpkgconf.h419
-rw-r--r--libpkgconf/license.c329
-rw-r--r--libpkgconf/output.c153
-rw-r--r--libpkgconf/parser.c183
-rw-r--r--libpkgconf/path.c229
-rw-r--r--libpkgconf/path.h9
-rw-r--r--libpkgconf/personality.c109
-rw-r--r--libpkgconf/pkg.c918
-rw-r--r--libpkgconf/queue.c113
-rw-r--r--libpkgconf/stdinc.h45
-rw-r--r--libpkgconf/tuple.c329
-rw-r--r--libpkgconf/variable.c162
-rw-r--r--libpkgconf/version.c252
-rw-r--r--libpkgconf/win-dirent.h1109
28 files changed, 4297 insertions, 2388 deletions
diff --git a/libpkgconf/argvsplit.c b/libpkgconf/argvsplit.c
index 1ff221ac63f8..8c5dac2917df 100644
--- a/libpkgconf/argvsplit.c
+++ b/libpkgconf/argvsplit.c
@@ -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,6 +41,9 @@
void
pkgconf_argv_free(char **argv)
{
+ if (argv == NULL)
+ return;
+
free(argv[0]);
free(argv);
}
@@ -118,8 +123,19 @@ pkgconf_argv_split(const char *src, int *argc, char ***argv)
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;
@@ -148,6 +164,7 @@ pkgconf_argv_split(const char *src, int *argc, char ***argv)
{
free(*argv);
free(buf);
+ *argv = NULL;
return -1;
}
diff --git a/libpkgconf/audit.c b/libpkgconf/audit.c
index a06eb24fc728..1b2a15ccbe04 100644
--- a/libpkgconf/audit.c
+++ b/libpkgconf/audit.c
@@ -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 *client, const char *format, ...)
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_client_t *client, const pkgconf_pkg_t *dep,
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));
+ }
}
diff --git a/libpkgconf/bsdstubs.c b/libpkgconf/bsdstubs.c
index a5291f7a5a93..7ef3556364c0 100644
--- a/libpkgconf/bsdstubs.c
+++ b/libpkgconf/bsdstubs.c
@@ -1,105 +1,7 @@
-/* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */
-/* $OpenBSD: strlcat.c,v 1.12 2005/03/30 20:13:52 otto Exp $ */
-
/*
- * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
- *
- * 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>
-#ifndef _WIN32
-#include <unistd.h>
-#endif
-
-#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 <nenolod@dereferenced.org>.
+ * Copyright (c) 2012 Ariadne Conill <ariadne@dereferenced.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -110,23 +12,39 @@ strlcat(char *dst, const char *src, size_t siz)
* 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.
*/
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;
@@ -135,9 +53,11 @@ pledge(const char *promises, const char *execpromises)
}
#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;
@@ -146,27 +66,11 @@ unveil(const char *path, const char *permissions)
}
#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)
{
@@ -178,20 +82,26 @@ reallocarray(void *ptr, size_t m, size_t n)
}
#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);
}
diff --git a/libpkgconf/bsdstubs.h b/libpkgconf/bsdstubs.h
index 21b9432a4a55..fc23b131e0da 100644
--- a/libpkgconf/bsdstubs.h
+++ b/libpkgconf/bsdstubs.h
@@ -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 <nenolod@dereferenced.org>.
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -16,6 +18,8 @@
#ifndef LIBPKGCONF_BSDSTUBS_H
#define LIBPKGCONF_BSDSTUBS_H
+#include <stddef.h>
+
#include <libpkgconf/libpkgconf-api.h>
#ifdef __cplusplus
diff --git a/libpkgconf/buffer.c b/libpkgconf/buffer.c
index 6857022db414..3c68caffe8ea 100644
--- a/libpkgconf/buffer.c
+++ b/libpkgconf/buffer.c
@@ -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
@@ -29,37 +31,193 @@
static inline size_t
target_allocation_size(size_t target_size)
{
- return 4096 + (4096 * (target_size / 4096));
+ return 128 + (128 * (target_size / 128));
}
-void
+#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.
+ *
+ * :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 needed = strlen(text) + 1;
size_t newsize = pkgconf_buffer_len(buffer) + needed;
char *newbase = realloc(buffer->base, target_allocation_size(newsize));
-
- /* XXX: silently failing here is antisocial */
if (newbase == NULL)
- return;
+ return false;
char *newend = newbase + pkgconf_buffer_len(buffer);
- pkgconf_strlcpy(newend, text, needed);
+ memcpy(newend, text, needed);
buffer->base = newbase;
- buffer->end = newend + needed;
+ buffer->end = newend + (needed - 1);
+
+ *buffer->end = '\0';
+ return true;
}
-void
+/*
+ * !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.
+ *
+ * :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)
+{
+ if (n == 0)
+ return true;
+
+ for (size_t i = 0; i < n; i++)
+ {
+ if (!pkgconf_buffer_push_byte(buf, p[i]))
+ return false;
+ }
+
+ 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)
+{
+ va_list va;
+ char *buf;
+ size_t needed;
+
+ va_copy(va, src_va);
+ needed = vsnprintf(NULL, 0, fmt, va) + 1;
+ va_end(va);
+
+ buf = malloc(needed);
+ if (buf == NULL)
+ return false;
+
+ va_copy(va, src_va);
+ vsnprintf(buf, needed, fmt, va);
+ va_end(va);
+
+ bool ret = pkgconf_buffer_append(buffer, buf);
+
+ free(buf);
+
+ return ret;
+}
+
+/*
+ * !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.
+ *
+ * :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;
+
+ 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))
+ pkgconf_buffer_copy(&tmpbuf, buffer);
+
+ pkgconf_buffer_finalize(&tmpbuf);
+
+ return true;
+}
+
+/*
+ * !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));
-
- /* XXX: silently failing here remains antisocial */
if (newbase == NULL)
- return;
+ return false;
char *newend = newbase + newsize;
*(newend - 1) = byte;
@@ -67,21 +225,320 @@ pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte)
buffer->base = newbase;
buffer->end = newend;
+
+ 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;
+ size_t len = pkgconf_buffer_len(buffer);
+ if (len == 0)
+ return false;
+
+ size_t newsize = len - 1;
char *newbase = realloc(buffer->base, target_allocation_size(newsize));
+ if (newbase == NULL)
+ return false;
+
buffer->base = newbase;
buffer->end = newbase + newsize;
*(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.
+ *
+ * :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 (pattern == NULL)
+ pattern = "";
+
+ if (value == NULL)
+ value = "";
+
+ size_t pattern_len = strlen(pattern);
+
+ 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.
+ *
+ * :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)
+{
+ const char *p = pkgconf_buffer_str(src);
+
+ if (!pkgconf_buffer_len(src))
+ return true;
+
+ for (; *p; p++)
+ {
+ if (pkgconf_span_contains((unsigned char) *p, spans, nspans))
+ {
+ if (!pkgconf_buffer_push_byte(dest, '\\'))
+ return false;
+ }
+
+ if (!pkgconf_buffer_push_byte(dest, *p))
+ return false;
+ }
+
+ return true;
}
diff --git a/libpkgconf/bufferset.c b/libpkgconf/bufferset.c
new file mode 100644
index 000000000000..16af128ec406
--- /dev/null
+++ b/libpkgconf/bufferset.c
@@ -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);
+ }
+}
diff --git a/libpkgconf/bytecode.c b/libpkgconf/bytecode.c
new file mode 100644
index 000000000000..a83b7d8fe72d
--- /dev/null
+++ b/libpkgconf/bytecode.c
@@ -0,0 +1,535 @@
+/*
+ * 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 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;
+
+ const uint8_t *p = bc->base;
+ const uint8_t *end = bc->base + 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 (p < end)
+ {
+ const pkgconf_bytecode_op_t *op =
+ (const pkgconf_bytecode_op_t *)p;
+
+ if ((const uint8_t *)op + sizeof(*op) > end)
+ return false;
+
+ if ((const uint8_t *)op + sizeof(*op) + op->size > end)
+ 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 = (const uint8_t *)pkgconf_bytecode_op_next(op);
+ }
+
+ 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 = (size_t)(buf->end - buf->base);
+}
+
+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, *end;
+ size_t klen;
+
+ if (buf == NULL || key == NULL)
+ return false;
+
+ klen = strlen(key);
+ p = (uint8_t *) buf->base;
+ end = (uint8_t *) buf->end;
+
+ while (p < end)
+ {
+ const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
+
+ if (p + sizeof(*op) > end)
+ return false;
+
+ if (p + sizeof(*op) + op->size > end)
+ return false;
+
+ if (op->tag == PKGCONF_BYTECODE_OP_VAR)
+ {
+ if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0)
+ return true;
+ }
+
+ p += sizeof(*op) + 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)
+{
+ const uint8_t *p = (uint8_t *) rhs->base;
+ const uint8_t *end = (uint8_t *) rhs->end;
+
+ while (p < end)
+ {
+ const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
+
+ if (p + sizeof(*op) > end)
+ return false;
+
+ if (p + sizeof(*op) + op->size > end)
+ 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, sizeof(*op) + op->size))
+ return false;
+ }
+
+ p += sizeof(*op) + op->size;
+ }
+
+ return true;
+}
diff --git a/libpkgconf/cache.c b/libpkgconf/cache.c
index 883c8df7127e..4f5f3cb62b97 100644
--- a/libpkgconf/cache.c
+++ b/libpkgconf/cache.c
@@ -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
@@ -127,18 +129,31 @@ pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
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 *));
+
+ /* 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;
client->cache_table[client->cache_count - 1] = pkg;
qsort(client->cache_table, client->cache_count,
sizeof(void *), cache_member_sort_cmp);
+
+ PKGCONF_TRACE(client, "added @%p to cache", pkg);
}
/*
@@ -193,8 +208,11 @@ pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
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
{
diff --git a/libpkgconf/client.c b/libpkgconf/client.c
index 4fe36ecd9af9..be64950a4a92 100644
--- a/libpkgconf/client.c
+++ b/libpkgconf/client.c
@@ -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,7 +62,7 @@ trace_path_list(const pkgconf_client_t *client, const char *desc, pkgconf_list_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))
{
@@ -72,10 +74,10 @@ pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_pers
(void) pkgconf_path_build_from_registry(HKEY_LOCAL_MACHINE, &client->dir_list, true);
#endif
- if (getenv("PKG_CONFIG_LIBDIR") != NULL)
+ 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;
}
@@ -87,7 +89,7 @@ pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_pers
/*
* !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.
*
@@ -95,11 +97,16 @@ pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_pers
* :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->personality = personality;
+ client->client_data = client_data;
+ client->environ_lookup_handler = environ_lookup_handler;
client->error_handler_data = error_handler_data;
client->error_handler = error_handler;
client->auditf = NULL;
@@ -121,36 +128,38 @@ pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error
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();
}
/*
@@ -163,17 +172,19 @@ pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error
* :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_t *out = calloc(1, sizeof(pkgconf_client_t));
if (out == NULL)
return NULL;
- pkgconf_client_init(out, error_handler, error_handler_data, personality);
+ pkgconf_client_init(out, error_handler, error_handler_data, personality, client_data, environ_lookup_handler);
return out;
}
@@ -182,7 +193,7 @@ unref_preload_list(pkgconf_client_t *client)
{
pkgconf_node_t *n, *tn;
- PKGCONF_FOREACH_LIST_ENTRY_SAFE(client->preloaded_pkgs.head, n, tn)
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(client->preloaded_pkgs.head, tn, n)
{
pkgconf_pkg_t *pkg = n->data;
pkgconf_pkg_unref(client, pkg);
@@ -221,6 +232,10 @@ pkgconf_client_deinit(pkgconf_client_t *client)
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));
}
/*
@@ -274,6 +289,12 @@ pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client)
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);
@@ -281,7 +302,7 @@ pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *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 : "");
}
/*
@@ -463,12 +484,18 @@ pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t linen
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);
@@ -753,6 +780,31 @@ pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler
/*
* !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.
@@ -769,10 +821,7 @@ pkgconf_client_preload_path(pkgconf_client_t *client, const char *path)
if (pkg == NULL)
return false;
- pkgconf_pkg_ref(client, pkg);
- pkgconf_node_insert_tail(&pkg->preload_node, pkg, &client->preloaded_pkgs);
-
- return true;
+ return pkgconf_client_preload_one(client, pkg);
}
/*
@@ -794,9 +843,9 @@ 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;
+ bool ret = true;
- data = getenv(env);
+ data = pkgconf_client_getenv(client, env);
if (data == NULL)
return true;
@@ -815,3 +864,43 @@ pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env)
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: yhe 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);
+}
diff --git a/libpkgconf/config.h.meson b/libpkgconf/config.h.meson
index 2ea7db2e20de..5add4e9e301d 100644
--- a/libpkgconf/config.h.meson
+++ b/libpkgconf/config.h.meson
@@ -12,12 +12,6 @@
/* Define to 1 if you have the `reallocarray' function. */
#mesondefine HAVE_REALLOCARRAY
-/* Define to 1 if you have the `strlcat' function. */
-#mesondefine HAVE_DECL_STRLCAT
-
-/* Define to 1 if you have the `strlcpy' function. */
-#mesondefine HAVE_DECL_STRLCPY
-
/* Define to 1 if you have the `strndup' function. */
#mesondefine HAVE_DECL_STRNDUP
@@ -30,6 +24,9 @@
/* Define to 1 if you have the `unveil' function. */
#mesondefine HAVE_DECL_UNVEIL
+/* Define to 1 if you have the `nl_langinfo_l' function. */
+#mesondefine HAVE_DECL_NL_LANGINFO_L
+
/* Name of package */
#mesondefine PACKAGE
diff --git a/libpkgconf/dependency.c b/libpkgconf/dependency.c
index 9b64809a4114..ce5237f2c154 100644
--- a/libpkgconf/dependency.c
+++ b/libpkgconf/dependency.c
@@ -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,16 @@ 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);
+ pkgconf_buffer_reset(buf);
+ pkgconf_buffer_append(buf, dep->package);
+
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_append_fmt(buf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version);
- return buf;
+ return pkgconf_buffer_str(buf);
}
/* find a colliding dependency that is coloured differently */
@@ -75,29 +74,33 @@ find_colliding_dependency(const pkgconf_dependency_t *dep, const pkgconf_list_t
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_to_buf(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_to_buf(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 +113,15 @@ add_or_replace_dependency_node(pkgconf_client_t *client, pkgconf_dependency_t *d
* 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
@@ -135,6 +142,11 @@ pkgconf_dependency_addraw(pkgconf_client_t *client, pkgconf_list_t *list, const
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);
@@ -169,6 +181,8 @@ pkgconf_dependency_add(pkgconf_client_t *client, pkgconf_list_t *list, const cha
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);
}
@@ -211,6 +225,9 @@ pkgconf_dependency_free_one(pkgconf_dependency_t *dep)
if (dep->version != NULL)
free(dep->version);
+ if (dep->why != NULL)
+ free(dep->why);
+
free(dep);
}
@@ -306,30 +323,25 @@ pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_h
{
parse_state_t state = OUTSIDE_MODULE;
pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY;
- char cmpname[PKGCONF_ITEM_SIZE];
- size_t package_sz = 0, version_sz = 0, buf_sz = 0;
- char *buf;
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t cmpname = PKGCONF_BUFFER_INITIALIZER;
+ size_t package_sz = 0, version_sz = 0;
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;
- if (!*depends)
+ if (depends == NULL || *depends == '\0')
return;
- memset(cmpname, '\0', sizeof cmpname);
-
- buf_sz = strlen(depends) * 2;
- buf = calloc(1, buf_sz);
- if (buf == NULL)
- return;
+ if (!pkgconf_buffer_append(&buf, depends))
+ goto out;
- pkgconf_strlcpy(buf, depends, buf_sz);
- pkgconf_strlcat(buf, " ", buf_sz);
+ if (!pkgconf_buffer_append(&buf, " "))
+ goto out;
- start = ptr = buf;
+ start = ptr = buf.base;
while (*ptr)
{
@@ -391,23 +403,20 @@ pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_h
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_reset(&cmpname);
+ pkgconf_buffer_append_slice(&cmpname, opstart, ptr - opstart);
+ 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:
@@ -428,9 +437,9 @@ pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_h
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_reset(&cmpname);
}
if (state == OUTSIDE_MODULE)
@@ -441,7 +450,9 @@ pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_h
ptr++;
}
- free(buf);
+out:
+ pkgconf_buffer_finalize(&cmpname);
+ pkgconf_buffer_finalize(&buf);
}
/*
@@ -463,7 +474,7 @@ pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_h
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);
@@ -490,6 +501,11 @@ pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *de
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);
diff --git a/libpkgconf/fileio.c b/libpkgconf/fileio.c
index d4f001b9c3e3..2acec22aaee6 100644
--- a/libpkgconf/fileio.c
+++ b/libpkgconf/fileio.c
@@ -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
@@ -16,98 +18,104 @@
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
+#define push_or_return_fail(buf, c) \
+ do { if (!pkgconf_buffer_push_byte((buf), (char) (c))) return false; } while (0)
+
+#define trim_or_return_fail(buf) \
+ do { if (!pkgconf_buffer_trim_byte((buf))) return false; } while (0)
+
bool
pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream)
{
bool quoted = false;
- int c = '\0', c2;
+ bool got_data = false;
+ char in[PKGCONF_ITEM_SIZE];
+ long unread = 0;
- while ((c = getc(stream)) != EOF)
+ while (fgets(in, sizeof in, stream) != NULL)
{
- 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);
+ char *p = in;
- quoted = false;
- continue;
- }
- else if (c == '\n')
- {
- if (quoted)
- {
- /* Trim spaces */
- do {
- c2 = getc(stream);
- } while (c2 == '\t' || c2 == ' ');
+ got_data = true;
- ungetc(c2, stream);
+ while (*p != '\0')
+ {
+ unsigned char c = (unsigned char) *p++;
- quoted = false;
+ if (c == '\\' && !quoted)
+ {
+ quoted = true;
continue;
}
- else
+ else if (c == '\n')
{
- pkgconf_buffer_push_byte(buffer, c);
+ if (quoted)
+ {
+ quoted = false;
+ continue;
+ }
+ else
+ push_or_return_fail(buffer, (char) c);
+
+ goto done;
}
+ else if (c == '\r')
+ {
+ if (*p == '\n')
+ {
+ p++;
+ }
+ else if (*p == '\0')
+ {
+ /*
+ * The matching '\n' may not have been read into `in`
+ * yet if '\r' landed exactly on the fgets() buffer
+ * boundary. Peek the real stream so a split CRLF
+ * isn't misparsed as two lines.
+ */
+ int next = getc(stream);
- break;
- }
- else if (c == '\r')
- {
- pkgconf_buffer_push_byte(buffer, '\n');
+ if (next != '\n' && next != EOF && ungetc(next, stream) == EOF)
+ return false;
+ }
- if ((c2 = getc(stream)) == '\n')
- {
if (quoted)
{
quoted = false;
continue;
}
- break;
+ push_or_return_fail(buffer, '\n');
+ /* unlike '\n', a lone '\r' doesn't bound the fgets() call above */
+ unread = (long) strlen(p);
+ goto done;
}
-
- ungetc(c2, stream);
-
- if (quoted)
+ else
{
- quoted = false;
- continue;
- }
+ if (quoted)
+ {
+ push_or_return_fail(buffer, '\\');
+ quoted = false;
+ }
- break;
- }
- else
- {
- if (quoted) {
- pkgconf_buffer_push_byte(buffer, '\\');
- quoted = false;
+ push_or_return_fail(buffer, (char) c);
}
- pkgconf_buffer_push_byte(buffer, c);
}
-
}
+done:
+ if (unread > 0 && fseek(stream, -unread, SEEK_CUR) != 0)
+ return false;
+
/* Remove newline character. */
if (pkgconf_buffer_lastc(buffer) == '\n')
- pkgconf_buffer_trim_byte(buffer);
+ trim_or_return_fail(buffer);
if (pkgconf_buffer_lastc(buffer) == '\r')
- pkgconf_buffer_trim_byte(buffer);
+ trim_or_return_fail(buffer);
+
+ if (!got_data)
+ return false;
- return !(c == EOF || ferror(stream));
+ return !ferror(stream);
}
diff --git a/libpkgconf/fragment.c b/libpkgconf/fragment.c
index 2b6109039b47..ab649c09db96 100644
--- a/libpkgconf/fragment.c
+++ b/libpkgconf/fragment.c
@@ -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,57 @@
*/
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;
+}
+
+static inline bool
+pkgconf_fragment_should_check_sysroot(const char *string)
+{
+ static const struct pkgconf_fragment_check check_fragments[] = {
+ {"-F", 2},
+ {"-I", 2},
+ {"-L", 2},
+ {"-isystem", 8},
+ {"-idirafter", 10},
+ };
+
+ 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))
+ return true;
+
+ return false;
+}
+
+static inline bool
pkgconf_fragment_is_unmergeable(const char *string)
{
static const struct pkgconf_fragment_check check_fragments[] = {
@@ -70,13 +128,18 @@ pkgconf_fragment_is_unmergeable(const char *string)
}
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 +149,11 @@ pkgconf_fragment_is_groupable(const char *string)
{
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 +162,15 @@ pkgconf_fragment_is_groupable(const char *string)
}
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,34 +190,6 @@ pkgconf_fragment_is_special(const char *string)
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)
-{
- *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");
-
- if (sysroot_dir != NULL && pkgconf_fragment_should_munge(source, sysroot_dir))
- pkgconf_strlcat(buf, sysroot_dir, buflen);
- }
-
- pkgconf_strlcat(buf, source, buflen);
-
- if (*buf == '/' && !(client->flags & PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS))
- pkgconf_path_relocate(buf, buflen);
-}
-
-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);
-}
-
/*
* !doc
*
@@ -168,13 +205,18 @@ pkgconf_fragment_copy_munged(const pkgconf_client_t *client, const char *source,
* :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));
+ if (frag == NULL)
+ return;
+
frag->type = type;
- frag->data = pkgconf_fragment_copy_munged(client, data, 0);
+ frag->data = strdup(data);
if (tail)
{
@@ -185,6 +227,97 @@ pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, ch
pkgconf_node_insert(&frag->iter, frag, list);
}
+static bool
+should_inject_sysroot(const pkgconf_client_t *client, const char *string, bool saw_sysroot, unsigned int flags)
+{
+ /* emulating original pkg-config: we never inject sysroot */
+ if (client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES)
+ 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;
+
+ if (saw_sysroot)
+ return false;
+
+ if (!pkgconf_fragment_should_check_sysroot(string))
+ return false;
+
+ if (!strncmp(string + 2, client->sysroot_dir, strlen(client->sysroot_dir)) &&
+ *(string + 2 + strlen(client->sysroot_dir)) == '/')
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+should_inject_sysroot_child(const pkgconf_client_t *client, const pkgconf_fragment_t *last, const char *string, bool saw_sysroot, unsigned int flags)
+{
+ /* emulating original pkg-config: we never inject sysroot */
+ if (client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES)
+ 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;
+
+ if (saw_sysroot)
+ return false;
+
+ if (!pkgconf_fragment_should_check_sysroot(last->data))
+ return false;
+
+ if (!strncmp(string, client->sysroot_dir, strlen(client->sysroot_dir)) &&
+ *(string + strlen(client->sysroot_dir)) == '/')
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool
+fragment_is_unquoted_var(const char *value)
+{
+ size_t len;
+
+ if (value == NULL)
+ return false;
+
+ len = strlen(value);
+
+ if (len < 4 || value[0] != '$')
+ return false;
+
+ if (value[1] == '{' && value[len - 1] == '}')
+ return true;
+
+ return false;
+}
+
/*
* !doc
*
@@ -199,28 +332,45 @@ pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, ch
* :return: nothing
*/
void
-pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags)
+pkgconf_fragment_add(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags)
{
pkgconf_list_t *target = list;
pkgconf_fragment_t *frag;
+ pkgconf_buffer_t evalbuf = PKGCONF_BUFFER_INITIALIZER;
+ bool saw_sysroot = false;
+ char *string;
+
+ if (!pkgconf_bytecode_eval_str_to_buf(client, vars, value, &saw_sysroot, &evalbuf))
+ {
+ pkgconf_buffer_finalize(&evalbuf);
+ return;
+ }
- if (*string == '\0')
+ string = pkgconf_buffer_freeze(&evalbuf);
+ if (string == NULL)
return;
+ if (fragment_is_unquoted_var(value))
+ {
+ pkgconf_fragment_parse(client, list, vars, string, flags);
+ free(string);
+ return;
+ }
+
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))
+ if (pkgconf_fragment_is_terminus(parent->data, string))
parent->flags |= PKGCONF_PKG_FRAGF_TERMINATED;
PKGCONF_TRACE(client, "adding fragment as child to list @%p", target);
@@ -231,25 +381,54 @@ pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const
if (frag == NULL)
{
PKGCONF_TRACE(client, "failed to add new fragment due to allocation failure to list @%p", target);
+ free(string);
return;
}
if (strlen(string) > 1 && !pkgconf_fragment_is_special(string))
{
frag->type = *(string + 1);
- frag->data = pkgconf_fragment_copy_munged(client, string + 2, flags);
+
+ if (should_inject_sysroot(client, string, saw_sysroot, flags))
+ {
+ pkgconf_buffer_t sysroot_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&sysroot_buf, client->sysroot_dir);
+ pkgconf_buffer_append(&sysroot_buf, string + 2);
+
+ frag->data = pkgconf_buffer_freeze(&sysroot_buf);
+ }
+ else
+ frag->data = strdup(string + 2);
PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list);
}
else
{
+ if (client->sysroot_dir != NULL && list->tail != NULL && list->tail->data != NULL)
+ {
+ pkgconf_fragment_t *last = list->tail->data;
+
+ if (should_inject_sysroot_child(client, last, string, saw_sysroot, flags))
+ {
+ pkgconf_buffer_t sysroot_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&sysroot_buf, client->sysroot_dir);
+ pkgconf_buffer_append(&sysroot_buf, string);
+
+ free(string);
+ string = pkgconf_buffer_freeze(&sysroot_buf);
+ }
+ }
+
frag->type = 0;
- frag->data = pkgconf_fragment_copy_munged(client, string, flags);
+ frag->data = strdup(string);
PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target);
}
pkgconf_node_insert_tail(&frag->iter, frag, target);
+ free(string);
}
static inline pkgconf_fragment_t *
@@ -264,6 +443,14 @@ pkgconf_fragment_lookup(pkgconf_list_t *list, const pkgconf_fragment_t *base)
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;
}
@@ -305,6 +492,9 @@ pkgconf_fragment_can_merge(const pkgconf_fragment_t *base, unsigned int flags, b
if (base->children.head != NULL)
return false;
+ if (base->data == NULL)
+ return false;
+
return pkgconf_fragment_is_unmergeable(base->data);
}
@@ -406,6 +596,8 @@ pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, cons
return;
frag = calloc(1, sizeof(pkgconf_fragment_t));
+ if (frag == NULL)
+ return;
frag->type = base->type;
pkgconf_fragment_copy_list(client, &frag->children, &base->children);
@@ -469,240 +661,159 @@ pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pk
}
}
-static inline char *
-fragment_quote(const pkgconf_fragment_t *frag)
-{
- const char *src = frag->data;
- ssize_t outlen = strlen(src) + 10;
- char *out, *dst;
-
- if (frag->data == NULL)
- return NULL;
-
- out = dst = calloc(1, outlen);
- if (out == NULL)
- return NULL;
-
- for (; *src; src++)
- {
- if (((*src < ' ') ||
- (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') ||
- (*src > '$' && *src < '(') ||
- (*src > ')' && *src < '+') ||
- (*src > ':' && *src < '=') ||
- (*src > '=' && *src < '@') ||
- (*src > 'Z' && *src < '\\') ||
+/*
+ * !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
- (*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;
-
- char *newout = realloc(out, outlen);
- if (newout == NULL)
- {
- free(out);
- return NULL;
- }
-
- out = newout;
- dst = out + offset;
- }
- }
-
- *dst = 0;
- return out;
+static bool
+codeset_is_utf8(const char *codeset)
+{
+ return codeset != NULL && (!strcasecmp(codeset, "UTF-8") || !strcasecmp(codeset, "UTF8"));
}
+#endif
-static inline size_t
-pkgconf_fragment_len(const pkgconf_fragment_t *frag)
+bool
+pkgconf_is_locale_utf8(void)
{
- size_t len = 1;
-
- if (frag->type)
- len += 2;
+#ifdef _WIN32
+ return GetACP() == CP_UTF8;
+#else
+ static int cached = -1;
- if (frag->data != NULL)
- {
- pkgconf_node_t *iter;
+ if (cached >= 0)
+ return cached;
- char *quoted = fragment_quote(frag);
- len += strlen(quoted);
- free(quoted);
+#if HAVE_DECL_NL_LANGINFO_L
+ locale_t loc = newlocale(LC_CTYPE_MASK, "", (locale_t)0);
- PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
- {
- const pkgconf_fragment_t *child_frag = iter->data;
- len += pkgconf_fragment_len(child_frag) + 1;
- }
+ if (loc != (locale_t)0)
+ {
+ 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 len;
+ return cached;
+#endif
}
-static size_t
-fragment_render_len(const pkgconf_list_t *list, bool escape)
+static void
+fragment_quote(pkgconf_buffer_t *out, const pkgconf_fragment_t *frag)
{
- (void) escape;
+ if (frag->data == NULL)
+ return;
- size_t out = 1; /* trailing nul */
- pkgconf_node_t *node;
+ const pkgconf_buffer_t *src = PKGCONF_BUFFER_FROM_STR(frag->data);
+ 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 },
+ };
- PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
- {
- const pkgconf_fragment_t *frag = node->data;
- out += pkgconf_fragment_len(frag);
- }
+ /* If the local is UTF-8 we must not split character over 0x7f because it would add "\" between each bytes.
+ So only DEL (0x7f) needs escaping */
+ 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 },
+ };
- return out;
+ if (pkgconf_is_locale_utf8())
+ pkgconf_buffer_escape(out, src, quote_spans_utf8, PKGCONF_ARRAY_SIZE(quote_spans_utf8));
+ else
+ pkgconf_buffer_escape(out, src, quote_spans, PKGCONF_ARRAY_SIZE(quote_spans));
}
-static inline size_t
-fragment_render_item(const pkgconf_fragment_t *frag, char *bptr, size_t bufremain)
+static void
+fragment_render(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf)
{
const pkgconf_node_t *iter;
- char *base = bptr;
+ pkgconf_buffer_t quoted = PKGCONF_BUFFER_INITIALIZER;
- char *quoted = fragment_quote(frag);
- if (quoted == NULL)
- return 0;
-
- if (strlen(quoted) > bufremain)
- {
- free(quoted);
- return 0;
- }
+ fragment_quote(&quoted, frag);
if (frag->type)
- {
- *bptr++ = '-';
- *bptr++ = frag->type;
- }
+ pkgconf_buffer_append_fmt(buf, "-%c", frag->type);
- if (quoted != NULL)
- {
- bptr += pkgconf_strlcpy(bptr, quoted, bufremain - (bptr - base));
- free(quoted);
- }
+ pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(&quoted));
+ pkgconf_buffer_finalize(&quoted);
PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
{
const pkgconf_fragment_t *child_frag = iter->data;
- *bptr++ = ' ';
- bptr += fragment_render_item(child_frag, bptr, bufremain - (bptr - base));
- }
-
- return bptr - base;
-}
-
-static void
-fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape)
-{
- (void) escape;
-
- pkgconf_node_t *node;
- char *bptr = buf;
-
- memset(buf, 0, buflen);
-
- PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
- {
- 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;
-
- if (node->next != NULL)
- *bptr++ = ' ';
+ pkgconf_buffer_push_byte(buf, ctx->delim);
+ fragment_render(ctx, child_frag, buf);
}
}
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)
+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_fragment_render_buf(list, buf, buflen, true, ops);
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+ {
+ const pkgconf_fragment_t *frag = node->data;
+ ops->render(&ctx, frag, buf);
- return buf;
+ if (node->next != NULL)
+ pkgconf_buffer_push_byte(buf, ctx.delim);
+ }
}
/*
@@ -765,39 +876,48 @@ pkgconf_fragment_free(pkgconf_list_t *list)
* :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);
+ ret = pkgconf_argv_split(value, &argc, &argv);
if (ret < 0)
{
- PKGCONF_TRACE(client, "unable to parse fragment string [%s]", repstr);
- free(repstr);
+ PKGCONF_TRACE(client, "unable to parse fragment string [%s]", value);
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);
+ bool greedy = pkgconf_fragment_is_greedy(argv[i]);
+
+ PKGCONF_TRACE(client, "processing [%s] greedy=%d", argv[i], greedy);
+
+ if (greedy && i + 1 < argc)
+ {
+ pkgconf_buffer_t greedybuf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&greedybuf, argv[i]);
+ pkgconf_buffer_append(&greedybuf, argv[i + 1]);
+ pkgconf_fragment_add(client, list, vars, pkgconf_buffer_str(&greedybuf), flags);
+ pkgconf_buffer_finalize(&greedybuf);
+
+ /* skip over next arg as we combined them */
+ i++;
+ }
+ else
+ pkgconf_fragment_add(client, list, vars, argv[i], flags);
}
pkgconf_argv_free(argv);
- free(repstr);
return true;
}
diff --git a/libpkgconf/iter.h b/libpkgconf/iter.h
index 199d299f6bed..af2ec139bfcd 100644
--- a/libpkgconf/iter.h
+++ b/libpkgconf/iter.h
@@ -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
diff --git a/libpkgconf/libpkgconf.h b/libpkgconf/libpkgconf.h
index ed0f1f996ce4..fc2c01ac8c0d 100644
--- a/libpkgconf/libpkgconf.h
+++ b/libpkgconf/libpkgconf.h
@@ -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)))
@@ -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
@@ -157,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;
@@ -178,11 +246,8 @@ 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;
@@ -195,6 +260,7 @@ typedef void (*pkgconf_pkg_traverse_func_t)(pkgconf_client_t *client, pkgconf_pk
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);
struct pkgconf_client_ {
pkgconf_list_t dir_list;
@@ -204,6 +270,7 @@ struct pkgconf_client_ {
pkgconf_list_t global_vars;
+ void *client_data;
void *error_handler_data;
void *warn_handler_data;
void *trace_handler_data;
@@ -212,6 +279,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;
@@ -232,6 +301,12 @@ struct pkgconf_client_ {
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_ {
@@ -248,9 +323,49 @@ 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);
+
/* 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 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 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);
@@ -270,8 +385,11 @@ PKGCONF_API void pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgc
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);
@@ -299,10 +417,12 @@ PKGCONF_API void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *p
#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_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
@@ -328,12 +448,6 @@ PKGCONF_API void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *p
# define DEPRECATED
#endif
-/* 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, ...);
-
-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);
-
/* pkg.c */
PKGCONF_API bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3);
PKGCONF_API bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3);
@@ -359,13 +473,14 @@ PKGCONF_API void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
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);
@@ -387,38 +502,49 @@ PKGCONF_API int pkgconf_argv_split(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);
+ void (*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);
+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 void 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_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_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 void pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base);
+PKGCONF_API void pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *expression, unsigned int flags);
+PKGCONF_API void 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 void pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data);
+PKGCONF_API void 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);
@@ -441,35 +567,106 @@ PKGCONF_API void pkgconf_audit_log_dependency(pkgconf_client_t *client, const pk
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(/* 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_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;
+}
-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);
+
+/*
+ * !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)
+{
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';
@@ -477,15 +674,117 @@ static inline char pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer) {
}
#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 char *pkgconf_buffer_freeze(pkgconf_buffer_t *buffer)
+ *
+ * Free the underlying buffer, copying the underlying string.
+ * The string must be freed by the caller.
+ *
+ * :param pkgconf_buffer_t *buffer: The buffer to freeze.
+ * :return: The underlying string, copied.
+ */
+static inline char *pkgconf_buffer_freeze(pkgconf_buffer_t *buffer)
+{
+ if (buffer->base == NULL)
+ return NULL;
+
+ char *out = pkgconf_strndup(pkgconf_buffer_str(buffer), pkgconf_buffer_len(buffer));
+ pkgconf_buffer_reset(buffer);
+ 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`.
+ *
+ * :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)
+{
+ 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
}
#endif
diff --git a/libpkgconf/license.c b/libpkgconf/license.c
new file mode 100644
index 000000000000..e9bef533584f
--- /dev/null
+++ b/libpkgconf/license.c
@@ -0,0 +1,329 @@
+/*
+ * 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 void 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] == ':')
+ {
+ pkgconf_buffer_push_byte(buf, s[i]);
+ }
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void 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: nothing
+ */
+void
+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)
+ continue;
+
+ cpy_license->type = license->type;
+
+ if (license->data != NULL)
+ {
+ cpy_license->data = strdup(license->data);
+ }
+
+ pkgconf_node_insert_tail(&cpy_license->iter, cpy_license, list);
+ }
+}
+
+/*
+ * !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);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void 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: nothing
+ */
+void
+pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
+{
+ (void) client;
+
+ pkgconf_license_t *license;
+
+ license = calloc(1, sizeof(pkgconf_license_t));
+ if (!license)
+ {
+ pkgconf_error(client, "pkgconf_license_insert: out of memory");
+ return;
+ }
+ license->type = type;
+ license->data = strdup(data);
+
+ pkgconf_node_insert_tail(&license->iter, license, list);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: 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: nothing
+ */
+void
+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;
+
+ buf_size = strlen(expression) + 1;
+ ret = pkgconf_argv_split(expression, &argc, &argv);
+ if (!ret)
+ {
+ /* This is not the first License:
+ * so add AND
+ */
+ if (license_list->head)
+ {
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND");
+ }
+
+ i = 0;
+ while (i < argc)
+ {
+ string_len = strnlen(argv[i], buf_size);
+ cur_word = argv[i];
+ if (string_len >= 1)
+ {
+ license_sanitize_string(cur_word, &out_buffer);
+ 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] == '(')
+ {
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_OPEN, "(");
+ /* 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 ++;
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word);
+ }
+ }
+ else if (cur_word[cur_len - 1] == ')')
+ {
+ if (cur_len >= 2)
+ {
+ cur_word[cur_len - 1] = 0x00;
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word);
+ }
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_CLOSE, ")");
+ }
+ else if (!strncasecmp(cur_word, "and", 3))
+ {
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND");
+ }
+ else if (!strncasecmp(cur_word, "or", 2))
+ {
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_OR, "OR");
+ }
+ else if (!strncasecmp(cur_word, "with", 2))
+ {
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_WITH, "WITH");
+ }
+ else
+ {
+ pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word);
+ }
+ pkgconf_buffer_finalize(&out_buffer);
+ }
+ i++;
+ }
+
+ pkgconf_argv_free(argv);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: 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: nothing
+ */
+void
+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);
+
+ pkgconf_license_evaluate_str(client, license_list, license_expression, flags);
+ free(license_expression);
+}
+
+/*
+ * !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
+ */
+void
+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);
+ pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(frag_string));
+
+ if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next)->type == PKGCONF_LICENSE_BRACKET_CLOSE))
+ {
+ is_delim = false;
+ }
+
+ if (node->next != NULL && is_delim)
+ {
+ pkgconf_buffer_push_byte(buf, ' ');
+ }
+ }
+}
diff --git a/libpkgconf/output.c b/libpkgconf/output.c
new file mode 100644
index 000000000000..fc7ab85ea2e1
--- /dev/null
+++ b/libpkgconf/output.c
@@ -0,0 +1,153 @@
+/*
+ * 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));
+
+ if (newline)
+ pkgconf_buffer_push_byte(&buf, '\n');
+
+ 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;
+
+ pkgconf_buffer_append(&buf, str);
+ pkgconf_buffer_push_byte(&buf, '\n');
+ 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);
+ pkgconf_buffer_append_vfmt(&buf, fmt, va);
+ va_end(va);
+
+ 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;
+}
diff --git a/libpkgconf/parser.c b/libpkgconf/parser.c
index 3a66b8af2a8d..11f023267388 100644
--- a/libpkgconf/parser.c
+++ b/libpkgconf/parser.c
@@ -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,97 +19,132 @@
#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++;
+ snprintf(warnprefix, sizeof warnprefix, "%s:" SIZE_FMT_SPECIFIER, filename, lineno);
+ pkgconf_parser_parse_buffer(data, ops, warnfunc, &readbuf, warnprefix);
pkgconf_buffer_reset(&readbuf);
}
diff --git a/libpkgconf/path.c b/libpkgconf/path.c
index dda77f5664ab..56d0bb4ddb27 100644
--- a/libpkgconf/path.c
+++ b/libpkgconf/path.c
@@ -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,21 +18,18 @@
#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>
# define PKGCONF_CACHE_INODES
#endif
-#ifdef _WIN32
-# define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH"
-#endif
-
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;
@@ -44,7 +43,7 @@ path_list_contains_entry(const char *text, pkgconf_list_t *dirlist)
return true;
#endif
- if (!strcmp(text, pn->path))
+ if (!strcmp(pkgconf_buffer_str(text), pn->path))
return true;
}
@@ -66,42 +65,65 @@ 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;
- pkgconf_strlcpy(path, text, sizeof path);
- pkgconf_path_relocate(path, sizeof path);
+ pkgconf_buffer_append(&pathbuf, text);
+ pkgconf_path_relocate(&pathbuf);
#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));
if (node == NULL)
+ {
+ pkgconf_buffer_finalize(&pathbuf);
return NULL;
+ }
- node->path = strdup(path);
+ 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;
}
@@ -177,6 +199,9 @@ pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter)
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);
@@ -196,6 +221,7 @@ pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter)
* 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.
@@ -204,11 +230,11 @@ pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter)
* :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);
@@ -235,21 +261,36 @@ 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;
+
+ pkgconf_buffer_append(&relocated, path);
+ 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;
}
@@ -278,6 +319,11 @@ pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
continue;
path->path = strdup(srcpath->path);
+ if (path->path == NULL)
+ {
+ free(path);
+ continue;
+ }
#ifdef PKGCONF_CACHE_INODES
path->handle_path = srcpath->handle_path;
@@ -313,6 +359,11 @@ pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
continue;
path->path = strdup(srcpath->path);
+ if (path->path == NULL)
+ {
+ free(path);
+ continue;
+ }
#ifdef PKGCONF_CACHE_INODES
path->handle_path = srcpath->handle_path;
@@ -350,11 +401,12 @@ pkgconf_path_free(pkgconf_list_t *dirlist)
}
static char *
-normpath(const char *path)
+normpath(const pkgconf_buffer_t *pathbuf)
{
- if (!path)
+ if (!pathbuf || pkgconf_buffer_len(pathbuf) == 0)
return NULL;
+ const char *path = pkgconf_buffer_str(pathbuf);
char *copy = strdup(path);
if (NULL == copy)
return NULL;
@@ -379,37 +431,96 @@ normpath(const char *path)
/*
* !doc
*
- * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen)
+ * .. c:function:: bool pkgconf_path_relocate(pkgconf_buffer_t *buf)
*
* Relocates a path, possibly calling normpath() on it.
*
- * :param char* buf: The path to relocate.
- * :param size_t buflen: The buffer length the path is contained in.
+ * :param pkgconf_buffer_t* buf: The path to relocate.
* :return: true on success, false on error
* :rtype: bool
*/
bool
-pkgconf_path_relocate(char *buf, size_t buflen)
+pkgconf_path_relocate(pkgconf_buffer_t *buf)
{
char *tmpbuf;
if ((tmpbuf = normpath(buf)) != NULL)
{
- size_t tmpbuflen = strlen(tmpbuf);
- if (tmpbuflen > buflen)
- {
- free(tmpbuf);
- return false;
- }
-
- pkgconf_strlcpy(buf, tmpbuf, buflen);
+ pkgconf_buffer_reset(buf);
+ pkgconf_buffer_append(buf, tmpbuf);
free(tmpbuf);
}
return true;
}
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_path_trim_basename(pkgconf_buffer_t *buf)
+ *
+ * Trims the basename from a path.
+ *
+ * :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_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)
+{
+ const char *sep;
+
+ 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
*
@@ -457,3 +568,47 @@ pkgconf_path_build_from_registry(void *hKey, pkgconf_list_t *dir_list, bool filt
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;
+}
diff --git a/libpkgconf/path.h b/libpkgconf/path.h
new file mode 100644
index 000000000000..cf760feff94f
--- /dev/null
+++ b/libpkgconf/path.h
@@ -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
diff --git a/libpkgconf/personality.c b/libpkgconf/personality.c
index 6c017e912776..191c222fe3b7 100644
--- a/libpkgconf/personality.c
+++ b/libpkgconf/personality.c
@@ -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
@@ -44,29 +42,38 @@ build_default_search_path(pkgconf_list_t* dirlist)
{
#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);
+ /* 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);
char * winslash;
namebuf[sizepath] = '\0';
- while ((winslash = strchr (namebuf, '\\')) != NULL)
- {
+
+ while ((winslash = strchr(namebuf, '\\')) != NULL)
*winslash = '/';
- }
+
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_reset(&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;
@@ -83,7 +90,7 @@ build_default_search_path(pkgconf_list_t* dirlist)
paths = NULL;
}
#else
- pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true);
+ pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, false);
#endif
}
@@ -168,7 +175,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;
@@ -176,30 +183,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);
@@ -224,8 +235,9 @@ personality_keyword_pair_cmp(const void *key, const void *ptr)
}
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);
@@ -233,7 +245,7 @@ personality_keyword_set(pkgconf_cross_personality_t *p, const size_t lineno, con
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] = {
@@ -250,39 +262,45 @@ personality_warn_func(void *p, const char *fmt, ...)
(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);
+ pkgconf_buffer_append(&pathbuf, path);
else if (datadir)
- snprintf(pathbuf, sizeof pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet);
+ pkgconf_buffer_append_fmt(&pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet);
else
- snprintf(pathbuf, sizeof pathbuf, "%s/%s.personality", path, triplet);
+ pkgconf_buffer_append_fmt(&pathbuf, "%s/%s.personality", path, triplet);
p = calloc(1, sizeof(pkgconf_cross_personality_t));
if (p == NULL)
+ {
+ pkgconf_buffer_finalize(&pathbuf);
return NULL;
+ }
if (triplet != NULL)
p->name = strdup(triplet);
- f = fopen(pathbuf, "r");
- if (f == NULL) {
+ 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, pathbuf);
+ pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pkgconf_buffer_str(&pathbuf));
+ pkgconf_buffer_finalize(&pathbuf);
return p;
}
@@ -304,7 +322,6 @@ pkgconf_cross_personality_find(const char *triplet)
pkgconf_node_t *n;
pkgconf_cross_personality_t *out = NULL;
#if ! defined(_WIN32) && ! defined(__HAIKU__)
- char pathbuf[PKGCONF_ITEM_SIZE];
const char *envvar;
#endif
@@ -319,16 +336,20 @@ pkgconf_cross_personality_find(const char *triplet)
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)
{
diff --git a/libpkgconf/pkg.c b/libpkgconf/pkg.c
index 491c0defcd65..79a4e1d6065f 100644
--- a/libpkgconf/pkg.c
+++ b/libpkgconf/pkg.c
@@ -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,13 +30,6 @@
* routines.
*/
-#ifdef _WIN32
-# 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
@@ -56,6 +43,9 @@ pkgconf_pkg_traverse_main(pkgconf_client_t *client,
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);
@@ -68,48 +58,56 @@ str_has_suffix(const char *str, const char *suffix)
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;
+
+ pkgconf_buffer_append(&buf, pkg->filename);
- 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 sourcebuf[PKGCONF_ITEM_SIZE];
+ char *targetfilename, *targetdir;
+
+ pkgconf_buffer_reset(&pathbuf);
+ pkgconf_buffer_append(&pathbuf, buf.base);
- char dirnamebuf[PKGCONF_ITEM_SIZE];
- pkgconf_strlcpy(dirnamebuf, buf, sizeof(dirnamebuf));
- const char* targetdir = dirname(dirnamebuf);
+ targetfilename = strrchr(pathbuf.base, '/');
+ if (targetfilename != NULL)
+ {
+ *targetfilename++ = '\0';
+ targetdir = pathbuf.base;
+ if (*targetdir == '\0')
+ targetdir = "/";
+ }
+ else
+ {
+ targetfilename = pathbuf.base;
+ targetdir = ".";
+ }
+
+#ifdef 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_reset(&buf);
+
/*
* The logic here can be a bit tricky, so here's a table:
*
@@ -120,26 +118,22 @@ 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 "." */
- {
- pkgconf_strlcat(buf, targetdir, sizeof buf);
- pkgconf_strlcat(buf, "/", sizeof buf);
- }
- pkgconf_strlcat(buf, sourcebuf, sizeof buf);
+ if ((sourcebuf[0] != '/') && strcmp(targetdir, "."))
+ pkgconf_buffer_append_fmt(&buf, "%s/", targetdir);
+
+ pkgconf_buffer_append(&buf, sourcebuf);
}
#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);
}
-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;
@@ -153,26 +147,77 @@ static int pkgconf_pkg_parser_keyword_pair_cmp(const void *key, const void *ptr)
}
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;
+
+ 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))
+ pkgconf_buffer_push_byte(&tag, (char) tolower((unsigned char) *p++));
+
+ 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))
@@ -180,88 +225,126 @@ pkgconf_pkg_parser_version_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, co
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);
+ (void)keyword;
+ (void)warnprefix;
+ pkgconf_license_evaluate(client, pkg, dest, value, 0);
+}
+
/* 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;
@@ -272,44 +355,27 @@ pkgconf_pkg_parser_keyword_set(void *opaque, const size_t lineno, const char *ke
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);
+ pkgconf_buffer_append(pathbuf, pkg->filename);
+ pkgconf_path_relocate(pathbuf);
- pathiter = strrchr(buf, PKG_DIR_SEP_S);
- if (pathiter == NULL)
- pathiter = strrchr(buf, '/');
- if (pathiter != NULL)
- pathiter[0] = '\0';
+ pkgconf_path_trim_basename(pathbuf);
- pathiter = strrchr(buf, PKG_DIR_SEP_S);
- if (pathiter == NULL)
- pathiter = strrchr(buf, '/');
- if (pathiter == NULL)
- return NULL;
-
- /* 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;
}
/*
@@ -325,25 +391,23 @@ determine_prefix(const pkgconf_pkg_t *pkg, char *buf, size_t buflen)
static char *
convert_path_to_value(const char *path)
{
- char *buf = calloc(1, (strlen(path) + 1) * 2);
- if (buf == NULL)
- return NULL;
-
- 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;
+ pkgconf_buffer_push_byte(&buf, '/');
+ else if (*i == ' ')
+ {
+ pkgconf_buffer_push_byte(&buf, '\\');
+ pkgconf_buffer_push_byte(&buf, ' ');
+ }
+ else
+ pkgconf_buffer_push_byte(&buf, *i);
}
- return buf;
+ return pkgconf_buffer_freeze(&buf);
}
static void
@@ -383,47 +447,102 @@ is_path_prefix_equal(const char *path1, const char *path2, size_t path2_len)
#endif
}
+static inline const char *
+lookup_val_from_env(const pkgconf_client_t *client, const char *pkg_id, const char *keyword)
+{
+ char env_var[PKGCONF_ITEM_SIZE];
+ char *c;
+
+ snprintf(env_var, sizeof env_var, "PKG_CONFIG_%s_%s", pkg_id, keyword);
+
+ for (c = env_var; *c; c++)
+ {
+ *c = (char) toupper((unsigned char) *c);
+
+ if (!isalnum((unsigned char) *c))
+ *c = '_';
+ }
+
+ return pkgconf_client_getenv(client, env_var);
+}
+
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) warnprefix;
- (void) lineno;
+ 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 (!pkgconf_buffer_append(&canonicalized_value, value))
+ goto out;
- pkgconf_strlcpy(canonicalized_value, value, sizeof canonicalized_value);
- canonicalize_path(canonicalized_value);
+ canonicalize_path(canonicalized_value.base);
+
+ if (!(pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX))
+ {
+ pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags);
+ goto out;
+ }
/* 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 size_t oplen = pkgconf_buffer_len(&pkg->orig_prefix);
+
+ if (is_path_prefix_equal(pkgconf_buffer_str(&canonicalized_value), op, oplen))
+ {
+ pkgconf_buffer_t newvalue = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&newvalue, pkgconf_buffer_str_or_empty(&pkg->calculated_prefix));
+ pkgconf_buffer_append(&newvalue, pkgconf_buffer_str(&canonicalized_value) + oplen);
+
+ 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);
+
+ pkgconf_buffer_append(&pkg->orig_prefix, pkgconf_buffer_str(&canonicalized_value));
+ pkgconf_buffer_append(&pkg->calculated_prefix, prefix_value);
+
+ 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 {
@@ -442,11 +561,12 @@ static const pkgconf_parser_operand_func_t pkg_parser_funcs[256] = {
['='] = 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;
@@ -504,33 +624,43 @@ pkg_free_object(pkgconf_pkg_t *pkg)
if (pkg->pc_filedir != NULL)
free(pkg->pc_filedir);
- if (pkg->license != NULL)
- free(pkg->license);
+ if (pkg->license_file != NULL)
+ free(pkg->license_file);
if (pkg->maintainer != NULL)
free(pkg->maintainer);
- if (pkg->copyright != NULL)
- free(pkg->copyright);
-
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);
}
@@ -560,7 +690,7 @@ pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *filename, unsign
if (!str_has_suffix(filename, PKG_CONFIG_EXT))
return NULL;
- f = fopen(filename, "r");
+ f = fopen(filename, "rb");
if (f == NULL)
return NULL;
@@ -598,26 +728,12 @@ pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *filename, unsign
* 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(idptr);
+ pkg->id = strdup(pkgconf_path_find_basename(pkg->filename));
if (pkg->id == NULL)
{
fclose(f);
@@ -637,7 +753,7 @@ pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *filename, unsign
*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))
@@ -647,7 +763,25 @@ pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *filename, unsign
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;
+
+ pkgconf_buffer_append(&abibuf, "c");
+ 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);
@@ -775,19 +909,22 @@ pkgconf_pkg_scan_dir(pkgconf_client_t *client, const char *path, void *data, pkg
for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir))
{
- char filebuf[PKGCONF_ITEM_SIZE];
+ pkgconf_buffer_t filebuf = PKGCONF_BUFFER_INITIALIZER;
pkgconf_pkg_t *pkg;
- pkgconf_strlcpy(filebuf, path, sizeof filebuf);
- pkgconf_strlcat(filebuf, "/", sizeof filebuf);
- pkgconf_strlcat(filebuf, dirent->d_name, sizeof filebuf);
+ pkgconf_buffer_join(&filebuf, '/', path, dirent->d_name, NULL);
- if (!str_has_suffix(filebuf, PKG_CONFIG_EXT))
+ if (!str_has_suffix(pkgconf_buffer_str(&filebuf), PKG_CONFIG_EXT))
+ {
+ pkgconf_buffer_finalize(&filebuf);
continue;
+ }
+
+ PKGCONF_TRACE(client, "trying file [%s]", pkgconf_buffer_str(&filebuf));
- PKGCONF_TRACE(client, "trying file [%s]", filebuf);
+ pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&filebuf), 0);
+ pkgconf_buffer_finalize(&filebuf);
- pkg = pkgconf_pkg_new_from_path(client, filebuf, 0);
if (pkg != NULL)
{
if (func(pkg, data))
@@ -913,13 +1050,6 @@ pkgconf_pkg_find(pkgconf_client_t *client, const char *name)
}
}
- /* 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))
{
@@ -952,240 +1082,6 @@ out:
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 {
@@ -1300,6 +1196,9 @@ pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep)
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);
@@ -1320,10 +1219,10 @@ static const pkgconf_pkg_provides_vermatch_rule_t pkgconf_pkg_provides_vermatch_
[PKGCONF_CMP_ANY] = {
.rulecmp = {
[PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
- },
+ },
.depcmp = {
[PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
- },
+ },
},
[PKGCONF_CMP_LESS_THAN] = {
.rulecmp = {
@@ -1397,7 +1296,7 @@ static const pkgconf_pkg_provides_vermatch_rule_t pkgconf_pkg_provides_vermatch_
},
.depcmp = {
[PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
- },
+ },
},
[PKGCONF_CMP_NOT_EQUAL] = {
.rulecmp = {
@@ -1411,7 +1310,7 @@ static const pkgconf_pkg_provides_vermatch_rule_t pkgconf_pkg_provides_vermatch_
},
.depcmp = {
[PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
- },
+ },
},
};
@@ -1428,11 +1327,11 @@ pkgconf_pkg_scan_provides_vercmp(const pkgconf_dependency_t *pkgdep, const pkgco
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;
@@ -1444,8 +1343,9 @@ pkgconf_pkg_scan_provides_vercmp(const pkgconf_dependency_t *pkgdep, const pkgco
* 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;
@@ -1472,7 +1372,7 @@ pkgconf_pkg_scan_providers(pkgconf_client_t *client, pkgconf_dependency_t *pkgde
.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);
@@ -1573,7 +1473,7 @@ pkgconf_pkg_report_graph_error(pkgconf_client_t *client, pkgconf_pkg_t *parent,
{
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);
@@ -1604,6 +1504,18 @@ pkgconf_pkg_report_graph_error(pkgconf_client_t *client, pkgconf_pkg_t *parent,
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,
@@ -1628,15 +1540,17 @@ pkgconf_pkg_walk_list(pkgconf_client_t *client,
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)
{
@@ -1650,10 +1564,11 @@ pkgconf_pkg_walk_list(pkgconf_client_t *client,
* 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);
@@ -1677,7 +1592,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)
{
@@ -1703,7 +1618,7 @@ pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client,
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))
@@ -1727,7 +1642,7 @@ pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client,
/*
* !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.
*
@@ -1785,6 +1700,15 @@ pkgconf_pkg_traverse_main(pkgconf_client_t *client,
if (eflags != PKGCONF_PKG_ERRF_OK)
return eflags;
+ 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);
+ if (eflags != PKGCONF_PKG_ERRF_OK)
+ return eflags;
+ }
+
PKGCONF_TRACE(client, "%s: walking 'Requires.private' list", root->id);
/* XXX: ugly */
@@ -1810,7 +1734,13 @@ pkgconf_pkg_traverse(pkgconf_client_t *client,
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);
}
@@ -1841,6 +1771,19 @@ pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg,
}
}
+static void
+pkgconf_pkg_cflags_shared_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+{
+ pkgconf_list_t *list = data;
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_shared.head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+ pkgconf_fragment_copy(client, list, frag, true);
+ }
+}
+
/*
* !doc
*
@@ -1864,8 +1807,17 @@ pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t
eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &frags, 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);
+ 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, &frags, maxdepth, skip_flags);
+ }
+ else
+ {
+ eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_shared_collect, &frags, maxdepth, skip_flags);
+ }
+ }
if (eflag != PKGCONF_PKG_ERRF_OK)
{
@@ -1902,6 +1854,14 @@ pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *dat
pkgconf_fragment_copy(client, list, frag, true);
}
}
+ else
+ {
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_shared.head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+ pkgconf_fragment_copy(client, list, frag, true);
+ }
+ }
}
/*
@@ -1933,3 +1893,71 @@ pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *
return eflag;
}
+
+static void
+pkgconf_pkg_link_abi_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+{
+ pkgconf_list_t *list = data;
+ pkgconf_node_t *node;
+
+ 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;
+ }
+
+ return eflag;
+}
diff --git a/libpkgconf/queue.c b/libpkgconf/queue.c
index 09c02e368f85..ff4e049612c0 100644
--- a/libpkgconf/queue.c
+++ b/libpkgconf/queue.c
@@ -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,35 @@
/*
* !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;
+
+ pkgconf_buffer_append(&depbuf, dep->package);
+ if (dep->version != NULL)
+ pkgconf_buffer_append_fmt(&depbuf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version);
+
+ pkgq->package = pkgconf_buffer_freeze(&depbuf);
+ 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,6 +76,9 @@ pkgconf_queue_push(pkgconf_list_t *list, const char *package)
{
pkgconf_queue_t *pkgq = calloc(1, sizeof(pkgconf_queue_t));
+ if (pkgq == NULL)
+ return;
+
pkgq->package = strdup(package);
pkgconf_node_insert_tail(&pkgq->iter, pkgq, list);
}
@@ -165,6 +199,12 @@ pkgconf_queue_collect_dependencies_walk(pkgconf_client_t *client,
eflags |= pkgconf_queue_collect_dependencies_main(client, pkg, data, depth - 1);
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);
}
@@ -189,6 +229,15 @@ pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client,
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);
+ if (eflags != PKGCONF_PKG_ERRF_OK)
+ return eflags;
+ }
+
PKGCONF_TRACE(client, "%s: collecting private dependencies, level %d", root->id, maxdepth);
/* XXX: ugly */
@@ -221,6 +270,50 @@ pkgconf_queue_collect_dependencies(pkgconf_client_t *client,
}
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);
+ pkgconf_node_insert(&flattened_conflict->iter, flattened_conflict, &world->conflicts);
+ }
+ }
+
+ return eflags;
+}
+
+static inline unsigned int
pkgconf_queue_verify(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth)
{
unsigned int result;
@@ -253,6 +346,13 @@ pkgconf_queue_verify(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_lis
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 +367,18 @@ pkgconf_queue_verify(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_lis
}
}
+ 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 +405,7 @@ pkgconf_solution_free(pkgconf_client_t *client, pkgconf_pkg_t *world)
{
pkgconf_dependency_free(&world->required);
pkgconf_dependency_free(&world->requires_private);
+ pkgconf_dependency_free(&world->conflicts);
}
}
diff --git a/libpkgconf/stdinc.h b/libpkgconf/stdinc.h
index 31284ed88261..4bc0596cb88e 100644
--- a/libpkgconf/stdinc.h
+++ b/libpkgconf/stdinc.h
@@ -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>
@@ -31,15 +38,29 @@
# 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
-# ifndef __MINGW32__
-# define SIZE_FMT_SPECIFIER "%I64u"
+# ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define SIZE_FMT_SPECIFIER "%zu"
# else
-# define SIZE_FMT_SPECIFIER "%llu"
+# 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__
@@ -52,9 +73,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"
@@ -65,11 +91,18 @@
# 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
diff --git a/libpkgconf/tuple.c b/libpkgconf/tuple.c
index 83f6a479e772..1e349711f0ec 100644
--- a/libpkgconf/tuple.c
+++ b/libpkgconf/tuple.c
@@ -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_t *client, const char *key, const char *
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_t *client, const char *key)
* :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_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);
}
/*
@@ -122,6 +113,9 @@ pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
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,101 @@ 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;
+
+ (void) client;
- pkgconf_tuple_find_delete(list, key);
+ 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;
+ }
- PKGCONF_TRACE(client, "adding tuple to @%p: %s => %s (parsed? %d)", list, key, tuple->value, parse);
+ v->flags = flags;
- pkgconf_node_insert(&tuple->iter, tuple, list);
+ if (!parse)
+ {
+ pkgconf_buffer_reset(&v->bcbuf);
+ pkgconf_bytecode_emit_text(&v->bcbuf, dequote_value, strlen(dequote_value));
+ pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+ free(dequote_value);
+ return (pkgconf_tuple_t *) v;
+ }
+ pkgconf_bytecode_compile(&rhs_bcbuf, dequote_value);
free(dequote_value);
- return tuple;
-}
+ /* ugh, we are doing var=${var}/foo stuff */
+ if (pkgconf_bytecode_references_var(&rhs_bcbuf, key))
+ {
+ pkgconf_buffer_t old_bcbuf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t new_bcbuf = PKGCONF_BUFFER_INITIALIZER;
-/*
- * !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;
+ /* preserve the old bytecode */
+ pkgconf_buffer_copy(&v->bcbuf, &old_bcbuf);
- global_tuple = lookup_global_tuple(client, key);
- if (global_tuple != NULL && global_tuple->flags & PKGCONF_PKG_TUPLEF_OVERRIDE)
- return global_tuple->value;
+ /* 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);
- PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
- {
- pkgconf_tuple_t *tuple = node->data;
+ return NULL;
+ }
- if (!strcmp(tuple->key, key))
- return tuple->value;
+ /* copy the spliced bytecode back to &rhs_bcbuf, replacing its contents */
+ pkgconf_buffer_copy(&new_bcbuf, &rhs_bcbuf);
+
+ pkgconf_buffer_finalize(&old_bcbuf);
+ pkgconf_buffer_finalize(&new_bcbuf);
}
- if (global_tuple != NULL)
- return global_tuple->value;
+ pkgconf_buffer_copy(&rhs_bcbuf, &v->bcbuf);
+ pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+ pkgconf_buffer_finalize(&rhs_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;
-
- 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;
- }
+ pkgconf_variable_t *v;
- 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_reset(&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 +290,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);
}
/*
diff --git a/libpkgconf/variable.c b/libpkgconf/variable.c
new file mode 100644
index 000000000000..fa39390910f5
--- /dev/null
+++ b/libpkgconf/variable.c
@@ -0,0 +1,162 @@
+/*
+ * 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;
+
+ if (key == NULL)
+ return NULL;
+
+ v = calloc(1, sizeof(*v));
+ if (v == NULL)
+ return NULL;
+
+ v->key = strdup(key);
+ if (v->key == NULL)
+ {
+ free(v);
+ return NULL;
+ }
+
+ return v;
+}
+
+void
+pkgconf_variable_free(pkgconf_variable_t *v)
+{
+ if (v == NULL)
+ return;
+
+ pkgconf_buffer_finalize(&v->bcbuf);
+ free(v->key);
+ 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);
+}
diff --git a/libpkgconf/version.c b/libpkgconf/version.c
new file mode 100644
index 000000000000..7b597bd721b2
--- /dev/null
+++ b/libpkgconf/version.c
@@ -0,0 +1,252 @@
+/*
+ * 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_OTHER
+} 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;
+ }
+
+ if (isalpha((unsigned char)*s))
+ {
+ tok.kind = PKGCONF_VERSION_TOKEN_ALPHA;
+ while (*tok.end && isalpha((unsigned char)*tok.end))
+ tok.end++;
+ it->cur = tok.end;
+ return tok;
+ }
+
+ tok.kind = PKGCONF_VERSION_TOKEN_OTHER;
+ tok.end = s + 1;
+ 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 (a->kind == PKGCONF_VERSION_TOKEN_ALPHA)
+ {
+ if (b->kind != PKGCONF_VERSION_TOKEN_ALPHA)
+ return -1;
+
+ return pkgconf_version_compare_alpha(a, b);
+ }
+
+ if (a->kind < b->kind)
+ return -1;
+ if (a->kind > b->kind)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * !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;
+ }
+ }
+}
diff --git a/libpkgconf/win-dirent.h b/libpkgconf/win-dirent.h
index 0734d6dc470f..8cfb1bd107de 100644
--- a/libpkgconf/win-dirent.h
+++ b/libpkgconf/win-dirent.h
@@ -1,1028 +1,247 @@
/*
- * Dirent interface for Microsoft Visual Studio
+ * win-dirent.h - minimal POSIX dirent shim for Windows (MSVC / MinGW-w64)
*
- * Copyright (C) 1998-2019 Toni Ronkko
- * This file is part of dirent. Dirent may be freely distributed
- * under the MIT license. For all details and documentation, see
- * https://github.com/tronkko/dirent
+ * Implements only opendir(), readdir(), closedir(): the subset used by
+ * pkgconf and its test suite.
+ *
+ * Deliberately not included under Cygwin, which provides its own dirent.h.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 Elizabeth Ashford. All rights reserved.
+ *
+ * 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 DIRENT_H
-#define DIRENT_H
-/* Hide warnings about unreferenced local functions */
-#if defined(__clang__)
-# pragma clang diagnostic ignored "-Wunused-function"
-#elif defined(_MSC_VER)
-# pragma warning(disable:4505)
-#elif defined(__GNUC__)
-# pragma GCC diagnostic ignored "-Wunused-function"
-#endif
+#ifndef PKGCONF_WIN_DIRENT_H
+#define PKGCONF_WIN_DIRENT_H
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
-/*
- * Include windows.h without Windows Sockets 1.1 to prevent conflicts with
- * Windows Sockets 2.0.
- */
#ifndef WIN32_LEAN_AND_MEAN
-# define WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <wchar.h>
-#include <string.h>
+#include <errno.h>
#include <stdlib.h>
-#include <malloc.h>
+#include <string.h>
#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <ctype.h>
-
-/* Indicates that d_type field is available in dirent structure */
-#define _DIRENT_HAVE_D_TYPE
-
-/* Indicates that d_namlen field is available in dirent structure */
-#define _DIRENT_HAVE_D_NAMLEN
-
-/* Entries missing from MSVC 6.0 */
-#if !defined(FILE_ATTRIBUTE_DEVICE)
-# define FILE_ATTRIBUTE_DEVICE 0x40
-#endif
-
-/* File type and permission flags for stat(), general mask */
-#if !defined(S_IFMT)
-# define S_IFMT _S_IFMT
-#endif
-
-/* Directory bit */
-#if !defined(S_IFDIR)
-# define S_IFDIR _S_IFDIR
-#endif
-
-/* Character device bit */
-#if !defined(S_IFCHR)
-# define S_IFCHR _S_IFCHR
-#endif
-
-/* Pipe bit */
-#if !defined(S_IFFIFO)
-# define S_IFFIFO _S_IFFIFO
-#endif
-
-/* Regular file bit */
-#if !defined(S_IFREG)
-# define S_IFREG _S_IFREG
-#endif
-
-/* Read permission */
-#if !defined(S_IREAD)
-# define S_IREAD _S_IREAD
-#endif
-
-/* Write permission */
-#if !defined(S_IWRITE)
-# define S_IWRITE _S_IWRITE
-#endif
-
-/* Execute permission */
-#if !defined(S_IEXEC)
-# define S_IEXEC _S_IEXEC
-#endif
-
-/* Pipe */
-#if !defined(S_IFIFO)
-# define S_IFIFO _S_IFIFO
-#endif
-
-/* Block device */
-#if !defined(S_IFBLK)
-# define S_IFBLK 0
-#endif
-/* Link */
-#if !defined(S_IFLNK)
-# define S_IFLNK 0
-#endif
-
-/* Socket */
-#if !defined(S_IFSOCK)
-# define S_IFSOCK 0
-#endif
-
-/* Read user permission */
-#if !defined(S_IRUSR)
-# define S_IRUSR S_IREAD
-#endif
-
-/* Write user permission */
-#if !defined(S_IWUSR)
-# define S_IWUSR S_IWRITE
-#endif
-
-/* Execute user permission */
-#if !defined(S_IXUSR)
-# define S_IXUSR 0
-#endif
-
-/* Read group permission */
-#if !defined(S_IRGRP)
-# define S_IRGRP 0
-#endif
-
-/* Write group permission */
-#if !defined(S_IWGRP)
-# define S_IWGRP 0
-#endif
-
-/* Execute group permission */
-#if !defined(S_IXGRP)
-# define S_IXGRP 0
-#endif
-
-/* Read others permission */
-#if !defined(S_IROTH)
-# define S_IROTH 0
-#endif
-
-/* Write others permission */
-#if !defined(S_IWOTH)
-# define S_IWOTH 0
-#endif
-
-/* Execute others permission */
-#if !defined(S_IXOTH)
-# define S_IXOTH 0
-#endif
-
-/* Maximum length of file name */
-#if !defined(PATH_MAX)
+#ifndef PATH_MAX
# define PATH_MAX MAX_PATH
#endif
-#if !defined(FILENAME_MAX)
-# define FILENAME_MAX MAX_PATH
+#ifndef NAME_MAX
+# define NAME_MAX MAX_PATH
#endif
-#if !defined(NAME_MAX)
-# define NAME_MAX FILENAME_MAX
-#endif
-
-/* File type flags for d_type */
-#define DT_UNKNOWN 0
-#define DT_REG S_IFREG
-#define DT_DIR S_IFDIR
-#define DT_FIFO S_IFIFO
-#define DT_SOCK S_IFSOCK
-#define DT_CHR S_IFCHR
-#define DT_BLK S_IFBLK
-#define DT_LNK S_IFLNK
-
-/* Macros for converting between st_mode and d_type */
-#define IFTODT(mode) ((mode) & S_IFMT)
-#define DTTOIF(type) (type)
-
-/*
- * File type macros. Note that block devices, sockets and links cannot be
- * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are
- * only defined for compatibility. These macros should always return false
- * on Windows.
- */
-#if !defined(S_ISFIFO)
-# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO)
-#endif
-#if !defined(S_ISDIR)
-# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
-#endif
-#if !defined(S_ISREG)
-# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
-#endif
-#if !defined(S_ISLNK)
-# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
-#endif
-#if !defined(S_ISSOCK)
-# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)
-#endif
-#if !defined(S_ISCHR)
-# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR)
-#endif
-#if !defined(S_ISBLK)
-# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK)
-#endif
-
-/* Return the exact length of the file name without zero terminator */
-#define _D_EXACT_NAMLEN(p) ((p)->d_namlen)
-
-/* Return the maximum size of a file name */
-#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1)
+// d_type constants: only the values pkgconf actually tests against
+#define DT_UNKNOWN 0
+#define DT_REG 8 // S_IFREG >> 12
+#define DT_DIR 4 // S_IFDIR >> 12
#ifdef __cplusplus
extern "C" {
#endif
+/* ============ */
+/* Public types */
+/* ============ */
-/* Wide-character version */
-struct _wdirent {
- /* Always zero */
- long d_ino;
-
- /* File position within stream */
- long d_off;
-
- /* Structure size */
- unsigned short d_reclen;
-
- /* Length of name without \0 */
+struct dirent
+{
+ // Standard fields pkgconf actually use
+ char d_name[PATH_MAX + 1];
size_t d_namlen;
-
- /* File type */
int d_type;
- /* File name */
- wchar_t d_name[PATH_MAX+1];
+ unsigned short d_reclen;
};
-typedef struct _wdirent _wdirent;
-
-struct _WDIR {
- /* Current directory entry */
- struct _wdirent ent;
- /* Private file data */
+typedef struct pkgconf_DIR
+{
WIN32_FIND_DATAW data;
-
- /* True if data is valid */
- int cached;
-
- /* Win32 search handle */
HANDLE handle;
-
- /* Initial directory name */
- wchar_t *patt;
-};
-typedef struct _WDIR _WDIR;
-
-/* Multi-byte character version */
-struct dirent {
- /* Always zero */
- long d_ino;
-
- /* File position within stream */
- long d_off;
-
- /* Structure size */
- unsigned short d_reclen;
-
- /* Length of name without \0 */
- size_t d_namlen;
-
- /* File type */
- int d_type;
-
- /* File name */
- char d_name[PATH_MAX+1];
-};
-typedef struct dirent dirent;
-
-struct DIR {
struct dirent ent;
- struct _WDIR *wdirp;
-};
-typedef struct DIR DIR;
-
-
-/* Dirent functions */
-static DIR *opendir(const char *dirname);
-static _WDIR *_wopendir(const wchar_t *dirname);
-
-static struct dirent *readdir(DIR *dirp);
-static struct _wdirent *_wreaddir(_WDIR *dirp);
-
-static int readdir_r(
- DIR *dirp, struct dirent *entry, struct dirent **result);
-static int _wreaddir_r(
- _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result);
-
-static int closedir(DIR *dirp);
-static int _wclosedir(_WDIR *dirp);
-
-static void rewinddir(DIR* dirp);
-static void _wrewinddir(_WDIR* dirp);
-
-static int scandir(const char *dirname, struct dirent ***namelist,
- int (*filter)(const struct dirent*),
- int (*compare)(const struct dirent**, const struct dirent**));
-
-static int alphasort(const struct dirent **a, const struct dirent **b);
-
-static int versionsort(const struct dirent **a, const struct dirent **b);
-
-static int strverscmp(const char *a, const char *b);
-
-/* For compatibility with Symbian */
-#define wdirent _wdirent
-#define WDIR _WDIR
-#define wopendir _wopendir
-#define wreaddir _wreaddir
-#define wclosedir _wclosedir
-#define wrewinddir _wrewinddir
-
-/* Compatibility with older Microsoft compilers and non-Microsoft compilers */
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-# define wcstombs_s dirent_wcstombs_s
-# define mbstowcs_s dirent_mbstowcs_s
-#endif
-
-/* Optimize dirent_set_errno() away on modern Microsoft compilers */
-#if defined(_MSC_VER) && _MSC_VER >= 1400
-# define dirent_set_errno _set_errno
-#endif
-
-
-/* Internal utility functions */
-static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp);
-static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp);
-
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-static int dirent_mbstowcs_s(
- size_t *pReturnValue, wchar_t *wcstr, size_t sizeInWords,
- const char *mbstr, size_t count);
-#endif
-
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-static int dirent_wcstombs_s(
- size_t *pReturnValue, char *mbstr, size_t sizeInBytes,
- const wchar_t *wcstr, size_t count);
-#endif
-
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-static void dirent_set_errno(int error);
-#endif
-
-
-/*
- * Open directory stream DIRNAME for read and return a pointer to the
- * internal working area that is used to retrieve individual directory
- * entries.
- */
-static _WDIR *_wopendir(const wchar_t *dirname)
-{
- wchar_t *p;
-
- /* Must have directory name */
- if (dirname == NULL || dirname[0] == '\0') {
- dirent_set_errno(ENOENT);
- return NULL;
- }
-
- /* Allocate new _WDIR structure */
- _WDIR *dirp = (_WDIR*) malloc(sizeof(struct _WDIR));
- if (!dirp)
- return NULL;
-
- /* Reset _WDIR structure */
- dirp->handle = INVALID_HANDLE_VALUE;
- dirp->patt = NULL;
- dirp->cached = 0;
-
- /*
- * Compute the length of full path plus zero terminator
- *
- * Note that on WinRT there's no way to convert relative paths
- * into absolute paths, so just assume it is an absolute path.
- */
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
- /* Desktop */
- DWORD n = GetFullPathNameW(dirname, 0, NULL, NULL);
-#else
- /* WinRT */
- size_t n = wcslen(dirname);
-#endif
-
- /* Allocate room for absolute directory name and search pattern */
- dirp->patt = (wchar_t*) malloc(sizeof(wchar_t) * n + 16);
- if (dirp->patt == NULL)
- goto exit_closedir;
+ int cached; // non-zero when data holds an unread entry
+} DIR;
- /*
- * Convert relative directory name to an absolute one. This
- * allows rewinddir() to function correctly even when current
- * working directory is changed between opendir() and rewinddir().
- *
- * Note that on WinRT there's no way to convert relative paths
- * into absolute paths, so just assume it is an absolute path.
- */
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
- /* Desktop */
- n = GetFullPathNameW(dirname, n, dirp->patt, NULL);
- if (n <= 0)
- goto exit_closedir;
-#else
- /* WinRT */
- wcsncpy_s(dirp->patt, n+1, dirname, n);
-#endif
-
- /* Append search pattern \* to the directory name */
- p = dirp->patt + n;
- switch (p[-1]) {
- case '\\':
- case '/':
- case ':':
- /* Directory ends in path separator, e.g. c:\temp\ */
- /*NOP*/;
- break;
-
- default:
- /* Directory name doesn't end in path separator */
- *p++ = '\\';
- }
- *p++ = '*';
- *p = '\0';
-
- /* Open directory stream and retrieve the first entry */
- if (!dirent_first(dirp))
- goto exit_closedir;
-
- /* Success */
- return dirp;
-
- /* Failure */
-exit_closedir:
- _wclosedir(dirp);
- return NULL;
-}
+/* ================ */
+/* Internal helpers */
+/* ================ */
/*
- * Read next directory entry.
- *
- * Returns pointer to static directory entry which may be overwritten by
- * subsequent calls to _wreaddir().
+ * Convert a Win32 file-attribute word to a DT_* constant.
+ * Devices can't be distinguished from regular files on win32, so we map everything that isn't a
+ * directory to DT_REG.
*/
-static struct _wdirent *_wreaddir(_WDIR *dirp)
+static inline int
+pkgconf__attr_to_dtype(DWORD attr)
{
- /*
- * Read directory entry to buffer. We can safely ignore the return
- * value as entry will be set to NULL in case of error.
- */
- struct _wdirent *entry;
- (void) _wreaddir_r(dirp, &dirp->ent, &entry);
-
- /* Return pointer to statically allocated directory entry */
- return entry;
+ if (attr & FILE_ATTRIBUTE_DIRECTORY)
+ return DT_DIR;
+ return DT_REG;
}
/*
- * Read next directory entry.
- *
- * Returns zero on success. If end of directory stream is reached, then sets
- * result to NULL and returns zero.
+ * Populate ent from the current data field.
+ * Returns 0 on success, -1 if the filename could not be converted to UTF-8.
*/
-static int _wreaddir_r(
- _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result)
+static inline int
+pkgconf__fill_dirent(DIR *dirp)
{
- /* Read next directory entry */
- WIN32_FIND_DATAW *datap = dirent_next(dirp);
- if (!datap) {
- /* Return NULL to indicate end of directory */
- *result = NULL;
- return /*OK*/0;
- }
+ int n = WideCharToMultiByte(
+ CP_UTF8, 0,
+ dirp->data.cFileName, -1,
+ dirp->ent.d_name, PATH_MAX + 1,
+ NULL, NULL);
- /*
- * Copy file name as wide-character string. If the file name is too
- * long to fit in to the destination buffer, then truncate file name
- * to PATH_MAX characters and zero-terminate the buffer.
- */
- size_t n = 0;
- while (n < PATH_MAX && datap->cFileName[n] != 0) {
- entry->d_name[n] = datap->cFileName[n];
- n++;
- }
- entry->d_name[n] = 0;
-
- /* Length of file name excluding zero terminator */
- entry->d_namlen = n;
-
- /* File type */
- DWORD attr = datap->dwFileAttributes;
- if ((attr & FILE_ATTRIBUTE_DEVICE) != 0)
- entry->d_type = DT_CHR;
- else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
- entry->d_type = DT_DIR;
- else
- entry->d_type = DT_REG;
-
- /* Reset dummy fields */
- entry->d_ino = 0;
- entry->d_off = 0;
- entry->d_reclen = sizeof(struct _wdirent);
-
- /* Set result address */
- *result = entry;
- return /*OK*/0;
-}
-
-/*
- * Close directory stream opened by opendir() function. This invalidates the
- * DIR structure as well as any directory entry read previously by
- * _wreaddir().
- */
-static int _wclosedir(_WDIR *dirp)
-{
- if (!dirp) {
- dirent_set_errno(EBADF);
- return /*failure*/-1;
+ if (n <= 0)
+ {
+ /*
+ * Conversion failed. Rather than returning a broken entry, signal the error so the caller can
+ * skip or abort.
+ */
+ errno = EILSEQ;
+ return -1;
}
- /* Release search handle */
- if (dirp->handle != INVALID_HANDLE_VALUE)
- FindClose(dirp->handle);
-
- /* Release search pattern */
- free(dirp->patt);
-
- /* Release directory structure */
- free(dirp);
- return /*success*/0;
-}
-
-/*
- * Rewind directory stream such that _wreaddir() returns the very first
- * file name again.
- */
-static void _wrewinddir(_WDIR* dirp)
-{
- if (!dirp)
- return;
-
- /* Release existing search handle */
- if (dirp->handle != INVALID_HANDLE_VALUE)
- FindClose(dirp->handle);
-
- /* Open new search handle */
- dirent_first(dirp);
+ dirp->ent.d_namlen = (size_t)(n - 1);
+ dirp->ent.d_type = pkgconf__attr_to_dtype(dirp->data.dwFileAttributes);
+ dirp->ent.d_reclen = sizeof(struct dirent);
+ return 0;
}
-/* Get first directory entry */
-static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp)
-{
- if (!dirp)
- return NULL;
-
- /* Open directory and retrieve the first entry */
- dirp->handle = FindFirstFileExW(
- dirp->patt, FindExInfoStandard, &dirp->data,
- FindExSearchNameMatch, NULL, 0);
- if (dirp->handle == INVALID_HANDLE_VALUE)
- goto error;
-
- /* A directory entry is now waiting in memory */
- dirp->cached = 1;
- return &dirp->data;
-
-error:
- /* Failed to open directory: no directory entry in memory */
- dirp->cached = 0;
-
- /* Set error code */
- DWORD errorcode = GetLastError();
- switch (errorcode) {
- case ERROR_ACCESS_DENIED:
- /* No read access to directory */
- dirent_set_errno(EACCES);
- break;
-
- case ERROR_DIRECTORY:
- /* Directory name is invalid */
- dirent_set_errno(ENOTDIR);
- break;
+/* ========= */
+/* API shims */
+/* ========= */
- case ERROR_PATH_NOT_FOUND:
- default:
- /* Cannot find the file */
- dirent_set_errno(ENOENT);
- }
- return NULL;
-}
-
-/* Get next directory entry */
-static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp)
+static inline DIR *
+opendir(const char *path)
{
- /* Is the next directory entry already in cache? */
- if (dirp->cached) {
- /* Yes, a valid directory entry found in memory */
- dirp->cached = 0;
- return &dirp->data;
- }
-
- /* No directory entry in cache */
- if (dirp->handle == INVALID_HANDLE_VALUE)
- return NULL;
-
- /* Read the next directory entry from stream */
- if (FindNextFileW(dirp->handle, &dirp->data) == FALSE)
- goto exit_close;
-
- /* Success */
- return &dirp->data;
-
- /* Failure */
-exit_close:
- FindClose(dirp->handle);
- dirp->handle = INVALID_HANDLE_VALUE;
- return NULL;
-}
+ DIR *dirp;
+ wchar_t wpath[PATH_MAX + 3]; // +3: possible '\', '*', NUL
+ wchar_t *p;
+ int wlen;
-/* Open directory stream using plain old C-string */
-static DIR *opendir(const char *dirname)
-{
- /* Must have directory name */
- if (dirname == NULL || dirname[0] == '\0') {
- dirent_set_errno(ENOENT);
+ if (path == NULL || path[0] == '\0')
+ {
+ errno = ENOENT;
return NULL;
}
- /* Allocate memory for DIR structure */
- struct DIR *dirp = (DIR*) malloc(sizeof(struct DIR));
- if (!dirp)
+ // Convert caller-supplied UTF-8 path to wide string
+ wlen = MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX + 1);
+ if (wlen <= 0)
+ {
+ errno = ENOENT;
return NULL;
-
- /* Convert directory name to wide-character string */
- wchar_t wname[PATH_MAX + 1];
- size_t n;
- int error = mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX+1);
- if (error)
- goto exit_failure;
-
- /* Open directory stream using wide-character name */
- dirp->wdirp = _wopendir(wname);
- if (!dirp->wdirp)
- goto exit_failure;
-
- /* Success */
- return dirp;
-
- /* Failure */
-exit_failure:
- free(dirp);
- return NULL;
-}
-
-/* Read next directory entry */
-static struct dirent *readdir(DIR *dirp)
-{
- /*
- * Read directory entry to buffer. We can safely ignore the return
- * value as entry will be set to NULL in case of error.
- */
- struct dirent *entry;
- (void) readdir_r(dirp, &dirp->ent, &entry);
-
- /* Return pointer to statically allocated directory entry */
- return entry;
-}
-
-/*
- * Read next directory entry into called-allocated buffer.
- *
- * Returns zero on success. If the end of directory stream is reached, then
- * sets result to NULL and returns zero.
- */
-static int readdir_r(
- DIR *dirp, struct dirent *entry, struct dirent **result)
-{
- /* Read next directory entry */
- WIN32_FIND_DATAW *datap = dirent_next(dirp->wdirp);
- if (!datap) {
- /* No more directory entries */
- *result = NULL;
- return /*OK*/0;
}
- /* Attempt to convert file name to multi-byte string */
- size_t n;
- int error = wcstombs_s(
- &n, entry->d_name, PATH_MAX + 1,
- datap->cFileName, PATH_MAX + 1);
-
- /*
- * If the file name cannot be represented by a multi-byte string, then
- * attempt to use old 8+3 file name. This allows the program to
- * access files although file names may seem unfamiliar to the user.
- *
- * Be ware that the code below cannot come up with a short file name
- * unless the file system provides one. At least VirtualBox shared
- * folders fail to do this.
- */
- if (error && datap->cAlternateFileName[0] != '\0') {
- error = wcstombs_s(
- &n, entry->d_name, PATH_MAX + 1,
- datap->cAlternateFileName, PATH_MAX + 1);
- }
-
- if (!error) {
- /* Length of file name excluding zero terminator */
- entry->d_namlen = n - 1;
-
- /* File attributes */
- DWORD attr = datap->dwFileAttributes;
- if ((attr & FILE_ATTRIBUTE_DEVICE) != 0)
- entry->d_type = DT_CHR;
- else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
- entry->d_type = DT_DIR;
- else
- entry->d_type = DT_REG;
-
- /* Reset dummy fields */
- entry->d_ino = 0;
- entry->d_off = 0;
- entry->d_reclen = sizeof(struct dirent);
- } else {
- /*
- * Cannot convert file name to multi-byte string so construct
- * an erroneous directory entry and return that. Note that
- * we cannot return NULL as that would stop the processing
- * of directory entries completely.
- */
- entry->d_name[0] = '?';
- entry->d_name[1] = '\0';
- entry->d_namlen = 1;
- entry->d_type = DT_UNKNOWN;
- entry->d_ino = 0;
- entry->d_off = -1;
- entry->d_reclen = 0;
- }
+ // Append \* search glob, handling paths that already end in a separator
+ p = wpath + wlen - 1;
+ if (*p != L'\\' && *p != L'/' && *p != L':')
+ *p++ = L'\\';
+ *p++ = L'*';
+ *p = L'\0';
- /* Return pointer to directory entry */
- *result = entry;
- return /*OK*/0;
-}
-
-/* Close directory stream */
-static int closedir(DIR *dirp)
-{
- int ok;
-
- if (!dirp)
- goto exit_failure;
-
- /* Close wide-character directory stream */
- ok = _wclosedir(dirp->wdirp);
- dirp->wdirp = NULL;
-
- /* Release multi-byte character version */
- free(dirp);
- return ok;
-
-exit_failure:
- /* Invalid directory stream */
- dirent_set_errno(EBADF);
- return /*failure*/-1;
-}
-
-/* Rewind directory stream to beginning */
-static void rewinddir(DIR* dirp)
-{
- if (!dirp)
- return;
-
- /* Rewind wide-character string directory stream */
- _wrewinddir(dirp->wdirp);
-}
-
-/* Scan directory for entries */
-static int scandir(
- const char *dirname, struct dirent ***namelist,
- int (*filter)(const struct dirent*),
- int (*compare)(const struct dirent**, const struct dirent**))
-{
- int result;
-
- /* Open directory stream */
- DIR *dir = opendir(dirname);
- if (!dir) {
- /* Cannot open directory */
- return /*Error*/ -1;
+ dirp = (DIR *)malloc(sizeof(DIR));
+ if (dirp == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
}
- /* Read directory entries to memory */
- struct dirent *tmp = NULL;
- struct dirent **files = NULL;
- size_t size = 0;
- size_t allocated = 0;
- while (1) {
- /* Allocate room for a temporary directory entry */
- if (!tmp) {
- tmp = (struct dirent*) malloc(sizeof(struct dirent));
- if (!tmp)
- goto exit_failure;
- }
-
- /* Read directory entry to temporary area */
- struct dirent *entry;
- if (readdir_r(dir, tmp, &entry) != /*OK*/0)
- goto exit_failure;
-
- /* Stop if we already read the last directory entry */
- if (entry == NULL)
- goto exit_success;
-
- /* Determine whether to include the entry in results */
- if (filter && !filter(tmp))
- continue;
-
- /* Enlarge pointer table to make room for another pointer */
- if (size >= allocated) {
- /* Compute number of entries in the new table */
- size_t num_entries = size * 2 + 16;
-
- /* Allocate new pointer table or enlarge existing */
- void *p = realloc(files, sizeof(void*) * num_entries);
- if (!p)
- goto exit_failure;
+ dirp->handle = FindFirstFileExW(
+ wpath,
+ FindExInfoBasic, // skip short (8.3) names, faster
+ &dirp->data,
+ FindExSearchNameMatch,
+ NULL,
+ FIND_FIRST_EX_LARGE_FETCH);
- /* Got the memory */
- files = (dirent**) p;
- allocated = num_entries;
+ if (dirp->handle == INVALID_HANDLE_VALUE)
+ {
+ DWORD err = GetLastError();
+ free(dirp);
+ switch (err)
+ {
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+ case ERROR_DIRECTORY:
+ errno = ENOTDIR;
+ break;
+ default:
+ errno = ENOENT;
+ break;
}
-
- /* Store the temporary entry to ptr table */
- files[size++] = tmp;
- tmp = NULL;
- }
-
-exit_failure:
- /* Release allocated file entries */
- for (size_t i = 0; i < size; i++) {
- free(files[i]);
+ return NULL;
}
- /* Release the pointer table */
- free(files);
- files = NULL;
-
- /* Exit with error code */
- result = /*error*/ -1;
- goto exit_status;
-
-exit_success:
- /* Sort directory entries */
- qsort(files, size, sizeof(void*),
- (int (*) (const void*, const void*)) compare);
-
- /* Pass pointer table to caller */
- if (namelist)
- *namelist = files;
-
- /* Return the number of directory entries read */
- result = (int) size;
-
-exit_status:
- /* Release temporary directory entry, if we had one */
- free(tmp);
-
- /* Close directory stream */
- closedir(dir);
- return result;
-}
-
-/* Alphabetical sorting */
-static int alphasort(const struct dirent **a, const struct dirent **b)
-{
- return strcoll((*a)->d_name, (*b)->d_name);
-}
-
-/* Sort versions */
-static int versionsort(const struct dirent **a, const struct dirent **b)
-{
- return strverscmp((*a)->d_name, (*b)->d_name);
+ dirp->cached = 1; // first entry already sitting in data
+ return dirp;
}
-/* Compare strings */
-static int strverscmp(const char *a, const char *b)
+static inline struct dirent *
+readdir(DIR *dirp)
{
- size_t i = 0;
- size_t j;
-
- /* Find first difference */
- while (a[i] == b[i]) {
- if (a[i] == '\0') {
- /* No difference */
- return 0;
- }
- ++i;
+ if (dirp == NULL)
+ {
+ errno = EBADF;
+ return NULL;
}
- /* Count backwards and find the leftmost digit */
- j = i;
- while (j > 0 && isdigit((unsigned char)a[j-1])) {
- --j;
+ if (dirp->cached)
+ {
+ // Consume the entry that opendir() or the previous FindNextFileW already placed in data
+ dirp->cached = 0;
}
+ else
+ {
+ // Advance to the next entry
+ if (dirp->handle == INVALID_HANDLE_VALUE)
+ return NULL;
- /* Determine mode of comparison */
- if (a[j] == '0' || b[j] == '0') {
- /* Find the next non-zero digit */
- while (a[j] == '0' && a[j] == b[j]) {
- j++;
- }
-
- /* String with more digits is smaller, e.g 002 < 01 */
- if (isdigit((unsigned char)a[j])) {
- if (!isdigit((unsigned char)b[j])) {
- return -1;
- }
- } else if ((unsigned char)isdigit(b[j])) {
- return 1;
- }
- } else if ((unsigned char)isdigit(a[j]) &&
- isdigit((unsigned char)b[j])) {
- /* Numeric comparison */
- size_t k1 = j;
- size_t k2 = j;
-
- /* Compute number of digits in each string */
- while (isdigit((unsigned char)a[k1])) {
- k1++;
+ if (!FindNextFileW(dirp->handle, &dirp->data))
+ {
+ // End of directory or hard error, stop
+ FindClose(dirp->handle);
+ dirp->handle = INVALID_HANDLE_VALUE;
+ return NULL;
}
- while (isdigit((unsigned char)b[k2])) {
- k2++;
- }
-
- /* Number with more digits is bigger, e.g 999 < 1000 */
- if (k1 < k2)
- return -1;
- else if (k1 > k2)
- return 1;
}
- /* Alphabetical comparison */
- return (int) ((unsigned char) a[i]) - ((unsigned char) b[i]);
-}
-
-/* Convert multi-byte string to wide character string */
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-static int dirent_mbstowcs_s(
- size_t *pReturnValue, wchar_t *wcstr,
- size_t sizeInWords, const char *mbstr, size_t count)
-{
- /* Older Visual Studio or non-Microsoft compiler */
- size_t n = mbstowcs(wcstr, mbstr, sizeInWords);
- if (wcstr && n >= count)
- return /*error*/ 1;
-
- /* Zero-terminate output buffer */
- if (wcstr && sizeInWords) {
- if (n >= sizeInWords)
- n = sizeInWords - 1;
- wcstr[n] = 0;
- }
-
- /* Length of multi-byte string with zero terminator */
- if (pReturnValue) {
- *pReturnValue = n + 1;
- }
+ if (pkgconf__fill_dirent(dirp) != 0)
+ return NULL;
- /* Success */
- return 0;
+ return &dirp->ent;
}
-#endif
-/* Convert wide-character string to multi-byte string */
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-static int dirent_wcstombs_s(
- size_t *pReturnValue, char *mbstr,
- size_t sizeInBytes, const wchar_t *wcstr, size_t count)
+static inline int
+closedir(DIR *dirp)
{
- /* Older Visual Studio or non-Microsoft compiler */
- size_t n = wcstombs(mbstr, wcstr, sizeInBytes);
- if (mbstr && n >= count)
- return /*error*/1;
-
- /* Zero-terminate output buffer */
- if (mbstr && sizeInBytes) {
- if (n >= sizeInBytes) {
- n = sizeInBytes - 1;
- }
- mbstr[n] = '\0';
+ if (dirp == NULL)
+ {
+ errno = EBADF;
+ return -1;
}
- /* Length of resulting multi-bytes string WITH zero-terminator */
- if (pReturnValue) {
- *pReturnValue = n + 1;
- }
+ if (dirp->handle != INVALID_HANDLE_VALUE)
+ FindClose(dirp->handle);
- /* Success */
+ free(dirp);
return 0;
}
-#endif
-
-/* Set errno variable */
-#if !defined(_MSC_VER) || _MSC_VER < 1400
-static void dirent_set_errno(int error)
-{
- /* Non-Microsoft compiler or older Microsoft compiler */
- errno = error;
-}
-#endif
#ifdef __cplusplus
}
#endif
-#endif /*DIRENT_H*/
+
+#endif // defined(_WIN32) && !defined(__CYGWIN__)
+#endif // PKGCONF_WIN_DIRENT_H