xref: /freebsd/contrib/pkgconf/libpkgconf/buffer.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
1 /*
2  * buffer.c
3  * dynamically-managed buffers
4  *
5  * SPDX-License-Identifier: pkgconf
6  *
7  * Copyright (c) 2024 pkgconf authors (see AUTHORS).
8  *
9  * Permission to use, copy, modify, and/or distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * This software is provided 'as is' and without any warranty, express or
14  * implied.  In no event shall the authors be liable for any damages arising
15  * from the use of this software.
16  */
17 
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20 
21 /*
22  * !doc
23  *
24  * libpkgconf `buffer` module
25  * ==========================
26  *
27  * The libpkgconf `buffer` module contains the functions related to managing
28  * dynamically-allocated buffers.
29  */
30 
31 static inline size_t
target_allocation_size(size_t target_size)32 target_allocation_size(size_t target_size)
33 {
34 	return 128 + (128 * (target_size / 128));
35 }
36 
37 #if 0
38 static void
39 buffer_debug(pkgconf_buffer_t *buffer)
40 {
41 	for (char *c = buffer->base; c <= buffer->end; c++)
42 	{
43 		fprintf(stderr, "%02x ", (unsigned char) *c);
44 	}
45 
46 	pkgconf_output_file_fmt(stderr, "\n");
47 }
48 #endif
49 
50 /*
51  * !doc
52  *
53  * .. c:function:: bool pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text)
54  *
55  *    Append a null-terminated string to the buffer, reallocating as necessary.
56  *
57  *    :param pkgconf_buffer_t *buffer: The buffer to append to.
58  *    :param char *text: The null-terminated string to append.
59  *    :return: :code:`true` on success, :code:`false` on allocation failure.
60  */
61 bool
pkgconf_buffer_append(pkgconf_buffer_t * buffer,const char * text)62 pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text)
63 {
64 	size_t needed = strlen(text) + 1;
65 	size_t newsize = pkgconf_buffer_len(buffer) + needed;
66 
67 	char *newbase = realloc(buffer->base, target_allocation_size(newsize));
68 	if (newbase == NULL)
69 		return false;
70 
71 	char *newend = newbase + pkgconf_buffer_len(buffer);
72 	memcpy(newend, text, needed);
73 
74 	buffer->base = newbase;
75 	buffer->end = newend + (needed - 1);
76 
77 	*buffer->end = '\0';
78 	return true;
79 }
80 
81 /*
82  * !doc
83  *
84  * .. c:function:: bool pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n)
85  *
86  *    Append a slice of *n* bytes to the buffer. Does nothing if *n* is zero.
87  *
88  *    :param pkgconf_buffer_t *buf: The buffer to append to.
89  *    :param char *p: Pointer to the byte sequence to append.
90  *    :param size_t n: Number of bytes to append.
91  *    :return: :code:`true` on success, :code:`false` on allocation failure.
92  */
93 bool
pkgconf_buffer_append_slice(pkgconf_buffer_t * buf,const char * p,size_t n)94 pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n)
95 {
96 	if (n == 0)
97 		return true;
98 
99 	for (size_t i = 0; i < n; i++)
100 	{
101 		if (!pkgconf_buffer_push_byte(buf, p[i]))
102 			return false;
103 	}
104 
105 	return true;
106 }
107 
108 /*
109  * !doc
110  *
111  * .. c:function:: bool pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list src_va)
112  *
113  *    Append a formatted string to the buffer using a :code:`va_list`.
114  *
115  *    :param pkgconf_buffer_t *buffer: The buffer to append to.
116  *    :param char *fmt: A printf-style format string.
117  *    :param va_list src_va: The variadic argument list for the format string.
118  *    :return: :code:`true` on success, :code:`false` on allocation failure.
119  */
120 bool
pkgconf_buffer_append_vfmt(pkgconf_buffer_t * buffer,const char * fmt,va_list src_va)121 pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list src_va)
122 {
123 	va_list va;
124 	char *buf;
125 	size_t needed;
126 
127 	va_copy(va, src_va);
128 	needed = vsnprintf(NULL, 0, fmt, va) + 1;
129 	va_end(va);
130 
131 	buf = malloc(needed);
132 	if (buf == NULL)
133 		return false;
134 
135 	va_copy(va, src_va);
136 	vsnprintf(buf, needed, fmt, va);
137 	va_end(va);
138 
139 	bool ret = pkgconf_buffer_append(buffer, buf);
140 
141 	free(buf);
142 
143 	return ret;
144 }
145 
146 /*
147  * !doc
148  *
149  * .. c:function:: bool pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...)
150  *
151  *    Append a formatted string to the buffer using variadic arguments.
152  *
153  *    :param pkgconf_buffer_t *buffer: The buffer to append to.
154  *    :param char *fmt: A printf-style format string.
155  *    :return: :code:`true` on success, :code:`false` on allocation failure.
156  */
157 bool
pkgconf_buffer_append_fmt(pkgconf_buffer_t * buffer,const char * fmt,...)158 pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...)
159 {
160 	va_list va;
161 
162 	va_start(va, fmt);
163 	bool ret = pkgconf_buffer_append_vfmt(buffer, fmt, va);
164 	va_end(va);
165 
166 	return ret;
167 }
168 
169 /*
170  * !doc
171  *
172  * .. c:function:: bool pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text)
173  *
174  *    Prepend a null-terminated string to the beginning of the buffer.
175  *    If *text* is NULL, the buffer contents are unchanged.
176  *
177  *    :param pkgconf_buffer_t *buffer: The buffer to prepend to.
178  *    :param char *text: The null-terminated string to prepend, or NULL.
179  *    :return: :code:`true` on success, :code:`false` on allocation failure.
180  */
181 bool
pkgconf_buffer_prepend(pkgconf_buffer_t * buffer,const char * text)182 pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text)
183 {
184 	pkgconf_buffer_t tmpbuf = PKGCONF_BUFFER_INITIALIZER;
185 
186 	if (text != NULL && !pkgconf_buffer_append(&tmpbuf, text))
187 		return false;
188 
189 	if (!pkgconf_buffer_append(&tmpbuf, pkgconf_buffer_str_or_empty(buffer)))
190 	{
191 		pkgconf_buffer_finalize(&tmpbuf);
192 		return false;
193 	}
194 
195 	if (pkgconf_buffer_len(&tmpbuf))
196 		pkgconf_buffer_copy(&tmpbuf, buffer);
197 
198 	pkgconf_buffer_finalize(&tmpbuf);
199 
200 	return true;
201 }
202 
203 /*
204  * !doc
205  *
206  * .. c:function:: bool pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte)
207  *
208  *    Append a single byte to the buffer, reallocating as necessary.
209  *
210  *    :param pkgconf_buffer_t *buffer: The buffer to append to.
211  *    :param char byte: The byte to append.
212  *    :return: :code:`true` on success, :code:`false` on allocation failure.
213  */
214 bool
pkgconf_buffer_push_byte(pkgconf_buffer_t * buffer,char byte)215 pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte)
216 {
217 	size_t newsize = pkgconf_buffer_len(buffer) + 1;
218 	char *newbase = realloc(buffer->base, target_allocation_size(newsize));
219 	if (newbase == NULL)
220 		return false;
221 
222 	char *newend = newbase + newsize;
223 	*(newend - 1) = byte;
224 	*newend = '\0';
225 
226 	buffer->base = newbase;
227 	buffer->end = newend;
228 
229 	return true;
230 }
231 
232 /*
233  * !doc
234  *
235  * .. c:function:: bool pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer)
236  *
237  *    Remove the last byte from the buffer. The buffer must be non-empty.
238  *
239  *    :param pkgconf_buffer_t *buffer: The buffer to trim.
240  *    :return: :code:`true` on success, :code:`false` on allocation failure.
241  */
242 bool
pkgconf_buffer_trim_byte(pkgconf_buffer_t * buffer)243 pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer)
244 {
245 	size_t len = pkgconf_buffer_len(buffer);
246 	if (len == 0)
247 		return false;
248 
249 	size_t newsize = len - 1;
250 	char *newbase = realloc(buffer->base, target_allocation_size(newsize));
251 
252 	if (newbase == NULL)
253 		return false;
254 
255 	buffer->base = newbase;
256 	buffer->end = newbase + newsize;
257 	*(buffer->end) = '\0';
258 
259 	return true;
260 }
261 
262 /*
263  * !doc
264  *
265  * .. c:function:: void pkgconf_buffer_finalize(pkgconf_buffer_t *buffer)
266  *
267  *    Free all memory owned by the buffer and reset it to an empty state.
268  *
269  *    :param pkgconf_buffer_t *buffer: The buffer to finalize.
270  *    :return: nothing
271  */
272 void
pkgconf_buffer_finalize(pkgconf_buffer_t * buffer)273 pkgconf_buffer_finalize(pkgconf_buffer_t *buffer)
274 {
275 	free(buffer->base);
276 	buffer->base = buffer->end = NULL;
277 }
278 
279 /*
280  * !doc
281  *
282  * .. c:function:: bool pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out)
283  *
284  *    Write the buffer contents followed by a newline to a file stream.
285  *    If the buffer is empty, only a newline is written.
286  *
287  *    :param pkgconf_buffer_t *buffer: The buffer to write.
288  *    :param FILE *out: The output file stream.
289  *    :return: :code:`true` on success, :code:`false` on I/O error.
290  *             :code:`errno` will be set by fputs/fputc.
291  */
292 bool
pkgconf_buffer_fputs(pkgconf_buffer_t * buffer,FILE * out)293 pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out)
294 {
295 	if (pkgconf_buffer_len(buffer) != 0)
296 	{
297 		if (fputs(pkgconf_buffer_str(buffer), out) == EOF)
298 			return false;
299 	}
300 
301 	if (fputc('\n', out) == EOF)
302 		return false;
303 
304 	return true;
305 }
306 
307 /*
308  * !doc
309  *
310  * .. c:function:: bool pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list src_va)
311  *
312  *    Join a NULL-terminated list of strings into the buffer, separated by *delim*.
313  *    Uses a :code:`va_list` for the string arguments.
314  *
315  *    :param pkgconf_buffer_t *buffer: The buffer to join into.
316  *    :param char delim: The delimiter byte inserted between each argument.
317  *    :param va_list src_va: The variadic argument list of :code:`const char *` strings, terminated by NULL.
318  *    :return: :code:`true` on success, :code:`false` on allocation failure.
319  */
320 bool
pkgconf_buffer_vjoin(pkgconf_buffer_t * buffer,char delim,va_list src_va)321 pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list src_va)
322 {
323 	va_list va;
324 	const char *arg;
325 
326 	va_copy(va, src_va);
327 
328 	while ((arg = va_arg(va, const char *)) != NULL)
329 	{
330 		if (pkgconf_buffer_str(buffer) != NULL)
331 		{
332 			if (!pkgconf_buffer_push_byte(buffer, delim))
333 			{
334 				va_end(va);
335 				return false;
336 			}
337 		}
338 
339 		if (!pkgconf_buffer_append(buffer, arg))
340 		{
341 			va_end(va);
342 			return false;
343 		}
344 	}
345 
346 	va_end(va);
347 
348 	return true;
349 }
350 
351 /*
352  * !doc
353  *
354  * .. c:function:: bool pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...)
355  *
356  *    Join a NULL-terminated list of strings into the buffer, separated by *delim*.
357  *    The *delim* parameter is typed as :code:`int` due to C variadic promotion rules.
358  *
359  *    :param pkgconf_buffer_t *buffer: The buffer to join into.
360  *    :param int delim: The delimiter byte inserted between each argument (cast to :code:`char` internally).
361  *    :return: :code:`true` on success, :code:`false` on allocation failure.
362  */
363 bool
pkgconf_buffer_join(pkgconf_buffer_t * buffer,int delim,...)364 pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...)
365 {
366 	va_list va;
367 
368 	va_start(va, delim);
369 	bool ret = pkgconf_buffer_vjoin(buffer, (char)delim, va);
370 	va_end(va);
371 
372 	return ret;
373 }
374 
375 /*
376  * !doc
377  *
378  * .. c:function:: bool pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix)
379  *
380  *    Test whether the buffer begins with the contents of *prefix*.
381  *
382  *    :param pkgconf_buffer_t *haystack: The buffer to search in.
383  *    :param pkgconf_buffer_t *prefix: The prefix to test for.
384  *    :return: :code:`true` if *haystack* starts with *prefix*, :code:`false` otherwise.
385  */
386 bool
pkgconf_buffer_has_prefix(const pkgconf_buffer_t * haystack,const pkgconf_buffer_t * prefix)387 pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix)
388 {
389 	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
390 	const char *prefix_str = pkgconf_buffer_str_or_empty(prefix);
391 
392 	return strncmp(haystack_str, prefix_str, strlen(prefix_str)) == 0;
393 }
394 
395 /*
396  * !doc
397  *
398  * .. c:function:: bool pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
399  *
400  *    Test whether the buffer contains the contents of *needle* as a substring.
401  *
402  *    :param pkgconf_buffer_t *haystack: The buffer to search in.
403  *    :param pkgconf_buffer_t *needle: The substring to search for.
404  *    :return: :code:`true` if *needle* is found, :code:`false` otherwise.
405  */
406 bool
pkgconf_buffer_contains(const pkgconf_buffer_t * haystack,const pkgconf_buffer_t * needle)407 pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
408 {
409 	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
410 	const char *needle_str = pkgconf_buffer_str_or_empty(needle);
411 
412 	return strstr(haystack_str, needle_str) != NULL;
413 }
414 
415 /*
416  * !doc
417  *
418  * .. c:function:: bool pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle)
419  *
420  *    Test whether the buffer contains a given byte.
421  *
422  *    :param pkgconf_buffer_t *haystack: The buffer to search in.
423  *    :param char needle: The byte to search for.
424  *    :return: :code:`true` if *needle* is found, :code:`false` otherwise.
425  */
426 
427 bool
pkgconf_buffer_contains_byte(const pkgconf_buffer_t * haystack,char needle)428 pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle)
429 {
430 	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
431 	return strchr(haystack_str, needle) != NULL;
432 }
433 
434 /*
435  * !doc
436  *
437  * .. c:function:: bool pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
438  *
439  *    Test whether two buffers have identical contents.
440  *
441  *    :param pkgconf_buffer_t *haystack: The first buffer.
442  *    :param pkgconf_buffer_t *needle: The second buffer.
443  *    :return: :code:`true` if the buffers have the same length and contents, :code:`false` otherwise.
444  */
445 bool
pkgconf_buffer_match(const pkgconf_buffer_t * haystack,const pkgconf_buffer_t * needle)446 pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
447 {
448 	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
449 	const char *needle_str = pkgconf_buffer_str_or_empty(needle);
450 
451 	if (pkgconf_buffer_len(haystack) != pkgconf_buffer_len(needle))
452 		return false;
453 
454 	return memcmp(haystack_str, needle_str, pkgconf_buffer_len(haystack)) == 0;
455 }
456 
457 /*
458  * !doc
459  *
460  * .. c:function:: bool pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value)
461  *
462  *    Copy *src* into *dest*, replacing all occurrences of *pattern* with *value*.
463  *    If *pattern* is empty, *src* is appended to *dest* unmodified.
464  *
465  *    :param pkgconf_buffer_t *dest: The destination buffer.
466  *    :param pkgconf_buffer_t *src: The source buffer.
467  *    :param char *pattern: The pattern string to search for.
468  *    :param char *value: The replacement string.
469  *    :return: :code:`true` on success, :code:`false` on allocation failure.
470  */
471 bool
pkgconf_buffer_subst(pkgconf_buffer_t * dest,const pkgconf_buffer_t * src,const char * pattern,const char * value)472 pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value)
473 {
474 	const char *iter = src->base;
475 
476 	if (pattern == NULL)
477 		pattern = "";
478 
479 	if (value == NULL)
480 		value = "";
481 
482 	size_t pattern_len = strlen(pattern);
483 
484 	if (!pkgconf_buffer_len(src))
485 		return true;
486 
487 	if (!pattern_len)
488 		return pkgconf_buffer_append(dest, pkgconf_buffer_str(src));
489 
490 	while (iter < src->end)
491 	{
492 		if ((size_t)(src->end - iter) >= pattern_len && !memcmp(iter, pattern, pattern_len))
493 		{
494 			if (!pkgconf_buffer_append(dest, value))
495 				return false;
496 
497 			iter += pattern_len;
498 		}
499 		else
500 		{
501 			if (!pkgconf_buffer_push_byte(dest, *iter++))
502 				return false;
503 		}
504 	}
505 
506 	return true;
507 }
508 
509 /*
510  * !doc
511  *
512  * .. c:function:: bool pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans)
513  *
514  *    Copy *src* into *dest*, inserting a backslash before any byte that falls
515  *    within the provided character spans.
516  *
517  *    :param pkgconf_buffer_t *dest: The destination buffer.
518  *    :param pkgconf_buffer_t *src: The source buffer.
519  *    :param pkgconf_span_t *spans: Array of character spans to escape.
520  *    :param size_t nspans: Number of entries in the *spans* array.
521  *    :return: :code:`true` on success, :code:`false` on allocation failure.
522  */
523 bool
pkgconf_buffer_escape(pkgconf_buffer_t * dest,const pkgconf_buffer_t * src,const pkgconf_span_t * spans,size_t nspans)524 pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans)
525 {
526 	const char *p = pkgconf_buffer_str(src);
527 
528 	if (!pkgconf_buffer_len(src))
529 		return true;
530 
531 	for (; *p; p++)
532 	{
533 		if (pkgconf_span_contains((unsigned char) *p, spans, nspans))
534 		{
535 			if (!pkgconf_buffer_push_byte(dest, '\\'))
536 				return false;
537 		}
538 
539 		if (!pkgconf_buffer_push_byte(dest, *p))
540 			return false;
541 	}
542 
543 	return true;
544 }
545