"""Install shared libraries from extracted Debian ARM packages into the image. Only libraries the image does not already have are added -- an existing file is never overwritten, so nothing Psion shipped is disturbed. Each library is put in place and then checked for the two things that make it unusable on its own terms: it must be ARM OABI, and every library it names in DT_NEEDED must exist in the image. Anything that fails is removed again and reported. Undefined symbols are reported but not fatal here. A library is not a closed unit: libpcreposix calls pcre_compile without naming libpcre in DT_NEEDED, and resolves it from whichever executable links both. Judging a library alone would reject it. install-built.py applies the strict symbol test where it actually means something -- against a binary, over its whole closure. Loadable modules are checked differently. A GStreamer plugin does not name libgstvideo or libgstbytestream in DT_NEEDED; GStreamer opens plugins with global symbol visibility, so a plugin resolves those against whichever sibling plugin was loaded first. Judging such a file by its own closure alone rejects working plugins -- ximagesink is undefined until libgstvideo.so is loaded -- so for anything in a module directory the siblings count as providers too. python3 scripts/install-libs.py [...] """ import os import shutil import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) import importlib.util spec = importlib.util.spec_from_file_location('ib', os.path.join(HERE, 'install-built.py')) ib = importlib.util.module_from_spec(spec) spec.loader.exec_module(ib) ROOT = ib.ROOT def collect(dirs): """Map destination path -> source path for every library in these packages.""" out = {} for d in dirs: for base, _, files in os.walk(d): for f in files: src = os.path.join(base, f) rel = os.path.relpath(src, d) if not (rel.startswith('usr/lib/') or rel.startswith('lib/')): continue if '.so' not in f: continue out.setdefault(rel, src) return out MODULE_DIRS = ('/gstreamer-0.8/', '/loaders/', '/modules/', '/plugins/') def check_lib(path): """Fatal faults only: wrong ABI, or a DT_NEEDED library that is not here.""" fl = ib.oabi(path) if fl is None: return ['not an ELF file'] problems = [] if fl != 0x2: problems.append(f'e_flags 0x{fl:x}, not OABI 0x2') _, missing = ib.closure(path) for m in missing: problems.append(f'library not in the image: {m}') return problems def is_module(rel): return any(d in '/' + rel for d in MODULE_DIRS) def check_module(path): """Like ib.check, but sibling modules in the same directory also count.""" fl = ib.oabi(path) if fl is None: return ['not an ELF file'] problems = [] if fl != 0x2: problems.append(f'e_flags 0x{fl:x}, not OABI 0x2') libs, missing = ib.closure(path) for m in missing: problems.append(f'library not in the image: {m}') provided = set() for p in libs.values(): if p: provided |= ib.symbols(p)[0] for sib in os.listdir(os.path.dirname(path)): sp = os.path.join(os.path.dirname(path), sib) if os.path.isfile(sp) and not os.path.islink(sp): provided |= ib.symbols(sp)[0] _, und = ib.symbols(path) unresolved = sorted(s for s in und if s not in provided) for s in unresolved[:8]: problems.append(f'unresolved symbol: {s}') if len(unresolved) > 8: problems.append(f'... and {len(unresolved) - 8} more unresolved') return problems def main(dirs): files = collect(dirs) added, skipped = [], 0 for rel, src in sorted(files.items()): dest = os.path.join(ROOT, rel) if os.path.exists(dest) or os.path.islink(dest): skipped += 1 continue os.makedirs(os.path.dirname(dest), exist_ok=True) if os.path.islink(src): os.symlink(os.readlink(src), dest) else: shutil.copy2(src, dest) os.chmod(dest, 0o755) added.append(rel) # verify only the real ELF files we just added bad = [] for rel in added: dest = os.path.join(ROOT, rel) if os.path.islink(dest): continue problems = check_module(dest) if is_module(rel) else check_lib(dest) if problems: bad.append((rel, problems)) print(f'added {len(added)} files, skipped {skipped} already present') if bad: print(f'\n{len(bad)} rejected and removed:') for rel, problems in bad: print(f' {rel}') for p in problems[:4]: print(f' {p}') os.remove(os.path.join(ROOT, rel)) # drop any symlink that pointed at it for other in added: op = os.path.join(ROOT, other) if os.path.islink(op) and not os.path.exists(op): os.remove(op) else: print('all added libraries verified') if __name__ == '__main__': main(sys.argv[1:])