diff --git a/board/common/Config.in b/board/common/Config.in index 08fc9839..376badb0 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -3,6 +3,7 @@ menu "Images" comment "DDI Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi-disk/Config.in" comment "ITB Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rootfs/Config.in" diff --git a/board/common/image/image-ddi-disk/Config.in b/board/common/image/image-ddi-disk/Config.in new file mode 100644 index 00000000..10f8f4fd --- /dev/null +++ b/board/common/image/image-ddi-disk/Config.in @@ -0,0 +1,8 @@ +menuconfig IMAGE_DDI_DISK + bool "LVM2 based provisioning disk (DDI)" + depends on IMAGE_BAREBOX_ESP + depends on IMAGE_DDI + help + Compose a full disk image consisting of an EFI System + Partition (ESP) containing Barebox, and an LVM PV in which + Infix is installed as an LV. diff --git a/board/common/image/image-ddi-disk/generate.sh b/board/common/image/image-ddi-disk/generate.sh new file mode 100755 index 00000000..887e8351 --- /dev/null +++ b/board/common/image/image-ddi-disk/generate.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +set -e + + +mkdir -p "${WORKDIR}"/root +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp + +"${BR2_EXTERNAL_INFIX_PATH}"/utils/lvm-mkinternal \ + "${BINARIES_DIR}"/"${ARTIFACT}".raw >"${WORKDIR}"/internal.lvm + +cat <"${WORKDIR}"/genimage.cfg + +image ${ARTIFACT}.disk { + hdimage { + partition-table-type = "gpt" + } + + partition esp { + partition-type-uuid = "esp" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition esp-backup { + partition-type-uuid = "esp" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition internal { + growfs = "true" + partition-type-uuid = "lvm" + image = "internal.lvm" + } +} + +# Silence genimage warnings +config {} +EOF + +genimage \ + --tmppath "${WORKDIR}"/tmp \ + --rootpath "${WORKDIR}"/root \ + --inputpath "${WORKDIR}" \ + --outputpath "${BINARIES_DIR}" \ + --config "${WORKDIR}"/genimage.cfg diff --git a/board/common/image/image-ddi-disk/image-ddi-disk.mk b/board/common/image/image-ddi-disk/image-ddi-disk.mk new file mode 100644 index 00000000..e666e023 --- /dev/null +++ b/board/common/image/image-ddi-disk/image-ddi-disk.mk @@ -0,0 +1,9 @@ +################################################################################ +# +# image-ddi-disk +# +################################################################################ + +IMAGE_DDI_DISK_DEPENDENCIES := image-barebox-esp image-ddi + +$(eval $(ix-image)) diff --git a/utils/lvm-mkinternal b/utils/lvm-mkinternal new file mode 100755 index 00000000..3bc83dc0 --- /dev/null +++ b/utils/lvm-mkinternal @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +import os +import random +import struct + +class ID: + def __init__(self): + charset = "abcdefghijklmnopqrstuvwxyz" + charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + charset += "0123456789" + + b = random.choices(charset, k=32) + self.bytes = "".join(b).encode() + + def dashed(self): + return self.bytes[0:6].decode() + \ + "-" + self.bytes[6:10].decode() + \ + "-" + self.bytes[10:14].decode() + \ + "-" + self.bytes[14:18].decode() + \ + "-" + self.bytes[18:22].decode() + \ + "-" + self.bytes[22:26].decode() + \ + "-" + self.bytes[26:32].decode() + +def crc32(data: bytes) -> int: + crc = 0xf597a6cf + + for byte in data: + crc ^= byte + for _ in range(8): + if crc & 1: + crc = (crc >> 1) ^ 0xedb88320 + else: + crc >>= 1 + + return crc & 0xffffffff + +def sendfile(dst, src, count): + while count > 0: + chunk = os.sendfile(dst, src, None, count) + if chunk < 0: + raise OSError("sendfile") + + count -= chunk + +class Linear: + def __init__(self, start, align, path, name=None): + self.id = ID() + self.name = name if name else os.path.basename(path) + self.path = path + self.start = start + self.align = align + self.size = os.path.getsize(path) + self.asize = (self.size + align - 1) & ~(align - 1) + + def sendfile(self, fd): + pad = self.asize - self.size + + with open(self.path, "rb") as f: + sendfile(fd, f.fileno(), self.size) + + if pad: + os.write(fd, b"\0" * pad) + + def meta(self): + return f""" {self.name} {{ + id = "{self.id.dashed()}" + status = ["READ", "WRITE", "VISIBLE"] + segment_count = 1 + + segment1 {{ + start_extent = 0 + extent_count = {self.asize // self.align } + type = "striped" + stripe_count = 1 + stripes = [ + "pv0", {self.start // self.align } + ] + }} + }}""" + +class InternalPV: + def __init__(self, images=[], align=(1 << 20), creation_time=0): + self.pvid = ID() + self.vgid = ID() + self.align = align + self.creation_time = creation_time + + self.lvs = [] + self.dsize = 0 + for img in images: + lv = Linear(self.dsize, align, img) + self.lvs.append(lv) + self.dsize += lv.asize + + # Reserve `align` bytes for primary metadata area, the size + # used by all LVs, and another `aligned` bytes for the backup + # metadata area + self.size = align + self.dsize + align + + def write(self, dst): + for i in range(4): + dst.write(self._label(i)) + + dst.write(b"\0" * 0x800) + + dst.write(self._metadata(True)) + + for lv in self.lvs: + lv.sendfile(dst.fileno()) + + dst.write(self._metadata(False)) + + def _label(self, sector: int): + headfmt, tailfmt = "<8s Q I", "", 1, + offs, size, + 512, len(txt), crc32(txt), 0, + 0, 0, 0, 0) + b"\0" * padsize + + head = struct.pack(headfmt, crc32(tail)) + + return head + tail + txt + bytes([0] * txtpadsize) + + def _meta_txt(self): + return f"""internal {{ + id = "{self.vgid.dashed()}" + seqno = 1 + status = ["RESIZEABLE", "READ", "WRITE"] + extent_size = {self.align >> 9} + max_pv = 0 + max_lv = 0 + metadata_copies = 2 + + physical_volumes {{ + pv0 {{ + id = "{self.pvid.dashed()}" + dev = "/dev/internal" + status = ["ALLOCATABLE"] + dev_size = {self.size >> 9} + pe_start = {self.align >> 9} + pe_count = {self.dsize // self.align } + }} + }} + + logical_volumes {{ +{"\n".join([lv.meta() for lv in self.lvs])} + }} +}} + +contents = "Text Format Volume Group" +version = 1 +description = "Internal image storage" +creation_host = "infix-build-system" +creation_time = {self.creation_time} +""".encode() + + +def main(): + import argparse + import datetime + import sys + + parser = argparse.ArgumentParser(description="Create LVM PV containing a set of images") + parser.add_argument("-T", "--creation-time", metavar="TIMESTAMP", + default=int(datetime.datetime.now().timestamp())) + parser.add_argument("images", metavar="IMAGE", nargs="*") + args = parser.parse_args() + + random.seed(args.creation_time) + + pv = InternalPV(args.images, creation_time=args.creation_time) + pv.write(sys.stdout.buffer) + +if __name__ == "__main__": + main()