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
path_list_contains_entry(const pkgconf_buffer_t * text,pkgconf_list_t * dirlist,struct stat * st)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 *
prepare_path_node(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_add(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_prepend(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_split(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_build_from_environ(const pkgconf_client_t * client,const char * envvarname,const char * fallback,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_match_list(const char * path,const pkgconf_list_t * dirlist)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
pkgconf_path_copy_list(pkgconf_list_t * dst,const pkgconf_list_t * src)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
pkgconf_path_prepend_list(pkgconf_list_t * dst,const pkgconf_list_t * src)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
pkgconf_path_free(pkgconf_list_t * dirlist)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 *
normpath(const pkgconf_buffer_t * pathbuf)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
pkgconf_path_relocate(pkgconf_buffer_t * buf)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
pkgconf_path_trim_basename(pkgconf_buffer_t * buf)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 *
pkgconf_path_find_basename(const char * path)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
pkgconf_path_build_from_registry(pkgconf_client_t * client,void * hKey,pkgconf_list_t * dir_list,bool filter)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
pkgconf_path_is_plausible(const pkgconf_buffer_t * buf)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