summaryrefslogtreecommitdiff
path: root/libpkgconf/path.c
diff options
context:
space:
mode:
authorPierre Pronchery <khorben@FreeBSD.org>2026-04-22 13:13:41 +0000
committerPierre Pronchery <khorben@FreeBSD.org>2026-04-22 13:13:41 +0000
commit6294b6ab217a2d5f1d2bc23a64505a228294c508 (patch)
tree333ee95d0d49af6cfc4390c1ec90098e6493e332 /libpkgconf/path.c
Diffstat (limited to 'libpkgconf/path.c')
-rw-r--r--libpkgconf/path.c459
1 files changed, 459 insertions, 0 deletions
diff --git a/libpkgconf/path.c b/libpkgconf/path.c
new file mode 100644
index 000000000000..dda77f5664ab
--- /dev/null
+++ b/libpkgconf/path.c
@@ -0,0 +1,459 @@
+/*
+ * path.c
+ * filesystem path management
+ *
+ * Copyright (c) 2016 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/config.h>
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+#if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32)
+# include <sys/stat.h>
+# define PKGCONF_CACHE_INODES
+#endif
+
+#ifdef _WIN32
+# define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH"
+#endif
+
+static bool
+#ifdef PKGCONF_CACHE_INODES
+path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st)
+#else
+path_list_contains_entry(const char *text, pkgconf_list_t *dirlist)
+#endif
+{
+ pkgconf_node_t *n;
+
+ PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
+ {
+ pkgconf_path_t *pn = n->data;
+
+#ifdef PKGCONF_CACHE_INODES
+ if (pn->handle_device == (void *)(intptr_t)st->st_dev && pn->handle_path == (void *)(intptr_t)st->st_ino)
+ return true;
+#endif
+
+ if (!strcmp(text, pn->path))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * !doc
+ *
+ * libpkgconf `path` module
+ * ========================
+ *
+ * The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably,
+ * it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment
+ * variables.
+ */
+
+static pkgconf_path_t *
+prepare_path_node(const char *text, pkgconf_list_t *dirlist, bool filter)
+{
+ pkgconf_path_t *node;
+ char path[PKGCONF_ITEM_SIZE];
+
+ pkgconf_strlcpy(path, text, sizeof path);
+ pkgconf_path_relocate(path, sizeof path);
+
+#ifdef PKGCONF_CACHE_INODES
+ struct stat st;
+
+ if (filter)
+ {
+ if (lstat(path, &st) == -1)
+ return NULL;
+ if (S_ISLNK(st.st_mode))
+ {
+ char pathbuf[PKGCONF_ITEM_SIZE * 4];
+ char *linkdest = realpath(path, pathbuf);
+
+ if (linkdest != NULL && stat(linkdest, &st) == -1)
+ return NULL;
+ }
+ if (path_list_contains_entry(path, dirlist, &st))
+ return NULL;
+ }
+#else
+ if (filter && path_list_contains_entry(path, dirlist))
+ return NULL;
+#endif
+
+ node = calloc(1, sizeof(pkgconf_path_t));
+ if (node == NULL)
+ return NULL;
+
+ node->path = strdup(path);
+
+#ifdef PKGCONF_CACHE_INODES
+ if (filter) {
+ node->handle_path = (void *)(intptr_t) st.st_ino;
+ node->handle_device = (void *)(intptr_t) st.st_dev;
+ }
+#endif
+
+ return node;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist)
+ *
+ * Adds a path node to a path list. If the path is already in the list, do nothing.
+ *
+ * :param char* text: The path text to add as a path node.
+ * :param pkgconf_list_t* dirlist: The path list to add the path node to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: nothing
+ */
+void
+pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter)
+{
+ pkgconf_path_t *node = prepare_path_node(text, dirlist, filter);
+ if (node == NULL)
+ return;
+
+ pkgconf_node_insert_tail(&node->lnode, node, dirlist);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist)
+ *
+ * Prepends a path node to a path list. If the path is already in the list, do nothing.
+ *
+ * :param char* text: The path text to add as a path node.
+ * :param pkgconf_list_t* dirlist: The path list to add the path node to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: nothing
+ */
+void
+pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist, bool filter)
+{
+ pkgconf_path_t *node = prepare_path_node(text, dirlist, filter);
+ if (node == NULL)
+ return;
+
+ pkgconf_node_insert(&node->lnode, node, dirlist);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist)
+ *
+ * Splits a given text input and inserts paths into a path list.
+ *
+ * :param char* text: The path text to split and add as path nodes.
+ * :param pkgconf_list_t* dirlist: The path list to have the path nodes added to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: number of path nodes added to the path list
+ * :rtype: size_t
+ */
+size_t
+pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter)
+{
+ size_t count = 0;
+ char *workbuf, *p, *iter;
+
+ if (text == NULL)
+ return 0;
+
+ iter = workbuf = strdup(text);
+ while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL)
+ {
+ pkgconf_path_add(p, dirlist, filter);
+
+ count++, iter = NULL;
+ }
+ free(workbuf);
+
+ return count;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist)
+ *
+ * Adds the paths specified in an environment variable to a path list. If the environment variable is not set,
+ * an optional default set of paths is added.
+ *
+ * :param char* envvarname: The environment variable to look up.
+ * :param char* fallback: The fallback paths to use if the environment variable is not set.
+ * :param pkgconf_list_t* dirlist: The path list to add the path nodes to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: number of path nodes added to the path list
+ * :rtype: size_t
+ */
+size_t
+pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter)
+{
+ const char *data;
+
+ data = getenv(envvarname);
+ if (data != NULL)
+ return pkgconf_path_split(data, dirlist, filter);
+
+ if (fallback != NULL)
+ return pkgconf_path_split(fallback, dirlist, filter);
+
+ /* no fallback and no environment variable, thusly no nodes added */
+ return 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
+ *
+ * Checks whether a path has a matching prefix in a path list.
+ *
+ * :param char* path: The path to check against a path list.
+ * :param pkgconf_list_t* dirlist: The path list to check the path against.
+ * :return: true if the path list has a matching prefix, otherwise false
+ * :rtype: bool
+ */
+bool
+pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
+{
+ pkgconf_node_t *n = NULL;
+ char relocated[PKGCONF_ITEM_SIZE];
+ const char *cpath = path;
+
+ pkgconf_strlcpy(relocated, path, sizeof relocated);
+ if (pkgconf_path_relocate(relocated, sizeof relocated))
+ cpath = relocated;
+
+ PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
+ {
+ pkgconf_path_t *pnode = n->data;
+
+ if (!strcmp(pnode->path, cpath))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
+ *
+ * Copies a path list to another path list.
+ *
+ * :param pkgconf_list_t* dst: The path list to copy to.
+ * :param pkgconf_list_t* src: The path list to copy from.
+ * :return: nothing
+ */
+void
+pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
+{
+ pkgconf_node_t *n;
+
+ PKGCONF_FOREACH_LIST_ENTRY(src->head, n)
+ {
+ pkgconf_path_t *srcpath = n->data, *path;
+
+ path = calloc(1, sizeof(pkgconf_path_t));
+ if (path == NULL)
+ continue;
+
+ path->path = strdup(srcpath->path);
+
+#ifdef PKGCONF_CACHE_INODES
+ path->handle_path = srcpath->handle_path;
+ path->handle_device = srcpath->handle_device;
+#endif
+
+ pkgconf_node_insert_tail(&path->lnode, path, dst);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
+ *
+ * Copies a path list to another path list.
+ *
+ * :param pkgconf_list_t* dst: The path list to copy to.
+ * :param pkgconf_list_t* src: The path list to copy from.
+ * :return: nothing
+ */
+void
+pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src)
+{
+ pkgconf_node_t *n;
+
+ PKGCONF_FOREACH_LIST_ENTRY(src->head, n)
+ {
+ pkgconf_path_t *srcpath = n->data, *path;
+
+ path = calloc(1, sizeof(pkgconf_path_t));
+ if (path == NULL)
+ continue;
+
+ path->path = strdup(srcpath->path);
+
+#ifdef PKGCONF_CACHE_INODES
+ path->handle_path = srcpath->handle_path;
+ path->handle_device = srcpath->handle_device;
+#endif
+
+ pkgconf_node_insert(&path->lnode, path, dst);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist)
+ *
+ * Releases any path nodes attached to the given path list.
+ *
+ * :param pkgconf_list_t* dirlist: The path list to clean up.
+ * :return: nothing
+ */
+void
+pkgconf_path_free(pkgconf_list_t *dirlist)
+{
+ pkgconf_node_t *n, *tn;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n)
+ {
+ pkgconf_path_t *pnode = n->data;
+
+ free(pnode->path);
+ free(pnode);
+ }
+
+ pkgconf_list_zero(dirlist);
+}
+
+static char *
+normpath(const char *path)
+{
+ if (!path)
+ return NULL;
+
+ char *copy = strdup(path);
+ if (NULL == copy)
+ return NULL;
+ char *ptr = copy;
+
+ for (int ii = 0; copy[ii]; ii++)
+ {
+ *ptr++ = path[ii];
+ if ('/' == path[ii])
+ {
+ ii++;
+ while ('/' == path[ii])
+ ii++;
+ ii--;
+ }
+ }
+ *ptr = '\0';
+
+ return copy;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen)
+ *
+ * Relocates a path, possibly calling normpath() on it.
+ *
+ * :param char* buf: The path to relocate.
+ * :param size_t buflen: The buffer length the path is contained in.
+ * :return: true on success, false on error
+ * :rtype: bool
+ */
+bool
+pkgconf_path_relocate(char *buf, size_t buflen)
+{
+ char *tmpbuf;
+
+ if ((tmpbuf = normpath(buf)) != NULL)
+ {
+ size_t tmpbuflen = strlen(tmpbuf);
+ if (tmpbuflen > buflen)
+ {
+ free(tmpbuf);
+ return false;
+ }
+
+ pkgconf_strlcpy(buf, tmpbuf, buflen);
+ free(tmpbuf);
+ }
+
+ return true;
+}
+
+#ifdef _WIN32
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_build_from_registry(HKEY hKey, pkgconf_list_t *dir_list, bool filter)
+ *
+ * Adds paths to a directory list discovered from a given registry key.
+ *
+ * :param HKEY hKey: The registry key to enumerate.
+ * :param pkgconf_list_t* dir_list: The directory list to append enumerated paths to.
+ * :param bool filter: Whether duplicate paths should be filtered.
+ * :return: number of path nodes added to the list
+ * :rtype: size_t
+ */
+size_t
+pkgconf_path_build_from_registry(void *hKey, pkgconf_list_t *dir_list, bool filter)
+{
+ HKEY key;
+ int i = 0;
+ size_t added = 0;
+
+ char buf[16384]; /* per registry limits */
+ DWORD bufsize = sizeof buf;
+ if (RegOpenKeyEx(hKey, PKG_CONFIG_REG_KEY,
+ 0, KEY_READ, &key) != ERROR_SUCCESS)
+ return 0;
+
+ while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL)
+ == ERROR_SUCCESS)
+ {
+ char pathbuf[PKGCONF_ITEM_SIZE];
+ DWORD type;
+ DWORD pathbuflen = sizeof pathbuf;
+
+ if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen)
+ == ERROR_SUCCESS && type == REG_SZ)
+ {
+ pkgconf_path_add(pathbuf, dir_list, filter);
+ added++;
+ }
+
+ bufsize = sizeof buf;
+ }
+
+ RegCloseKey(key);
+ return added;
+}
+#endif