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