From 6e8915572db65cf63b7a82f9b24af6f9cad92ba7 Mon Sep 17 00:00:00 2001
From: Wolfgang Grandegger <wg@grandegger.com>
Date: Mon, 20 Feb 2017 16:29:24 +0100
Subject: [PATCH] Add option to make the rpath relative under a specified root
 directory

Running "patchelf" with the option "--make-rpath-relative ROOTDIR" will
modify or delete the RPATHDIRs according the following rules
similar to Martin's patches [1] making the Buildroot toolchaing/SDK
relocatable.

RPATHDIR starts with "$ORIGIN":
    The original build-system already took care of setting a relative
    RPATH, resolve it and test if it's valid (does exist)

RPATHDIR starts with ROOTDIR:
    The original build-system added some absolute RPATH (absolute on
    the build machine). Test if it's valid (does exist).

ROOTDIR/RPATHDIR exists:
    The original build-system already took care of setting an absolute
    RPATH (absolute in the final rootfs), resolve it and test if it's
    valid (does exist).

RPATHDIR points somewhere else:
    (can be anywhere: build trees, staging tree, host location,
    non-existing location, etc.). Just discard such a path.

The option "--no-standard-libs" will discard RPATHDIRs ROOTDIR/lib and
ROOTDIR/usr/lib. Like "--shrink-rpath", RPATHDIRs are also discarded
if the directories do not contain a library referenced by the
DT_NEEDED fields.
If the option "--relative-to-file" is given, the rpath will start
with "$ORIGIN" making it relative to the ELF file, otherwise an
absolute path relative to ROOTDIR will be used.

A pull request for a similar patch [2] for mainline inclusion is
pending.

[1] http://lists.busybox.net/pipermail/buildroot/2016-April/159422.html
[2] https://github.com/NixOS/patchelf/pull/118

Signed-off-by: Wolfgang Grandegger <wg@grandegger.com>
[Fabrice: update for 0.13]
Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
[Dario: make the patch to be applied with fuzz factor 0]
Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
---
 src/patchelf.cc | 199 +++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 171 insertions(+), 28 deletions(-)

diff --git a/src/patchelf.cc b/src/patchelf.cc
index fd1e7b7b61c3..f3b7ba8867bf 100644
--- a/src/patchelf.cc
+++ b/src/patchelf.cc
@@ -45,6 +45,10 @@ static bool debugMode = false;
 
 static bool forceRPath = false;
 
+static bool noStandardLibDirs = false;
+
+static bool relativeToFile = false;
+
 static std::vector<std::string> fileNames;
 static std::string outputFileName;
 static bool alwaysWrite = false;
@@ -81,6 +85,49 @@ static bool hasAllowedPrefix(const std::string & s, const std::vector<std::strin
     return std::any_of(allowedPrefixes.begin(), allowedPrefixes.end(), [&](const std::string & i) { return !s.compare(0, i.size(), i); });
 }
 
+static bool absolutePathExists(const std::string & path, std::string & canonicalPath)
+{
+    char *cpath = realpath(path.c_str(), NULL);
+    if (cpath) {
+        canonicalPath = cpath;
+        free(cpath);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static std::string makePathRelative(const std::string & path,
+    const std::string & refPath)
+{
+    std::string relPath = "$ORIGIN";
+    std::string p = path, refP = refPath;
+    size_t pos;
+
+    /* Strip the common part of path and refPath */
+    while (true) {
+        pos = p.find_first_of('/', 1);
+        if (refP.find_first_of('/', 1) != pos)
+            break;
+        if (p.substr(0, pos) != refP.substr(0, pos))
+            break;
+        if (pos == std::string::npos)
+            break;
+        p = p.substr(pos);
+        refP = refP.substr(pos);
+    }
+    /* Check if both pathes are equal */
+    if (p != refP) {
+        pos = 0;
+        while (pos != std::string::npos) {
+            pos =refP.find_first_of('/', pos + 1);
+            relPath.append("/..");
+        }
+        relPath.append(p);
+    }
+
+    return relPath;
+}
 
 template<ElfFileParams>
 class ElfFile
@@ -197,9 +244,13 @@ public:
 
     void setInterpreter(const std::string & newInterpreter);
 
-    typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove } RPathOp;
+    typedef enum { rpPrint, rpShrink, rpMakeRelative, rpSet, rpAdd, rpRemove} RPathOp;
 
-    void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath);
+    bool libFoundInRPath(const std::string & dirName,
+                         const std::vector<std::string> neededLibs,
+                         std::vector<bool> & neededLibFound);
+
+    void modifyRPath(RPathOp op, std::string rootDir, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath, const std::string & fileName);
 
     void addNeeded(const std::set<std::string> & libs);
 
@@ -1267,8 +1318,28 @@ static void concatToRPath(std::string & rpath, const std::string & path)
 
 
 template<ElfFileParams>
-void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
-    const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath)
+bool ElfFile<ElfFileParamNames>::libFoundInRPath(const std::string & dirName,
+    const std::vector<std::string> neededLibs, std::vector<bool> & neededLibFound)
+{
+    /* For each library that we haven't found yet, see if it
+       exists in this directory. */
+    bool libFound = false;
+    for (unsigned int j = 0; j < neededLibs.size(); ++j)
+        if (!neededLibFound[j]) {
+            std::string libName = dirName + "/" + neededLibs[j];
+            struct stat st;
+            if (stat(libName.c_str(), &st) == 0) {
+                neededLibFound[j] = true;
+                libFound = true;
+            }
+        }
+    return libFound;
+}
+
+
+template<ElfFileParams>
+void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op, std::string rootDir,
+    const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath, const std::string & fileName)
 {
     auto shdrDynamic = findSection(".dynamic");
 
@@ -1314,6 +1385,11 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
         return;
     }
 
+    if (op == rpMakeRelative && !rpath) {
+        debug("no RPATH to make relative\n");
+        return;
+    }
+
     if (op == rpShrink && !rpath) {
         debug("no RPATH to shrink\n");
         return;
@@ -1343,31 +1419,80 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
                 continue;
             }
 
-            /* For each library that we haven't found yet, see if it
-               exists in this directory. */
-            bool libFound = false;
-            for (unsigned int j = 0; j < neededLibs.size(); ++j)
-                if (!neededLibFound[j]) {
-                    std::string libName = dirName + "/" + neededLibs[j];
-                    try {
-                        Elf32_Half library_e_machine = getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine;
-                        if (rdi(library_e_machine) == rdi(hdr->e_machine)) {
-                            neededLibFound[j] = true;
-                            libFound = true;
-                        } else
-                            debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
-                    } catch (SysError & e) {
-                        if (e.errNo != ENOENT) throw;
-                    }
-                }
-
-            if (!libFound)
+            if (!libFoundInRPath(dirName, neededLibs, neededLibFound))
                 debug("removing directory '%s' from RPATH\n", dirName.c_str());
             else
                 concatToRPath(newRPath, dirName);
         }
     }
 
+    /* Make the the RPATH relative to the specified path */
+    if (op == rpMakeRelative) {
+        std::vector<bool> neededLibFound(neededLibs.size(), false);
+        std::string fileDir = fileName.substr(0, fileName.find_last_of("/"));
+
+        newRPath = "";
+
+        std::vector<std::string> rpathDirs = splitColonDelimitedString(rpath);
+        for (std::vector<std::string>::iterator it = rpathDirs.begin(); it != rpathDirs.end(); ++it) {
+            const std::string & dirName = *it;
+
+            std::string canonicalPath;
+
+            /* Figure out if we should keep or discard the path. There are several
+               cases to be handled:
+               "dirName" starts with "$ORIGIN":
+                   The original build-system already took care of setting a relative
+                   RPATH. Resolve it and test if it's valid (does exist).
+               "dirName" start with "rootDir":
+                   The original build-system added some absolute RPATH (absolute on
+                   the build machine). Test if it's valid (does exist).
+               "rootDir"/"dirName" exists:
+                    The original build-system already took care of setting an absolute
+                    RPATH (absolute in the final rootfs). Resolve it and test if it's
+                    valid (does exist).
+               "dirName" points somewhere else:
+                    (can be anywhere: build trees, staging tree, host location,
+                    non-existing location, etc.). Just discard such a path. */
+            if (!dirName.compare(0, 7, "$ORIGIN")) {
+                std::string path = fileDir + dirName.substr(7);
+                if (!absolutePathExists(path, canonicalPath)) {
+                    debug("removing directory '%s' from RPATH because '%s' doesn't exist\n",
+                          dirName.c_str(), path.c_str());
+                    continue;
+                }
+            } else if (!dirName.compare(0, rootDir.length(), rootDir)) {
+                if (!absolutePathExists(dirName, canonicalPath)) {
+                    debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
+                    continue;
+                }
+            } else {
+                std::string path = rootDir + dirName;
+                if (!absolutePathExists(path, canonicalPath)) {
+                    debug("removing directory '%s' from RPATH because it's not in rootdir\n",
+                          dirName.c_str());
+                    continue;
+                }
+            }
+
+            if (noStandardLibDirs) {
+                if (!canonicalPath.compare(rootDir + "/lib") ||
+                    !canonicalPath.compare(rootDir + "/usr/lib")) {
+                    debug("removing directory '%s' from RPATH because it's a standard library directory\n",
+                         dirName.c_str());
+                    continue;
+                }
+            }
+
+            /* Finally make "canonicalPath" relative to "filedir" in "rootDir" */
+            if (relativeToFile)
+                concatToRPath(newRPath, makePathRelative(canonicalPath, fileDir));
+            else
+                concatToRPath(newRPath, canonicalPath.substr(rootDir.length()));
+            debug("keeping relative path of %s\n", canonicalPath.c_str());
+        }
+    }
+
     if (op == rpRemove) {
         if (!rpath) {
             debug("no RPATH to delete\n");
@@ -1736,7 +1861,9 @@ static bool removeRPath = false;
 static bool setRPath = false;
 static bool addRPath = false;
 static bool printRPath = false;
+static bool makeRPathRelative = false;
 static std::string newRPath;
+static std::string rootDir;
 static std::set<std::string> neededLibsToRemove;
 static std::map<std::string, std::string> neededLibsToReplace;
 static std::set<std::string> neededLibsToAdd;
@@ -1760,16 +1887,18 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
         elfFile.setInterpreter(newInterpreter);
 
     if (printRPath)
-        elfFile.modifyRPath(elfFile.rpPrint, {}, "");
+        elfFile.modifyRPath(elfFile.rpPrint, "", {}, "", fileName);
 
     if (shrinkRPath)
-        elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
+        elfFile.modifyRPath(elfFile.rpShrink, "", allowedRpathPrefixes, "", fileName);
     else if (removeRPath)
-        elfFile.modifyRPath(elfFile.rpRemove, {}, "");
+        elfFile.modifyRPath(elfFile.rpRemove, "", {}, "", fileName);
     else if (setRPath)
-        elfFile.modifyRPath(elfFile.rpSet, {}, newRPath);
+        elfFile.modifyRPath(elfFile.rpSet, "", {}, newRPath, fileName);
     else if (addRPath)
-        elfFile.modifyRPath(elfFile.rpAdd, {}, newRPath);
+        elfFile.modifyRPath(elfFile.rpAdd, "", {}, newRPath, fileName);
+    else if (makeRPathRelative)
+        elfFile.modifyRPath(elfFile.rpMakeRelative, rootDir, {}, "", fileName);
 
     if (printNeeded) elfFile.printNeededLibs();
 
@@ -1821,6 +1950,9 @@ void showHelp(const std::string & progName)
   [--remove-rpath]\n\
   [--shrink-rpath]\n\
   [--allowed-rpath-prefixes PREFIXES]\t\tWith '--shrink-rpath', reject rpath entries not starting with the allowed prefix\n\
+  [--make-rpath-relative ROOTDIR]\n\
+  [--no-standard-lib-dirs]\n\
+  [--relative-to-file]\n\
   [--print-rpath]\n\
   [--force-rpath]\n\
   [--add-needed LIBRARY]\n\
@@ -1889,6 +2021,17 @@ int mainWrapped(int argc, char * * argv)
             addRPath = true;
             newRPath = argv[i];
         }
+        else if (arg == "--make-rpath-relative") {
+            if (++i == argc) error("missing argument to --make-rpath-relative");
+            makeRPathRelative = true;
+            rootDir = argv[i];
+        }
+        else if (arg == "--no-standard-lib-dirs") {
+            noStandardLibDirs = true;
+        }
+        else if (arg == "--relative-to-file") {
+            relativeToFile = true;
+        }
         else if (arg == "--print-rpath") {
             printRPath = true;
         }
-- 
2.43.0

