1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7CHRPATH_BIN ?= "chrpath"
8PREPROCESS_RELOCATE_DIRS ?= ""
9
10def process_file_linux(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = False):
11    import subprocess, oe.qa
12
13    with oe.qa.ELFFile(fpath) as elf:
14        try:
15            elf.open()
16        except oe.qa.NotELFFileError:
17            return
18
19    try:
20        out = subprocess.check_output([cmd, "-l", fpath], universal_newlines=True)
21    except subprocess.CalledProcessError:
22        return
23
24    # Handle RUNPATH as well as RPATH
25    out = out.replace("RUNPATH=","RPATH=")
26    # Throw away everything other than the rpath list
27    curr_rpath = out.partition("RPATH=")[2]
28    #bb.note("Current rpath for %s is %s" % (fpath, curr_rpath.strip()))
29    rpaths = curr_rpath.strip().split(":")
30    new_rpaths = []
31    modified = False
32    for rpath in rpaths:
33        # If rpath is already dynamic copy it to new_rpath and continue
34        if rpath.find("$ORIGIN") != -1:
35            new_rpaths.append(rpath)
36            continue
37        rpath =  os.path.normpath(rpath)
38        if baseprefix not in rpath and tmpdir not in rpath:
39            # Skip standard search paths
40            if rpath in ['/lib', '/usr/lib', '/lib64/', '/usr/lib64']:
41                bb.warn("Skipping RPATH %s as is a standard search path for %s" % (rpath, fpath))
42                modified = True
43                continue
44            new_rpaths.append(rpath)
45            continue
46        new_rpaths.append("$ORIGIN/" + os.path.relpath(rpath, os.path.dirname(fpath.replace(rootdir, "/"))))
47        modified = True
48
49    # if we have modified some rpaths call chrpath to update the binary
50    if modified:
51        if break_hardlinks:
52            bb.utils.break_hardlinks(fpath)
53
54        args = ":".join(new_rpaths)
55        #bb.note("Setting rpath for %s to %s" %(fpath, args))
56        try:
57            subprocess.check_output([cmd, "-r", args, fpath],
58            stderr=subprocess.PIPE, universal_newlines=True)
59        except subprocess.CalledProcessError as e:
60            bb.fatal("chrpath command failed with exit code %d:\n%s\n%s" % (e.returncode, e.stdout, e.stderr))
61
62def process_file_darwin(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = False):
63    import subprocess as sub
64
65    p = sub.Popen([d.expand("${HOST_PREFIX}otool"), '-L', fpath],stdout=sub.PIPE,stderr=sub.PIPE, text=True)
66    out, err = p.communicate()
67    # If returned successfully, process stdout for results
68    if p.returncode != 0:
69        return
70    for l in out.split("\n"):
71        if "(compatibility" not in l:
72            continue
73        rpath = l.partition("(compatibility")[0].strip()
74        if baseprefix not in rpath:
75            continue
76
77        if break_hardlinks:
78            bb.utils.break_hardlinks(fpath)
79
80        newpath = "@loader_path/" + os.path.relpath(rpath, os.path.dirname(fpath.replace(rootdir, "/")))
81        p = sub.Popen([d.expand("${HOST_PREFIX}install_name_tool"), '-change', rpath, newpath, fpath],stdout=sub.PIPE,stderr=sub.PIPE)
82        out, err = p.communicate()
83
84def process_dir(rootdir, directory, d, break_hardlinks = False):
85    bb.debug(2, "Checking %s for binaries to process" % directory)
86    if not os.path.exists(directory):
87        return
88
89    import stat
90
91    rootdir = os.path.normpath(rootdir)
92    cmd = d.expand('${CHRPATH_BIN}')
93    tmpdir = os.path.normpath(d.getVar('TMPDIR', False))
94    baseprefix = os.path.normpath(d.expand('${base_prefix}'))
95    hostos = d.getVar("HOST_OS")
96
97    if "linux" in hostos:
98        process_file = process_file_linux
99    elif "darwin" in hostos:
100        process_file = process_file_darwin
101    else:
102        # Relocations not supported
103        return
104
105    dirs = os.listdir(directory)
106    for file in dirs:
107        fpath = directory + "/" + file
108        fpath = os.path.normpath(fpath)
109        if os.path.islink(fpath):
110            # Skip symlinks
111            continue
112
113        if os.path.isdir(fpath):
114            process_dir(rootdir, fpath, d, break_hardlinks = break_hardlinks)
115        else:
116            #bb.note("Testing %s for relocatability" % fpath)
117
118            # We need read and write permissions for chrpath, if we don't have
119            # them then set them temporarily. Take a copy of the files
120            # permissions so that we can restore them afterwards.
121            perms = os.stat(fpath)[stat.ST_MODE]
122            if os.access(fpath, os.W_OK|os.R_OK):
123                perms = None
124            else:
125                # Temporarily make the file writeable so we can chrpath it
126                os.chmod(fpath, perms|stat.S_IRWXU)
127
128            process_file(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = break_hardlinks)
129
130            if perms:
131                os.chmod(fpath, perms)
132
133def rpath_replace (path, d):
134    bindirs = d.expand("${bindir} ${sbindir} ${base_sbindir} ${base_bindir} ${libdir} ${base_libdir} ${libexecdir} ${PREPROCESS_RELOCATE_DIRS}").split()
135
136    for bindir in bindirs:
137        #bb.note ("Processing directory " + bindir)
138        directory = path + "/" + bindir
139        process_dir (path, directory, d)
140
141