xref: /freebsd/contrib/pkgconf/libpkgconf/fragment.c (revision a3cefe7f2b4df0f70ff92d4570ce18e517af43ec)
1 /*
2  * fragment.c
3  * Management of fragment lists.
4  *
5  * Copyright (c) 2012, 2013, 2014 pkgconf authors (see AUTHORS).
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * This software is provided 'as is' and without any warranty, express or
12  * implied.  In no event shall the authors be liable for any damages arising
13  * from the use of this software.
14  */
15 
16 #include <libpkgconf/stdinc.h>
17 #include <libpkgconf/libpkgconf.h>
18 
19 /*
20  * !doc
21  *
22  * libpkgconf `fragment` module
23  * ============================
24  *
25  * The `fragment` module provides low-level management and rendering of fragment lists.  A
26  * `fragment list` contains various `fragments` of text (such as ``-I /usr/include``) in a matter
27  * which is composable, mergeable and reorderable.
28  */
29 
30 struct pkgconf_fragment_check {
31 	char *token;
32 	size_t len;
33 };
34 
35 static inline bool
pkgconf_fragment_is_unmergeable(const char * string)36 pkgconf_fragment_is_unmergeable(const char *string)
37 {
38 	static const struct pkgconf_fragment_check check_fragments[] = {
39 		{"-framework", 10},
40 		{"-isystem", 8},
41 		{"-idirafter", 10},
42 		{"-pthread", 8},
43 		{"-Wa,", 4},
44 		{"-Wl,", 4},
45 		{"-Wp,", 4},
46 		{"-trigraphs", 10},
47 		{"-pedantic", 9},
48 		{"-ansi", 5},
49 		{"-std=", 5},
50 		{"-stdlib=", 8},
51 		{"-include", 8},
52 		{"-nostdinc", 9},
53 		{"-nostdlibinc", 12},
54 		{"-nobuiltininc", 13},
55 		{"-nodefaultlibs", 14},
56 	};
57 
58 	if (*string != '-')
59 		return true;
60 
61 	for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
62 		if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
63 			return true;
64 
65 	/* only one pair of {-flag, arg} may be merged together */
66 	if (strchr(string, ' ') != NULL)
67 		return false;
68 
69 	return false;
70 }
71 
72 static inline bool
pkgconf_fragment_should_munge(const char * string,const char * sysroot_dir)73 pkgconf_fragment_should_munge(const char *string, const char *sysroot_dir)
74 {
75 	if (*string != '/')
76 		return false;
77 
78 	if (sysroot_dir != NULL && strncmp(sysroot_dir, string, strlen(sysroot_dir)))
79 		return true;
80 
81 	return false;
82 }
83 
84 static inline bool
pkgconf_fragment_is_groupable(const char * string)85 pkgconf_fragment_is_groupable(const char *string)
86 {
87 	static const struct pkgconf_fragment_check check_fragments[] = {
88 		{"-Wl,--start-group", 17},
89 		{"-framework", 10},
90 		{"-isystem", 8},
91 		{"-idirafter", 10},
92 		{"-include", 8},
93 	};
94 
95 	for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
96 		if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
97 			return true;
98 
99 	return false;
100 }
101 
102 static inline bool
pkgconf_fragment_is_terminus(const char * string)103 pkgconf_fragment_is_terminus(const char *string)
104 {
105 	static const struct pkgconf_fragment_check check_fragments[] = {
106 		{"-Wl,--end-group", 15},
107 	};
108 
109 	for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
110 		if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
111 			return true;
112 
113 	return false;
114 }
115 
116 static inline bool
pkgconf_fragment_is_special(const char * string)117 pkgconf_fragment_is_special(const char *string)
118 {
119 	if (*string != '-')
120 		return true;
121 
122 	if (!strncmp(string, "-lib:", 5))
123 		return true;
124 
125 	return pkgconf_fragment_is_unmergeable(string);
126 }
127 
128 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)129 pkgconf_fragment_munge(const pkgconf_client_t *client, char *buf, size_t buflen, const char *source, const char *sysroot_dir, unsigned int flags)
130 {
131 	*buf = '\0';
132 
133 	if (!(flags & PKGCONF_PKG_PROPF_UNINSTALLED) || (client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES))
134 	{
135 		if (sysroot_dir == NULL)
136 			sysroot_dir = pkgconf_tuple_find_global(client, "pc_sysrootdir");
137 
138 		if (sysroot_dir != NULL && pkgconf_fragment_should_munge(source, sysroot_dir))
139 			pkgconf_strlcat(buf, sysroot_dir, buflen);
140 	}
141 
142 	pkgconf_strlcat(buf, source, buflen);
143 
144 	if (*buf == '/' && !(client->flags & PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS))
145 		pkgconf_path_relocate(buf, buflen);
146 }
147 
148 static inline char *
pkgconf_fragment_copy_munged(const pkgconf_client_t * client,const char * source,unsigned int flags)149 pkgconf_fragment_copy_munged(const pkgconf_client_t *client, const char *source, unsigned int flags)
150 {
151 	char mungebuf[PKGCONF_ITEM_SIZE];
152 	pkgconf_fragment_munge(client, mungebuf, sizeof mungebuf, source, client->sysroot_dir, flags);
153 	return strdup(mungebuf);
154 }
155 
156 /*
157  * !doc
158  *
159  * .. c:function:: void pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail)
160  *
161  *    Adds a `fragment` of text to a `fragment list` directly without interpreting it.
162  *
163  *    :param pkgconf_client_t* client: The pkgconf client being accessed.
164  *    :param pkgconf_list_t* list: The fragment list.
165  *    :param char type: The type of the fragment.
166  *    :param char* data: The data of the fragment.
167  *    :param bool tail: Whether to place the fragment at the beginning of the list or the end.
168  *    :return: nothing
169  */
170 void
pkgconf_fragment_insert(const pkgconf_client_t * client,pkgconf_list_t * list,char type,const char * data,bool tail)171 pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail)
172 {
173 	pkgconf_fragment_t *frag;
174 
175 	frag = calloc(1, sizeof(pkgconf_fragment_t));
176 	frag->type = type;
177 	frag->data = pkgconf_fragment_copy_munged(client, data, 0);
178 
179 	if (tail)
180 	{
181 		pkgconf_node_insert_tail(&frag->iter, frag, list);
182 		return;
183 	}
184 
185 	pkgconf_node_insert(&frag->iter, frag, list);
186 }
187 
188 /*
189  * !doc
190  *
191  * .. c:function:: void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags)
192  *
193  *    Adds a `fragment` of text to a `fragment list`, possibly modifying the fragment if a sysroot is set.
194  *
195  *    :param pkgconf_client_t* client: The pkgconf client being accessed.
196  *    :param pkgconf_list_t* list: The fragment list.
197  *    :param char* string: The string of text to add as a fragment to the fragment list.
198  *    :param uint flags: Parsing-related flags for the package.
199  *    :return: nothing
200  */
201 void
pkgconf_fragment_add(const pkgconf_client_t * client,pkgconf_list_t * list,const char * string,unsigned int flags)202 pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags)
203 {
204 	pkgconf_list_t *target = list;
205 	pkgconf_fragment_t *frag;
206 
207 	if (*string == '\0')
208 		return;
209 
210 	if (list->tail != NULL && list->tail->data != NULL &&
211 	    !(client->flags & PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS))
212 	{
213 		pkgconf_fragment_t *parent = list->tail->data;
214 
215 		/* only attempt to merge 'special' fragments together */
216 		if (!parent->type && parent->data != NULL &&
217 		    pkgconf_fragment_is_unmergeable(parent->data) &&
218 		    !(parent->flags & PKGCONF_PKG_FRAGF_TERMINATED))
219 		{
220 			if (pkgconf_fragment_is_groupable(parent->data))
221 				target = &parent->children;
222 
223 			if (pkgconf_fragment_is_terminus(string))
224 				parent->flags |= PKGCONF_PKG_FRAGF_TERMINATED;
225 
226 			PKGCONF_TRACE(client, "adding fragment as child to list @%p", target);
227 		}
228 	}
229 
230 	frag = calloc(1, sizeof(pkgconf_fragment_t));
231 	if (frag == NULL)
232 	{
233 		PKGCONF_TRACE(client, "failed to add new fragment due to allocation failure to list @%p", target);
234 		return;
235 	}
236 
237 	if (strlen(string) > 1 && !pkgconf_fragment_is_special(string))
238 	{
239 		frag->type = *(string + 1);
240 		frag->data = pkgconf_fragment_copy_munged(client, string + 2, flags);
241 
242 		PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list);
243 	}
244 	else
245 	{
246 		frag->type = 0;
247 		frag->data = pkgconf_fragment_copy_munged(client, string, flags);
248 
249 		PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target);
250 	}
251 
252 	pkgconf_node_insert_tail(&frag->iter, frag, target);
253 }
254 
255 static inline pkgconf_fragment_t *
pkgconf_fragment_lookup(pkgconf_list_t * list,const pkgconf_fragment_t * base)256 pkgconf_fragment_lookup(pkgconf_list_t *list, const pkgconf_fragment_t *base)
257 {
258 	pkgconf_node_t *node;
259 
260 	PKGCONF_FOREACH_LIST_ENTRY_REVERSE(list->tail, node)
261 	{
262 		pkgconf_fragment_t *frag = node->data;
263 
264 		if (base->type != frag->type)
265 			continue;
266 
267 		if (!strcmp(base->data, frag->data))
268 			return frag;
269 	}
270 
271 	return NULL;
272 }
273 
274 static inline bool
pkgconf_fragment_can_merge_back(const pkgconf_fragment_t * base,unsigned int flags,bool is_private)275 pkgconf_fragment_can_merge_back(const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
276 {
277 	(void) flags;
278 
279 	if (base->type == 'l')
280 	{
281 		if (is_private)
282 			return false;
283 
284 		return true;
285 	}
286 
287 	if (base->type == 'F')
288 		return false;
289 	if (base->type == 'L')
290 		return false;
291 	if (base->type == 'I')
292 		return false;
293 
294 	return true;
295 }
296 
297 static inline bool
pkgconf_fragment_can_merge(const pkgconf_fragment_t * base,unsigned int flags,bool is_private)298 pkgconf_fragment_can_merge(const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
299 {
300 	(void) flags;
301 
302 	if (is_private)
303 		return false;
304 
305 	if (base->children.head != NULL)
306 		return false;
307 
308 	return pkgconf_fragment_is_unmergeable(base->data);
309 }
310 
311 static inline pkgconf_fragment_t *
pkgconf_fragment_exists(pkgconf_list_t * list,const pkgconf_fragment_t * base,unsigned int flags,bool is_private)312 pkgconf_fragment_exists(pkgconf_list_t *list, const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
313 {
314 	if (!pkgconf_fragment_can_merge_back(base, flags, is_private))
315 		return NULL;
316 
317 	if (!pkgconf_fragment_can_merge(base, flags, is_private))
318 		return NULL;
319 
320 	return pkgconf_fragment_lookup(list, base);
321 }
322 
323 static inline bool
pkgconf_fragment_should_merge(const pkgconf_fragment_t * base)324 pkgconf_fragment_should_merge(const pkgconf_fragment_t *base)
325 {
326 	const pkgconf_fragment_t *parent;
327 
328 	/* if we are the first fragment, that means the next fragment is the same, so it's always safe. */
329 	if (base->iter.prev == NULL)
330 		return true;
331 
332 	/* this really shouldn't ever happen, but handle it */
333 	parent = base->iter.prev->data;
334 	if (parent == NULL)
335 		return true;
336 
337 	switch (parent->type)
338 	{
339 	case 'l':
340 	case 'L':
341 	case 'I':
342 		return true;
343 	default:
344 		return !base->type || parent->type == base->type;
345 	}
346 }
347 
348 /*
349  * !doc
350  *
351  * .. c:function:: bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag)
352  *
353  *    Checks if a `fragment` contains a `system path`.  System paths are detected at compile time and optionally overridden by
354  *    the ``PKG_CONFIG_SYSTEM_INCLUDE_PATH`` and ``PKG_CONFIG_SYSTEM_LIBRARY_PATH`` environment variables.
355  *
356  *    :param pkgconf_client_t* client: The pkgconf client object the fragment belongs to.
357  *    :param pkgconf_fragment_t* frag: The fragment being checked.
358  *    :return: true if the fragment contains a system path, else false
359  *    :rtype: bool
360  */
361 bool
pkgconf_fragment_has_system_dir(const pkgconf_client_t * client,const pkgconf_fragment_t * frag)362 pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag)
363 {
364 	const pkgconf_list_t *check_paths = NULL;
365 
366 	switch (frag->type)
367 	{
368 	case 'L':
369 		check_paths = &client->filter_libdirs;
370 		break;
371 	case 'I':
372 		check_paths = &client->filter_includedirs;
373 		break;
374 	default:
375 		return false;
376 	}
377 
378 	return pkgconf_path_match_list(frag->data, check_paths);
379 }
380 
381 /*
382  * !doc
383  *
384  * .. c:function:: void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private)
385  *
386  *    Copies a `fragment` to another `fragment list`, possibly removing a previous copy of the `fragment`
387  *    in a process known as `mergeback`.
388  *
389  *    :param pkgconf_client_t* client: The pkgconf client being accessed.
390  *    :param pkgconf_list_t* list: The list the fragment is being added to.
391  *    :param pkgconf_fragment_t* base: The fragment being copied.
392  *    :param bool is_private: Whether the fragment list is a `private` fragment list (static linking).
393  *    :return: nothing
394  */
395 void
pkgconf_fragment_copy(const pkgconf_client_t * client,pkgconf_list_t * list,const pkgconf_fragment_t * base,bool is_private)396 pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private)
397 {
398 	pkgconf_fragment_t *frag;
399 
400 	if ((frag = pkgconf_fragment_exists(list, base, client->flags, is_private)) != NULL)
401 	{
402 		if (pkgconf_fragment_should_merge(frag))
403 			pkgconf_fragment_delete(list, frag);
404 	}
405 	else if (!is_private && !pkgconf_fragment_can_merge_back(base, client->flags, is_private) && (pkgconf_fragment_lookup(list, base) != NULL))
406 		return;
407 
408 	frag = calloc(1, sizeof(pkgconf_fragment_t));
409 
410 	frag->type = base->type;
411 	pkgconf_fragment_copy_list(client, &frag->children, &base->children);
412 	if (base->data != NULL)
413 		frag->data = strdup(base->data);
414 
415 	pkgconf_node_insert_tail(&frag->iter, frag, list);
416 }
417 
418 /*
419  * !doc
420  *
421  * .. c:function:: void pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
422  *
423  *    Copies a `fragment list` to another `fragment list`, possibly removing a previous copy of the fragments
424  *    in a process known as `mergeback`.
425  *
426  *    :param pkgconf_client_t* client: The pkgconf client being accessed.
427  *    :param pkgconf_list_t* list: The list the fragments are being added to.
428  *    :param pkgconf_list_t* base: The list the fragments are being copied from.
429  *    :return: nothing
430  */
431 void
pkgconf_fragment_copy_list(const pkgconf_client_t * client,pkgconf_list_t * list,const pkgconf_list_t * base)432 pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
433 {
434 	pkgconf_node_t *node;
435 
436 	PKGCONF_FOREACH_LIST_ENTRY(base->head, node)
437 	{
438 		pkgconf_fragment_t *frag = node->data;
439 
440 		pkgconf_fragment_copy(client, list, frag, true);
441 	}
442 }
443 
444 /*
445  * !doc
446  *
447  * .. c:function:: void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func)
448  *
449  *    Copies a `fragment list` to another `fragment list` which match a user-specified filtering function.
450  *
451  *    :param pkgconf_client_t* client: The pkgconf client being accessed.
452  *    :param pkgconf_list_t* dest: The destination list.
453  *    :param pkgconf_list_t* src: The source list.
454  *    :param pkgconf_fragment_filter_func_t filter_func: The filter function to use.
455  *    :param void* data: Optional data to pass to the filter function.
456  *    :return: nothing
457  */
458 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)459 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)
460 {
461 	pkgconf_node_t *node;
462 
463 	PKGCONF_FOREACH_LIST_ENTRY(src->head, node)
464 	{
465 		pkgconf_fragment_t *frag = node->data;
466 
467 		if (filter_func(client, frag, data))
468 			pkgconf_fragment_copy(client, dest, frag, true);
469 	}
470 }
471 
472 static inline char *
fragment_quote(const pkgconf_fragment_t * frag)473 fragment_quote(const pkgconf_fragment_t *frag)
474 {
475 	const char *src = frag->data;
476 	ssize_t outlen = strlen(src) + 10;
477 	char *out, *dst;
478 
479 	if (frag->data == NULL)
480 		return NULL;
481 
482 	out = dst = calloc(1, outlen);
483 	if (out == NULL)
484 		return NULL;
485 
486 	for (; *src; src++)
487 	{
488 		if (((*src < ' ') ||
489 		    (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') ||
490 		    (*src > '$' && *src < '(') ||
491 		    (*src > ')' && *src < '+') ||
492 		    (*src > ':' && *src < '=') ||
493 		    (*src > '=' && *src < '@') ||
494 		    (*src > 'Z' && *src < '\\') ||
495 #ifndef _WIN32
496 		    (*src == '\\') ||
497 #endif
498 		    (*src > '\\' && *src < '^') ||
499 		    (*src == '`') ||
500 		    (*src > 'z' && *src < '~') ||
501 		    (*src > '~')))
502 			*dst++ = '\\';
503 
504 		*dst++ = *src;
505 
506 		if ((ptrdiff_t)(dst - out) + 2 > outlen)
507 		{
508 			ptrdiff_t offset = dst - out;
509 			outlen *= 2;
510 
511 			char *newout = realloc(out, outlen);
512 			if (newout == NULL)
513 			{
514 				free(out);
515 				return NULL;
516 			}
517 
518 			out = newout;
519 			dst = out + offset;
520 		}
521 	}
522 
523 	*dst = 0;
524 	return out;
525 }
526 
527 static inline size_t
pkgconf_fragment_len(const pkgconf_fragment_t * frag)528 pkgconf_fragment_len(const pkgconf_fragment_t *frag)
529 {
530 	size_t len = 1;
531 
532 	if (frag->type)
533 		len += 2;
534 
535 	if (frag->data != NULL)
536 	{
537 		pkgconf_node_t *iter;
538 
539 		char *quoted = fragment_quote(frag);
540 		len += strlen(quoted);
541 		free(quoted);
542 
543 		PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
544 		{
545 			const pkgconf_fragment_t *child_frag = iter->data;
546 			len += pkgconf_fragment_len(child_frag) + 1;
547 		}
548 	}
549 
550 	return len;
551 }
552 
553 static size_t
fragment_render_len(const pkgconf_list_t * list,bool escape)554 fragment_render_len(const pkgconf_list_t *list, bool escape)
555 {
556 	(void) escape;
557 
558 	size_t out = 1;		/* trailing nul */
559 	pkgconf_node_t *node;
560 
561 	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
562 	{
563 		const pkgconf_fragment_t *frag = node->data;
564 		out += pkgconf_fragment_len(frag);
565 	}
566 
567 	return out;
568 }
569 
570 static inline size_t
fragment_render_item(const pkgconf_fragment_t * frag,char * bptr,size_t bufremain)571 fragment_render_item(const pkgconf_fragment_t *frag, char *bptr, size_t bufremain)
572 {
573 	const pkgconf_node_t *iter;
574 	char *base = bptr;
575 
576 	char *quoted = fragment_quote(frag);
577 	if (quoted == NULL)
578 		return 0;
579 
580 	if (strlen(quoted) > bufremain)
581 	{
582 		free(quoted);
583 		return 0;
584 	}
585 
586 	if (frag->type)
587 	{
588 		*bptr++ = '-';
589 		*bptr++ = frag->type;
590 	}
591 
592 	if (quoted != NULL)
593 	{
594 		bptr += pkgconf_strlcpy(bptr, quoted, bufremain - (bptr - base));
595 		free(quoted);
596 	}
597 
598 	PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
599 	{
600 		const pkgconf_fragment_t *child_frag = iter->data;
601 
602 		*bptr++ = ' ';
603 		bptr += fragment_render_item(child_frag, bptr, bufremain - (bptr - base));
604 	}
605 
606 	return bptr - base;
607 }
608 
609 static void
fragment_render_buf(const pkgconf_list_t * list,char * buf,size_t buflen,bool escape)610 fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape)
611 {
612 	(void) escape;
613 
614 	pkgconf_node_t *node;
615 	char *bptr = buf;
616 
617 	memset(buf, 0, buflen);
618 
619 	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
620 	{
621 		const pkgconf_fragment_t *frag = node->data;
622 		size_t buf_remaining = buflen - (bptr - buf);
623 		size_t written = fragment_render_item(frag, bptr, buf_remaining);
624 
625 		bptr += written;
626 
627 		if (node->next != NULL)
628 			*bptr++ = ' ';
629 	}
630 }
631 
632 static const pkgconf_fragment_render_ops_t default_render_ops = {
633 	.render_len = fragment_render_len,
634 	.render_buf = fragment_render_buf
635 };
636 
637 /*
638  * !doc
639  *
640  * .. c:function:: size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops)
641  *
642  *    Calculates the required memory to store a `fragment list` when rendered as a string.
643  *
644  *    :param pkgconf_list_t* list: The `fragment list` being rendered.
645  *    :param bool escape: Whether or not to escape special shell characters (deprecated).
646  *    :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``.
647  *    :return: the amount of bytes required to represent the `fragment list` when rendered
648  *    :rtype: size_t
649  */
650 size_t
pkgconf_fragment_render_len(const pkgconf_list_t * list,bool escape,const pkgconf_fragment_render_ops_t * ops)651 pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops)
652 {
653 	(void) escape;
654 
655 	ops = ops != NULL ? ops : &default_render_ops;
656 	return ops->render_len(list, true);
657 }
658 
659 /*
660  * !doc
661  *
662  * .. 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)
663  *
664  *    Renders a `fragment list` into a buffer.
665  *
666  *    :param pkgconf_list_t* list: The `fragment list` being rendered.
667  *    :param char* buf: The buffer to render the fragment list into.
668  *    :param size_t buflen: The length of the buffer.
669  *    :param bool escape: Whether or not to escape special shell characters (deprecated).
670  *    :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``.
671  *    :return: nothing
672  */
673 void
pkgconf_fragment_render_buf(const pkgconf_list_t * list,char * buf,size_t buflen,bool escape,const pkgconf_fragment_render_ops_t * ops)674 pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops)
675 {
676 	(void) escape;
677 
678 	ops = ops != NULL ? ops : &default_render_ops;
679 	ops->render_buf(list, buf, buflen, true);
680 }
681 
682 /*
683  * !doc
684  *
685  * .. c:function:: char *pkgconf_fragment_render(const pkgconf_list_t *list)
686  *
687  *    Allocate memory and render a `fragment list` into it.
688  *
689  *    :param pkgconf_list_t* list: The `fragment list` being rendered.
690  *    :param bool escape: Whether or not to escape special shell characters (deprecated).
691  *    :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``.
692  *    :return: An allocated string containing the rendered `fragment list`.
693  *    :rtype: char *
694  */
695 char *
pkgconf_fragment_render(const pkgconf_list_t * list,bool escape,const pkgconf_fragment_render_ops_t * ops)696 pkgconf_fragment_render(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops)
697 {
698 	(void) escape;
699 
700 	size_t buflen = pkgconf_fragment_render_len(list, true, ops);
701 	char *buf = calloc(1, buflen);
702 
703 	pkgconf_fragment_render_buf(list, buf, buflen, true, ops);
704 
705 	return buf;
706 }
707 
708 /*
709  * !doc
710  *
711  * .. c:function:: void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node)
712  *
713  *    Delete a `fragment node` from a `fragment list`.
714  *
715  *    :param pkgconf_list_t* list: The `fragment list` to delete from.
716  *    :param pkgconf_fragment_t* node: The `fragment node` to delete.
717  *    :return: nothing
718  */
719 void
pkgconf_fragment_delete(pkgconf_list_t * list,pkgconf_fragment_t * node)720 pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node)
721 {
722 	pkgconf_node_delete(&node->iter, list);
723 
724 	free(node->data);
725 	free(node);
726 }
727 
728 /*
729  * !doc
730  *
731  * .. c:function:: void pkgconf_fragment_free(pkgconf_list_t *list)
732  *
733  *    Delete an entire `fragment list`.
734  *
735  *    :param pkgconf_list_t* list: The `fragment list` to delete.
736  *    :return: nothing
737  */
738 void
pkgconf_fragment_free(pkgconf_list_t * list)739 pkgconf_fragment_free(pkgconf_list_t *list)
740 {
741 	pkgconf_node_t *node, *next;
742 
743 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
744 	{
745 		pkgconf_fragment_t *frag = node->data;
746 
747 		pkgconf_fragment_free(&frag->children);
748 		free(frag->data);
749 		free(frag);
750 	}
751 }
752 
753 /*
754  * !doc
755  *
756  * .. c:function:: bool pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value)
757  *
758  *    Parse a string into a `fragment list`.
759  *
760  *    :param pkgconf_client_t* client: The pkgconf client being accessed.
761  *    :param pkgconf_list_t* list: The `fragment list` to add the fragment entries to.
762  *    :param pkgconf_list_t* vars: A list of variables to use for variable substitution.
763  *    :param uint flags: Any parsing flags to be aware of.
764  *    :param char* value: The string to parse into fragments.
765  *    :return: true on success, false on parse error
766  */
767 bool
pkgconf_fragment_parse(const pkgconf_client_t * client,pkgconf_list_t * list,pkgconf_list_t * vars,const char * value,unsigned int flags)768 pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags)
769 {
770 	int i, ret, argc;
771 	char **argv;
772 	char *repstr = pkgconf_tuple_parse(client, vars, value, flags);
773 
774 	PKGCONF_TRACE(client, "post-subst: [%s] -> [%s]", value, repstr);
775 
776 	ret = pkgconf_argv_split(repstr, &argc, &argv);
777 	if (ret < 0)
778 	{
779 		PKGCONF_TRACE(client, "unable to parse fragment string [%s]", repstr);
780 		free(repstr);
781 		return false;
782 	}
783 
784 	for (i = 0; i < argc; i++)
785 	{
786 		PKGCONF_TRACE(client, "processing %s", argv[i]);
787 
788 		if (argv[i] == NULL)
789 		{
790 			PKGCONF_TRACE(client, "parsed fragment string is inconsistent: argc = %d while argv[%d] == NULL", argc, i);
791 			pkgconf_argv_free(argv);
792 			free(repstr);
793 			return false;
794 		}
795 
796 		pkgconf_fragment_add(client, list, argv[i], flags);
797 	}
798 
799 	pkgconf_argv_free(argv);
800 	free(repstr);
801 
802 	return true;
803 }
804