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 (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 27 stripcmd = [strip] 28 skip_strip = False 29 # kernel module 30 if elftype & 16: 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"]) 37 # .so and shared library 38 elif ".so" in file and elftype & 8: 39 stripcmd.extend(["--remove-section=.comment", "--remove-section=.note", "--strip-unneeded"]) 40 # shared or executable: 41 elif elftype & 8 or elftype & 4: 42 stripcmd.extend(["--remove-section=.comment", "--remove-section=.note"]) 43 44 stripcmd.append(file) 45 bb.debug(1, "runstrip: %s" % stripcmd) 46 47 if not skip_strip: 48 output = subprocess.check_output(stripcmd, stderr=subprocess.STDOUT) 49 50 if newmode: 51 os.chmod(file, origmode) 52 53# Detect .ko module by searching for "vermagic=" string 54def 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 57 58# Detect if .ko module is signed 59def 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)) 64 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 72def 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 89def 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 99def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, d, qa_already_stripped=False): 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 """ 110 import stat, errno, oe.path, oe.utils 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 # 120 checkelf = [] 121 inodecache = {} 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? 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) 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 160 if inodecache[file] in inodes: 161 os.unlink(file) 162 os.link(inodes[inodecache[file]], file) 163 else: 164 # break hardlinks so that we do not strip the original. 165 inodes[inodecache[file]] = file 166 bb.utils.break_hardlinks(file) 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 177 oe.utils.multiprocess_launch(runstrip, sfiles, d) 178 179 180def 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 189def filedeprunner(arg): 190 import re, subprocess, shlex 191 192 (pkg, pkgfiles, rpmdeps, pkgdest) = arg 193 provides = {} 194 requires = {} 195 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*') 199 200 def process_deps(pipe, pkg, pkgdest, provides, requires): 201 file = None 202 for line in pipe.split("\n"): 203 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': 218 i = requires 219 elif type == 'P': 220 i = provides 221 else: 222 continue 223 224 if dep.startswith("python("): 225 continue 226 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 232 233 # Ignore perl dependencies on .pl files. 234 if dep.startswith('perl(') and dep.endswith('.pl)'): 235 continue 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 245 if file not in i: 246 i[file] = [] 247 i[file].append(dep) 248 249 return provides, requires 250 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) 253 254 return (pkg, provides, requires) 255 256 257def read_shlib_providers(d): 258 import re 259 260 shlib_provider = {} 261 shlibs_dirs = d.getVar('SHLIBSDIRS').split() 262 list_re = re.compile(r'^(.*)\.list$') 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 268 for file in os.listdir(dir): 269 m = list_re.match(file) 270 if m: 271 dep_pkg = m.group(1) 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 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 286 287 288def 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('_', '-') 307 pkgname = pkgname.replace('@', '') 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()) 313 packages[pkgname] = (relpth, data) 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 + '~')) 319