xref: /freebsd/contrib/pkgconf/libpkgconf/path.c (revision a3cefe7f2b4df0f70ff92d4570ce18e517af43ec)
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
path_list_contains_entry(const char * text,pkgconf_list_t * dirlist,struct stat * st)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 *
prepare_path_node(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_add(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_prepend(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_split(const char * text,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_build_from_environ(const char * envvarname,const char * fallback,pkgconf_list_t * dirlist,bool filter)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
pkgconf_path_match_list(const char * path,const pkgconf_list_t * dirlist)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
pkgconf_path_copy_list(pkgconf_list_t * dst,const pkgconf_list_t * src)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
pkgconf_path_prepend_list(pkgconf_list_t * dst,const pkgconf_list_t * src)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
pkgconf_path_free(pkgconf_list_t * dirlist)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 *
normpath(const char * path)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
pkgconf_path_relocate(char * buf,size_t buflen)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
pkgconf_path_build_from_registry(void * hKey,pkgconf_list_t * dir_list,bool filter)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