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 *
dependency_to_buf(const pkgconf_dependency_t * dep,pkgconf_buffer_t * buf)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 *
find_colliding_dependency(const pkgconf_dependency_t * dep,const pkgconf_list_t * list)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 *
add_or_replace_dependency_node(pkgconf_client_t * client,pkgconf_dependency_t * dep,pkgconf_list_t * list)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 *
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)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 *
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)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
pkgconf_dependency_append(pkgconf_list_t * list,pkgconf_dependency_t * tail)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
pkgconf_dependency_free_one(pkgconf_dependency_t * dep)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 *
pkgconf_dependency_ref(pkgconf_client_t * client,pkgconf_dependency_t * dep)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
pkgconf_dependency_unref(pkgconf_client_t * client,pkgconf_dependency_t * dep)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
pkgconf_dependency_free(pkgconf_list_t * list)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
pkgconf_dependency_parse_str(pkgconf_client_t * client,pkgconf_list_t * deplist_head,const char * depends,unsigned int flags)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
pkgconf_dependency_parse(pkgconf_client_t * client,pkgconf_pkg_t * pkg,pkgconf_list_t * deplist,const char * depends,unsigned int flags)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 *
pkgconf_dependency_copy(pkgconf_client_t * client,const pkgconf_dependency_t * dep)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