xref: /openbmc/openbmc/poky/meta/lib/oe/package.py (revision 595f6308)
1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5import stat
6import mmap
7import subprocess
8
9def runstrip(arg):
10    # Function to strip a single file, called from split_and_strip_files below
11    # A working 'file' (one which works on the target architecture)
12    #
13    # The elftype is a bit pattern (explained in is_elf below) to tell
14    # us what type of file we're processing...
15    # 4 - executable
16    # 8 - shared library
17    # 16 - kernel module
18
19    if len(arg) == 3:
20        (file, elftype, strip) = arg
21        extra_strip_sections = ''
22    else:
23        (file, elftype, strip, extra_strip_sections) = arg
24
25    newmode = None
26    if not os.access(file, os.W_OK) or os.access(file, os.R_OK):
27        origmode = os.stat(file)[stat.ST_MODE]
28        newmode = origmode | stat.S_IWRITE | stat.S_IREAD
29        os.chmod(file, newmode)
30
31    stripcmd = [strip]
32    skip_strip = False
33    # kernel module
34    if elftype & 16:
35        if is_kernel_module_signed(file):
36            bb.debug(1, "Skip strip on signed module %s" % file)
37            skip_strip = True
38        else:
39            stripcmd.extend(["--strip-debug", "--remove-section=.comment",
40                "--remove-section=.note", "--preserve-dates"])
41    # .so and shared library
42    elif ".so" in file and elftype & 8:
43        stripcmd.extend(["--remove-section=.comment", "--remove-section=.note", "--strip-unneeded"])
44    # shared or executable:
45    elif elftype & 8 or elftype & 4:
46        stripcmd.extend(["--remove-section=.comment", "--remove-section=.note"])
47        if extra_strip_sections != '':
48            for section in extra_strip_sections.split():
49                stripcmd.extend(["--remove-section=" + section])
50
51    stripcmd.append(file)
52    bb.debug(1, "runstrip: %s" % stripcmd)
53
54    if not skip_strip:
55        output = subprocess.check_output(stripcmd, stderr=subprocess.STDOUT)
56
57    if newmode:
58        os.chmod(file, origmode)
59
60# Detect .ko module by searching for "vermagic=" string
61def is_kernel_module(path):
62    with open(path) as f:
63        return mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ).find(b"vermagic=") >= 0
64
65# Detect if .ko module is signed
66def is_kernel_module_signed(path):
67    with open(path, "rb") as f:
68        f.seek(-28, 2)
69        module_tail = f.read()
70        return "Module signature appended" in "".join(chr(c) for c in bytearray(module_tail))
71
72# Return type (bits):
73# 0 - not elf
74# 1 - ELF
75# 2 - stripped
76# 4 - executable
77# 8 - shared library
78# 16 - kernel module
79def is_elf(path):
80    exec_type = 0
81    result = subprocess.check_output(["file", "-b", path], stderr=subprocess.STDOUT).decode("utf-8")
82
83    if "ELF" in result:
84        exec_type |= 1
85        if "not stripped" not in result:
86            exec_type |= 2
87        if "executable" in result:
88            exec_type |= 4
89        if "shared" in result:
90            exec_type |= 8
91        if "relocatable" in result:
92            if path.endswith(".ko") and path.find("/lib/modules/") != -1 and is_kernel_module(path):
93                exec_type |= 16
94    return (path, exec_type)
95
96def is_static_lib(path):
97    if path.endswith('.a') and not os.path.islink(path):
98        with open(path, 'rb') as fh:
99            # The magic must include the first slash to avoid
100            # matching golang static libraries
101            magic = b'!<arch>\x0a/'
102            start = fh.read(len(magic))
103            return start == magic
104    return False
105
106def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, d, qa_already_stripped=False):
107    """
108    Strip executable code (like executables, shared libraries) _in_place_
109    - Based on sysroot_strip in staging.bbclass
110    :param dstdir: directory in which to strip files
111    :param strip_cmd: Strip command (usually ${STRIP})
112    :param libdir: ${libdir} - strip .so files in this directory
113    :param base_libdir: ${base_libdir} - strip .so files in this directory
114    :param qa_already_stripped: Set to True if already-stripped' in ${INSANE_SKIP}
115    This is for proper logging and messages only.
116    """
117    import stat, errno, oe.path, oe.utils
118
119    elffiles = {}
120    inodes = {}
121    libdir = os.path.abspath(dstdir + os.sep + libdir)
122    base_libdir = os.path.abspath(dstdir + os.sep + base_libdir)
123    exec_mask = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
124    #
125    # First lets figure out all of the files we may have to process
126    #
127    checkelf = []
128    inodecache = {}
129    for root, dirs, files in os.walk(dstdir):
130        for f in files:
131            file = os.path.join(root, f)
132
133            try:
134                ltarget = oe.path.realpath(file, dstdir, False)
135                s = os.lstat(ltarget)
136            except OSError as e:
137                (err, strerror) = e.args
138                if err != errno.ENOENT:
139                    raise
140                # Skip broken symlinks
141                continue
142            if not s:
143                continue
144            # Check its an excutable
145            if s[stat.ST_MODE] & exec_mask \
146                    or ((file.startswith(libdir) or file.startswith(base_libdir)) and ".so" in f) \
147                    or file.endswith('.ko'):
148                # If it's a symlink, and points to an ELF file, we capture the readlink target
149                if os.path.islink(file):
150                    continue
151
152                # It's a file (or hardlink), not a link
153                # ...but is it ELF, and is it already stripped?
154                checkelf.append(file)
155                inodecache[file] = s.st_ino
156    results = oe.utils.multiprocess_launch(is_elf, checkelf, d)
157    for (file, elf_file) in results:
158                #elf_file = is_elf(file)
159                if elf_file & 1:
160                    if elf_file & 2:
161                        if qa_already_stripped:
162                            bb.note("Skipping file %s from %s for already-stripped QA test" % (file[len(dstdir):], pn))
163                        else:
164                            bb.warn("File '%s' from %s was already stripped, this will prevent future debugging!" % (file[len(dstdir):], pn))
165                        continue
166
167                    if inodecache[file] in inodes:
168                        os.unlink(file)
169                        os.link(inodes[inodecache[file]], file)
170                    else:
171                        # break hardlinks so that we do not strip the original.
172                        inodes[inodecache[file]] = file
173                        bb.utils.break_hardlinks(file)
174                        elffiles[file] = elf_file
175
176    #
177    # Now strip them (in parallel)
178    #
179    sfiles = []
180    for file in elffiles:
181        elf_file = int(elffiles[file])
182        sfiles.append((file, elf_file, strip_cmd))
183
184    oe.utils.multiprocess_launch(runstrip, sfiles, d)
185
186
187def file_translate(file):
188    ft = file.replace("@", "@at@")
189    ft = ft.replace(" ", "@space@")
190    ft = ft.replace("\t", "@tab@")
191    ft = ft.replace("[", "@openbrace@")
192    ft = ft.replace("]", "@closebrace@")
193    ft = ft.replace("_", "@underscore@")
194    return ft
195
196def filedeprunner(arg):
197    import re, subprocess, shlex
198
199    (pkg, pkgfiles, rpmdeps, pkgdest) = arg
200    provides = {}
201    requires = {}
202
203    file_re = re.compile(r'\s+\d+\s(.*)')
204    dep_re = re.compile(r'\s+(\S)\s+(.*)')
205    r = re.compile(r'[<>=]+\s+\S*')
206
207    def process_deps(pipe, pkg, pkgdest, provides, requires):
208        file = None
209        for line in pipe.split("\n"):
210
211            m = file_re.match(line)
212            if m:
213                file = m.group(1)
214                file = file.replace(pkgdest + "/" + pkg, "")
215                file = file_translate(file)
216                continue
217
218            m = dep_re.match(line)
219            if not m or not file:
220                continue
221
222            type, dep = m.groups()
223
224            if type == 'R':
225                i = requires
226            elif type == 'P':
227                i = provides
228            else:
229               continue
230
231            if dep.startswith("python("):
232                continue
233
234            # Ignore all perl(VMS::...) and perl(Mac::...) dependencies. These
235            # are typically used conditionally from the Perl code, but are
236            # generated as unconditional dependencies.
237            if dep.startswith('perl(VMS::') or dep.startswith('perl(Mac::'):
238                continue
239
240            # Ignore perl dependencies on .pl files.
241            if dep.startswith('perl(') and dep.endswith('.pl)'):
242                continue
243
244            # Remove perl versions and perl module versions since they typically
245            # do not make sense when used as package versions.
246            if dep.startswith('perl') and r.search(dep):
247                dep = dep.split()[0]
248
249            # Put parentheses around any version specifications.
250            dep = r.sub(r'(\g<0>)',dep)
251
252            if file not in i:
253                i[file] = []
254            i[file].append(dep)
255
256        return provides, requires
257
258    output = subprocess.check_output(shlex.split(rpmdeps) + pkgfiles, stderr=subprocess.STDOUT).decode("utf-8")
259    provides, requires = process_deps(output, pkg, pkgdest, provides, requires)
260
261    return (pkg, provides, requires)
262
263
264def read_shlib_providers(d):
265    import re
266
267    shlib_provider = {}
268    shlibs_dirs = d.getVar('SHLIBSDIRS').split()
269    list_re = re.compile(r'^(.*)\.list$')
270    # Go from least to most specific since the last one found wins
271    for dir in reversed(shlibs_dirs):
272        bb.debug(2, "Reading shlib providers in %s" % (dir))
273        if not os.path.exists(dir):
274            continue
275        for file in sorted(os.listdir(dir)):
276            m = list_re.match(file)
277            if m:
278                dep_pkg = m.group(1)
279                try:
280                    fd = open(os.path.join(dir, file))
281                except IOError:
282                    # During a build unrelated shlib files may be deleted, so
283                    # handle files disappearing between the listdirs and open.
284                    continue
285                lines = fd.readlines()
286                fd.close()
287                for l in lines:
288                    s = l.strip().split(":")
289                    if s[0] not in shlib_provider:
290                        shlib_provider[s[0]] = {}
291                    shlib_provider[s[0]][s[1]] = (dep_pkg, s[2])
292    return shlib_provider
293