From e6c1d4b8bd9610444d9fb2d145610f46c881c210 Mon Sep 17 00:00:00 2001 From: David Seifert Date: Wed, 9 Oct 2024 19:06:05 +0200 Subject: [PATCH] ${pcfiledir} should point to parent dir of actual file, not symlink * In situations where we have a real /foo.pc that uses ${pcfiledir} and a symlink /foo.pc that points to /foo.pc, then ${pcfiledir} should resolve to and not . --- libpkgconf/pkg.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/libpkgconf/pkg.c b/libpkgconf/pkg.c index feb8d9a..c05be2c 100644 --- a/libpkgconf/pkg.c +++ b/libpkgconf/pkg.c @@ -17,6 +17,15 @@ #include #include +#ifndef _WIN32 +#include // open +#include // basename/dirname +#include // lstat, S_ISLNK +#include // close, readlinkat + +#include +#endif + /* * !doc * @@ -63,6 +72,65 @@ pkg_get_parent_dir(pkgconf_pkg_t *pkg) char buf[PKGCONF_ITEM_SIZE], *pathbuf; pkgconf_strlcpy(buf, pkg->filename, sizeof buf); +#ifndef _WIN32 + /* + * We want to resolve symlinks, since ${pcfiledir} should point to the + * parent of the file symlinked to. + */ + struct stat path_stat; + while (!lstat(buf, &path_stat) && S_ISLNK(path_stat.st_mode)) + { + /* + * Have to split the path into the dir + file components, + * in order to extract the directory file descriptor. + * + * The nomenclature here uses the + * + * ln + * + * model. + */ + char basenamebuf[PKGCONF_ITEM_SIZE]; + pkgconf_strlcpy(basenamebuf, buf, sizeof(basenamebuf)); + const char* targetfilename = basename(basenamebuf); + + char dirnamebuf[PKGCONF_ITEM_SIZE]; + pkgconf_strlcpy(dirnamebuf, buf, sizeof(dirnamebuf)); + const char* targetdir = dirname(dirnamebuf); + + const int dirfd = open(targetdir, O_DIRECTORY); + if (dirfd == -1) + break; + + char sourcebuf[PKGCONF_ITEM_SIZE]; + ssize_t len = readlinkat(dirfd, targetfilename, sourcebuf, sizeof(sourcebuf) - 1); + close(dirfd); + + if (len == -1) + break; + sourcebuf[len] = '\0'; + + memset(buf, '\0', sizeof buf); + /* + * The logic here can be a bit tricky, so here's a table: + * + * | | result + * ----------------------------------------------------------------------- + * /bar (absolute) | foo/link (relative) | /bar (absolute) + * ../bar (relative) | foo/link (relative) | foo/../bar (relative) + * /bar (absolute) | /foo/link (absolute) | /bar (absolute) + * ../bar (relative) | /foo/link (absolute) | /foo/../bar (relative) + */ + if ((sourcebuf[0] != '/') /* absolute path in wins */ + && (strcmp(targetdir, "."))) /* do not prepend "." */ + { + pkgconf_strlcat(buf, targetdir, sizeof buf); + pkgconf_strlcat(buf, "/", sizeof buf); + } + pkgconf_strlcat(buf, sourcebuf, sizeof buf); + } +#endif + pathbuf = strrchr(buf, PKG_DIR_SEP_S); if (pathbuf == NULL) pathbuf = strrchr(buf, '/');