jcs's openbsd hax
openbsd
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
25static bool
26#ifdef PKGCONF_CACHE_INODES
27path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st)
28#else
29path_list_contains_entry(const char *text, pkgconf_list_t *dirlist)
30#endif
31{
32 pkgconf_node_t *n;
33
34 PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
35 {
36 pkgconf_path_t *pn = n->data;
37
38#ifdef PKGCONF_CACHE_INODES
39 if (pn->handle_device == (void *)(intptr_t)st->st_dev && pn->handle_path == (void *)(intptr_t)st->st_ino)
40 return true;
41#endif
42
43 if (!strcmp(text, pn->path))
44 return true;
45 }
46
47 return false;
48}
49
50/*
51 * !doc
52 *
53 * libpkgconf `path` module
54 * ========================
55 *
56 * The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably,
57 * it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment
58 * variables.
59 */
60
61static pkgconf_path_t *
62prepare_path_node(const char *text, pkgconf_list_t *dirlist, bool filter)
63{
64 pkgconf_path_t *node;
65 char path[PKGCONF_ITEM_SIZE];
66
67 pkgconf_strlcpy(path, text, sizeof path);
68 pkgconf_path_relocate(path, sizeof path);
69
70#ifdef PKGCONF_CACHE_INODES
71 struct stat st;
72
73 if (filter)
74 {
75 if (lstat(path, &st) == -1)
76 return NULL;
77 if (S_ISLNK(st.st_mode))
78 {
79 char pathbuf[PKGCONF_ITEM_SIZE * 4];
80 char *linkdest = realpath(path, pathbuf);
81
82 if (linkdest != NULL && stat(linkdest, &st) == -1)
83 return NULL;
84 }
85 if (path_list_contains_entry(path, dirlist, &st))
86 return NULL;
87 }
88#else
89 if (filter && path_list_contains_entry(path, dirlist))
90 return NULL;
91#endif
92
93 node = calloc(1, sizeof(pkgconf_path_t));
94 node->path = strdup(path);
95
96#ifdef PKGCONF_CACHE_INODES
97 if (filter) {
98 node->handle_path = (void *)(intptr_t) st.st_ino;
99 node->handle_device = (void *)(intptr_t) st.st_dev;
100 }
101#endif
102
103 return node;
104}
105
106/*
107 * !doc
108 *
109 * .. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist)
110 *
111 * Adds a path node to a path list. If the path is already in the list, do nothing.
112 *
113 * :param char* text: The path text to add as a path node.
114 * :param pkgconf_list_t* dirlist: The path list to add the path node to.
115 * :param bool filter: Whether to perform duplicate filtering.
116 * :return: nothing
117 */
118void
119pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter)
120{
121 pkgconf_path_t *node = prepare_path_node(text, dirlist, filter);
122 if (node == NULL)
123 return;
124
125 pkgconf_node_insert_tail(&node->lnode, node, dirlist);
126}
127
128/*
129 * !doc
130 *
131 * .. c:function:: void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist)
132 *
133 * Prepends a path node to a path list. If the path is already in the list, do nothing.
134 *
135 * :param char* text: The path text to add as a path node.
136 * :param pkgconf_list_t* dirlist: The path list to add the path node to.
137 * :param bool filter: Whether to perform duplicate filtering.
138 * :return: nothing
139 */
140void
141pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist, bool filter)
142{
143 pkgconf_path_t *node = prepare_path_node(text, dirlist, filter);
144 if (node == NULL)
145 return;
146
147 pkgconf_node_insert(&node->lnode, node, dirlist);
148}
149
150/*
151 * !doc
152 *
153 * .. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist)
154 *
155 * Splits a given text input and inserts paths into a path list.
156 *
157 * :param char* text: The path text to split and add as path nodes.
158 * :param pkgconf_list_t* dirlist: The path list to have the path nodes added to.
159 * :param bool filter: Whether to perform duplicate filtering.
160 * :return: number of path nodes added to the path list
161 * :rtype: size_t
162 */
163size_t
164pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter)
165{
166 size_t count = 0;
167 char *workbuf, *p, *iter;
168
169 if (text == NULL)
170 return 0;
171
172 iter = workbuf = strdup(text);
173 while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL)
174 {
175 pkgconf_path_add(p, dirlist, filter);
176
177 count++, iter = NULL;
178 }
179 free(workbuf);
180
181 return count;
182}
183
184/*
185 * !doc
186 *
187 * .. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist)
188 *
189 * Adds the paths specified in an environment variable to a path list. If the environment variable is not set,
190 * an optional default set of paths is added.
191 *
192 * :param char* envvarname: The environment variable to look up.
193 * :param char* fallback: The fallback paths to use if the environment variable is not set.
194 * :param pkgconf_list_t* dirlist: The path list to add the path nodes to.
195 * :param bool filter: Whether to perform duplicate filtering.
196 * :return: number of path nodes added to the path list
197 * :rtype: size_t
198 */
199size_t
200pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter)
201{
202 const char *data;
203
204 data = getenv(envvarname);
205 if (data != NULL)
206 return pkgconf_path_split(data, dirlist, filter);
207
208 if (fallback != NULL)
209 return pkgconf_path_split(fallback, dirlist, filter);
210
211 /* no fallback and no environment variable, thusly no nodes added */
212 return 0;
213}
214
215/*
216 * !doc
217 *
218 * .. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
219 *
220 * Checks whether a path has a matching prefix in a path list.
221 *
222 * :param char* path: The path to check against a path list.
223 * :param pkgconf_list_t* dirlist: The path list to check the path against.
224 * :return: true if the path list has a matching prefix, otherwise false
225 * :rtype: bool
226 */
227bool
228pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
229{
230 pkgconf_node_t *n = NULL;
231 char relocated[PKGCONF_ITEM_SIZE];
232 const char *cpath = path;
233
234 pkgconf_strlcpy(relocated, path, sizeof relocated);
235 if (pkgconf_path_relocate(relocated, sizeof relocated))
236 cpath = relocated;
237
238 PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
239 {
240 pkgconf_path_t *pnode = n->data;
241
242 if (!strcmp(pnode->path, cpath))
243 return true;
244 }
245
246 return false;
247}
248
249/*
250 * !doc
251 *
252 * .. c:function:: void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
253 *
254 * Copies a path list to another path list.
255 *
256 * :param pkgconf_list_t* dst: The path list to copy to.
257 * :param pkgconf_list_t* src: The path list to copy from.
258 * :return: nothing
259 */
260void
261pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
262{
263 pkgconf_node_t *n;
264
265 PKGCONF_FOREACH_LIST_ENTRY(src->head, n)
266 {
267 pkgconf_path_t *srcpath = n->data, *path;
268
269 path = calloc(1, sizeof(pkgconf_path_t));
270 path->path = strdup(srcpath->path);
271
272#ifdef PKGCONF_CACHE_INODES
273 path->handle_path = srcpath->handle_path;
274 path->handle_device = srcpath->handle_device;
275#endif
276
277 pkgconf_node_insert_tail(&path->lnode, path, dst);
278 }
279}
280
281/*
282 * !doc
283 *
284 * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist)
285 *
286 * Releases any path nodes attached to the given path list.
287 *
288 * :param pkgconf_list_t* dirlist: The path list to clean up.
289 * :return: nothing
290 */
291void
292pkgconf_path_free(pkgconf_list_t *dirlist)
293{
294 pkgconf_node_t *n, *tn;
295
296 PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n)
297 {
298 pkgconf_path_t *pnode = n->data;
299
300 free(pnode->path);
301 free(pnode);
302 }
303
304 pkgconf_list_zero(dirlist);
305}
306
307static char *
308normpath(const char *path)
309{
310 if (!path)
311 return NULL;
312
313 char *copy = strdup(path);
314 if (NULL == copy)
315 return NULL;
316 char *ptr = copy;
317
318 for (int ii = 0; copy[ii]; ii++)
319 {
320 *ptr++ = path[ii];
321 if ('/' == path[ii])
322 {
323 ii++;
324 while ('/' == path[ii])
325 ii++;
326 ii--;
327 }
328 }
329 *ptr = '\0';
330
331 return copy;
332}
333
334/*
335 * !doc
336 *
337 * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen)
338 *
339 * Relocates a path, possibly calling normpath() on it.
340 *
341 * :param char* buf: The path to relocate.
342 * :param size_t buflen: The buffer length the path is contained in.
343 * :return: true on success, false on error
344 * :rtype: bool
345 */
346bool
347pkgconf_path_relocate(char *buf, size_t buflen)
348{
349 char *tmpbuf;
350
351 if ((tmpbuf = normpath(buf)) != NULL)
352 {
353 size_t tmpbuflen = strlen(tmpbuf);
354 if (tmpbuflen > buflen)
355 {
356 free(tmpbuf);
357 return false;
358 }
359
360 pkgconf_strlcpy(buf, tmpbuf, buflen);
361 free(tmpbuf);
362 }
363
364 return true;
365}