Files
freebsd-src/libpkgconf/buffer.c
T
Pierre Pronchery ebcc78644e Vendor import of pkgconf 2.9.90
Obtained from https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.9.90.tar.gz

SHA1: ebfb0525c019eda14190d463680cb1625f3177f9  -
SHA256: f5c10610be8ea7278fc31ad63b943525b5b2a2f6e5038d3c11947f5bd6fd214d  -
SHA512: f93e6d15d25d02869a03ba339e18582f01a9b7241be89a87183c0f373a3df78d1be005dee376ea464f7942735700f51dab57b8688bc802ec152ff343af721f46  -

One test folder in tests/lib1 had to be removed to avoid UTF-8 filenames
in the tree. (Breaks `make create-source-packages`)

Reviewed by:	markj
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D57837
2026-06-29 07:21:29 +02:00

545 lines
14 KiB
C

/*
* 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
* 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 `buffer` module
* ==========================
*
* The libpkgconf `buffer` module contains the functions related to managing
* dynamically-allocated buffers.
*/
static inline size_t
target_allocation_size(size_t target_size)
{
return 128 + (128 * (target_size / 128));
}
#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));
if (newbase == NULL)
return false;
char *newend = newbase + pkgconf_buffer_len(buffer);
memcpy(newend, text, needed);
buffer->base = newbase;
buffer->end = newend + (needed - 1);
*buffer->end = '\0';
return true;
}
/*
* !doc
*
* .. c:function:: bool pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n)
*
* Append a slice of *n* bytes to the buffer. Does nothing if *n* is zero.
*
* :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));
if (newbase == NULL)
return false;
char *newend = newbase + newsize;
*(newend - 1) = byte;
*newend = '\0';
buffer->base = newbase;
buffer->end = newend;
return true;
}
/*
* !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 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;
}