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