xref: /openbmc/openbmc/poky/scripts/bblock (revision 20137395)
1#!/usr/bin/env python3
2# bblock
3# lock/unlock task to latest signature
4#
5# Copyright (c) 2023 BayLibre, SAS
6# Author: Julien Stepahn <jstephan@baylibre.com>
7#
8# SPDX-License-Identifier: GPL-2.0-only
9#
10
11import os
12import sys
13import logging
14
15scripts_path = os.path.dirname(os.path.realpath(__file__))
16lib_path = scripts_path + "/lib"
17sys.path = sys.path + [lib_path]
18
19import scriptpath
20
21scriptpath.add_bitbake_lib_path()
22
23import bb.tinfoil
24import bb.msg
25
26import argparse_oe
27
28myname = os.path.basename(sys.argv[0])
29logger = bb.msg.logger_create(myname)
30
31
32def getTaskSignatures(tinfoil, pn, tasks):
33    tinfoil.set_event_mask(
34        [
35            "bb.event.GetTaskSignatureResult",
36            "logging.LogRecord",
37            "bb.command.CommandCompleted",
38            "bb.command.CommandFailed",
39        ]
40    )
41    ret = tinfoil.run_command("getTaskSignatures", pn, tasks)
42    if ret:
43        while True:
44            event = tinfoil.wait_event(1)
45            if event:
46                if isinstance(event, bb.command.CommandCompleted):
47                    break
48                elif isinstance(event, bb.command.CommandFailed):
49                    logger.error(str(event))
50                    sys.exit(2)
51                elif isinstance(event, bb.event.GetTaskSignatureResult):
52                    sig = event.sig
53                elif isinstance(event, logging.LogRecord):
54                    logger.handle(event)
55    else:
56        logger.error("No result returned from getTaskSignatures command")
57        sys.exit(2)
58    return sig
59
60
61def parseRecipe(tinfoil, recipe):
62    try:
63        tinfoil.parse_recipes()
64        d = tinfoil.parse_recipe(recipe)
65    except Exception:
66        logger.error("Failed to get recipe info for: %s" % recipe)
67        sys.exit(1)
68    return d
69
70
71def bblockDump(lockfile):
72    try:
73        with open(lockfile, "r") as lockfile:
74            for line in lockfile:
75                print(line.strip())
76    except IOError:
77        return 1
78    return 0
79
80
81def bblockReset(lockfile, pns, package_archs, tasks):
82    if not pns:
83        logger.info("Unlocking all recipes")
84        try:
85            os.remove(lockfile)
86        except FileNotFoundError:
87            pass
88    else:
89        logger.info("Unlocking {pns}".format(pns=pns))
90        tmp_lockfile = lockfile + ".tmp"
91        with open(lockfile, "r") as infile, open(tmp_lockfile, "w") as outfile:
92            for line in infile:
93                if not (
94                    any(element in line for element in pns)
95                    and any(element in line for element in package_archs.split())
96                ):
97                    outfile.write(line)
98                else:
99                    if tasks and not any(element in line for element in tasks):
100                        outfile.write(line)
101        os.remove(lockfile)
102        os.rename(tmp_lockfile, lockfile)
103
104
105def main():
106    parser = argparse_oe.ArgumentParser(description="Lock and unlock a recipe")
107    parser.add_argument("pn", nargs="*", help="Space separated list of recipe to lock")
108    parser.add_argument(
109        "-t",
110        "--tasks",
111        help="Comma separated list of tasks",
112        type=lambda s: [
113            task if task.startswith("do_") else "do_" + task for task in s.split(",")
114        ],
115    )
116    parser.add_argument(
117        "-r",
118        "--reset",
119        action="store_true",
120        help="Unlock pn recipes, or all recipes if pn is empty",
121    )
122    parser.add_argument(
123        "-d",
124        "--dump",
125        action="store_true",
126        help="Dump generated bblock.conf file",
127    )
128
129    global_args, unparsed_args = parser.parse_known_args()
130
131    with bb.tinfoil.Tinfoil() as tinfoil:
132        tinfoil.prepare(config_only=True)
133
134        package_archs = tinfoil.config_data.getVar("PACKAGE_ARCHS")
135        builddir = tinfoil.config_data.getVar("TOPDIR")
136        lockfile = "{builddir}/conf/bblock.conf".format(builddir=builddir)
137
138        if global_args.dump:
139            bblockDump(lockfile)
140            return 0
141
142        if global_args.reset:
143            bblockReset(lockfile, global_args.pn, package_archs, global_args.tasks)
144            return 0
145
146        with open(lockfile, "a") as lockfile:
147            s = ""
148            if lockfile.tell() == 0:
149                s = "# Generated by bblock\n"
150                s += 'SIGGEN_LOCKEDSIGS_TASKSIG_CHECK = "info"\n'
151                s += 'SIGGEN_LOCKEDSIGS_TYPES += "${PACKAGE_ARCHS}"\n'
152                s += "\n"
153
154            for pn in global_args.pn:
155                d = parseRecipe(tinfoil, pn)
156                package_arch = d.getVar("PACKAGE_ARCH")
157                siggen_locked_sigs_package_arch = d.getVar(
158                    "SIGGEN_LOCKEDSIGS_{package_arch}".format(package_arch=package_arch)
159                )
160                sigs = getTaskSignatures(tinfoil, [pn], global_args.tasks)
161                for sig in sigs:
162                    new_entry = "{pn}:{taskname}:{sig}".format(
163                        pn=sig[0], taskname=sig[1], sig=sig[2]
164                    )
165                    if (
166                        siggen_locked_sigs_package_arch
167                        and not new_entry in siggen_locked_sigs_package_arch
168                    ) or not siggen_locked_sigs_package_arch:
169                        s += 'SIGGEN_LOCKEDSIGS_{package_arch} += "{new_entry}"\n'.format(
170                            package_arch=package_arch, new_entry=new_entry
171                        )
172            lockfile.write(s)
173    return 0
174
175
176if __name__ == "__main__":
177    try:
178        ret = main()
179    except Exception:
180        ret = 1
181        import traceback
182
183        traceback.print_exc()
184    sys.exit(ret)
185