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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
#! /usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# depthcharge-tools depthchargectl check subcommand
# Copyright (C) 2020-2023 Alper Nebi Yasak <alpernebiyasak@gmail.com>
# See COPYRIGHT and LICENSE files for full copyright information.
import argparse
import logging
from pathlib import Path
from depthcharge_tools import __version__
from depthcharge_tools.utils.argparse import (
Command,
Argument,
Group,
CommandExit,
)
from depthcharge_tools.utils.subprocess import (
fdtget,
vbutil_kernel,
)
from depthcharge_tools.depthchargectl import depthchargectl
class SizeTooBigError(CommandExit):
def __init__(self, image, image_size, max_size):
message = (
"Image '{}' ({} bytes) must be smaller than {} bytes."
.format(image, image_size, max_size)
)
self.image = image
self.image_size = image_size
self.max_size = max_size
super().__init__(output=False, returncode=3, message=message)
class NotADepthchargeImageError(CommandExit):
def __init__(self, image):
message = (
"Image '{}' is not a depthcharge image."
.format(image)
)
self.image = image
super().__init__(output=False, returncode=4, message=message)
class VbootSignatureError(CommandExit):
def __init__(self, image):
message = (
"Depthcharge image '{}' is not signed by the configured keys."
.format(image)
)
self.image = image
super().__init__(output=False, returncode=5, message=message)
class ImageFormatError(CommandExit):
def __init__(self, image, board_format):
message = (
"Image '{}' must be in '{}' format."
.format(image, board_format)
)
self.image = image
self.board_format = board_format
super().__init__(output=False, returncode=6, message=message)
class MissingDTBError(CommandExit):
def __init__(self, image, compat):
message = (
"Image '{}' must have a device-tree binary compatible with pattern '{}'."
.format(image, compat)
)
self.image = image
self.compat = compat
super().__init__(output=False, returncode=7, message=message)
@depthchargectl.subcommand("check")
class depthchargectl_check(
depthchargectl,
prog="depthchargectl check",
usage = "%(prog)s [options] IMAGE",
add_help=False,
):
"""Check if a depthcharge image can be booted."""
_logger = depthchargectl._logger.getChild("check")
config_section = "depthchargectl/check"
@Group
def positionals(self):
"""Positional arguments"""
@positionals.add
@Argument
def image(self, image):
"""Depthcharge image to check validity of."""
image = Path(image)
if not image.is_file():
raise ValueError("Image argument must be a file")
return image
@depthchargectl.board.copy()
def board(self, codename=""):
"""Assume we're running on the specified board"""
board = super().board
if board is None:
raise ValueError(
"Cannot check depthcharge images when no board is specified.",
)
return board
@depthchargectl.zimage_initramfs_hack.copy()
def zimage_initramfs_hack(self, hack=None):
hack = super().zimage_initramfs_hack
if hack not in (None, "set-init-size", "pad-vmlinuz"):
raise ValueError(
"Unknown zimage initramfs support hack '{}'."
.format(hack)
)
return hack
def __call__(self):
image = self.image
self.logger.warning(
"Verifying depthcharge image for board '{}' ('{}')."
.format(self.board.name, self.board.codename)
)
self.logger.info("Checking if image fits into size limit.")
image_size = image.stat().st_size
if image_size > self.board.image_max_size:
raise SizeTooBigError(
image,
image_size,
self.board.image_max_size,
)
self.logger.info("Checking depthcharge image validity.")
if vbutil_kernel(
"--verify", image,
check=False,
).returncode != 0:
raise NotADepthchargeImageError(image)
self.logger.info("Checking depthcharge image signatures.")
if self.vboot_public_key is not None:
if vbutil_kernel(
"--verify", image,
"--signpubkey", self.vboot_public_key,
check=False,
).returncode != 0:
raise VbootSignatureError(image)
itb = self.tmpdir / "{}.itb".format(image.name)
vbutil_kernel(
"--get-vmlinuz", image,
"--vmlinuz-out", itb,
check=False,
)
if self.board.image_format == "fit":
self.logger.info("Checking FIT image format.")
nodes = fdtget.subnodes(itb)
if "images" not in nodes and "configurations" not in nodes:
raise ImageFormatError(image, self.board.image_format)
def is_compatible(dt_file, conf_path):
return any(
self.board.dt_compatible.fullmatch(compat)
for compat in fdtget.get(
dt_file, conf_path, "compatible", default="",
).split()
)
self.logger.info("Checking included DTB binaries.")
for conf in fdtget.subnodes(itb, "/configurations"):
conf_path = "/configurations/{}".format(conf)
if is_compatible(itb, conf_path):
break
dtb = fdtget.get(itb, conf_path, "fdt")
dtb_path = "/images/{}".format(dtb)
dtb_data = fdtget.get(itb, dtb_path, "data", type=bytes)
dtb_file = self.tmpdir / "{}.dtb".format(conf)
dtb_file.write_bytes(dtb_data)
if is_compatible(dtb_file, "/"):
break
else:
raise MissingDTBError(
image, self.board.dt_compatible.pattern,
)
self.logger.warning(
"This command is incomplete, the image might be unbootable "
"despite passing currently implemented checks."
)
global_options = depthchargectl.global_options
config_options = depthchargectl.config_options
|