diff options
Diffstat (limited to 'libpkgconf')
| -rw-r--r-- | libpkgconf/argvsplit.c | 19 | ||||
| -rw-r--r-- | libpkgconf/audit.c | 28 | ||||
| -rw-r--r-- | libpkgconf/bsdstubs.c | 174 | ||||
| -rw-r--r-- | libpkgconf/bsdstubs.h | 4 | ||||
| -rw-r--r-- | libpkgconf/buffer.c | 483 | ||||
| -rw-r--r-- | libpkgconf/bufferset.c | 65 | ||||
| -rw-r--r-- | libpkgconf/bytecode.c | 535 | ||||
| -rw-r--r-- | libpkgconf/cache.c | 24 | ||||
| -rw-r--r-- | libpkgconf/client.c | 141 | ||||
| -rw-r--r-- | libpkgconf/config.h.meson | 9 | ||||
| -rw-r--r-- | libpkgconf/dependency.c | 102 | ||||
| -rw-r--r-- | libpkgconf/fileio.c | 130 | ||||
| -rw-r--r-- | libpkgconf/fragment.c | 608 | ||||
| -rw-r--r-- | libpkgconf/iter.h | 4 | ||||
| -rw-r--r-- | libpkgconf/libpkgconf.h | 419 | ||||
| -rw-r--r-- | libpkgconf/license.c | 329 | ||||
| -rw-r--r-- | libpkgconf/output.c | 153 | ||||
| -rw-r--r-- | libpkgconf/parser.c | 183 | ||||
| -rw-r--r-- | libpkgconf/path.c | 229 | ||||
| -rw-r--r-- | libpkgconf/path.h | 9 | ||||
| -rw-r--r-- | libpkgconf/personality.c | 109 | ||||
| -rw-r--r-- | libpkgconf/pkg.c | 918 | ||||
| -rw-r--r-- | libpkgconf/queue.c | 113 | ||||
| -rw-r--r-- | libpkgconf/stdinc.h | 45 | ||||
| -rw-r--r-- | libpkgconf/tuple.c | 329 | ||||
| -rw-r--r-- | libpkgconf/variable.c | 162 | ||||
| -rw-r--r-- | libpkgconf/version.c | 252 | ||||
| -rw-r--r-- | libpkgconf/win-dirent.h | 1109 |
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("ed, 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("ed)); + pkgconf_buffer_finalize("ed); 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 |
