1 /* 2 * path.c 3 * filesystem path management 4 * 5 * SPDX-License-Identifier: pkgconf 6 * 7 * Copyright (c) 2016 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/config.h> 19 #include <libpkgconf/stdinc.h> 20 #include <libpkgconf/libpkgconf.h> 21 #include <libpkgconf/path.h> 22 23 #if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32) 24 # include <sys/stat.h> 25 # define PKGCONF_CACHE_INODES 26 #endif 27 28 static bool 29 #ifdef PKGCONF_CACHE_INODES 30 path_list_contains_entry(const pkgconf_buffer_t *text, pkgconf_list_t *dirlist, struct stat *st) 31 #else 32 path_list_contains_entry(const pkgconf_buffer_t *text, pkgconf_list_t *dirlist) 33 #endif 34 { 35 pkgconf_node_t *n; 36 37 PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) 38 { 39 pkgconf_path_t *pn = n->data; 40 41 #ifdef PKGCONF_CACHE_INODES 42 if (pn->handle_device == (void *)(intptr_t)st->st_dev && pn->handle_path == (void *)(intptr_t)st->st_ino) 43 return true; 44 #endif 45 46 if (!strcmp(pkgconf_buffer_str(text), pn->path)) 47 return true; 48 } 49 50 return false; 51 } 52 53 /* 54 * !doc 55 * 56 * libpkgconf `path` module 57 * ======================== 58 * 59 * The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably, 60 * it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment 61 * variables. 62 */ 63 64 static pkgconf_path_t * 65 prepare_path_node(const char *text, pkgconf_list_t *dirlist, bool filter) 66 { 67 pkgconf_path_t *node; 68 pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; 69 70 pkgconf_buffer_append(&pathbuf, text); 71 pkgconf_path_relocate(&pathbuf); 72 73 #ifdef PKGCONF_CACHE_INODES 74 struct stat st; 75 76 if (filter) 77 { 78 if (lstat(pkgconf_buffer_str(&pathbuf), &st) == -1) 79 { 80 pkgconf_buffer_finalize(&pathbuf); 81 return NULL; 82 } 83 84 if (S_ISLNK(st.st_mode)) 85 { 86 char realpathbuf[PKGCONF_ITEM_SIZE * 4]; 87 char *linkdest = realpath(pkgconf_buffer_str(&pathbuf), realpathbuf); 88 89 if (linkdest != NULL && stat(linkdest, &st) == -1) 90 { 91 pkgconf_buffer_finalize(&pathbuf); 92 return NULL; 93 } 94 } 95 96 if (path_list_contains_entry(&pathbuf, dirlist, &st)) 97 { 98 pkgconf_buffer_finalize(&pathbuf); 99 return NULL; 100 } 101 } 102 #else 103 if (filter && path_list_contains_entry(&pathbuf, dirlist)) 104 { 105 pkgconf_buffer_finalize(&pathbuf); 106 return NULL; 107 } 108 #endif 109 110 node = calloc(1, sizeof(pkgconf_path_t)); 111 if (node == NULL) 112 { 113 pkgconf_buffer_finalize(&pathbuf); 114 return NULL; 115 } 116 117 node->path = pkgconf_buffer_freeze(&pathbuf); 118 if (node->path == NULL) 119 { 120 free(node); 121 return NULL; 122 } 123 124 #ifdef PKGCONF_CACHE_INODES 125 if (filter) 126 { 127 node->handle_path = (void *)(intptr_t) st.st_ino; 128 node->handle_device = (void *)(intptr_t) st.st_dev; 129 } 130 #endif 131 132 return node; 133 } 134 135 /* 136 * !doc 137 * 138 * .. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist) 139 * 140 * Adds a path node to a path list. If the path is already in the list, do nothing. 141 * 142 * :param char* text: The path text to add as a path node. 143 * :param pkgconf_list_t* dirlist: The path list to add the path node to. 144 * :param bool filter: Whether to perform duplicate filtering. 145 * :return: nothing 146 */ 147 void 148 pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter) 149 { 150 pkgconf_path_t *node = prepare_path_node(text, dirlist, filter); 151 if (node == NULL) 152 return; 153 154 pkgconf_node_insert_tail(&node->lnode, node, dirlist); 155 } 156 157 /* 158 * !doc 159 * 160 * .. c:function:: void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist) 161 * 162 * Prepends a path node to a path list. If the path is already in the list, do nothing. 163 * 164 * :param char* text: The path text to add as a path node. 165 * :param pkgconf_list_t* dirlist: The path list to add the path node to. 166 * :param bool filter: Whether to perform duplicate filtering. 167 * :return: nothing 168 */ 169 void 170 pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist, bool filter) 171 { 172 pkgconf_path_t *node = prepare_path_node(text, dirlist, filter); 173 if (node == NULL) 174 return; 175 176 pkgconf_node_insert(&node->lnode, node, dirlist); 177 } 178 179 /* 180 * !doc 181 * 182 * .. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist) 183 * 184 * Splits a given text input and inserts paths into a path list. 185 * 186 * :param char* text: The path text to split and add as path nodes. 187 * :param pkgconf_list_t* dirlist: The path list to have the path nodes added to. 188 * :param bool filter: Whether to perform duplicate filtering. 189 * :return: number of path nodes added to the path list 190 * :rtype: size_t 191 */ 192 size_t 193 pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter) 194 { 195 size_t count = 0; 196 char *workbuf, *p, *iter; 197 198 if (text == NULL) 199 return 0; 200 201 iter = workbuf = strdup(text); 202 if (workbuf == NULL) 203 return 0; 204 205 while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL) 206 { 207 pkgconf_path_add(p, dirlist, filter); 208 209 count++, iter = NULL; 210 } 211 free(workbuf); 212 213 return count; 214 } 215 216 /* 217 * !doc 218 * 219 * .. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist) 220 * 221 * Adds the paths specified in an environment variable to a path list. If the environment variable is not set, 222 * an optional default set of paths is added. 223 * 224 * :param pkgconf_client_t* client: The client to use for environmental variable lookup (can be NULL). 225 * :param char* envvarname: The environment variable to look up. 226 * :param char* fallback: The fallback paths to use if the environment variable is not set. 227 * :param pkgconf_list_t* dirlist: The path list to add the path nodes to. 228 * :param bool filter: Whether to perform duplicate filtering. 229 * :return: number of path nodes added to the path list 230 * :rtype: size_t 231 */ 232 size_t 233 pkgconf_path_build_from_environ(const pkgconf_client_t *client, const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter) 234 { 235 const char *data; 236 237 data = pkgconf_client_getenv(client, envvarname); 238 if (data != NULL) 239 return pkgconf_path_split(data, dirlist, filter); 240 241 if (fallback != NULL) 242 return pkgconf_path_split(fallback, dirlist, filter); 243 244 /* no fallback and no environment variable, thusly no nodes added */ 245 return 0; 246 } 247 248 /* 249 * !doc 250 * 251 * .. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) 252 * 253 * Checks whether a path has a matching prefix in a path list. 254 * 255 * :param char* path: The path to check against a path list. 256 * :param pkgconf_list_t* dirlist: The path list to check the path against. 257 * :return: true if the path list has a matching prefix, otherwise false 258 * :rtype: bool 259 */ 260 bool 261 pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) 262 { 263 pkgconf_node_t *n = NULL; 264 pkgconf_buffer_t relocated = PKGCONF_BUFFER_INITIALIZER; 265 const char *cpath = path; 266 267 if (path == NULL) 268 return false; 269 270 pkgconf_buffer_append(&relocated, path); 271 cpath = pkgconf_buffer_str(&relocated); 272 273 if (pkgconf_path_relocate(&relocated)) 274 cpath = pkgconf_buffer_str(&relocated); 275 276 if (cpath == NULL) 277 { 278 pkgconf_buffer_finalize(&relocated); 279 return false; 280 } 281 282 PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) 283 { 284 pkgconf_path_t *pnode = n->data; 285 286 if (!strcmp(pnode->path, cpath)) 287 { 288 pkgconf_buffer_finalize(&relocated); 289 return true; 290 } 291 } 292 293 pkgconf_buffer_finalize(&relocated); 294 return false; 295 } 296 297 /* 298 * !doc 299 * 300 * .. c:function:: void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 301 * 302 * Copies a path list to another path list. 303 * 304 * :param pkgconf_list_t* dst: The path list to copy to. 305 * :param pkgconf_list_t* src: The path list to copy from. 306 * :return: nothing 307 */ 308 void 309 pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 310 { 311 pkgconf_node_t *n; 312 313 PKGCONF_FOREACH_LIST_ENTRY(src->head, n) 314 { 315 pkgconf_path_t *srcpath = n->data, *path; 316 317 path = calloc(1, sizeof(pkgconf_path_t)); 318 if (path == NULL) 319 continue; 320 321 path->path = strdup(srcpath->path); 322 if (path->path == NULL) 323 { 324 free(path); 325 continue; 326 } 327 328 #ifdef PKGCONF_CACHE_INODES 329 path->handle_path = srcpath->handle_path; 330 path->handle_device = srcpath->handle_device; 331 #endif 332 333 pkgconf_node_insert_tail(&path->lnode, path, dst); 334 } 335 } 336 337 /* 338 * !doc 339 * 340 * .. c:function:: void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 341 * 342 * Copies a path list to another path list. 343 * 344 * :param pkgconf_list_t* dst: The path list to copy to. 345 * :param pkgconf_list_t* src: The path list to copy from. 346 * :return: nothing 347 */ 348 void 349 pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 350 { 351 pkgconf_node_t *n; 352 353 PKGCONF_FOREACH_LIST_ENTRY(src->head, n) 354 { 355 pkgconf_path_t *srcpath = n->data, *path; 356 357 path = calloc(1, sizeof(pkgconf_path_t)); 358 if (path == NULL) 359 continue; 360 361 path->path = strdup(srcpath->path); 362 if (path->path == NULL) 363 { 364 free(path); 365 continue; 366 } 367 368 #ifdef PKGCONF_CACHE_INODES 369 path->handle_path = srcpath->handle_path; 370 path->handle_device = srcpath->handle_device; 371 #endif 372 373 pkgconf_node_insert(&path->lnode, path, dst); 374 } 375 } 376 377 /* 378 * !doc 379 * 380 * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist) 381 * 382 * Releases any path nodes attached to the given path list. 383 * 384 * :param pkgconf_list_t* dirlist: The path list to clean up. 385 * :return: nothing 386 */ 387 void 388 pkgconf_path_free(pkgconf_list_t *dirlist) 389 { 390 pkgconf_node_t *n, *tn; 391 392 PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n) 393 { 394 pkgconf_path_t *pnode = n->data; 395 396 free(pnode->path); 397 free(pnode); 398 } 399 400 pkgconf_list_zero(dirlist); 401 } 402 403 static char * 404 normpath(const pkgconf_buffer_t *pathbuf) 405 { 406 if (!pathbuf || pkgconf_buffer_len(pathbuf) == 0) 407 return NULL; 408 409 const char *path = pkgconf_buffer_str(pathbuf); 410 char *copy = strdup(path); 411 if (NULL == copy) 412 return NULL; 413 char *ptr = copy; 414 415 for (int ii = 0; copy[ii]; ii++) 416 { 417 *ptr++ = path[ii]; 418 if ('/' == path[ii]) 419 { 420 ii++; 421 while ('/' == path[ii]) 422 ii++; 423 ii--; 424 } 425 } 426 *ptr = '\0'; 427 428 return copy; 429 } 430 431 /* 432 * !doc 433 * 434 * .. c:function:: bool pkgconf_path_relocate(pkgconf_buffer_t *buf) 435 * 436 * Relocates a path, possibly calling normpath() on it. 437 * 438 * :param pkgconf_buffer_t* buf: The path to relocate. 439 * :return: true on success, false on error 440 * :rtype: bool 441 */ 442 bool 443 pkgconf_path_relocate(pkgconf_buffer_t *buf) 444 { 445 char *tmpbuf; 446 447 if ((tmpbuf = normpath(buf)) != NULL) 448 { 449 pkgconf_buffer_reset(buf); 450 pkgconf_buffer_append(buf, tmpbuf); 451 free(tmpbuf); 452 } 453 454 return true; 455 } 456 457 /* 458 * !doc 459 * 460 * .. c:function:: bool pkgconf_path_trim_basename(pkgconf_buffer_t *buf) 461 * 462 * Trims the basename from a path. 463 * 464 * :param pkgconf_buffer_t* buf: The path to trim. 465 * :return: true if a separator was found and the path was trimmed, false otherwise 466 * :rtype: bool 467 */ 468 bool 469 pkgconf_path_trim_basename(pkgconf_buffer_t *buf) 470 { 471 char *sep; 472 473 if (!pkgconf_buffer_len(buf)) 474 return false; 475 476 sep = strrchr(buf->base, PKG_DIR_SEP_S); 477 #ifdef _WIN32 478 char *sep2 = strrchr(buf->base, '/'); 479 if (sep2 != NULL && (sep == NULL || sep2 > sep)) 480 sep = sep2; 481 #endif 482 483 if (sep != NULL) 484 { 485 *sep = '\0'; 486 buf->end = sep; 487 return true; 488 } 489 490 return false; 491 } 492 493 /* 494 * !doc 495 * 496 * .. c:function:: const char *pkgconf_path_find_basename(const char *path) 497 * 498 * Finds the basename from a path. 499 * 500 * :param char* path: The path to find the basename from. 501 * :return: a pointer to the basename 502 * :rtype: const char * 503 */ 504 const char * 505 pkgconf_path_find_basename(const char *path) 506 { 507 const char *sep; 508 509 sep = strrchr(path, PKG_DIR_SEP_S); 510 #ifdef _WIN32 511 const char *sep2 = strrchr(path, '/'); 512 if (sep2 != NULL && (sep == NULL || sep2 > sep)) 513 sep = sep2; 514 #endif 515 516 if (sep != NULL) 517 return sep + 1; 518 519 return path; 520 } 521 522 #ifdef _WIN32 523 #define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH" 524 /* 525 * !doc 526 * 527 * .. c:function:: void pkgconf_path_build_from_registry(HKEY hKey, pkgconf_list_t *dir_list, bool filter) 528 * 529 * Adds paths to a directory list discovered from a given registry key. 530 * 531 * .. warning:: 532 * The Windows registry search path mechanism is deprecated and will be 533 * removed in pkgconf 3.1. Use ``PKG_CONFIG_PATH`` or configure search 534 * paths explicitly instead. Avoid using this function directly in new 535 * code. 536 * 537 * :param pkgconf_client_t* client: pkgconf client 538 * :param HKEY hKey: The registry key to enumerate. 539 * :param pkgconf_list_t* dir_list: The directory list to append enumerated paths to. 540 * :param bool filter: Whether duplicate paths should be filtered. 541 * :return: number of path nodes added to the list 542 * :rtype: size_t 543 */ 544 size_t 545 pkgconf_path_build_from_registry(pkgconf_client_t *client, void *hKey, pkgconf_list_t *dir_list, bool filter) 546 { 547 HKEY key; 548 int i = 0; 549 size_t added = 0; 550 551 char buf[16384]; /* per registry limits */ 552 DWORD bufsize = sizeof buf; 553 if (RegOpenKeyEx(hKey, PKG_CONFIG_REG_KEY, 554 0, KEY_READ, &key) != ERROR_SUCCESS) 555 return 0; 556 557 pkgconf_warn(client, 558 "WARNING: support for reading PKG_CONFIG_PATH from the Windows registry " 559 "is deprecated and will be removed in pkgconf 3.1\n"); 560 561 while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL) 562 == ERROR_SUCCESS) 563 { 564 char pathbuf[PKGCONF_ITEM_SIZE]; 565 DWORD type; 566 DWORD pathbuflen = sizeof pathbuf; 567 568 if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen) 569 == ERROR_SUCCESS && type == REG_SZ) 570 { 571 pkgconf_path_add(pathbuf, dir_list, filter); 572 added++; 573 } 574 575 bufsize = sizeof buf; 576 } 577 578 RegCloseKey(key); 579 return added; 580 } 581 #endif 582 583 bool 584 pkgconf_path_is_plausible(const pkgconf_buffer_t *buf) 585 { 586 const char *s; 587 588 if (buf == NULL) 589 return false; 590 591 s = pkgconf_buffer_str(buf); 592 if (s == NULL) 593 return false; 594 595 /* skip leading whitespace */ 596 while (*s != '\0' && isspace((unsigned char)*s)) 597 s++; 598 599 if (*s == '\0') 600 return false; 601 602 /* POSIX absolute path */ 603 if (*s == '/') 604 return true; 605 606 /* ./ or ../ relative path */ 607 if (s[0] == '.' && (s[1] == '/' || s[1] == '\\')) 608 return true; 609 610 if (s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == '\\')) 611 return true; 612 613 /* Windows drive path: C:/... or C:\... */ 614 if (isalpha((unsigned char)s[0]) && s[1] == ':' && (s[2] == '/' || s[2] == '\\')) 615 return true; 616 617 /* anything with a path separator seems plausible, for example "Program Files/MySDK" */ 618 for (const char *p = s; *p != '\0'; p++) 619 { 620 if (*p == '/' || *p == '\\') 621 return true; 622 } 623 624 return false; 625 } 626