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