1 /* 2 * path.c 3 * filesystem path management 4 * 5 * Copyright (c) 2016 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/config.h> 17 #include <libpkgconf/stdinc.h> 18 #include <libpkgconf/libpkgconf.h> 19 20 #if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32) 21 # include <sys/stat.h> 22 # define PKGCONF_CACHE_INODES 23 #endif 24 25 #ifdef _WIN32 26 # define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH" 27 #endif 28 29 static bool 30 #ifdef PKGCONF_CACHE_INODES 31 path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st) 32 #else 33 path_list_contains_entry(const char *text, pkgconf_list_t *dirlist) 34 #endif 35 { 36 pkgconf_node_t *n; 37 38 PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) 39 { 40 pkgconf_path_t *pn = n->data; 41 42 #ifdef PKGCONF_CACHE_INODES 43 if (pn->handle_device == (void *)(intptr_t)st->st_dev && pn->handle_path == (void *)(intptr_t)st->st_ino) 44 return true; 45 #endif 46 47 if (!strcmp(text, pn->path)) 48 return true; 49 } 50 51 return false; 52 } 53 54 /* 55 * !doc 56 * 57 * libpkgconf `path` module 58 * ======================== 59 * 60 * The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably, 61 * it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment 62 * variables. 63 */ 64 65 static pkgconf_path_t * 66 prepare_path_node(const char *text, pkgconf_list_t *dirlist, bool filter) 67 { 68 pkgconf_path_t *node; 69 char path[PKGCONF_ITEM_SIZE]; 70 71 pkgconf_strlcpy(path, text, sizeof path); 72 pkgconf_path_relocate(path, sizeof path); 73 74 #ifdef PKGCONF_CACHE_INODES 75 struct stat st; 76 77 if (filter) 78 { 79 if (lstat(path, &st) == -1) 80 return NULL; 81 if (S_ISLNK(st.st_mode)) 82 { 83 char pathbuf[PKGCONF_ITEM_SIZE * 4]; 84 char *linkdest = realpath(path, pathbuf); 85 86 if (linkdest != NULL && stat(linkdest, &st) == -1) 87 return NULL; 88 } 89 if (path_list_contains_entry(path, dirlist, &st)) 90 return NULL; 91 } 92 #else 93 if (filter && path_list_contains_entry(path, dirlist)) 94 return NULL; 95 #endif 96 97 node = calloc(1, sizeof(pkgconf_path_t)); 98 if (node == NULL) 99 return NULL; 100 101 node->path = strdup(path); 102 103 #ifdef PKGCONF_CACHE_INODES 104 if (filter) { 105 node->handle_path = (void *)(intptr_t) st.st_ino; 106 node->handle_device = (void *)(intptr_t) st.st_dev; 107 } 108 #endif 109 110 return node; 111 } 112 113 /* 114 * !doc 115 * 116 * .. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist) 117 * 118 * Adds a path node to a path list. If the path is already in the list, do nothing. 119 * 120 * :param char* text: The path text to add as a path node. 121 * :param pkgconf_list_t* dirlist: The path list to add the path node to. 122 * :param bool filter: Whether to perform duplicate filtering. 123 * :return: nothing 124 */ 125 void 126 pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter) 127 { 128 pkgconf_path_t *node = prepare_path_node(text, dirlist, filter); 129 if (node == NULL) 130 return; 131 132 pkgconf_node_insert_tail(&node->lnode, node, dirlist); 133 } 134 135 /* 136 * !doc 137 * 138 * .. c:function:: void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist) 139 * 140 * Prepends 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_prepend(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(&node->lnode, node, dirlist); 155 } 156 157 /* 158 * !doc 159 * 160 * .. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist) 161 * 162 * Splits a given text input and inserts paths into a path list. 163 * 164 * :param char* text: The path text to split and add as path nodes. 165 * :param pkgconf_list_t* dirlist: The path list to have the path nodes added to. 166 * :param bool filter: Whether to perform duplicate filtering. 167 * :return: number of path nodes added to the path list 168 * :rtype: size_t 169 */ 170 size_t 171 pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter) 172 { 173 size_t count = 0; 174 char *workbuf, *p, *iter; 175 176 if (text == NULL) 177 return 0; 178 179 iter = workbuf = strdup(text); 180 while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL) 181 { 182 pkgconf_path_add(p, dirlist, filter); 183 184 count++, iter = NULL; 185 } 186 free(workbuf); 187 188 return count; 189 } 190 191 /* 192 * !doc 193 * 194 * .. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist) 195 * 196 * Adds the paths specified in an environment variable to a path list. If the environment variable is not set, 197 * an optional default set of paths is added. 198 * 199 * :param char* envvarname: The environment variable to look up. 200 * :param char* fallback: The fallback paths to use if the environment variable is not set. 201 * :param pkgconf_list_t* dirlist: The path list to add the path nodes to. 202 * :param bool filter: Whether to perform duplicate filtering. 203 * :return: number of path nodes added to the path list 204 * :rtype: size_t 205 */ 206 size_t 207 pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter) 208 { 209 const char *data; 210 211 data = getenv(envvarname); 212 if (data != NULL) 213 return pkgconf_path_split(data, dirlist, filter); 214 215 if (fallback != NULL) 216 return pkgconf_path_split(fallback, dirlist, filter); 217 218 /* no fallback and no environment variable, thusly no nodes added */ 219 return 0; 220 } 221 222 /* 223 * !doc 224 * 225 * .. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) 226 * 227 * Checks whether a path has a matching prefix in a path list. 228 * 229 * :param char* path: The path to check against a path list. 230 * :param pkgconf_list_t* dirlist: The path list to check the path against. 231 * :return: true if the path list has a matching prefix, otherwise false 232 * :rtype: bool 233 */ 234 bool 235 pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) 236 { 237 pkgconf_node_t *n = NULL; 238 char relocated[PKGCONF_ITEM_SIZE]; 239 const char *cpath = path; 240 241 pkgconf_strlcpy(relocated, path, sizeof relocated); 242 if (pkgconf_path_relocate(relocated, sizeof relocated)) 243 cpath = relocated; 244 245 PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) 246 { 247 pkgconf_path_t *pnode = n->data; 248 249 if (!strcmp(pnode->path, cpath)) 250 return true; 251 } 252 253 return false; 254 } 255 256 /* 257 * !doc 258 * 259 * .. c:function:: void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 260 * 261 * Copies a path list to another path list. 262 * 263 * :param pkgconf_list_t* dst: The path list to copy to. 264 * :param pkgconf_list_t* src: The path list to copy from. 265 * :return: nothing 266 */ 267 void 268 pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 269 { 270 pkgconf_node_t *n; 271 272 PKGCONF_FOREACH_LIST_ENTRY(src->head, n) 273 { 274 pkgconf_path_t *srcpath = n->data, *path; 275 276 path = calloc(1, sizeof(pkgconf_path_t)); 277 if (path == NULL) 278 continue; 279 280 path->path = strdup(srcpath->path); 281 282 #ifdef PKGCONF_CACHE_INODES 283 path->handle_path = srcpath->handle_path; 284 path->handle_device = srcpath->handle_device; 285 #endif 286 287 pkgconf_node_insert_tail(&path->lnode, path, dst); 288 } 289 } 290 291 /* 292 * !doc 293 * 294 * .. c:function:: void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 295 * 296 * Copies a path list to another path list. 297 * 298 * :param pkgconf_list_t* dst: The path list to copy to. 299 * :param pkgconf_list_t* src: The path list to copy from. 300 * :return: nothing 301 */ 302 void 303 pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) 304 { 305 pkgconf_node_t *n; 306 307 PKGCONF_FOREACH_LIST_ENTRY(src->head, n) 308 { 309 pkgconf_path_t *srcpath = n->data, *path; 310 311 path = calloc(1, sizeof(pkgconf_path_t)); 312 if (path == NULL) 313 continue; 314 315 path->path = strdup(srcpath->path); 316 317 #ifdef PKGCONF_CACHE_INODES 318 path->handle_path = srcpath->handle_path; 319 path->handle_device = srcpath->handle_device; 320 #endif 321 322 pkgconf_node_insert(&path->lnode, path, dst); 323 } 324 } 325 326 /* 327 * !doc 328 * 329 * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist) 330 * 331 * Releases any path nodes attached to the given path list. 332 * 333 * :param pkgconf_list_t* dirlist: The path list to clean up. 334 * :return: nothing 335 */ 336 void 337 pkgconf_path_free(pkgconf_list_t *dirlist) 338 { 339 pkgconf_node_t *n, *tn; 340 341 PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n) 342 { 343 pkgconf_path_t *pnode = n->data; 344 345 free(pnode->path); 346 free(pnode); 347 } 348 349 pkgconf_list_zero(dirlist); 350 } 351 352 static char * 353 normpath(const char *path) 354 { 355 if (!path) 356 return NULL; 357 358 char *copy = strdup(path); 359 if (NULL == copy) 360 return NULL; 361 char *ptr = copy; 362 363 for (int ii = 0; copy[ii]; ii++) 364 { 365 *ptr++ = path[ii]; 366 if ('/' == path[ii]) 367 { 368 ii++; 369 while ('/' == path[ii]) 370 ii++; 371 ii--; 372 } 373 } 374 *ptr = '\0'; 375 376 return copy; 377 } 378 379 /* 380 * !doc 381 * 382 * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen) 383 * 384 * Relocates a path, possibly calling normpath() on it. 385 * 386 * :param char* buf: The path to relocate. 387 * :param size_t buflen: The buffer length the path is contained in. 388 * :return: true on success, false on error 389 * :rtype: bool 390 */ 391 bool 392 pkgconf_path_relocate(char *buf, size_t buflen) 393 { 394 char *tmpbuf; 395 396 if ((tmpbuf = normpath(buf)) != NULL) 397 { 398 size_t tmpbuflen = strlen(tmpbuf); 399 if (tmpbuflen > buflen) 400 { 401 free(tmpbuf); 402 return false; 403 } 404 405 pkgconf_strlcpy(buf, tmpbuf, buflen); 406 free(tmpbuf); 407 } 408 409 return true; 410 } 411 412 #ifdef _WIN32 413 /* 414 * !doc 415 * 416 * .. c:function:: void pkgconf_path_build_from_registry(HKEY hKey, pkgconf_list_t *dir_list, bool filter) 417 * 418 * Adds paths to a directory list discovered from a given registry key. 419 * 420 * :param HKEY hKey: The registry key to enumerate. 421 * :param pkgconf_list_t* dir_list: The directory list to append enumerated paths to. 422 * :param bool filter: Whether duplicate paths should be filtered. 423 * :return: number of path nodes added to the list 424 * :rtype: size_t 425 */ 426 size_t 427 pkgconf_path_build_from_registry(void *hKey, pkgconf_list_t *dir_list, bool filter) 428 { 429 HKEY key; 430 int i = 0; 431 size_t added = 0; 432 433 char buf[16384]; /* per registry limits */ 434 DWORD bufsize = sizeof buf; 435 if (RegOpenKeyEx(hKey, PKG_CONFIG_REG_KEY, 436 0, KEY_READ, &key) != ERROR_SUCCESS) 437 return 0; 438 439 while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL) 440 == ERROR_SUCCESS) 441 { 442 char pathbuf[PKGCONF_ITEM_SIZE]; 443 DWORD type; 444 DWORD pathbuflen = sizeof pathbuf; 445 446 if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen) 447 == ERROR_SUCCESS && type == REG_SZ) 448 { 449 pkgconf_path_add(pathbuf, dir_list, filter); 450 added++; 451 } 452 453 bufsize = sizeof buf; 454 } 455 456 RegCloseKey(key); 457 return added; 458 } 459 #endif 460