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