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