Compare commits

...
13 Commits
Author SHA1 Message Date
Tobias Waldekranz ea315bdb10 image-ddi: Mount persistent storage from internal/rw LVM LV 2026-03-08 21:56:45 +00:00
Tobias Waldekranz 3f23aa92be image-itb-rootfs: Assume ownership of legacy filesystem setup
This opens up for DDI images to ship their own implementation.
2026-03-08 21:55:20 +00:00
Tobias Waldekranz 4984d5ac7f image: Allow images to contribute files to the rootfs
This makes it possible to ship differing system files depending on the
images being built. E.g. an ITB image might expect a different
/etc/fstab than a DDI.
2026-03-08 21:55:20 +00:00
Tobias Waldekranz d945bb703b qemu: Support running from DDI images
run.sh has degenerated quite a bit over the years - in no small part
due to the way the qemu-system argv is constructed.

Cut our losses by taking a fresh look at this problem, now that lots
of things need to change anyway. Keep the old script as-is for targets
that still use ITB based images.
2026-03-08 21:55:20 +00:00
Tobias Waldekranz b047bd2a52 x86_64: Switch from GRUB to Barebox 2026-03-08 21:54:35 +00:00
Tobias Waldekranz efc97f6566 initramfs: Build initramfs capable of booting DDI images 2026-03-08 21:54:35 +00:00
Tobias Waldekranz 39f9c1e166 board/common: ddi: Add helper tool to work with DDIs
First user is the initramfs, where it is used to setup the verity
device.

Later on, it will be extended for use by RAUC during upgrades, to
fetch image info in statd, etc.
2026-03-08 21:54:35 +00:00
Tobias Waldekranz f81de1d3ec image-lvm-stub: Base image for use with 'make run' 2026-03-08 21:54:35 +00:00
Tobias Waldekranz e7fabfc37d image-ddi-disk: Full system image 2026-03-08 21:54:35 +00:00
Tobias Waldekranz 956e604dcb image-ddi: Add Discoverable Disk Image (DDI) 2026-03-03 07:35:35 +00:00
Tobias Waldekranz 9d3669b647 image-barebox-esp: EFI System Partition with Barebox 2026-03-03 07:35:31 +00:00
Tobias Waldekranz 17a3ca58a0 qemu: Only allow custom kernel options with QEMU's native loader
This is the only mode that supports the `-append` option. It has
nothing to do with "not MMC".
2026-02-27 23:28:05 +00:00
Tobias Waldekranz 7e2d0cf2f8 buildroot: linux: Allow kernel installation to target with dracut 2026-02-27 23:27:53 +00:00
34 changed files with 2318 additions and 19 deletions
+10
View File
@@ -1,11 +1,21 @@
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"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-aux/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-qcow/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-gns3a/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rauc/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-dl-release/Config.in"
comment "General"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-barebox-esp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-lvm-stub/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-readme/Config.in"
endmenu
+102
View File
@@ -0,0 +1,102 @@
#Simple dracut config for a system without systemd
#Dracut configuration
show_modules=yes
i18n_install_all=no
lvmconf=no
mdadmconf=no
early_microcode=no
hostonly=no
hostonly_cmdline=no
use_fstab=no
kernel_cmdline="rd.break=initqueue"
do_strip=no
# Dracut modules needed
add_dracutmodules+=" \
busybox \
"
# Modules to ignore
omit_dracutmodules+=" \
base \
bash \
biosdevname \
btrfs \
bluetooth \
caps \
cifs \
connman \
crypt \
crypt-gpg \
dash \
dbus-broker \
dbus-daemon \
dm \
dmraid \
dmsquash-live-ntfs \
dracut-systemd \
fcoe \
fcoe-uefi \
fs-lib \
iscsi \
i18n \
kernel-modules \
kernel-modules-extra \
lvmmerge \
lvm \
lunmask \
mdraid \
memstrack \
mksh \
modsign \
mount-root \
multipath \
nbd \
network-legacy \
network-wicked \
nfs \
nvdimm \
nvmf \
parse-lunmask \
pcsc \
qemu \
qemu-net \
resume \
rootfs-block \
rngd \
shutdown \
systemd \
systemd-udevd \
systemd-coredump \
systemd-ask-password \
systemd-timedated \
systemd-rfkill \
systemd-resolved \
systemd-hostnamed \
systemd-initrd \
systemd-integritysetup \
systemd-journald \
systemd-ldconfig \
systemd-networkd \
systemd-timesyncd \
systemd-veritysetup \
systemd-modules-load \
systemd-pcrphase \
systemd-portabled \
systemd-pstore \
systemd-sysctl \
systemd-repart \
systemd-sysext \
systemd-sysusers \
systemd-tmpfiles \
squash \
ssh-client \
stratis \
tpm2-tss \
udev-rules \
url-lib \
usrmount \
virtiofs \
"
@@ -0,0 +1,6 @@
config IMAGE_BAREBOX_ESP
bool "Barebox EFI System Partition"
depends on BR2_TARGET_BAREBOX
default y if IMAGE_DDI
help
An EFI System Partition (ESP) with Barebox installed.
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
set -e
case "$BR2_ARCH" in
x86_64)
BOOT_EFI=BOOTX64.EFI
;;
*)
echo "Unknown EFI boot path for $BR2_ARCH" >&2
exit 1
;;
esac
mkdir -p "${WORKDIR}"/root
rm -rf "${WORKDIR}"/tmp
mkdir -p "${WORKDIR}"/tmp
cat <<EOF >"${WORKDIR}"/genimage.cfg
image barebox-esp.vfat {
size = "16M"
vfat {
file EFI/BOOT/$BOOT_EFI {
image = $BINARIES_DIR/barebox.efi
}
}
}
# Silence genimage warnings
config {}
EOF
genimage \
--tmppath "${WORKDIR}"/tmp \
--rootpath "${WORKDIR}"/root \
--inputpath "${WORKDIR}" \
--outputpath "${BINARIES_DIR}" \
--config "${WORKDIR}"/genimage.cfg
@@ -0,0 +1,7 @@
################################################################################
#
# image-barebox-esp
#
################################################################################
$(eval $(ix-image))
@@ -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.
+46
View File
@@ -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 <<EOF >"${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
@@ -0,0 +1,9 @@
################################################################################
#
# image-ddi-disk
#
################################################################################
IMAGE_DDI_DISK_DEPENDENCIES := image-barebox-esp image-ddi
$(eval $(ix-image))
+33
View File
@@ -0,0 +1,33 @@
menuconfig IMAGE_DDI
bool "Discoverable Disk Image (DDI)"
select BR2_TARGET_ROOTFS_SQUASHFS
select BR2_PACKAGE_HOST_GENIMAGE
help
Create a Discoverable Disk Image containing:
- SquashFS of the root filesystem
- dm-verity hash tree of the root filesystem
- DDI compliant JSON signature object of the hash tree
https://uapi-group.org/specifications/specs/discoverable_disk_image/
config IMAGE_DDI_KEY
string "signing key"
depends on IMAGE_DDI
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key"
help
Path to the private RSA key, in PKCS#8 format, used to sign
the dm-verity hash tree.
config IMAGE_DDI_CERT
string "signing cert"
depends on IMAGE_DDI
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt"
help
Path to the X509 certificate whose associated public key can
validate the authenticity of the generated signature.
The SHA256 fingerprint of this certificate is recorded
alongside the standard PKCS#7 signature in the JSON object,
which can be useful to locate corresponding key on systems
that lack full X509 parsing capabilities.
+81
View File
@@ -0,0 +1,81 @@
#!/bin/sh
# Generate the DDI in two phases. First we create the verity hash tree
# and signature, then we collect them together with the rootfs in the
# ddi. The reason for this is that genimage (at least up to v19) can
# not infer the size of a partition if the image to be placed in it is
# created by the same genimage instance. With the two-phase approach,
# we work around that problem and can create a DDI without any wasted
# space.
set -e
rm -rf "${WORKDIR}"/tmp
mkdir -p "${WORKDIR}"/tmp "${WORKDIR}"/verity
cat <<EOF >"${WORKDIR}"/genimage-verity.cfg
image rootfs.verity {
verity {
image = "rootfs.squashfs"
}
}
image rootfs.verity-sig {
verity-sig {
image = "rootfs.verity"
cert = "${CERT}"
key = "${KEY}"
}
}
config {}
EOF
genimage \
--loglevel 1 \
--tmppath "${WORKDIR}"/tmp \
--rootpath "${WORKDIR}" \
--inputpath "${BINARIES_DIR}" \
--outputpath "${WORKDIR}"/verity \
--config "${WORKDIR}"/genimage-verity.cfg
case "${BR2_ARCH}" in
"x86_64")
arch=x86-64
;;
*)
echo "ERROR: missing mapping from ${BR2_ARCH} to genimage arch" >&2
exit 1
;;
esac
cat <<EOF >"${WORKDIR}"/genimage-ddi.cfg
image ${ARTIFACT}.raw {
hdimage {
partition-table-type = "gpt"
}
partition root {
partition-type-uuid = "root-${arch}"
image = "rootfs.squashfs"
}
partition root-verity {
partition-type-uuid = "root-${arch}-verity"
image = "${WORKDIR}/verity/rootfs.verity"
}
partition root-verity-sig {
partition-type-uuid = "root-${arch}-verity-sig"
image = "${WORKDIR}/verity/rootfs.verity-sig"
}
}
config {}
EOF
genimage \
--loglevel 1 \
--tmppath "${WORKDIR}"/tmp \
--rootpath "${WORKDIR}" \
--inputpath "${BINARIES_DIR}" \
--outputpath "${BINARIES_DIR}" \
--config "${WORKDIR}"/genimage-ddi.cfg
+32
View File
@@ -0,0 +1,32 @@
image rootfs.verity {
verity {
image = "rootfs.squashfs"
}
}
image rootfs.verity-sig {
verity-sig {
image = "rootfs.verity"
cert = "@CERT@"
key = "@KEY@"
}
}
image @ARTIFACT@.raw {
hdimage {
partition-table-type = "gpt"
}
partition root {
partition-type-uuid = "root-@ARCH@"
image = "rootfs.squashfs"
}
partition root-verity {
partition-type-uuid = "root-@ARCH@-verity"
image = "rootfs.verity"
}
partition root-verity-sig {
partition-type-uuid = "root-@ARCH@-verity-sig"
image = "rootfs.verity-sig"
}
}
+10
View File
@@ -0,0 +1,10 @@
################################################################################
#
# image-ddi
#
################################################################################
IMAGE_DDI_DEPENDENCIES := host-genimage rootfs-squashfs
IMAGE_DDI_CONFIG_VARS := KEY CERT
$(eval $(ix-image))
@@ -0,0 +1,15 @@
tmpfs /tmp tmpfs mode=0755,nosuid,nodev 0 0
tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0
tmpfs /media tmpfs mode=1755,nosuid,nodev 0 0
tmpfs /mnt/tmp tmpfs defaults 0 0
# Locate and mount /cfg, /home, /root and /var from the internal
# volume, wherever that is located - or, as a last resort, setup tmpfs
# mounts at these points. Setup an ephemeral writable overlay on /etc.
/usr/libexec/infix/mnt# /mnt/rw helper none 0 0
# Optional, nice-to-have mounts
debugfs /sys/kernel/debug debugfs nofail 0 0
tracefs /sys/kernel/tracing tracefs nofail 0 0
cfgfs /config configfs nofail,noauto 0 0
host /mnt/host 9p nofail,noauto,cache=none,msize=16384 0 0
+211
View File
@@ -0,0 +1,211 @@
#!/bin/sh
# Called from /etc/fstab to ensure we have something writable mounted
# at /cfg, /home, /root, and /var.
#
# In the normal case, the "rw" LV from the "internal" LVM VG is used
# to back all persistant storage. In the event that "internal" is
# present, but the "rw" LV is missing, one will be provisioned, along
# with the required BTRFS subvolumes.
#
# In all other scenarios where the subvolumes are not available, the
# system will fall back to temporary storage for all mount points.
#
# For /etc, a writable, tmpfs backed, layer is always overlayed on top
# of the read-only contents from the OS image.
set -e
nm=$(basename "$0")
need_restore=
code=0
logmsg()
{
logger -k -t "$nm" -p user.crit "$@"
}
status()
{
GREEN="$(printf '\033[1;32m')"
RED="$(printf '\033[1;31m')"
YELLOW="$(printf '\033[1;33m')"
BOLD="$(printf '\033[1m')"
RESET="$(printf '\033[0m')"
case $1 in
0) color=$GREEN; text=' OK ' ;;
1) color=$RED; text='FAIL' ;;
2) color=$YELLOW;text='WARN' ;;
*) color=$YELLOW;text=' ⋯ ' ;;
esac
printf '%s[%s%s%s%s]%s ' "$BOLD" "$color" "$text" "$RESET" "$BOLD" "$RESET"
}
print_start()
{
printf '\r\033[K%s%s' "$(status 3)" "$*" > /dev/console
need_restore=YES
}
print_end()
{
rc=$1; shift
if [ $# -gt 0 ]; then
printf '\r\033[K%s%s\n' "$(status "$rc")" "$*" > /dev/console
else
printf '\r%s\n' "$(status "$rc")" > /dev/console
fi
}
# Restore Finit's original progress message so its [ OK ] appears correctly
print_restore()
{
[ "$need_restore" ] || return 0
print_start "Mounting filesystems from /etc/fstab"
}
# # External button or bootloader changed kernel command line
# check_factory()
# {
# if [ -f /mnt/cfg/infix/.reset ]; then
# return 0;
# fi
# if grep -q 'finit.cond=factory-reset' /proc/cmdline; then
# return 0;
# fi
# # Add to your br2-external to extend factory-reset check
# if [ ! -x /usr/libexec/infix/check-factory ]; then
# return 1;
# fi
# /usr/libexec/infix/check-factory
# }
# factory_reset()
# {
# find /sys/class/leds/ -type l -exec sh -c 'echo 100 > $0/brightness' {} \;
# print_start "Resetting to factory defaults."
# logger $opt -p user.notice -t "$nm" "Resetting to factory defaults."
# rm -rf /mnt/cfg/* /mnt/var/*
# sync
# logger $opt -p user.notice -t "$nm" "Factory reset complete."
# print_end 0 "Factory reset complete."
# print_restore
# }
subvols="cfg var root home"
internal_rw_provision()
{
print_start "Provisioning persistent storage on internal volume"
lvm lvcreate --name rw --extents 50%FREE internal || {
logmsg "No rw LV available, nor could it be created"
print_end 1
return 1
}
mkfs.btrfs -L rw /dev/internal/rw || {
logmsg "Could not create BTRFS on rw LV"
print_end 1
return 1
}
mount -t btrfs /dev/internal/rw /mnt/rw || {
logmsg "Failed to mount rw"
print_end 1
return 1
}
for subvol in $subvols; do
btrfs subvolume create /mnt/rw/@"$subvol" || {
logmsg "Failed to create @$subvol on rw"
print_end 1
return 1
}
done
umount /mnt/rw || {
logmsg "Failed to unmount rw"
print_end 1
return 1
}
print_end 0
}
internal_rw_activate()
{
lvm vgs internal || {
logmsg "No internal volume available"
return 1
}
lvm vgchange -ay internal || {
logmsg "Unable to bring internal volume online"
return 1
}
# The only condition under which we do BTRFS provisioning is when
# the LV is absent. In all other failure scenarios: fall back to
# tmpfs and let the user sort it out. This way we do not risk any
# loss of data.
[ -b /dev/internal/rw ] && return 0
internal_rw_provision
}
internal_rw_mount()
{
internal_rw_activate || return 1
for subvol in $subvols; do
mount -t btrfs /dev/internal/rw /"$subvol" -o subvol=@"$subvol" || {
logmsg "Failed to mount /$subvol"
return 1
}
done
}
tmpfs_rw_mount()
{
for subvol in $subvols; do
mkdir -p -m 0755 /mnt/tmp/"$subvol"
mount /mnt/tmp/"$subvol" /"$subvol" -o bind || {
logmsg "Failed to mount temporary /$subvol"
return 1
}
done
}
tmpfs_etc_mount()
{
mkdir -p -m 0755 /mnt/tmp/etc/
tar c -C /etc . | tar x -C /mnt/tmp/etc
mount /mnt/tmp/etc /etc -o bind
}
exec >/mnt/tmp/mnt.log 2>&1
tmpfs_etc_mount || {
code=1
logmsg "Failed to make /etc writable"
}
if ! internal_rw_mount; then
code=1
print_end 1 "Mounting persistent storage from internal volume"
logmsg "Internal volume not found, falling back to tmpfs"
print_start "Falling back to ephemeral tmpfs storage!"
tmpfs_rw_mount
print_end $?
fi
print_restore
exit $code
@@ -0,0 +1,8 @@
config IMAGE_LVM_STUB
bool "LVM2 stub image"
default y if IMAGE_DDI
help
An empty LVM2 physical volume attached to the internal
volume group. This is useful as a base image for persistent
storage in QEMU emulation scenarios where the OS is supplied
in a separate image.
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
set -e
mkdir -p "${WORKDIR}"/root
rm -rf "${WORKDIR}"/tmp
mkdir -p "${WORKDIR}"/tmp
"${BR2_EXTERNAL_INFIX_PATH}"/utils/lvm-mkinternal >"${WORKDIR}"/stub.lvm
cat <<EOF >"${WORKDIR}"/genimage.cfg
image lvm-stub.disk {
hdimage {
partition-table-type = "gpt"
}
partition internal {
growfs = "true"
image = "stub.lvm"
partition-type-uuid = "lvm"
}
}
# Silence genimage warnings
config {}
EOF
genimage \
--tmppath "${WORKDIR}"/tmp \
--rootpath "${WORKDIR}"/root \
--inputpath "${WORKDIR}" \
--outputpath "${BINARIES_DIR}" \
--config "${WORKDIR}"/genimage.cfg
@@ -0,0 +1,7 @@
################################################################################
#
# image-lvm-stub
#
################################################################################
$(eval $(ix-image))
+2
View File
@@ -10,6 +10,7 @@ $(1): $$($(2)_DEPENDENCIES)
PKGDIR=$$($(2)_DIR) \
WORKDIR=$$(BUILD_DIR)/$(1) \
BINARIES_DIR=$$(BINARIES_DIR) \
BR2_ARCH=$$(BR2_ARCH) \
BR2_EXTERNAL_INFIX_PATH=$$(BR2_EXTERNAL_INFIX_PATH) \
ARTIFACT=$$(INFIX_ARTIFACT) \
COMPATIBLE=$$(INFIX_COMPATIBLE) \
@@ -19,6 +20,7 @@ $(1): $$($(2)_DEPENDENCIES)
ifeq ($$($(2)),y)
TARGETS_ROOTFS += $(1)
BR2_ROOTFS_OVERLAY := "$$(realpath $$(pkgdir)/rootfs) $$(call qstrip,$$(BR2_ROOTFS_OVERLAY))"
endif
endef
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
check() {
return 0
}
depends() {
return 0
}
install() {
# Not sure if this should be handled by buildroot's merged-usr
# module. Fix it here for now.
ln -s "lib" "${initdir?}/lib64"
}
+279
View File
@@ -0,0 +1,279 @@
#!/bin/sh
echo -ne "\e[31m●\e[0m \e[33m●\e[0m \e[32m●\e[0m Infix Initramfs — " >/dev/console
source /etc/os-release >/dev/console
echo "$VERSION"
set -e
getkopt()
{
grep -q -E "(^| )$1( |$)" /proc/cmdline
}
getkparam()
{
sed -n "s/.*\<$1=\([^ ]*\).*/\1/p" /proc/cmdline
}
debug_shell()
{
[ "$(getkparam rd.shell)" = "$1" ] || return 0
exec </dev/console &>/dev/console
echo "Starting debug shell at $1"
exec sh -i
}
die()
{
step="${*:-$step}"
echo -e "\r\e[0K[\e[31mFAIL\e[0m] $step" >/dev/console
echo "<0>ix-initramfs: ERROR: $step" >/dev/kmsg
debug_shell error
exit 1
}
warn()
{
step="${*:-$step}"
echo -e "\r\e[0K[\e[33mWARN\e[0m] $step" >/dev/console
echo "<5>ix-initramfs: WARNING: $step" >/dev/kmsg
debug_shell warning
}
ok()
{
step="${*:-$step}"
echo -e "\r\e[0K[\e[32m OK \e[0m] $step" >/dev/console
echo "<5>ix-initramfs: $step" >/dev/kmsg
}
begin()
{
step="$*"
echo -ne "[\e[33m ⋯ \e[0m] $step" >/dev/console
}
debug()
{
echo "<7>ix-initramfs: $*" >/dev/kmsg
}
mountcore()
{
mount -t proc proc /proc
mount -t sysfs sys /sys
mount -t devtmpfs dev /dev
}
mountroot()
{
mkdir /newroot
mount "$1" /newroot || \
die "Unable to mount rootfs on $1"
ok "Mounting root filesystem on $(basename $1)"
}
switchroot()
{
local innerinit=$(getkparam init)
ok "Switching to root filesystem"
exec switch_root /newroot ${innerinit:-/sbin/init}
}
internal_get_part()
{
local _i _pv
for _i in $(seq 50); do
_pv=$(lvm pvs --reportformat json \
| jq -r '.report[].pv[] | select(.vg_name == "internal") | .pv_name')
if [ "$_pv" ]; then
[ $_i -gt 5 ] && ok
echo "$_pv"
return 0
fi
[ $_i -eq 5 ] && begin "Waiting for the internal volume to come online"
sleep .2
done
warn "Proceeding without the internal volume"
return 0
}
internal_inflate()
{
local _dev _growfs _index _part
getkopt rd.nointernal && return 0
GPT_PART_GROWFS_BIT=59
_part=$(internal_get_part)
[ "$_part" ] || return 0
_index=$(cat /sys/class/block/$(basename "$_part")/partition)
[ "$_index" ] || return 1
_dev=/dev/$(basename $(realpath /sys/class/block/$(basename "$_part")/../))
[ -b "$_dev" ] || return 1
sgdisk "$_dev" -A "$_index":get:$GPT_PART_GROWFS_BIT \
| grep -q "$_index:$GPT_PART_GROWFS_BIT:1" \
|| return 0
begin "Inflating internal VG in $_part"
# First, expand the GPT to cover the entire disk and expand the
# LVM partition as much as possible.
if ! sgdisk "$_dev" \
--disk-guid=random \
--set-alignment=1 \
--move-second-header \
--delete="$_index" \
--largest-new="$_index" --typecode="$_index":8e00 \
>/tmp/internal-inflate.log 2>&1; then
warn "Failed to inflate GPT on $_dev"
return 0
fi
if ! lvm pvresize "$_part" >>/tmp/internal-inflate.log 2>&1; then
warn "Failed to resize PV $_part"
return 0
fi
ok "Inflated internal VG in $_part"
}
addparts()
{
local _disk="$1"
local _partscan="/sys/class/block/"$(basename "$_disk")/partscan
[ -f "$_partscan" ] && [ $(cat "$_partscan") -eq 1 ] && return
kpartx -a "$_disk" 2>/dev/null || die "addparts: Unable to add partitions from $_disk"
}
gptuuid_probe()
{
local _uuid=$(echo "$1" | sed -e 's/^GPTUUID=//' | tr ABCDEF abcdef)
local _cand _disk _initrd
[ -f /initrd.image ] && _initrd=/initrd.image
for _disk in $_initrd $(find /dev/ -type b); do
_cand=$(ddi "$_disk" inspect uuid | tr ABCDEF abcdef)
[ $? -eq 0 ] || continue
if [ "$_cand" = "$_uuid" ]; then
ok "Located $_uuid in $_disk"
echo "$_disk"
return
fi
done
die "gptuuid_probe: Unable to find disk $_uuid"
}
ddi_probe()
{
local _disk="$1"
local _dm="$(basename $_disk).root"
local _hash=$(getkparam rd.ddi.roothash)
local _v _trust
if [ -n "$_hash" ]; then
ok "Trusting pre-authenticated root hash"
_v="-V $_hash"
else
_v="-v"
fi
_trust=$(ddi "$_disk" part root open $_v "$_dm" 2>&1 \
|| die "ddi_probe: veritysetup of $_disk failed")
ok "$_trust"
ok "Opened verity protected root from $(basename $_disk)"
echo /dev/mapper/"$_dm"
}
lvm_probe()
{
local _vg=$(dirname "$1") _lv=$(basename "$1")
lvm vgchange -ay "$_vg" &>/dev/null || \
die "lvm_probe: Failed to activate the $_vg VG"
[ -b /dev/"$1" ] || die "lvm_probe: Unable to locate the $_lv LV in $_vg"
echo /dev/"$1"
}
lo_probe()
{
local _lo=$(losetup -f || die "probe_lo: found no free loop device for $1")
losetup -P "$_lo" "$1" || die "probe_lo: losetup of $1 to $_lo failed"
debug "lo_probe: setup $_lo backed by $1"
echo "$_lo"
}
probe()
{
local _real _kind
case "$1" in
/*)
_real=$(realpath "$1")
_kind=$(stat -c %F "$_real" 2>/dev/null || true)
case "$_kind" in
"block special file")
addparts "$_real"
echo "$_real"
;;
"regular file")
lo_probe "$_real"
;;
"")
die "Unknown root \"$1\""
;;
*)
die "Unsupported root \"$1\": $_kind"
;;
esac
;;
lvm)
shift
probe $(lvm_probe "$@")
;;
ddi)
shift
ddi_probe $(probe "$@")
;;
GPTUUID=*)
gptuuid_probe "$@"
;;
esac
}
mountcore
debug_shell internal
internal_inflate
debug_shell probe
rootdev=$(probe $(getkparam root | sed -e 's/:/ /g'))
debug_shell mount
mountroot "$rootdev"
debug_shell switchroot
switchroot
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
progs="blkid ddi dmsetup kpartx jq lvm openssl sgdisk veritysetup"
check() {
require_binaries $progs || return 1
return 0
}
depends() {
return 0
}
install() {
inst_multiple $progs
inst '/etc/os-release'
inst '/etc/rauc/keys/*'
cp "$moddir/init" "${initdir?}/init"
}
+279
View File
@@ -0,0 +1,279 @@
mainmenu "QEMU @ARCH@ Virtualization"
config QEMU_@ARCH@
bool
default y
config QEMU_ARCH
string
default "@ARCH@"
config QEMU_VIRTIO_BUS
string
default "device" if QEMU_arm
default "pci"
config QEMU_BIN_DDI
string
default "@IMAGE@"
config QEMU_BIN_DISK
string
default "@DISK@"
config QEMU_BIN_ESP
string
default "@ESP@"
config QEMU_BIN_KERNEL
string
depends on QEMU_LOADER_QEMU
default "../zImage" if QEMU_arm
default "../Image" if QEMU_aarch64
default "../bzImage" if QEMU_x86_64
config QEMU_BIN_LVM_STUB
string
default "../lvm-stub.disk"
choice
prompt "Loader"
default QEMU_LOADER_QEMU
config QEMU_LOADER_QEMU
bool "QEMU"
config QEMU_LOADER_OVMF
bool "OVMF (UEFI)"
endchoice
config QEMU_BIN_BIOS
string
depends on !QEMU_LOADER_KERNEL
default "OVMF.fd" if QEMU_LOADER_OVMF
menu "Machine"
config QEMU_MACHINE
string "Machine"
default "pc" if QEMU_x86_64
default "virt"
config QEMU_CPU
string "CPU"
default "host" if QEMU_x86_64
default "cortex-a53" if QEMU_aarch64
default "max"
config QEMU_RAM
string "RAM size (k/M/G)"
default "384M"
choice
prompt "Console"
default QEMU_CONSOLE_VIRTIO
config QEMU_CONSOLE_VIRTIO
bool "Virtio (hvc0)"
config QEMU_CONSOLE_SERIAL
bool "Serial (ttyS0/ttyAMA0)"
endchoice
config QEMU_CONSOLE
string
default "hvc0" if QEMU_CONSOLE_VIRTIO
default "ttyAMA0" if QEMU_arm
default "ttyAMA0" if QEMU_aarch64
default "ttyS0"
endmenu
menu "Disks & Filesystems"
config QEMU_DISK_INITRD
bool "Use the DDI as initrd"
depends on QEMU_LOADER_QEMU
help
Pass, and boot from, the DDI as the initial RAM disk.
This is useful to test netbooting scenarios, e.g., in
combination with an empty system disk to simulate an initial
deployment.
comment "System disk"
choice
prompt "Interface"
default QEMU_DISK_SYS_IF_VIRTIO
config QEMU_DISK_SYS_IF_VIRTIO
bool "virtio"
help
Attach system disks using virtio block devices.
endchoice
choice
prompt "Contents"
default QEMU_DISK_SYS_SPLIT
config QEMU_DISK_SYS_EMPTY
bool "Empty disk"
help
Attach an empty disk.
This is useful for testing things like installing Infix from a
USB mass storage device (see "USB Mass Storage" below).
config QEMU_DISK_SYS_FULL
bool "Full system disk"
help
Use the full system disk containing:
- EFI partition (and backup) with Barebox
- LVM PV with Infix installed
This is useful when testing system upgrades, fallback
mechanisms related to corrupt disks etc.
WARNING: Usage of this option is fraught with peril as
incremental builds of Infix are likely to cause corruption in
the QCoW2 layer. If you do not understand the meaning of this
you most likely to NOT want to make use of this feature.
config QEMU_DISK_SYS_NONE
bool "None"
help
Do not attach any system disk.
This is useful for testing how Infix behaves when no persistent
storage is available - a common scenario during bringup of a
new board.
config QEMU_DISK_SYS_SPLIT
bool "Split ESP/DDI/LVM"
help
Attach the EFI System partition, containing Barebox; the raw
DDI image; and an (initially empty) LVM stub for persistent
storage - each as a separate disk.
This is the simplest way of testing out incremental builds of
Infix. Since the DDI is attached as a raw read-only disk, there
is no risk of CoW corruption like with a full system disk.
NOTE: In this setup, the size below refers to the size of the
LVM stub.
endchoice
config QEMU_DISK_SYS_SIZE
string "Size (k/M/G)"
default "8G"
comment "USB mass storage"
choice
prompt "Contents"
default QEMU_DISK_USB_NONE
config QEMU_DISK_USB_DISK
bool "Full system disk"
help
Attach the full system disk containing:
- EFI partition (and backup) with Barebox
- LVM PV with Infix installed
config QEMU_DISK_USB_NONE
bool "None"
help
Do not attach any disk over USB.
endchoice
config QEMU_DISK_USB_SIZE
string "Size (k/M/G)"
depends on !QEMU_DISK_USB_NONE
default "4G"
comment "Host filesystem passthrough (9P)"
config QEMU_DISK_HOST
string "Path"
default "/tmp"
endmenu
menu "Networking"
choice
prompt "Mode"
default QEMU_NET_USER
config QEMU_NET_BRIDGE
bool "Bridged"
config QEMU_NET_NONE
bool "None"
config QEMU_NET_ROCKER
bool "Rocker"
config QEMU_NET_TAP
bool "TAP"
config QEMU_NET_USER
bool "User"
endchoice
config QEMU_NET_MODE
string
default "bridge" if QEMU_NET_BRIDGE
default "none" if QEMU_NET_NONE
default "rocker" if QEMU_NET_ROCKER
default "tap" if QEMU_NET_TAP
default "user" if QEMU_NET_USER
config QEMU_NET_MODEL
string "Interface model"
default "virtio-net-device" if QEMU_arm
default "virtio-net-pci"
help
The default virtio NIC works for most use-cases, but if you
want to play with low-level stuff like ethtool, you might
want to test the Intel 82545EM driver, e1000.
Note: ARM 32-bit uses virtio-net-device (MMIO) by default.
config QEMU_NET_BRIDGE_DEV
string "Bridge device"
depends on QEMU_NET_BRIDGE
default "virbr0"
config QEMU_NET_USER_OPTS
string "User mode options"
depends on QEMU_NET_USER
help
Extra -nic user,<OPTIONS>
config QEMU_NET_TAP_N
int "Number of TAPs"
depends on QEMU_NET_TAP
default 1
config QEMU_NET_PORTS
int "Number of Rocker switch ports"
depends on QEMU_NET_ROCKER
default 10
endmenu
config QEMU_EXTRA
string "Extra QEMU options"
config QEMU_APPEND
string "Extra kernel options"
depends on QEMU_LOADER_QEMU
+480
View File
@@ -0,0 +1,480 @@
#!/bin/sh
# This script can be used to start an Infix OS image in Qemu. It reads
# either a .config, generated from Config.in, or qemu.cfg from a release
# tarball, for the required configuration data.
#
# Debian/Ubuntu users can change the configuration post-release, install
# the kconfig-frontends package:
#
# sudo apt install kconfig-frontends
#
# and then call this script with:
#
# ./run.sh -c
#
# To bring up a menuconfig dialog. Select `Exit` and save the changes.
# For more help, see:_
#
# ./run.sh -h
#
# shellcheck disable=SC3037
# Add /sbin to PATH for mkfs.ext4 and such (not default in debian)
export PATH="/sbin:/usr/sbin:$PATH"
qdir=$(dirname "$(readlink -f "$0")")
imgdir=$(readlink -f "${qdir}/..")
prognm=$(basename "$0")
usage()
{
cat <<EOF
usage: $prognm [OPTIONS] [-- KERNEL-ARGS]
Start Infix in a VM.
Any arguments after -- are passed to the kernel, provided that QEMU's
native loader is being used (i.e., not UEFI, for example). This
includes a second --, which the kernel uses to delimit between kernel
arguments and those passed to the init process.
Options:
-0
Clear any copy-on-write layers, resetting all disks to their
initial states, and exit.
-c
Run menuconfig to change Qemu settings. This requires the
'kconfig-frontends' package (Debian/Ubuntu).
-G
Skip config generation. This is useful if you are testing out new
QEMU features by manually modifying qemu.cfg.
-h
Show this help message
Example:
$prognm -- loglevel=4 -- finit.debug
Will set the kernel's loglevel to 4, and pass finit.debug as an
argument to PID 1.
EOF
}
die()
{
echo "$prognm: $*" >&2
exit 1
}
binpath()
{
case "$1" in
./*|../*)
# Relative paths are relative to the location of .config
printf "$qdir/$1"
;;
*)
printf "$1"
;;
esac
}
q()
{
local _cmd="$1"
shift
case "$_cmd" in
sect)
printf ' \\\n\t' >>"$qdir"/qemu.sh
case $# in
1)
printf -- "-$1" >>"$qdir"/qemu.sh
_delim=" "
;;
2)
printf -- "-$1 $2" >>"$qdir"/qemu.sh
_delim=","
;;
*)
die "q sect: Invalid arguments"
;;
esac
;;
param)
[ $# -eq 2 ] || die "q param: Takes exactly two arguments"
printf -- "$_delim$1=$2" >>"$qdir"/qemu.sh
_delim=","
;;
option)
[ $# -eq 1 ] || die "q option: Takes exactly one argument"
printf -- "$_delim$1" >>"$qdir"/qemu.sh
_delim=","
;;
*)
die "q: Unknown command: $_cmd"
;;
esac
}
q_sect_device_virtio()
{
q sect device virtio-$1-$QEMU_VIRTIO_BUS
}
q_add_disk()
{
if [ "$QEMU_DISK_SYS_IF_VIRTIO" ]; then
q_sect_device_virtio blk
q param drive "$1"
else
die "Unknown system disk interface"
fi
}
q_sect_device_usb()
{
if [ -z "$usb_bus_added" ]; then
q sect usb
q sect device usb-ehci
q param id ehci
usb_bus_added=YES
fi
q sect device "$1"
q param bus ehci.0
}
qcowed()
{
local _qcow="$qdir"/"$1".qcow2
local _base _size
if [ -f "$_qcow" ] && qemu-img check -q "$_qcow"; then
echo "$_qcow"
return
fi
rm -f "$_qcow"
case $# in
2)
_size="$2"
qemu-img create -q -f qcow2 "$_qcow" "$_size" \
|| die "Unable to create $1"
;;
3)
_base="$2"
_size="$3"
[ "$_size" = "auto" ] && _size=
qemu-img create -q -f qcow2 -F raw \
-o backing_file="$_base" "$_qcow" $_size \
|| die "Unable to create CoW layer for $_base"
;;
*)
die "qcowed: usage: qcowed <name> [<backing-file>] <size>"
;;
esac
echo "$_qcow"
}
append()
{
echo -n " $*" >>"$qdir"/append
}
gen_machine()
{
q sect machine
q param type "$QEMU_MACHINE"
q param accel kvm:tcg
q sect cpu "$QEMU_CPU"
q sect m
q param size "$QEMU_RAM"
}
gen_loader()
{
if [ "$QEMU_LOADER_QEMU" ]; then
q sect kernel $(binpath "$QEMU_BIN_KERNEL")
elif [ "$QEMU_LOADER_OVMF" ]; then
q sect bios $(binpath "$QEMU_BIN_BIOS")
fi
}
gen_serial()
{
q sect display none
q_sect_device_virtio serial
q sect chardev stdio
q param id console0
q param mux on
q sect mon
q param chardev console0
case "$QEMU_CONSOLE" in
hvc0)
q sect device virtconsole
q param nr 0
q param name console
q param chardev console0
;;
ttyS0|ttyAMA0)
q sect serial chardev:console0
;;
*)
die "Unsupported console: $QEMU_CONSOLE"
;;
esac
append console="$QEMU_CONSOLE"
[ "$V" ] && append debug || append loglevel=4
}
gpt_uuid()
{
sgdisk "$1" -p | awk '
BEGIN { err = 1; } END { exit(err); }
/^Creating new GPT/ { exit; }
/^Disk identifier \(GUID\): / {
print($4); err = 0; exit;
}' || die "Unable to determine GPT UUID of $1"
}
gen_disk_sys_empty()
{
q sect drive
q param id disk-sys
q param format qcow2
q param if none
q param file $(qcowed disk-sys "$QEMU_DISK_SYS_SIZE")
q_add_disk disk-sys
}
gen_disk_sys_full()
{
q sect drive
q param id disk-sys
q param format qcow2
q param if none
q param file $(qcowed disk-sys $(binpath "$QEMU_BIN_DISK") \
"$QEMU_DISK_SYS_SIZE")
q_add_disk disk-sys
append root=ddi:lvm:internal/$(basename "$QEMU_BIN_DDI")
}
gen_disk_sys_split()
{
# EFI System Partition
q sect drive
q param id disk-esp
q param format qcow2
q param if none
q param file $(qcowed disk-esp $(binpath "$QEMU_BIN_ESP") auto)
q_add_disk disk-esp
# LVM stub
q sect drive
q param id disk-sys
q param format qcow2
q param if none
q param file $(qcowed disk-sys $(binpath "$QEMU_BIN_LVM_STUB") \
"$QEMU_DISK_SYS_SIZE")
q_add_disk disk-sys
# DDI (ro)
q sect drive
q param id disk-ddi
q param format raw
q param file $(binpath "$QEMU_BIN_DDI")
q param if none
q param read-only on
q_add_disk disk-ddi
append root=ddi:GPTUUID=$(gpt_uuid $(binpath "$QEMU_BIN_DDI"))
}
gen_disk_sys()
{
[ "$QEMU_DISK_SYS_NONE" ] && return
if [ "$QEMU_DISK_SYS_EMPTY" ]; then
gen_disk_sys_empty
elif [ "$QEMU_DISK_SYS_FULL" ]; then
gen_disk_sys_full
elif [ "$QEMU_DISK_SYS_SPLIT" ]; then
gen_disk_sys_split
else
die "Unknown system disk mode"
fi
}
gen_disk_usb()
{
[ "$QEMU_DISK_USB_NONE" ] && return
if [ "$QEMU_DISK_USB_DISK" ]; then
q sect drive
q param id disk-usb
q param format qcow2
q param file $(qcowed disk-usb \
$(binpath "$QEMU_BIN_DISK") \
"$QEMU_DISK_USB_SIZE")
q param if none
q_sect_device_usb usb-storage
q param drive disk-usb
else
die "Unknown USB attachment"
fi
}
gen_disk_initrd()
{
[ "$QEMU_DISK_INITRD" ] || return
q sect initrd $(binpath "$QEMU_BIN_DDI")
append root=ddi:/initrd.image
}
internal_is_available()
{
[ "$QEMU_DISK_SYS_FULL" ] || [ "$QEMU_DISK_SYS_SPLIT" ] \
|| [ "$QEMU_DISK_USB_DISK" ]
}
gen_disks()
{
gen_disk_sys
gen_disk_usb
gen_disk_initrd
internal_is_available || append rd.nointernal
}
gen_gdb()
{
# Create a UNIX socket on the host that is connected to a virtio
# console in the guest, which gdbserver can attach to for
# userspace debugging.
q sect chardev socket
q param id gdbserver
q param path "$qdir"/gdbserver.sock
q param server on
q param wait off
q sect device virtconsole
q param nr 1
q param name gdbserver
q param chardev gdbserver
# Create a UNIX socket on the host that is connected to QEMU's GDB
# stub, for bootloader/kernel debugging.
q sect chardev socket
q param id gdbqemu
q param path "$qdir"/gdbqemu.sock
q param server on
q param wait off
q sect gdb chardev:gdbqemu
}
gen_all()
{
local _append
: >"$qdir"/append
cat <<EOF >"$qdir"/qemu.sh
#!/bin/sh
echo "Starting Qemu :: Ctrl-a x -- exit | Ctrl-a c -- toggle console/monitor"
line=\$(stty -g)
stty raw
trap 'stty "\$line"' EXIT INT TERM
qemu-system-$QEMU_ARCH -nodefaults \\
EOF
chmod +x "$qdir"/qemu.sh
gen_machine
gen_loader
gen_serial
gen_disks
gen_gdb
if [ "$QEMU_LOADER_QEMU" ]; then
_append=$(cat "$qdir"/append)
q sect append "\"$_append $QEMU_APPEND $*\""
fi
echo >>"$qdir"/qemu.sh
}
menuconfig()
{
command -v kconfig-mconf >/dev/null \
|| die "cannot find kconfig-mconf for menuconfig"
CONFIG_= KCONFIG_CONFIG="$qdir"/.config \
kconfig-mconf "$qdir"/Config.in
}
_generate=YES
while getopts "0cGh-" opt; do
case ${opt} in
0)
echo "Clearing all copy-on-write layers" >&2
rm -f "$qdir"/*.qcow2
exit 0
;;
c)
menuconfig
;;
G)
_generate=
;;
h)
usage && exit 0
;;
-)
break
;;
*)
usage && exit 1
;;
esac
done
shift $((OPTIND - 1))
# shellcheck disable=SC1090
. "$qdir"/.config
[ "$_generate" ] && gen_all "$*"
exec "$qdir"/qemu.sh
@@ -149,7 +149,7 @@ config QEMU_HOST
config QEMU_APPEND
string "Extra kernel options"
depends on !QEMU_ROOTFS_MMC
depends on QEMU_LOADER_KERNEL
config QEMU_EXTRA
string "Extra QEMU options"
+15 -7
View File
@@ -4,9 +4,15 @@
#
################################################################################
QEMU_SCRIPTS_DIR := $(pkgdir)
qemu-scripts-dir := $(pkgdir)/$(if $(IMAGE_DDI),ddi,itb)
qemu-image := ../$(INFIX_ARTIFACT).$(if $(IMAGE_DDI),raw,qcow2)
qemu-disk := $(if $(IMAGE_DDI_DISK),../$(INFIX_ARTIFACT).disk)
qemu-esp := $(if $(IMAGE_BAREBOX_ESP),../barebox-esp.vfat)
qemu-kconfig-prefix := $(if $(IMAGE_DDI),,CONFIG_)
qemu-kconfig = \
CONFIG_="CONFIG_" \
CONFIG_="$(qemu-kconfig-prefix)" \
BR2_CONFIG="$(BINARIES_DIR)/qemu/.config" \
$(BUILD_DIR)/buildroot-config/$(1) $(2) "$(BINARIES_DIR)/qemu/Config.in"
@@ -25,17 +31,19 @@ qemu-scripts: \
$(BINARIES_DIR)/qemu/Config.in \
$(BINARIES_DIR)/qemu/.config
$(BINARIES_DIR)/qemu/run.sh: $(QEMU_SCRIPTS_DIR)/run.sh
$(BINARIES_DIR)/qemu/run.sh: $(qemu-scripts-dir)/run.sh
@$(call IXMSG,"Installing QEMU scripts")
@mkdir -p $(dir $@)
@cp $< $@
$(BINARIES_DIR)/qemu/Config.in: $(QEMU_SCRIPTS_DIR)/Config.in.in
$(BINARIES_DIR)/qemu/Config.in: $(qemu-scripts-dir)/Config.in.in
@mkdir -p $(dir $@)
@sed \
-e "s:@ARCH@:QEMU_$(BR2_ARCH):" \
-e "s:@DISK_IMG@:../$(INFIX_ARTIFACT).qcow2:" \
< $< >$@
-e "s:@ARCH@:$(BR2_ARCH):g" \
-e "s:@IMAGE@:$(qemu-image):g" \
-e "s:@DISK@:$(qemu-disk):g" \
-e "s:@ESP@:$(qemu-esp):g" \
<$< >$@
$(BINARIES_DIR)/qemu/.config: $(BINARIES_DIR)/qemu/Config.in
@$(call qemu-kconfig,conf,--olddefconfig)
+343
View File
@@ -0,0 +1,343 @@
#!/bin/sh
set -e
capath=/etc/rauc/keys
usage()
{
local _ddi="$(basename $0)"
cat <<EOF
usage: $_ddi [<options>] <image> <command> [<args>]
Discoverable Disk Image manipulation
Options:
-A <arch>
Override the system architecture.
-C <path>
Source trusted signing keys from the supplied directory.
Default: $capath
-h
Show this message and exit.
Commands:
inspect <property>
uuid
Print GPT disk identifier
part <partition> <command> [<args]
Where <partition> is one of:
root The first root partition
open [<options>] <name>
Create a device mapping for the specified partition, called
<name>.
Options:
-v
Use the associated dm-verity hash tree, whose root hash is
available in the associated signature partition.
-V <root-hash>
Use the associated dm-verity hash tree, along with an
externally validated root hash.
verify [<options>]
Verify that the specified partition exists and, optionally,
is accompanied by a trusted dm-verity hash tree.
Options:
-v
Require the presence of an associated dm-verity hash tree,
whose root hash is available in the associated signature
partition.
-V <root-hash>
Require the presence of an associated dm-verity hash tree
whose root hash matches the provided one.
Examples:
Map and mount the verity protected SquashFS root partition from an
image:
$_ddi infix-x86_64.raw part root open -v infix
mount -t squashfs /dev/mapper/infix /opt
EOF
}
X86_64_CODE_ROOT=0x8304
X86_64_CODE_ROOT_VERITY=0x830c
X86_64_CODE_ROOT_VERITY_SIG=0x8370
die()
{
echo "ERROR: $*" >&2
exit 1
}
ok()
{
echo "$*" >&2
}
setup_arch()
{
case "$1" in
x86_64)
CODE_ROOT=$X86_64_CODE_ROOT
CODE_ROOT_VERITY=$X86_64_CODE_ROOT_VERITY
CODE_ROOT_VERITY_SIG=$X86_64_CODE_ROOT_VERITY_SIG
;;
*)
die "Unsupported architecture"
esac
}
setup_work()
{
local _umask
_umask=$(umask)
umask 077
workdir=$(mktemp -d)
umask $_umask
trap 'rm -rf "$workdir"' EXIT INT TERM
}
ddi_to_block()
{
local _kind _lo
_kind=$(stat -Lc %F "$1")
case "$_kind" in
"block special file")
echo "$1"
return
;;
"regular file")
_lo=$(losetup -f || die "Found no free loop device for $1")
losetup -P "$_lo" "$1" || die "Failed to setup loop device of $1"
echo "$_lo"
return
;;
esac
die "$1 is a $_kind, only regular and block devices are supported"
}
ddi_get_part_info()
{
local _code _item
case "$2" in
root) _code="$CODE_ROOT";;
root-verity) _code="$CODE_ROOT_VERITY";;
root-verity-sig) _code="$CODE_ROOT_VERITY_SIG";;
*) die "Unknown partition type \"$1\"" ;;
esac
_code=$(printf "%04X" "$_code")
_item=${3:-exists}
sgdisk "$1" -p | awk -v code="$_code" -v item="$_item" '
/^[ ]*[0-9]+/ {
if ($6 != code)
next;
if (item == "exists") exit(0);
if (item == "index") { print($1); exit(0); }
exit(1);
}
'
}
ddi_get_part_dev()
{
local _dev _index _lvuuid _subsys
_index=$(ddi_get_part_info "$1" "$2" index \
|| die "No $2 partition found on $1")
_subsys=$(dmsetup info -c --noheadings -o subsystem "$1" 2>/dev/null || true)
if [ "$_subsys" = "LVM" ]; then
_lvuuid=$(dmsetup info -c --noheadings -o uuid "$1")
_dev=/dev/mapper/$(dmsetup info -c --noheadings -o name -u "part${_index}-$_lvuuid")
else
_dev=$(basename "$1")
_dev=$(find /sys/block/"$_dev"/ -mindepth 1 -maxdepth 1 -type d \
-name "${_dev}$_index" -o -name "${_dev}p$_index")
_dev=/dev/$(basename "$_dev")
fi
[ -b "$_dev" ] || die "Failed to locate $2 partition in $1"
echo "$_dev"
}
ddi_get_root_hash()
{
local _subj
command -v openssl >/dev/null || die "Missing dependency: openssl"
command -v jq >/dev/null || die "Missing dependency: jq"
jq -r .rootHash "$1" | tr -d '\n' >"$workdir"/hash \
|| die "Invalid signature: rootHash is missing"
jq -r .signature "$1" | base64 -d >"$workdir"/sig \
|| die "Invalid signature: signature data is missing"
openssl cms -verify -verify_retcode -CApath "$capath" \
-content "$workdir"/hash \
-in "$workdir"/sig -inform DER \
-certsout "$workdir"/certs >/dev/null 2>&1 \
|| die "Signature verification failed"
_subj=$(openssl x509 -noout -subject <"$workdir"/certs | sed -e 's/^subject=//')
ok "Trusting root hash signature from $_subj"
cat "$workdir"/hash
}
ddipart_get_verity()
{
local _hash
while getopts "vV:" opt; do
case "$opt" in
V)
vkind="${kind}-verity"
vhash="$OPTARG"
;;
v)
vkind="${kind}-verity"
;;
esac
done
[ "$vkind" ] || return
vpart=$(ddi_get_part_dev "$img" "$vkind")
[ "$vhash" ] && return
vskind="${vkind}-sig"
vspart=$(ddi_get_part_dev "$img" "$vskind")
vhash=$(ddi_get_root_hash "$vspart")
}
ddipart_open()
{
[ "$vpart" ] || die "$kind partition is already accessible at $part"
shift $((OPTIND - 1))
veritysetup open "$part" "$1" "$vpart" "$vhash"
}
ddipart_verify()
{
[ "$vpart" ] || return
veritysetup verify "$part" "$vpart" "$vhash"
ok "Image in $img has $kind partition with valid dm-verity hash tree"
}
ddipart()
{
local _cmd
kind="$1"
shift
ddi_get_part_info "$img" "$kind" \
|| die "No $kind partition found on $img"
_cmd="$1"
shift
case "$_cmd" in
open|verify)
img=$(ddi_to_block $img)
part=$(ddi_get_part_dev "$img" "$kind")
ddipart_get_verity "$@"
ddipart_$_cmd "$@"
;;
*)
die "Unknown partition command \"$_cmd\""
;;
esac
}
ddiinspect()
{
case "$1" in
uuid)
sgdisk "$img" -p | awk '
BEGIN { err = 1; } END { exit(err); }
/^Creating new GPT/ { exit; }
/^Disk identifier \(GUID\): / {
print($4); err = 0; exit;
}'
;;
*)
die "Unknown property: $1"
;;
esac
}
_arch=$(uname -m)
while getopts "A:C:h" opt; do
case ${opt} in
A)
_arch="$OPTARG"
;;
C)
capath="$OPTARG"
;;
h)
usage && exit 0
;;
esac
done
shift $((OPTIND - 1))
if [ $# -lt 2 ]; then
usage && exit 1
fi
img="$1"
cmd="$2"
shift 2
setup_arch "$_arch"
setup_work
case "$cmd" in
inspect)
ddiinspect "$@"
;;
part)
ddipart "$@"
;;
*)
echo "Unknown command \"$cmd\"" >2
exit 1
;;
esac
@@ -0,0 +1,3 @@
CODE_ROOT=0x8304
CODE_ROOT_VERITY=0x830c
CODE_ROOT_VERITY_SIG=0x8370
+13 -10
View File
@@ -37,6 +37,7 @@ BR2_PACKAGE_STRESS_NG=y
BR2_PACKAGE_JQ=y
BR2_PACKAGE_E2FSPROGS=y
BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
BR2_PACKAGE_CRYPTSETUP=y
BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=y
BR2_PACKAGE_EUDEV_RULES_GEN=y
@@ -87,6 +88,7 @@ BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_GETENT=y
BR2_PACKAGE_KMOD_TOOLS=y
BR2_PACKAGE_MULTIPATH_TOOLS=y
BR2_PACKAGE_PWGEN=y
BR2_PACKAGE_RAUC=y
BR2_PACKAGE_RAUC_DBUS=y
@@ -95,6 +97,7 @@ BR2_PACKAGE_RAUC_NETWORK=y
BR2_PACKAGE_RAUC_JSON=y
BR2_PACKAGE_SYSKLOGD=y
BR2_PACKAGE_SYSKLOGD_LOGGER=y
BR2_PACKAGE_UTIL_LINUX_PARTX=y
BR2_PACKAGE_WATCHDOGD=y
BR2_PACKAGE_WATCHDOGD_GENERIC=y
BR2_PACKAGE_WATCHDOGD_LOADAVG=y
@@ -105,13 +108,16 @@ BR2_PACKAGE_WATCHDOGD_TEMPMON=y
BR2_PACKAGE_LESS=y
BR2_PACKAGE_MG=y
BR2_PACKAGE_NANO=y
BR2_TARGET_ROOTFS_CPIO_DRACUT=y
BR2_TARGET_ROOTFS_CPIO_DRACUT_MODULES="${BR2_EXTERNAL_INFIX_PATH}/board/common/initramfs"
BR2_TARGET_ROOTFS_CPIO_DRACUT_CONF_FILES="${BR2_EXTERNAL_INFIX_PATH}/board/common/dracut.conf"
BR2_TARGET_ROOTFS_INITRAMFS=y
# BR2_TARGET_ROOTFS_TAR is not set
BR2_TARGET_EDK2=y
BR2_TARGET_GRUB2=y
BR2_TARGET_GRUB2_X86_64_EFI=y
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot linux ext2 squash4 part_gpt normal efi_gop configfile loadenv test echo reboot net efinet tftp loopback cat search"
BR2_TARGET_GRUB2_BUILTIN_CONFIG_EFI="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/grub-embed.cfg"
BR2_TARGET_GRUB2_INSTALL_TOOLS=y
BR2_TARGET_BAREBOX=y
BR2_TARGET_BAREBOX_CUSTOM_VERSION=y
BR2_TARGET_BAREBOX_CUSTOM_VERSION_VALUE="2025.11.0"
BR2_TARGET_BAREBOX_BOARD_DEFCONFIG="efi"
BR2_TARGET_BAREBOX_IMAGE_FILE="barebox.efi"
BR2_PACKAGE_HOST_DOSFSTOOLS=y
BR2_PACKAGE_HOST_E2FSPROGS=y
BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y
@@ -150,9 +156,6 @@ BR2_PACKAGE_MDNS_ALIAS=y
BR2_PACKAGE_ONIEPROM=y
BR2_PACKAGE_ROUSETTE=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
IMAGE_DDI=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
+209
View File
@@ -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", "<I8s 32sQ QQ QQ QQ QQ QQ II"
padsize = 512 - struct.calcsize(headfmt) - struct.calcsize(tailfmt)
tail = struct.pack(tailfmt,
32, b"LVM2 001",
self.pvid.bytes, self.size,
# Data area (size=0 ~= all available space (?))
self.align, 0,
0, 0,
# Primary metadata area
0x1000, self.align - 0x1000,
# Backup metadata area
self.align + self.dsize, self.align,
0, 0,
# Supported extensions (Unsure which
# extensions this maps to, but it makes the
# LVM tooling happy :))
2, 1) + b"\0" * padsize
head = struct.pack(headfmt, b"LABELONE", sector, crc32(tail))
return head + tail
def _metadata(self, primary):
headfmt, tailfmt = "<I", "<16sI QQ QQII QQII"
padsize = 512 - struct.calcsize(headfmt) - struct.calcsize(tailfmt)
offs = 0x1000 if primary else (self.align + self.dsize)
size = self.align - 0x1000 if primary else self.align
txt = self._meta_txt()
txtpadsize = size - 512 - len(txt)
tail = struct.pack(tailfmt,
b" LVM2 x[5A%r0N*>", 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()