${pcfiledir} should point to parent dir of actual file, not symlink

* In situations where we have a real <path1>/foo.pc that uses ${pcfiledir} and
  a symlink <path2>/foo.pc that points to <path1>/foo.pc, then ${pcfiledir}
  should resolve to <path1> and not <path2>.
master
David Seifert 2024-10-09 19:06:05 +02:00
parent 444846dd2d
commit e6c1d4b8bd
No known key found for this signature in database
GPG Key ID: CE36E117202E3842
1 changed files with 68 additions and 0 deletions

View File

@ -17,6 +17,15 @@
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
#ifndef _WIN32
#include <fcntl.h> // open
#include <libgen.h> // basename/dirname
#include <sys/stat.h> // lstat, S_ISLNK
#include <unistd.h> // close, readlinkat
#include <string.h>
#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 <source> <target>
*
* 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:
*
* <source> | <target> | 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 <source> 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, '/');