xref: /freebsd/contrib/pkgconf/libpkgconf/dependency.c (revision f5dc2263ab1be8a35a7e27e82103f9ccd41ae584)
1 /*
2  * dependency.c
3  * dependency parsing and management
4  *
5  * SPDX-License-Identifier: pkgconf
6  *
7  * Copyright (c) 2011, 2012, 2013 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 `dependency` module
25  * ==============================
26  *
27  * The `dependency` module provides support for building `dependency lists` (the basic component of the overall `dependency graph`) and
28  * `dependency nodes` which store dependency information.
29  */
30 
31 typedef enum {
32 	OUTSIDE_MODULE = 0,
33 	INSIDE_MODULE_NAME = 1,
34 	BEFORE_OPERATOR = 2,
35 	INSIDE_OPERATOR = 3,
36 	AFTER_OPERATOR = 4,
37 	INSIDE_VERSION = 5
38 } parse_state_t;
39 
40 #define DEBUG_PARSE 0
41 
42 static inline const char *
43 dependency_to_buf(const pkgconf_dependency_t *dep, pkgconf_buffer_t *buf)
44 {
45 	pkgconf_buffer_reset(buf);
46 	pkgconf_buffer_append(buf, dep->package);
47 
48 	if (dep->version != NULL)
49 		pkgconf_buffer_append_fmt(buf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version);
50 
51 	return pkgconf_buffer_str(buf);
52 }
53 
54 /* find a colliding dependency that is coloured differently */
55 static inline pkgconf_dependency_t *
56 find_colliding_dependency(const pkgconf_dependency_t *dep, const pkgconf_list_t *list)
57 {
58 	const pkgconf_node_t *n;
59 
60 	PKGCONF_FOREACH_LIST_ENTRY(list->head, n)
61 	{
62 		pkgconf_dependency_t *dep2 = n->data;
63 
64 		if (strcmp(dep->package, dep2->package))
65 			continue;
66 
67 		if (dep->flags != dep2->flags)
68 			return dep2;
69 	}
70 
71 	return NULL;
72 }
73 
74 static inline pkgconf_dependency_t *
75 add_or_replace_dependency_node(pkgconf_client_t *client, pkgconf_dependency_t *dep, pkgconf_list_t *list)
76 {
77 	pkgconf_buffer_t depbuf = PKGCONF_BUFFER_INITIALIZER;
78 	pkgconf_dependency_t *dep2 = find_colliding_dependency(dep, list);
79 	const char *depstr = dependency_to_buf(dep, &depbuf);
80 
81 	/* there is already a node in the graph which describes this dependency */
82 	if (dep2 != NULL)
83 	{
84 		pkgconf_buffer_t depbuf2 = PKGCONF_BUFFER_INITIALIZER;
85 		const char *depstr2 = dependency_to_buf(dep2, &depbuf2);
86 
87 		PKGCONF_TRACE(client, "dependency collision: [%s/%x] -- [%s/%x]",
88 			depstr, dep->flags, depstr2, dep2->flags);
89 
90 		/* prefer the uncoloured node, either dep or dep2 */
91 		if (dep->flags && dep2->flags == 0)
92 		{
93 			PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depstr, dep);
94 
95 			pkgconf_buffer_finalize(&depbuf);
96 			pkgconf_buffer_finalize(&depbuf2);
97 			pkgconf_dependency_unref(dep->owner, dep);
98 
99 			return NULL;
100 		}
101 		else if (dep2->flags && dep->flags == 0)
102 		{
103 			PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depstr2, dep2);
104 
105 			pkgconf_node_delete(&dep2->iter, list);
106 			pkgconf_dependency_unref(dep2->owner, dep2);
107 		}
108 		else
109 			/* If both dependencies have equal strength, we keep both, because of situations like:
110 			 *    Requires: foo > 1, foo < 3
111 			 *
112 			 * If the situation is that both dependencies are literally equal, it is still harmless because
113 			 * fragment deduplication will handle the excessive fragments.
114 			 */
115 			PKGCONF_TRACE(client, "keeping both dependencies (harmless)");
116 
117 		pkgconf_buffer_finalize(&depbuf2);
118 	}
119 
120 	PKGCONF_TRACE(client, "added dependency [%s] to list @%p; flags=%x", depstr, list, dep->flags);
121 	pkgconf_node_insert_tail(&dep->iter, pkgconf_dependency_ref(dep->owner, dep), list);
122 
123 	pkgconf_buffer_finalize(&depbuf);
124 
125 	/* This dependency is intentionally unowned.
126 	 *
127 	 * Internally we have no use for the returned type, and usually just
128 	 * discard it. However, there is a publig pkgconf_dependency_add
129 	 * function, which references this return value before returning it,
130 	 * giving ownership at that point.
131 	 */
132 	return dep;
133 }
134 
135 static inline pkgconf_dependency_t *
136 pkgconf_dependency_addraw(pkgconf_client_t *client, pkgconf_list_t *list, const char *package, size_t package_sz, const char *version, size_t version_sz, pkgconf_pkg_comparator_t compare, unsigned int flags)
137 {
138 	pkgconf_dependency_t *dep;
139 
140 	dep = calloc(1, sizeof(pkgconf_dependency_t));
141 	if (dep == NULL)
142 		return NULL;
143 
144 	dep->package = pkgconf_strndup(package, package_sz);
145 	if (dep->package == NULL)
146 	{
147 		pkgconf_dependency_free_one(dep);
148 		return NULL;
149 	}
150 
151 	if (version_sz != 0)
152 		dep->version = pkgconf_strndup(version, version_sz);
153 
154 	dep->compare = compare;
155 	dep->flags = flags;
156 	dep->owner = client;
157 	dep->refcount = 0;
158 
159 	return add_or_replace_dependency_node(client, dep, list);
160 }
161 
162 /*
163  * !doc
164  *
165  * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_add(pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare)
166  *
167  *    Adds a parsed dependency to a dependency list as a dependency node.
168  *
169  *    :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
170  *    :param pkgconf_list_t* list: The dependency list to add a dependency node to.
171  *    :param char* package: The package `atom` to set on the dependency node.
172  *    :param char* version: The package `version` to set on the dependency node.
173  *    :param pkgconf_pkg_comparator_t compare: The comparison operator to set on the dependency node.
174  *    :param uint flags: Any flags to attach to the dependency node.
175  *    :return: A dependency node.
176  *    :rtype: pkgconf_dependency_t *
177  */
178 pkgconf_dependency_t *
179 pkgconf_dependency_add(pkgconf_client_t *client, pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare, unsigned int flags)
180 {
181 	pkgconf_dependency_t *dep;
182 	dep = pkgconf_dependency_addraw(client, list, package, strlen(package), version,
183 					version != NULL ? strlen(version) : 0, compare, flags);
184 	if (dep == NULL)
185 		return NULL;
186 	return pkgconf_dependency_ref(dep->owner, dep);
187 }
188 
189 /*
190  * !doc
191  *
192  * .. c:function:: void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail)
193  *
194  *    Adds a dependency node to a pre-existing dependency list.
195  *
196  *    :param pkgconf_list_t* list: The dependency list to add a dependency node to.
197  *    :param pkgconf_dependency_t* tail: The dependency node to add to the tail of the dependency list.
198  *    :return: nothing
199  */
200 void
201 pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail)
202 {
203 	pkgconf_node_insert_tail(&tail->iter, tail, list);
204 }
205 
206 /*
207  * !doc
208  *
209  * .. c:function:: void pkgconf_dependency_free_one(pkgconf_dependency_t *dep)
210  *
211  *    Frees a dependency node.
212  *
213  *    :param pkgconf_dependency_t* dep: The dependency node to free.
214  *    :return: nothing
215  */
216 void
217 pkgconf_dependency_free_one(pkgconf_dependency_t *dep)
218 {
219 	if (dep->match != NULL)
220 		pkgconf_pkg_unref(dep->match->owner, dep->match);
221 
222 	if (dep->package != NULL)
223 		free(dep->package);
224 
225 	if (dep->version != NULL)
226 		free(dep->version);
227 
228 	if (dep->why != NULL)
229 		free(dep->why);
230 
231 	free(dep);
232 }
233 
234 /*
235  * !doc
236  *
237  * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_ref(pkgconf_client_t *owner, pkgconf_dependency_t *dep)
238  *
239  *    Increases a dependency node's refcount.
240  *
241  *    :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node.
242  *    :param pkgconf_dependency_t* dep: The dependency to increase the refcount of.
243  *    :return: the dependency node on success, else NULL
244  */
245 pkgconf_dependency_t *
246 pkgconf_dependency_ref(pkgconf_client_t *client, pkgconf_dependency_t *dep)
247 {
248 	if (client != dep->owner)
249 		return NULL;
250 
251 	dep->refcount++;
252 	PKGCONF_TRACE(client, "%s refcount@%p: %d", dep->package, dep, dep->refcount);
253 	return dep;
254 }
255 
256 /*
257  * !doc
258  *
259  * .. c:function:: void pkgconf_dependency_unref(pkgconf_client_t *owner, pkgconf_dependency_t *dep)
260  *
261  *    Decreases a dependency node's refcount and frees it if necessary.
262  *
263  *    :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node.
264  *    :param pkgconf_dependency_t* dep: The dependency to decrease the refcount of.
265  *    :return: nothing
266  */
267 void
268 pkgconf_dependency_unref(pkgconf_client_t *client, pkgconf_dependency_t *dep)
269 {
270 	if (client != dep->owner)
271 		return;
272 
273 	--dep->refcount;
274 	PKGCONF_TRACE(client, "%s refcount@%p: %d", dep->package, dep, dep->refcount);
275 
276 	if (dep->refcount <= 0)
277 		pkgconf_dependency_free_one(dep);
278 }
279 
280 /*
281  * !doc
282  *
283  * .. c:function:: void pkgconf_dependency_free(pkgconf_list_t *list)
284  *
285  *    Release a dependency list and its child dependency nodes.
286  *
287  *    :param pkgconf_list_t* list: The dependency list to release.
288  *    :return: nothing
289  */
290 void
291 pkgconf_dependency_free(pkgconf_list_t *list)
292 {
293 	pkgconf_node_t *node, *next;
294 
295 	PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
296 	{
297 		pkgconf_dependency_t *dep = node->data;
298 
299 		pkgconf_node_delete(&dep->iter, list);
300 		pkgconf_dependency_unref(dep->owner, dep);
301 	}
302 
303 	pkgconf_list_zero(list);
304 }
305 
306 /*
307  * !doc
308  *
309  * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends)
310  *
311  *    Parse a dependency declaration into a dependency list.
312  *    Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed
313  *    into ``, zlib``.
314  *
315  *    :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
316  *    :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes.
317  *    :param char* depends: The dependency data to parse.
318  *    :param uint flags: Any flags to attach to the dependency nodes.
319  *    :return: nothing
320  */
321 void
322 pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags)
323 {
324 	parse_state_t state = OUTSIDE_MODULE;
325 	pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY;
326 	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
327 	pkgconf_buffer_t cmpname = PKGCONF_BUFFER_INITIALIZER;
328 	size_t package_sz = 0, version_sz = 0;
329 	char *start = NULL;
330 	char *ptr = NULL;
331 	char *vstart = NULL;
332 	char *package = NULL, *version = NULL;
333 	char *opstart = NULL;
334 
335 	if (depends == NULL || *depends == '\0')
336 		return;
337 
338 	if (!pkgconf_buffer_append(&buf, depends))
339 		goto out;
340 
341 	if (!pkgconf_buffer_append(&buf, " "))
342 		goto out;
343 
344 	start = ptr = buf.base;
345 
346 	while (*ptr)
347 	{
348 		switch (state)
349 		{
350 		case OUTSIDE_MODULE:
351 			if (!PKGCONF_IS_MODULE_SEPARATOR(*ptr))
352 				state = INSIDE_MODULE_NAME;
353 
354 			break;
355 
356 		case INSIDE_MODULE_NAME:
357 			if (isspace((unsigned char)*ptr))
358 			{
359 				const char *sptr = ptr;
360 
361 				while (*sptr && isspace((unsigned char)*sptr))
362 					sptr++;
363 
364 				if (*sptr == '\0')
365 					state = OUTSIDE_MODULE;
366 				else if (PKGCONF_IS_MODULE_SEPARATOR(*sptr))
367 					state = OUTSIDE_MODULE;
368 				else if (PKGCONF_IS_OPERATOR_CHAR(*sptr))
369 					state = BEFORE_OPERATOR;
370 				else
371 					state = OUTSIDE_MODULE;
372 			}
373 			else if (PKGCONF_IS_MODULE_SEPARATOR(*ptr))
374 				state = OUTSIDE_MODULE;
375 			else if (*(ptr + 1) == '\0')
376 			{
377 				ptr++;
378 				state = OUTSIDE_MODULE;
379 			}
380 
381 			if (state != INSIDE_MODULE_NAME && start != ptr)
382 			{
383 				char *iter = start;
384 
385 				while (PKGCONF_IS_MODULE_SEPARATOR(*iter))
386 					iter++;
387 
388 				package = iter;
389 				package_sz = ptr - iter;
390 				start = ptr;
391 			}
392 
393 			if (state == OUTSIDE_MODULE)
394 			{
395 				pkgconf_dependency_addraw(client, deplist_head, package, package_sz, NULL, 0, compare, flags);
396 
397 				compare = PKGCONF_CMP_ANY;
398 				package_sz = 0;
399 			}
400 
401 			break;
402 
403 		case BEFORE_OPERATOR:
404 			if (PKGCONF_IS_OPERATOR_CHAR(*ptr))
405 			{
406 				opstart = ptr;
407 				state = INSIDE_OPERATOR;
408 			}
409 
410 			break;
411 
412 		case INSIDE_OPERATOR:
413 			if (PKGCONF_IS_OPERATOR_CHAR(*ptr))
414 				break;
415 
416 			pkgconf_buffer_reset(&cmpname);
417 			pkgconf_buffer_append_slice(&cmpname, opstart, ptr - opstart);
418 			compare = pkgconf_pkg_comparator_lookup_by_name(pkgconf_buffer_str(&cmpname));
419 			state = AFTER_OPERATOR;
420 			// fallthrough
421 
422 		case AFTER_OPERATOR:
423 			if (!isspace((unsigned char)*ptr))
424 			{
425 				vstart = ptr;
426 				state = INSIDE_VERSION;
427 			}
428 			break;
429 
430 		case INSIDE_VERSION:
431 			if (PKGCONF_IS_MODULE_SEPARATOR(*ptr) || *(ptr + 1) == '\0')
432 			{
433 				version = vstart;
434 				version_sz = ptr - vstart;
435 				state = OUTSIDE_MODULE;
436 
437 				pkgconf_dependency_addraw(client, deplist_head, package, package_sz, version, version_sz, compare, flags);
438 
439 				compare = PKGCONF_CMP_ANY;
440 				package_sz = 0;
441 				opstart = NULL;
442 				pkgconf_buffer_reset(&cmpname);
443 			}
444 
445 			if (state == OUTSIDE_MODULE)
446 				start = ptr;
447 			break;
448 		}
449 
450 		ptr++;
451 	}
452 
453 out:
454 	pkgconf_buffer_finalize(&cmpname);
455 	pkgconf_buffer_finalize(&buf);
456 }
457 
458 /*
459  * !doc
460  *
461  * .. c:function:: void pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends)
462  *
463  *    Preprocess dependency data and then process that dependency declaration into a dependency list.
464  *    Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed
465  *    into ``, zlib``.
466  *
467  *    :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
468  *    :param pkgconf_pkg_t* pkg: The package object that owns this dependency list.
469  *    :param pkgconf_list_t* deplist: The dependency list to populate with dependency nodes.
470  *    :param char* depends: The dependency data to parse.
471  *    :param uint flags: Any flags to attach to the dependency nodes.
472  *    :return: nothing
473  */
474 void
475 pkgconf_dependency_parse(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends, unsigned int flags)
476 {
477 	char *kvdepends = pkgconf_bytecode_eval_str(client, &pkg->vars, depends, NULL);
478 
479 	pkgconf_dependency_parse_str(client, deplist, kvdepends, flags);
480 	free(kvdepends);
481 }
482 
483 /*
484  * !doc
485  *
486  * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep)
487  *
488  *    Copies a dependency node to a new one.
489  *
490  *    :param pkgconf_client_t* client: The client object that will own this dependency.
491  *    :param pkgconf_dependency_t* dep: The dependency node to copy.
492  *    :return: a pointer to a new dependency node, else NULL
493  */
494 pkgconf_dependency_t *
495 pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep)
496 {
497 	pkgconf_dependency_t *new_dep;
498 
499 	new_dep = calloc(1, sizeof(pkgconf_dependency_t));
500 	if (new_dep == NULL)
501 		return NULL;
502 
503 	new_dep->package = strdup(dep->package);
504 	if (new_dep->package == NULL)
505 	{
506 		pkgconf_dependency_free_one(new_dep);
507 		return NULL;
508 	}
509 
510 	if (dep->version != NULL)
511 		new_dep->version = strdup(dep->version);
512 
513 	new_dep->compare = dep->compare;
514 	new_dep->flags = dep->flags;
515 	new_dep->owner = client;
516 	new_dep->refcount = 0;
517 
518 	if (dep->match != NULL)
519 		new_dep->match = pkgconf_pkg_ref(client, dep->match);
520 
521 	return pkgconf_dependency_ref(client, new_dep);
522 }
523