summaryrefslogtreecommitdiffstats
path: root/meta/recipes-core/systemd/dlopen-deps.inc
blob: eaf6ca1f79ad4b37e6833fea49c53c74263e7d9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
PACKAGEFUNCS =+ "package_generate_dlopen_deps"

python package_generate_dlopen_deps() {
    # https://systemd.io/ELF_DLOPEN_METADATA/

    import struct, json

    def extract_segment(filename, segment):
        """
        Return the named segment from the ELF.
        """
        import tempfile, subprocess

        with tempfile.NamedTemporaryFile() as f:
            cmd = [d.getVar("OBJCOPY"), "--dump-section", f"{segment}={f.name}", filename]
            subprocess.run(cmd, check=True)
            return f.read()

    def parse(buffer, is_little):
        deps = []
        offset = 0
        while offset < len(buffer):
            format = f"{'<' if is_little else '>'}iii"
            name_size, desc_size, note_type = struct.unpack_from(format, buffer, offset)
            offset += struct.calcsize(format)

            format = f"{name_size}s0i{desc_size}s0i"
            if note_type == 0x407c0c0a:
                name_b, desc_b = struct.unpack_from(format, buffer, offset)
                name = name_b.strip(b"\x00").decode("ascii")
                if name == "FDO":
                    desc = desc_b.strip(b"\x00").decode("utf-8")
                    deps.append(*json.loads(desc))
            offset += struct.calcsize(format)
        return deps

    dep_map = {
        "required": "RDEPENDS",
        "recommended": "RRECOMMENDS",
        "suggested": "RSUGGESTS"
    }

    shlibs = oe.package.read_shlib_providers(d)

    for pkg, files in pkgfiles.items():
        # Skip -dbg packages as we won't need to generate dependencies for those
        # but scanning can take time
        if pkg.endswith("-dbg"):
            continue

        for f in files:
            # Skip symlinks, just look for real libraries
            if cpath.islink(f):
                continue

            if ".so." in f or f.endswith(".so"):
                try:
                    elf = oe.qa.ELFFile(f)
                    elf.open()
                    for dep in parse(extract_segment(f, ".note.dlopen"), elf.isLittleEndian()):
                        for soname in dep["soname"]:
                            if soname in shlibs:
                                # TODO assumes the first match is good
                                package, version = list(shlibs[soname].values())[0]
                                dependency = dep_map[dep["priority"]]
                                bb.note(f"{pkg}: adding {dependency} on {package} via .note.dlopen")
                                d.appendVar(f"{dependency}:{pkg}", f" {package} (>= {version})")
                            else:
                                bb.warn(f"cannot find {soname}")
                except oe.qa.NotELFFileError as e:
                    bb.note(f"Cannot extract ELF notes: {e}")
                    pass
}