jcs's openbsd hax
openbsd
at jcs 488 lines 14 kB view raw
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 29typedef 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 40static const char * 41dependency_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 */ 56static inline pkgconf_dependency_t * 57find_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 75static inline pkgconf_dependency_t * 76add_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 128static inline pkgconf_dependency_t * 129pkgconf_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 dep->package = pkgconf_strndup(package, package_sz); 135 136 if (version_sz != 0) 137 dep->version = pkgconf_strndup(version, version_sz); 138 139 dep->compare = compare; 140 dep->flags = flags; 141 dep->owner = client; 142 dep->refcount = 0; 143 144 return add_or_replace_dependency_node(client, dep, list); 145} 146 147/* 148 * !doc 149 * 150 * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_add(pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare) 151 * 152 * Adds a parsed dependency to a dependency list as a dependency node. 153 * 154 * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. 155 * :param pkgconf_list_t* list: The dependency list to add a dependency node to. 156 * :param char* package: The package `atom` to set on the dependency node. 157 * :param char* version: The package `version` to set on the dependency node. 158 * :param pkgconf_pkg_comparator_t compare: The comparison operator to set on the dependency node. 159 * :param uint flags: Any flags to attach to the dependency node. 160 * :return: A dependency node. 161 * :rtype: pkgconf_dependency_t * 162 */ 163pkgconf_dependency_t * 164pkgconf_dependency_add(pkgconf_client_t *client, pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare, unsigned int flags) 165{ 166 pkgconf_dependency_t *dep; 167 dep = pkgconf_dependency_addraw(client, list, package, strlen(package), version, 168 version != NULL ? strlen(version) : 0, compare, flags); 169 return pkgconf_dependency_ref(dep->owner, dep); 170} 171 172/* 173 * !doc 174 * 175 * .. c:function:: void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail) 176 * 177 * Adds a dependency node to a pre-existing dependency list. 178 * 179 * :param pkgconf_list_t* list: The dependency list to add a dependency node to. 180 * :param pkgconf_dependency_t* tail: The dependency node to add to the tail of the dependency list. 181 * :return: nothing 182 */ 183void 184pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail) 185{ 186 pkgconf_node_insert_tail(&tail->iter, tail, list); 187} 188 189/* 190 * !doc 191 * 192 * .. c:function:: void pkgconf_dependency_free_one(pkgconf_dependency_t *dep) 193 * 194 * Frees a dependency node. 195 * 196 * :param pkgconf_dependency_t* dep: The dependency node to free. 197 * :return: nothing 198 */ 199void 200pkgconf_dependency_free_one(pkgconf_dependency_t *dep) 201{ 202 if (dep->match != NULL) 203 pkgconf_pkg_unref(dep->match->owner, dep->match); 204 205 if (dep->package != NULL) 206 free(dep->package); 207 208 if (dep->version != NULL) 209 free(dep->version); 210 211 free(dep); 212} 213 214/* 215 * !doc 216 * 217 * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_ref(pkgconf_client_t *owner, pkgconf_dependency_t *dep) 218 * 219 * Increases a dependency node's refcount. 220 * 221 * :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node. 222 * :param pkgconf_dependency_t* dep: The dependency to increase the refcount of. 223 * :return: the dependency node on success, else NULL 224 */ 225pkgconf_dependency_t * 226pkgconf_dependency_ref(pkgconf_client_t *client, pkgconf_dependency_t *dep) 227{ 228 if (client != dep->owner) 229 return NULL; 230 231 dep->refcount++; 232 PKGCONF_TRACE(client, "%s refcount@%p: %d", dep->package, dep, dep->refcount); 233 return dep; 234} 235 236/* 237 * !doc 238 * 239 * .. c:function:: void pkgconf_dependency_unref(pkgconf_client_t *owner, pkgconf_dependency_t *dep) 240 * 241 * Decreases a dependency node's refcount and frees it if necessary. 242 * 243 * :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node. 244 * :param pkgconf_dependency_t* dep: The dependency to decrease the refcount of. 245 * :return: nothing 246 */ 247void 248pkgconf_dependency_unref(pkgconf_client_t *client, pkgconf_dependency_t *dep) 249{ 250 if (client != dep->owner) 251 return; 252 253 --dep->refcount; 254 PKGCONF_TRACE(client, "%s refcount@%p: %d", dep->package, dep, dep->refcount); 255 256 if (dep->refcount <= 0) 257 pkgconf_dependency_free_one(dep); 258} 259 260/* 261 * !doc 262 * 263 * .. c:function:: void pkgconf_dependency_free(pkgconf_list_t *list) 264 * 265 * Release a dependency list and its child dependency nodes. 266 * 267 * :param pkgconf_list_t* list: The dependency list to release. 268 * :return: nothing 269 */ 270void 271pkgconf_dependency_free(pkgconf_list_t *list) 272{ 273 pkgconf_node_t *node, *next; 274 275 PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) 276 { 277 pkgconf_dependency_t *dep = node->data; 278 279 pkgconf_node_delete(&dep->iter, list); 280 pkgconf_dependency_unref(dep->owner, dep); 281 } 282 283 pkgconf_list_zero(list); 284} 285 286/* 287 * !doc 288 * 289 * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends) 290 * 291 * Parse a dependency declaration into a dependency list. 292 * Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed 293 * into ``, zlib``. 294 * 295 * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. 296 * :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes. 297 * :param char* depends: The dependency data to parse. 298 * :param uint flags: Any flags to attach to the dependency nodes. 299 * :return: nothing 300 */ 301void 302pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags) 303{ 304 parse_state_t state = OUTSIDE_MODULE; 305 pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY; 306 char cmpname[PKGCONF_ITEM_SIZE]; 307 char buf[PKGCONF_BUFSIZE]; 308 size_t package_sz = 0, version_sz = 0; 309 char *start = buf; 310 char *ptr = buf; 311 char *vstart = NULL; 312 char *package = NULL, *version = NULL; 313 char *cnameptr = cmpname; 314 char *cnameend = cmpname + PKGCONF_ITEM_SIZE - 1; 315 316 memset(cmpname, '\0', sizeof cmpname); 317 318 pkgconf_strlcpy(buf, depends, sizeof buf); 319 pkgconf_strlcat(buf, " ", sizeof buf); 320 321 while (*ptr) 322 { 323 switch (state) 324 { 325 case OUTSIDE_MODULE: 326 if (!PKGCONF_IS_MODULE_SEPARATOR(*ptr)) 327 state = INSIDE_MODULE_NAME; 328 329 break; 330 331 case INSIDE_MODULE_NAME: 332 if (isspace((unsigned char)*ptr)) 333 { 334 const char *sptr = ptr; 335 336 while (*sptr && isspace((unsigned char)*sptr)) 337 sptr++; 338 339 if (*sptr == '\0') 340 state = OUTSIDE_MODULE; 341 else if (PKGCONF_IS_MODULE_SEPARATOR(*sptr)) 342 state = OUTSIDE_MODULE; 343 else if (PKGCONF_IS_OPERATOR_CHAR(*sptr)) 344 state = BEFORE_OPERATOR; 345 else 346 state = OUTSIDE_MODULE; 347 } 348 else if (PKGCONF_IS_MODULE_SEPARATOR(*ptr)) 349 state = OUTSIDE_MODULE; 350 else if (*(ptr + 1) == '\0') 351 { 352 ptr++; 353 state = OUTSIDE_MODULE; 354 } 355 356 if (state != INSIDE_MODULE_NAME && start != ptr) 357 { 358 char *iter = start; 359 360 while (PKGCONF_IS_MODULE_SEPARATOR(*iter)) 361 iter++; 362 363 package = iter; 364 package_sz = ptr - iter; 365 start = ptr; 366 } 367 368 if (state == OUTSIDE_MODULE) 369 { 370 pkgconf_dependency_addraw(client, deplist_head, package, package_sz, NULL, 0, compare, flags); 371 372 compare = PKGCONF_CMP_ANY; 373 package_sz = 0; 374 } 375 376 break; 377 378 case BEFORE_OPERATOR: 379 if (PKGCONF_IS_OPERATOR_CHAR(*ptr)) 380 { 381 state = INSIDE_OPERATOR; 382 if (cnameptr < cnameend) 383 *cnameptr++ = *ptr; 384 } 385 386 break; 387 388 case INSIDE_OPERATOR: 389 if (PKGCONF_IS_OPERATOR_CHAR(*ptr)) 390 { 391 if (cnameptr < cnameend) 392 *cnameptr++ = *ptr; 393 break; 394 } 395 396 state = AFTER_OPERATOR; 397 compare = pkgconf_pkg_comparator_lookup_by_name(cmpname); 398 // fallthrough 399 400 case AFTER_OPERATOR: 401 if (!isspace((unsigned char)*ptr)) 402 { 403 vstart = ptr; 404 state = INSIDE_VERSION; 405 } 406 break; 407 408 case INSIDE_VERSION: 409 if (PKGCONF_IS_MODULE_SEPARATOR(*ptr) || *(ptr + 1) == '\0') 410 { 411 version = vstart; 412 version_sz = ptr - vstart; 413 state = OUTSIDE_MODULE; 414 415 pkgconf_dependency_addraw(client, deplist_head, package, package_sz, version, version_sz, compare, flags); 416 417 compare = PKGCONF_CMP_ANY; 418 cnameptr = cmpname; 419 memset(cmpname, 0, sizeof cmpname); 420 package_sz = 0; 421 } 422 423 if (state == OUTSIDE_MODULE) 424 start = ptr; 425 break; 426 } 427 428 ptr++; 429 } 430} 431 432/* 433 * !doc 434 * 435 * .. c:function:: void pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends) 436 * 437 * Preprocess dependency data and then process that dependency declaration into a dependency list. 438 * Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed 439 * into ``, zlib``. 440 * 441 * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. 442 * :param pkgconf_pkg_t* pkg: The package object that owns this dependency list. 443 * :param pkgconf_list_t* deplist: The dependency list to populate with dependency nodes. 444 * :param char* depends: The dependency data to parse. 445 * :param uint flags: Any flags to attach to the dependency nodes. 446 * :return: nothing 447 */ 448void 449pkgconf_dependency_parse(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends, unsigned int flags) 450{ 451 char *kvdepends = pkgconf_tuple_parse(client, &pkg->vars, depends, pkg->flags); 452 453 pkgconf_dependency_parse_str(client, deplist, kvdepends, flags); 454 free(kvdepends); 455} 456 457/* 458 * !doc 459 * 460 * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep) 461 * 462 * Copies a dependency node to a new one. 463 * 464 * :param pkgconf_client_t* client: The client object that will own this dependency. 465 * :param pkgconf_dependency_t* dep: The dependency node to copy. 466 * :return: a pointer to a new dependency node, else NULL 467 */ 468pkgconf_dependency_t * 469pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep) 470{ 471 pkgconf_dependency_t *new_dep; 472 473 new_dep = calloc(1, sizeof(pkgconf_dependency_t)); 474 new_dep->package = strdup(dep->package); 475 476 if (dep->version != NULL) 477 new_dep->version = strdup(dep->version); 478 479 new_dep->compare = dep->compare; 480 new_dep->flags = dep->flags; 481 new_dep->owner = client; 482 new_dep->refcount = 0; 483 484 if (dep->match != NULL) 485 new_dep->match = pkgconf_pkg_ref(client, dep->match); 486 487 return pkgconf_dependency_ref(client, new_dep); 488}