1From 618220bfb55c875d6a4d197cb24fe632ac93ec85 Mon Sep 17 00:00:00 2001
2From: Wolfgang Grandegger <wg@grandegger.com>
3Date: Mon, 20 Feb 2017 16:29:24 +0100
4Subject: [PATCH] Add option to make the rpath relative under a specified root
5 directory
6
7Running "patchelf" with the option "--make-rpath-relative ROOTDIR" will
8modify or delete the RPATHDIRs according the following rules
9similar to Martin's patches [1] making the Buildroot toolchaing/SDK
10relocatable.
11
12RPATHDIR starts with "$ORIGIN":
13    The original build-system already took care of setting a relative
14    RPATH, resolve it and test if it's valid (does exist)
15
16RPATHDIR starts with ROOTDIR:
17    The original build-system added some absolute RPATH (absolute on
18    the build machine). Test if it's valid (does exist).
19
20ROOTDIR/RPATHDIR exists:
21    The original build-system already took care of setting an absolute
22    RPATH (absolute in the final rootfs), resolve it and test if it's
23    valid (does exist).
24
25RPATHDIR points somewhere else:
26    (can be anywhere: build trees, staging tree, host location,
27    non-existing location, etc.). Just discard such a path.
28
29The option "--no-standard-libs" will discard RPATHDIRs ROOTDIR/lib and
30ROOTDIR/usr/lib. Like "--shrink-rpath", RPATHDIRs are also discarded
31if the directories do not contain a library referenced by the
32DT_NEEDED fields.
33If the option "--relative-to-file" is given, the rpath will start
34with "$ORIGIN" making it relative to the ELF file, otherwise an
35absolute path relative to ROOTDIR will be used.
36
37A pull request for a similar patch [2] for mainline inclusion is
38pending.
39
40[1] http://lists.busybox.net/pipermail/buildroot/2016-April/159422.html
41[2] https://github.com/NixOS/patchelf/pull/118
42
43Signed-off-by: Wolfgang Grandegger <wg@grandegger.com>
44---
45 src/patchelf.cc | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++------
46 1 file changed, 175 insertions(+), 21 deletions(-)
47
48diff --git a/src/patchelf.cc b/src/patchelf.cc
49index 1d9a772..35b4a33 100644
50--- a/src/patchelf.cc
51+++ b/src/patchelf.cc
52@@ -46,6 +46,10 @@ static bool debugMode = false;
53
54 static bool forceRPath = false;
55
56+static bool noStandardLibDirs = false;
57+
58+static bool relativeToFile = false;
59+
60 static string fileName;
61 static int pageSize = PAGESIZE;
62
63@@ -77,6 +81,49 @@ static unsigned int getPageSize(){
64     return pageSize;
65 }
66
67+static bool absolutePathExists(const string & path, string & canonicalPath)
68+{
69+    char *cpath = realpath(path.c_str(), NULL);
70+    if (cpath) {
71+        canonicalPath = cpath;
72+        free(cpath);
73+        return true;
74+    } else {
75+        return false;
76+    }
77+}
78+
79+static string makePathRelative(const string & path,
80+    const string & refPath)
81+{
82+    string relPath = "$ORIGIN";
83+    string p = path, refP = refPath;
84+    size_t pos;
85+
86+    /* Strip the common part of path and refPath */
87+    while (true) {
88+        pos = p.find_first_of('/', 1);
89+        if (refP.find_first_of('/', 1) != pos)
90+            break;
91+        if (p.substr(0, pos) != refP.substr(0, pos))
92+            break;
93+        if (pos == string::npos)
94+            break;
95+        p = p.substr(pos);
96+        refP = refP.substr(pos);
97+    }
98+    /* Check if both pathes are equal */
99+    if (p != refP) {
100+        pos = 0;
101+        while (pos != string::npos) {
102+            pos =refP.find_first_of('/', pos + 1);
103+            relPath.append("/..");
104+        }
105+        relPath.append(p);
106+    }
107+
108+    return relPath;
109+}
110
111 template<ElfFileParams>
112 class ElfFile
113@@ -183,9 +230,13 @@ public:
114
115     void setInterpreter(const string & newInterpreter);
116
117-    typedef enum { rpPrint, rpShrink, rpSet, rpRemove } RPathOp;
118+    typedef enum { rpPrint, rpShrink, rpMakeRelative, rpSet, rpRemove} RPathOp;
119+
120+    bool libFoundInRPath(const string & dirName,
121+                         const vector<string> neededLibs,
122+                         vector<bool> & neededLibFound);
123
124-    void modifyRPath(RPathOp op, string newRPath);
125+    void modifyRPath(RPathOp op, string rootDir, string newRPath);
126
127     void addNeeded(set<string> libs);
128
129@@ -1041,7 +1092,27 @@ static void concatToRPath(string & rpath, const string & path)
130
131
132 template<ElfFileParams>
133-void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op, string newRPath)
134+bool ElfFile<ElfFileParamNames>::libFoundInRPath(const string & dirName,
135+    const vector<string> neededLibs, vector<bool> & neededLibFound)
136+{
137+    /* For each library that we haven't found yet, see if it
138+       exists in this directory. */
139+    bool libFound = false;
140+    for (unsigned int j = 0; j < neededLibs.size(); ++j)
141+        if (!neededLibFound[j]) {
142+            string libName = dirName + "/" + neededLibs[j];
143+            struct stat st;
144+            if (stat(libName.c_str(), &st) == 0) {
145+                neededLibFound[j] = true;
146+                libFound = true;
147+            }
148+        }
149+    return libFound;
150+}
151+
152+
153+template<ElfFileParams>
154+void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op, string rootDir, string newRPath)
155 {
156     Elf_Shdr & shdrDynamic = findSection(".dynamic");
157
158@@ -1096,6 +1167,11 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op, string newRPath)
159         return;
160     }
161
162+    if (op == rpMakeRelative && !rpath) {
163+        debug("no RPATH to make relative\n");
164+        return;
165+    }
166+
167     if (op == rpShrink && !rpath) {
168         debug("no RPATH to shrink\n");
169         return;
170@@ -1120,26 +1196,80 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op, string newRPath)
171                 continue;
172             }
173
174-            /* For each library that we haven't found yet, see if it
175-               exists in this directory. */
176-            bool libFound = false;
177-            for (unsigned int j = 0; j < neededLibs.size(); ++j)
178-                if (!neededLibFound[j]) {
179-                    string libName = dirName + "/" + neededLibs[j];
180-                    struct stat st;
181-                    if (stat(libName.c_str(), &st) == 0) {
182-                        neededLibFound[j] = true;
183-                        libFound = true;
184-                    }
185-                }
186-
187-            if (!libFound)
188+            if (!libFoundInRPath(dirName, neededLibs, neededLibFound))
189                 debug("removing directory `%s' from RPATH\n", dirName.c_str());
190             else
191                 concatToRPath(newRPath, dirName);
192         }
193     }
194
195+    /* Make the the RPATH relative to the specified path */
196+    if (op == rpMakeRelative) {
197+        vector<bool> neededLibFound(neededLibs.size(), false);
198+        string fileDir = fileName.substr(0, fileName.find_last_of("/"));
199+
200+        newRPath = "";
201+
202+        vector<string> rpathDirs = splitColonDelimitedString(rpath);
203+        for (vector<string>::iterator it = rpathDirs.begin(); it != rpathDirs.end(); ++it) {
204+            const string & dirName = *it;
205+
206+            string canonicalPath;
207+
208+            /* Figure out if we should keep or discard the path. There are several
209+               cases to be handled:
210+               "dirName" starts with "$ORIGIN":
211+                   The original build-system already took care of setting a relative
212+                   RPATH. Resolve it and test if it's valid (does exist).
213+               "dirName" start with "rootDir":
214+                   The original build-system added some absolute RPATH (absolute on
215+                   the build machine). Test if it's valid (does exist).
216+               "rootDir"/"dirName" exists:
217+                    The original build-system already took care of setting an absolute
218+                    RPATH (absolute in the final rootfs). Resolve it and test if it's
219+                    valid (does exist).
220+               "dirName" points somewhere else:
221+                    (can be anywhere: build trees, staging tree, host location,
222+                    non-existing location, etc.). Just discard such a path. */
223+            if (!dirName.compare(0, 7, "$ORIGIN")) {
224+                string path = fileDir + dirName.substr(7);
225+                if (!absolutePathExists(path, canonicalPath)) {
226+                    debug("removing directory '%s' from RPATH because '%s' doesn't exist\n",
227+                          dirName.c_str(), path.c_str());
228+                    continue;
229+                }
230+            } else if (!dirName.compare(0, rootDir.length(), rootDir)) {
231+                if (!absolutePathExists(dirName, canonicalPath)) {
232+                    debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
233+                    continue;
234+                }
235+            } else {
236+                string path = rootDir + dirName;
237+                if (!absolutePathExists(path, canonicalPath)) {
238+                    debug("removing directory '%s' from RPATH because it's not in rootdir\n",
239+                          dirName.c_str());
240+                    continue;
241+                }
242+            }
243+
244+            if (noStandardLibDirs) {
245+                if (!canonicalPath.compare(rootDir + "/lib") ||
246+                    !canonicalPath.compare(rootDir + "/usr/lib")) {
247+                    debug("removing directory '%s' from RPATH because it's a standard library directory\n",
248+                         dirName.c_str());
249+                    continue;
250+                }
251+            }
252+
253+            /* Finally make "canonicalPath" relative to "filedir" in "rootDir" */
254+            if (relativeToFile)
255+                concatToRPath(newRPath, makePathRelative(canonicalPath, fileDir));
256+            else
257+                concatToRPath(newRPath, canonicalPath.substr(rootDir.length()));
258+            debug("keeping relative path of %s\n", canonicalPath.c_str());
259+        }
260+    }
261+
262     if (op == rpRemove) {
263         if (!rpath) {
264             debug("no RPATH to delete\n");
265@@ -1413,7 +1543,9 @@ static bool shrinkRPath = false;
266 static bool removeRPath = false;
267 static bool setRPath = false;
268 static bool printRPath = false;
269+static bool makeRPathRelative = false;
270 static string newRPath;
271+static string rootDir;
272 static set<string> neededLibsToRemove;
273 static map<string, string> neededLibsToReplace;
274 static set<string> neededLibsToAdd;
275@@ -1438,14 +1570,16 @@ static void patchElf2(ElfFile & elfFile)
276         elfFile.setInterpreter(newInterpreter);
277
278     if (printRPath)
279-        elfFile.modifyRPath(elfFile.rpPrint, "");
280+        elfFile.modifyRPath(elfFile.rpPrint, "", "");
281
282     if (shrinkRPath)
283-        elfFile.modifyRPath(elfFile.rpShrink, "");
284+        elfFile.modifyRPath(elfFile.rpShrink, "", "");
285     else if (removeRPath)
286-        elfFile.modifyRPath(elfFile.rpRemove, "");
287+        elfFile.modifyRPath(elfFile.rpRemove, "", "");
288     else if (setRPath)
289-        elfFile.modifyRPath(elfFile.rpSet, newRPath);
290+        elfFile.modifyRPath(elfFile.rpSet, "", newRPath);
291+    else if (makeRPathRelative)
292+        elfFile.modifyRPath(elfFile.rpMakeRelative, rootDir, "");
293
294     if (printNeeded) elfFile.printNeededLibs();
295
296@@ -1508,6 +1642,9 @@ void showHelp(const string & progName)
297   [--set-rpath RPATH]\n\
298   [--remove-rpath]\n\
299   [--shrink-rpath]\n\
300+  [--make-rpath-relative ROOTDIR]\n\
301+  [--no-standard-lib-dirs]\n\
302+  [--relative-to-file]\n\
303   [--print-rpath]\n\
304   [--force-rpath]\n\
305   [--add-needed LIBRARY]\n\
306@@ -1564,6 +1701,17 @@ int main(int argc, char * * argv)
307             setRPath = true;
308             newRPath = argv[i];
309         }
310+        else if (arg == "--make-rpath-relative") {
311+            if (++i == argc) error("missing argument to --make-rpath-relative");
312+            makeRPathRelative = true;
313+            rootDir = argv[i];
314+        }
315+        else if (arg == "--no-standard-lib-dirs") {
316+            noStandardLibDirs = true;
317+        }
318+        else if (arg == "--relative-to-file") {
319+            relativeToFile = true;
320+        }
321         else if (arg == "--print-rpath") {
322             printRPath = true;
323         }
324--
3251.9.1
326
327