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