diff --git a/board/common/Config.in b/board/common/Config.in index f81d61451..48e0181fa 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -1,5 +1,12 @@ + 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" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi-rauc/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-boot/Config.in" @@ -8,10 +15,36 @@ 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-ext4-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 + +menuconfig IX_KERNEL_SIGN + bool "Sign Linux Kernel" + default y if BR2_x86_64 + +config IX_KERNEL_SIGN_KEY + string "signing key" + depends on IX_KERNEL_SIGN + 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 kernel image. + +config IX_KERNEL_SIGN_CERT + string "signing cert" + depends on IX_KERNEL_SIGN + 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. + + config IX_QEMU_SCRIPTS bool "QEMU scripts" default y diff --git a/board/common/barebox/barebox.mk b/board/common/barebox/barebox.mk new file mode 100644 index 000000000..42812f411 --- /dev/null +++ b/board/common/barebox/barebox.mk @@ -0,0 +1,13 @@ +ifeq ($(IX_IMAGE_BAREBOX_ESP),y) + +IX_BAREBOX_DIR := $(BR2_EXTERNAL_INFIX_PATH)/board/common/barebox +IX_BAREBOX_BUILD := $(BUILD_DIR)/infix-bareboxenv + +define BAREBOX_PRE_BUILD_CREATE_ENV + @$(call IXMSG,"Creating Barebox environment") + rsync -a $(IX_BAREBOX_DIR)/env/ $(IX_BAREBOX_BUILD) + dtc <$(IX_BAREBOX_DIR)/state.dts >$(IX_BAREBOX_BUILD)/state.dtb +endef +BAREBOX_PRE_BUILD_HOOKS += BAREBOX_PRE_BUILD_CREATE_ENV + +endif diff --git a/board/common/barebox/env/boot/net b/board/common/barebox/env/boot/net new file mode 100644 index 000000000..641a199fd --- /dev/null +++ b/board/common/barebox/env/boot/net @@ -0,0 +1,24 @@ +#!/bin/sh + +if dhcp $1; then + if [ -z "${global.dhcp.bootfile}" ]; then + echo "net: ERROR: DHCP did not supply a bootfile" + exit 1 + fi +else + echo "net: ERROR: DHCP Failed" + exit 1 +fi + +echo "net: Downloading ${global.dhcp.bootfile}..." +if tftp ${global.dhcp.bootfile} /initrd; then +else + echo "net: ERROR: Download failed" + exit 1 +fi + +global bootm.initrd=/initrd +dmsetup loop initrd /initrd && boot /dev/initrd + +echo "net: ERROR: Unable to boot from ${global.dhcp.bootfile}" +exit 1 diff --git a/board/common/barebox/env/init/infix b/board/common/barebox/env/init/infix new file mode 100755 index 000000000..d83079a9e --- /dev/null +++ b/board/common/barebox/env/init/infix @@ -0,0 +1,20 @@ +#!/bin/sh + +global allow_color=1 +global model=efi +export PS1="\e[1;32mbarebox@\h\e[0m:\e[1;36m\w\e[0m# " + +global autoboot_timeout=1 +global boot.default="bootchooser storage" +global bootchooser.reset_attempts="all-zero" +global bootchooser.retry=1 +global bootchooser.state_prefix="state.bootstate" +global bootchooser.targets="internal-primary internal-secondary net" + +global blspec.require_trust=${efi.secure_mode} +global ddi.require_trust=${efi.secure_mode} + +keys -l os @efi + +lvm activate + diff --git a/board/common/barebox/env/menu/mainmenu b/board/common/barebox/env/menu/mainmenu new file mode 100644 index 000000000..6b8c2fc52 --- /dev/null +++ b/board/common/barebox/env/menu/mainmenu @@ -0,0 +1,4 @@ +#!/bin/sh + +boot -m +reset diff --git a/board/common/barebox/state.dts b/board/common/barebox/state.dts new file mode 100644 index 000000000..2991b3af9 --- /dev/null +++ b/board/common/barebox/state.dts @@ -0,0 +1,79 @@ +/dts-v1/; + +/ { + aliases { + state = &state; + }; + + partitions { + compatible = "fixed-partitions"; + + statepart: state { + partuuid = "80ca5554-c020-11f1-ba4d-b3c49d5dbf47"; + }; + }; + + state: state { + compatible = "barebox,state"; + magic = <0x80ca5554>; + backend-type = "raw"; + backend = <&statepart>; + backend-stridesize = <0x200>; + #address-cells = <1>; + #size-cells = <1>; + + bootstate { + internal-primary { + #address-cells = <1>; + #size-cells = <1>; + remaining_attempts@0 { + reg = <0x0 0x4>; + type = "uint32"; + default = <3>; + }; + priority@4 { + reg = <0x4 0x4>; + type = "uint32"; + default = <20>; + }; + }; + + internal-secondary { + #address-cells = <1>; + #size-cells = <1>; + remaining_attempts@8 { + reg = <0x8 0x4>; + type = "uint32"; + default = <3>; + }; + priority@c { + reg = <0xc 0x4>; + type = "uint32"; + default = <10>; + }; + }; + + net { + #address-cells = <1>; + #size-cells = <1>; + remaining_attempts@10 { + reg = <0x10 0x4>; + type = "uint32"; + default = <3>; + }; + priority@14 { + reg = <0x14 0x4>; + type = "uint32"; + default = <0>; + }; + }; + + last_chosen@18 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0x18 0x4>; + type = "uint32"; + }; + }; + }; +}; diff --git a/board/common/common.mk b/board/common/common.mk index e0586379d..fd31bfb01 100644 --- a/board/common/common.mk +++ b/board/common/common.mk @@ -1,6 +1,20 @@ +include $(BR2_EXTERNAL_INFIX_PATH)/board/common/barebox/barebox.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/image/image.mk +include $(BR2_EXTERNAL_INFIX_PATH)/board/common/initramfs/initramfs.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/qemu/qemu.mk +ifeq ($(IX_KERNEL_SIGN),y) +define LINUX_POST_BUILD_SIGN + @$(call IXMSG,"Signing $(notdir $(LINUX_IMAGE_PATH))") + sbsign \ + --key "$(IX_KERNEL_SIGN_KEY)" \ + --cert "$(IX_KERNEL_SIGN_CERT)" \ + --output "$(LINUX_IMAGE_PATH)" \ + "$(LINUX_IMAGE_PATH)" +endef +LINUX_POST_BUILD_HOOKS += LINUX_POST_BUILD_SIGN +endif + ifeq ($(IX_TRUSTED_KEYS),y) include $(BR2_EXTERNAL_INFIX_PATH)/board/common/uboot/uboot.mk diff --git a/board/common/image/image-barebox-esp/Config.in b/board/common/image/image-barebox-esp/Config.in new file mode 100644 index 000000000..af12ba9d6 --- /dev/null +++ b/board/common/image/image-barebox-esp/Config.in @@ -0,0 +1,23 @@ +menuconfig IX_IMAGE_BAREBOX_ESP + bool "Barebox EFI System Partition" + depends on BR2_TARGET_BAREBOX + select BR2_PACKAGE_DT_UTILS + help + An EFI System Partition (ESP) with Barebox installed. + +config IX_IMAGE_BAREBOX_ESP_KEY + string "signing key" + depends on IX_IMAGE_BAREBOX_ESP + 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 Barebox EFI payload. + +config IX_IMAGE_BAREBOX_ESP_CERT + string "signing cert" + depends on IX_IMAGE_BAREBOX_ESP + 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. + diff --git a/board/common/image/image-barebox-esp/generate.sh b/board/common/image/image-barebox-esp/generate.sh new file mode 100755 index 000000000..acec48910 --- /dev/null +++ b/board/common/image/image-barebox-esp/generate.sh @@ -0,0 +1,44 @@ +#!/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 + +sbsign \ + --key "${KEY}" \ + --cert "${CERT}" \ + --output "${BINARIES_DIR}"/barebox.efi.signed \ + "${BINARIES_DIR}"/barebox.efi + +cat <"${WORKDIR}"/genimage.cfg +image barebox-esp.vfat { + size = "16M" + vfat { + file EFI/BOOT/$BOOT_EFI { + image = $BINARIES_DIR/barebox.efi.signed + } + } +} + +# 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-barebox-esp/image-barebox-esp.mk b/board/common/image/image-barebox-esp/image-barebox-esp.mk new file mode 100644 index 000000000..758448ef1 --- /dev/null +++ b/board/common/image/image-barebox-esp/image-barebox-esp.mk @@ -0,0 +1,9 @@ +################################################################################ +# +# image-barebox-esp +# +################################################################################ + +IMAGE_BAREBOX_ESP_CONFIG_VARS := KEY CERT + +$(eval $(ix-image)) 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 000000000..7b1f60b49 --- /dev/null +++ b/board/common/image/image-ddi-disk/Config.in @@ -0,0 +1,8 @@ +menuconfig IX_IMAGE_DDI_DISK + bool "LVM2 based provisioning disk (DDI)" + depends on IX_IMAGE_DDI + select IX_IMAGE_BAREBOX_ESP + 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 000000000..a4e5966c1 --- /dev/null +++ b/board/common/image/image-ddi-disk/generate.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +set -e + + +mkdir -p "${WORKDIR}"/root +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp + +"${BR2_EXTERNAL_INFIX_PATH}"/utils/lvm-mkinternal \ + primary "${BINARIES_DIR}"/"${ARTIFACT}".raw >"${WORKDIR}"/internal.lvm + +. $BR2_EXTERNAL_INFIX_PATH/board/common/rootfs/etc/partition-uuid +[ -n "${BAREBOX_STATE_UUID}" ] +[ -n "${ESP_UUID}" ] +[ -n "${ESP_BACKUP_UUID}" ] + +cat <"${WORKDIR}"/genimage.cfg + +image ${ARTIFACT}.disk { + hdimage { + partition-table-type = "gpt" + } + + partition esp { + partition-type-uuid = "esp" + partition-uuid = "$ESP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition esp-backup { + partition-type-uuid = "esp" + partition-uuid = "$ESP_BACKUP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition barebox-state { + partition-type-uuid = "barebox-state" + partition-uuid = "$BAREBOX_STATE_UUID" + size = 2048 + } + + partition internal { + growfs = "true" + partition-type-uuid = "lvm" + image = "internal.lvm" + } +} + +image ${ARTIFACT}.qcow2 { + qemu { + format = "qcow2" + } + + partition disk { + image = "${ARTIFACT}.disk" + } +} + +# 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 000000000..e666e0238 --- /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/board/common/image/image-ddi-rauc/Config.in b/board/common/image/image-ddi-rauc/Config.in new file mode 100644 index 000000000..6389bf363 --- /dev/null +++ b/board/common/image/image-ddi-rauc/Config.in @@ -0,0 +1,27 @@ +menuconfig IX_IMAGE_DDI_RAUC + bool "RAUC upgrade bundle (DDI)" + select IX_IMAGE_DDI + select BR2_PACKAGE_HOST_RAUC + help + Create RAUC upgrade bundle, for targets using DDI images, + that can be used to upgrade a running system to this version + of Infix. + +config IX_IMAGE_DDI_RAUC_KEY + string "signing key" + depends on IX_IMAGE_DDI_RAUC + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key" + help + Path to the private key, in PKCS#8 format, used to sign + the RAUC bundle; or a PKCS#11 URI. + +config IX_IMAGE_DDI_RAUC_CERT + string "signing certificate" + depends on IX_IMAGE_DDI_RAUC + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt" + help + Path to the X509 certificate which will be associated with + the bundle signature. + + NOTE: This cert MUST be included in the trust store of the + system on which this bundle is to be installed. diff --git a/board/common/image/image-ddi-rauc/generate.sh b/board/common/image/image-ddi-rauc/generate.sh new file mode 100755 index 000000000..1caf05118 --- /dev/null +++ b/board/common/image/image-ddi-rauc/generate.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +set -e + +ddi="${BINARIES_DIR}"/"${ARTIFACT}.raw" +pkg="${BINARIES_DIR}"/"${ARTIFACT}.pkg" + +cp -f "${PKGDIR}"/hooks.sh "${WORKDIR}"/hooks.sh + +# RAUC <= 1.15 uses the file extension to find a suitable install +# handler, which must be .img in order to select the "raw" type. +cp -f "${ddi}" "${WORKDIR}"/ +ln -sf "${ARTIFACT}.raw" "${WORKDIR}"/ddi.img + +cat >"${WORKDIR}"/manifest.raucm <&2 +} + +die() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +case "$1" in + slot-pre-install) + [ "$RAUC_SLOT_CLASS" = "rootfs" ] || break + + dst=/dev/mapper/"$RAUC_SLOT_BOOTNAME" \ + || die "$RAUC_SLOT_BOOTNAME is not available" + dstsize=$(lvs --reportformat=json --units b "$dst" \ + | jq -r .report[0].lv[0].lv_size \ + | tr -d B) + + src="$RAUC_BUNDLE_MOUNT_POINT"/ddi.img + [ -f "$src" ] \ + || die "No DDI in bundle" + srcsize=$(stat -L -c %s "$src") + + kpartx -d "$dst" \ + || die "Unable to tear down partitions" + + [ "$dstsize" -eq "$srcsize" ] || { + lvm lvresize --yes --size ${newsize}B "$dst" \ + || die "Unable to resize $RAUC_SLOT_BOOTNAME to ${srcsize}B" + } + + note "$RAUC_SLOT_BOOTNAME is ready to accept update" + ;; + *) + exit 1 + ;; +esac diff --git a/board/common/image/image-ddi-rauc/image-itb-rauc.mk b/board/common/image/image-ddi-rauc/image-itb-rauc.mk new file mode 100644 index 000000000..942b050b6 --- /dev/null +++ b/board/common/image/image-ddi-rauc/image-itb-rauc.mk @@ -0,0 +1,10 @@ +################################################################################ +# +# image-ddi-rauc +# +################################################################################ + +IMAGE_DDI_RAUC_DEPENDENCIES := host-rauc image-ddi +IMAGE_DDI_RAUC_CONFIG_VARS := KEY CERT + +$(eval $(ix-image)) diff --git a/board/common/image/image-ddi/Config.in b/board/common/image/image-ddi/Config.in new file mode 100644 index 000000000..42a80abe6 --- /dev/null +++ b/board/common/image/image-ddi/Config.in @@ -0,0 +1,41 @@ +menuconfig IX_IMAGE_DDI + bool "Discoverable Disk Image (DDI)" + select BR2_TARGET_ROOTFS_SQUASHFS + select BR2_PACKAGE_BTRFS_PROGS + select BR2_PACKAGE_CRYPTSETUP + select BR2_PACKAGE_GPTFDISK + select BR2_PACKAGE_GPTFDISK_SGDISK + select BR2_PACKAGE_LVM2 + select BR2_PACKAGE_MULTIPATH_TOOLS + select BR2_PACKAGE_UTIL_LINUX_BINARIES + select BR2_PACKAGE_HOST_GENIMAGE + select BR2_PACKAGE_HOST_DRACUT + 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 IX_IMAGE_DDI_KEY + string "signing key" + depends on IX_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 IX_IMAGE_DDI_CERT + string "signing cert" + depends on IX_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. + diff --git a/board/common/image/image-ddi/generate.sh b/board/common/image/image-ddi/generate.sh new file mode 100755 index 000000000..13d0e9723 --- /dev/null +++ b/board/common/image/image-ddi/generate.sh @@ -0,0 +1,109 @@ +#!/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 + +uuidfmt() +{ + echo $1 | \ + sed -E 's/^([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})$/\1-\2-\3-\4-\5/' +} + +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp "${WORKDIR}"/verity + +cat <"${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 + +# As described in the DPS spec, setting the data partition's UUID to +# the first 128 bits of the roothash and the verity partition's UUID +# to the last 128 bits of the roothash allows for auto-discovery of +# the partitions based only on the roothash. +roothash=$(tr -d '\000' <"${WORKDIR}"/verity/rootfs.verity-sig | jq -r .rootHash) +rootuuid=$(uuidfmt $(echo $roothash | cut -c -32)) +hashuuid=$(uuidfmt $(echo $roothash | rev | cut -c -32 | rev)) + +# Make sure that the final image size is aligned to a 32k byte +# boundary. This lines up with the extent size used by lvm-mkinternal, +# which means that we won't get GPT location warnings from Barebox +# when we activate the LV. +ALIGN_K=32 +blks=$((2+32+32)) +blks=$((blks + (($(stat -c%s "${BINARIES_DIR}"/rootfs.squashfs) + 511) >> 9))) +blks=$((blks + (($(stat -c%s "${WORKDIR}"/verity/rootfs.verity) + 511) >> 9))) +blks=$((blks + (($(stat -c%s "${WORKDIR}"/verity/rootfs.verity-sig) + 511) >> 9))) +size=$((((blks << 9) + ((ALIGN_K << 10) - 1)) & ~((ALIGN_K << 10) - 1))) + +cat <"${WORKDIR}"/genimage-ddi.cfg +image ${ARTIFACT}.raw { + size = ${size} + hdimage { + partition-table-type = "gpt" + } + + partition root { + partition-type-uuid = "root-${arch}" + partition-uuid = "$rootuuid" + image = "rootfs.squashfs" + } + partition root-verity { + partition-type-uuid = "root-${arch}-verity" + partition-uuid = "$hashuuid" + 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 diff --git a/board/common/image/image-ddi/image-ddi.mk b/board/common/image/image-ddi/image-ddi.mk new file mode 100644 index 000000000..bd51942f3 --- /dev/null +++ b/board/common/image/image-ddi/image-ddi.mk @@ -0,0 +1,10 @@ +################################################################################ +# +# image-ddi +# +################################################################################ + +IMAGE_DDI_DEPENDENCIES := host-dracut host-genimage rootfs-squashfs +IMAGE_DDI_CONFIG_VARS := KEY CERT + +$(eval $(ix-image)) diff --git a/board/common/image/image-ddi/rootfs/boot/efi-backup/.empty b/board/common/image/image-ddi/rootfs/boot/efi-backup/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/board/common/image/image-ddi/rootfs/boot/efi/.empty b/board/common/image/image-ddi/rootfs/boot/efi/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf b/board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf new file mode 100644 index 000000000..ac2c5cdbe --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf @@ -0,0 +1 @@ +task name:mark-boot-ok [2345] rauc status mark-good -- diff --git a/board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf b/board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf new file mode 100644 index 000000000..d79a3eac4 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf @@ -0,0 +1,5 @@ +set G_MESSAGES_DEBUG=nocolor +service [2345] notify:systemd \ + pre:/usr/libexec/infix/rauc-config-gen \ + env:-/etc/default/rauc \ + rauc service $RAUC_ARGS -- Software update service diff --git a/board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf b/board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf new file mode 120000 index 000000000..2bdda4b62 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf @@ -0,0 +1 @@ +../available/mark-boot-ok.conf \ No newline at end of file diff --git a/board/common/image/image-ddi/rootfs/etc/fstab b/board/common/image/image-ddi/rootfs/etc/fstab new file mode 100644 index 000000000..9c648940d --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/fstab @@ -0,0 +1,18 @@ +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 + +/dev/disk/by-partuuid/82bb760e-c08e-11f1-a791-7bd312fe0c17 /boot/efi vfat defaults 0 0 +/dev/disk/by-partuuid/835b97d8-c08e-11f1-a2f7-73ad4bbf39e9 /boot/efi-backup vfat 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 diff --git a/board/common/image/image-ddi/rootfs/mnt/rw/.empty b/board/common/image/image-ddi/rootfs/mnt/rw/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt b/board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt new file mode 100755 index 000000000..7bec8c915 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt @@ -0,0 +1,253 @@ +#!/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 + +logcrit() +{ + logger -k -t "$nm" -p user.crit "$@" +} + +lognote() +{ + logger -k -t "$nm" -p user.notice "$@" +} + +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" +} + +internal_rw_factory_reset() +{ + factory --check-clear || return 0 + [ -b /dev/internal/rw ] || return 0 + + print_start "Resetting to factory defaults" + lognote "Resetting to factory defaults" + + lvm lvremove -y internal/rw || { + logcrit "Unable to remove rw volume" + print_end 1 + return 0 + } + + lognote "Factory reset complete" + print_end 0 +} + +subvols="cfg var root home" + +internal_rw_provision() +{ + # 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 + + print_start "Provisioning persistent storage on internal volume" + + lvm lvcreate -y --name rw --extents 50%FREE internal || { + logcrit "No rw LV available, nor could it be created" + print_end 1 + return 1 + } + + mkfs.btrfs -L rw /dev/internal/rw || { + logcrit "Could not create BTRFS on rw LV" + print_end 1 + return 1 + } + + mount -t btrfs /dev/internal/rw /mnt/rw || { + logcrit "Failed to mount rw" + print_end 1 + return 1 + } + + for subvol in $subvols; do + btrfs subvolume create /mnt/rw/@"$subvol" || { + logcrit "Failed to create @$subvol on rw" + print_end 1 + return 1 + } + done + + umount /mnt/rw || { + logcrit "Failed to unmount rw" + print_end 1 + return 1 + } + + print_end 0 +} + +internal_secondary_provision() +{ + local _free _size + + # On the first system boot, if a primary image is available, but a + # secondary one is not, provision a duplicate of primary as the + # secondary image. This let's the distribution disk image be + # small, yet automatically deploys a redundant setup. + [ -f /dev/firstboot ] || return 0 + [ -b /dev/internal/primary ] || return 0 + [ -b /dev/internal/secondary ] && return 0 + + print_start "Provisioning redundant image" + + _free=$(lvm vgs --reportformat=json --units b internal \ + | jq -r .report[0].vg[0].vg_free \ + | tr -d B) + _size=$(lvs --reportformat=json --units b internal/primary \ + | jq -r .report[0].lv[0].lv_size \ + | tr -d B) + + [ "$_free" -ge "$_size" ] || { + logcrit "Skipping redundant $((_size >> 20))MB image, $((_free >> 20))MB available" + print_end 1 + return 0 + } + + lvm lvcreate --name secondary --size "${_size}B" internal || { + logcrit "Unable to create secondary LV" + print_end 1 + return 1 + } + + dd if=/dev/internal/primary of=/dev/internal/secondary bs=1M status=none || { + logcrit "Redundant image replication failed" + print_end 1 + return 1 + } + + kpartx -a -p-part /dev/internal/secondary || { + logcrit "Failed to setup partitions from replicated secondary image" + } + + print_end 0 +} + +internal_activate() +{ + lvm vgs internal || { + logcrit "No internal volume available" + return 1 + } + + lvm vgchange -ay internal || { + logcrit "Unable to bring internal volume online" + return 1 + } + + internal_rw_factory_reset || return 1 + internal_rw_provision || return 1 + internal_secondary_provision || return 1 +} + +internal_rw_mount() +{ + [ -b /dev/internal/rw ] || return 1 + + for subvol in $subvols; do + mount -t btrfs /dev/internal/rw /"$subvol" -o subvol=@"$subvol" || { + logcrit "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 || { + logcrit "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 + logcrit "Failed to make /etc writable" +} + +internal_activate || { + print_end 1 "Activating internal volume" +} + +internal_rw_mount || { + code=1 + print_end 1 "Mounting persistent storage from internal volume" + logcrit "Internal volume not found, falling back to tmpfs" + + print_start "Falling back to ephemeral tmpfs storage!" + tmpfs_rw_mount + print_end $? +} + +print_restore +exit $code diff --git a/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen b/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen new file mode 100755 index 000000000..76fce556c --- /dev/null +++ b/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen @@ -0,0 +1,87 @@ +#!/bin/sh + +. /etc/os-release + +{ + cat </etc/rauc/system.conf + +mkdir -p /var/lib/rauc + +[ -f /var/lib/rauc/central.raucs ] && exit + +unixepoch=$(date -d1970-01-01 -u +%FT%TZ) + +{ + cat </var/lib/rauc/central.raucs + diff --git a/board/common/rootfs/etc/finit.d/available/rauc.conf b/board/common/image/image-itb-rootfs/rootfs/etc/finit.d/available/rauc.conf similarity index 100% rename from board/common/rootfs/etc/finit.d/available/rauc.conf rename to board/common/image/image-itb-rootfs/rootfs/etc/finit.d/available/rauc.conf diff --git a/board/common/rootfs/etc/fstab b/board/common/image/image-itb-rootfs/rootfs/etc/fstab similarity index 100% rename from board/common/rootfs/etc/fstab rename to board/common/image/image-itb-rootfs/rootfs/etc/fstab diff --git a/board/common/rootfs/usr/libexec/infix/mnt b/board/common/image/image-itb-rootfs/rootfs/usr/libexec/infix/mnt similarity index 96% rename from board/common/rootfs/usr/libexec/infix/mnt rename to board/common/image/image-itb-rootfs/rootfs/usr/libexec/infix/mnt index 522d72f01..63974d5eb 100755 --- a/board/common/rootfs/usr/libexec/infix/mnt +++ b/board/common/image/image-itb-rootfs/rootfs/usr/libexec/infix/mnt @@ -22,25 +22,6 @@ err=0 mmc="" opt="-k" -# 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' {} \; @@ -393,7 +374,7 @@ if ! mount_rw cfg >/dev/null 2>&1; then vlibsrc= fi -if check_factory; then +if factory --check-clear; then factory_reset fi diff --git a/board/common/image/image-lvm-stub/Config.in b/board/common/image/image-lvm-stub/Config.in new file mode 100644 index 000000000..48a31f635 --- /dev/null +++ b/board/common/image/image-lvm-stub/Config.in @@ -0,0 +1,9 @@ +config IX_IMAGE_LVM_STUB + bool "LVM2 stub image" + default y if IX_IMAGE_DDI + depends on IX_IMAGE_BAREBOX_ESP + help + A disk image with Barebox and 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. diff --git a/board/common/image/image-lvm-stub/generate.sh b/board/common/image/image-lvm-stub/generate.sh new file mode 100755 index 000000000..5f54f60f8 --- /dev/null +++ b/board/common/image/image-lvm-stub/generate.sh @@ -0,0 +1,58 @@ +#!/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 + +. $BR2_EXTERNAL_INFIX_PATH/board/common/rootfs/etc/partition-uuid +[ -n "${BAREBOX_STATE_UUID}" ] +[ -n "${ESP_UUID}" ] +[ -n "${ESP_BACKUP_UUID}" ] + +cat <"${WORKDIR}"/genimage.cfg +image lvm-stub.disk { + hdimage { + partition-table-type = "gpt" + } + + partition esp { + partition-type-uuid = "esp" + partition-uuid = "$ESP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition esp-backup { + partition-type-uuid = "esp" + partition-uuid = "$ESP_BACKUP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition barebox-state { + partition-type-uuid = "barebox-state" + partition-uuid = "$BAREBOX_STATE_UUID" + size = 2048 + } + + partition internal { + growfs = "true" + image = "stub.lvm" + partition-type-uuid = "lvm" + size = 2M + } +} + +# 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-lvm-stub/image-lvm-stub.mk b/board/common/image/image-lvm-stub/image-lvm-stub.mk new file mode 100644 index 000000000..b12dfa90e --- /dev/null +++ b/board/common/image/image-lvm-stub/image-lvm-stub.mk @@ -0,0 +1,9 @@ +################################################################################ +# +# image-lvm-stub +# +################################################################################ + +IMAGE_LVM_STUB_DEPENDENCIES := image-barebox-esp + +$(eval $(ix-image)) diff --git a/board/common/image/ix-image.mk b/board/common/image/ix-image.mk index 12587b228..d026d1c40 100644 --- a/board/common/image/ix-image.mk +++ b/board/common/image/ix-image.mk @@ -13,6 +13,7 @@ $(1): $$($(2)_DEPENDENCIES) WORKDIR=$$(BUILD_DIR)/$(1) \ BINARIES_DIR=$$(BINARIES_DIR) \ TARGET_DIR=$$(TARGET_DIR) \ + BR2_ARCH=$$(BR2_ARCH) \ BR2_EXTERNAL_INFIX_PATH=$$(BR2_EXTERNAL_INFIX_PATH) \ ARTIFACT=$$(INFIX_ARTIFACT) \ COMPATIBLE=$$(IX_COMPATIBLE) \ @@ -22,6 +23,7 @@ $(1): $$($(2)_DEPENDENCIES) ifeq ($$($(3)),y) TARGETS_ROOTFS += $(1) +BR2_ROOTFS_OVERLAY := "$$(realpath $$(pkgdir)/rootfs) $$(call qstrip,$$(BR2_ROOTFS_OVERLAY))" endif endef diff --git a/board/common/initramfs/dracut.conf b/board/common/initramfs/dracut.conf new file mode 100644 index 000000000..126b38d55 --- /dev/null +++ b/board/common/initramfs/dracut.conf @@ -0,0 +1,104 @@ +#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 \ +infix-init \ +infix-lib64 +" + +# 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 \ +" diff --git a/board/common/initramfs/initramfs.mk b/board/common/initramfs/initramfs.mk new file mode 100644 index 000000000..1a033464a --- /dev/null +++ b/board/common/initramfs/initramfs.mk @@ -0,0 +1,38 @@ +ifeq ($(IX_IMAGE_DDI),y) + +IX_INITRAMFS_DIR := $(BR2_EXTERNAL_INFIX_PATH)/board/common/initramfs +IX_INITRAMFS_BUILD := $(BUILD_DIR)/infix-initramfs +IX_INITRAMFS_DEPS := \ + host-dracut \ + host-fakeroot \ + busybox \ + cryptsetup \ + gptfdisk \ + jq \ + lvm2 \ + multipath-tools \ + util-linux + +define LINUX_PRE_BUILD_INITRAMFS + @$(call IXMSG,"Creating Infix initramfs") + mkdir -p $(IX_INITRAMFS_BUILD) + cp -a $(IX_INITRAMFS_DIR)/modules.d/* $(HOST_DIR)/lib/dracut/modules.d/ + $(HOST_DIR)/bin/fakeroot $(HOST_DIR)/bin/dracut \ + -c $(IX_INITRAMFS_DIR)/dracut.conf \ + --kver $(LINUX_VERSION) --no-kernel \ + --sysroot $(TARGET_DIR) \ + --tmpdir $(IX_INITRAMFS_BUILD) \ + -M \ + --force \ + --no-compress \ + $(@D)/infix-initramfs.cpio +endef +LINUX_PRE_BUILD_HOOKS += LINUX_PRE_BUILD_INITRAMFS + +# We are late to the party here, buildroot/linux/linux.mk has already +# been sourced, so in addition to the dependency list, we also need to +# "manually" extend the deps for the kernel's configure target. +LINUX_DEPENDENCIES += $(IX_INITRAMFS_DEPS) +$(LINUX_TARGET_CONFIGURE): | $(IX_INITRAMFS_DEPS) + +endif diff --git a/board/common/initramfs/modules.d/01infix-lib64/module-setup.sh b/board/common/initramfs/modules.d/01infix-lib64/module-setup.sh new file mode 100755 index 000000000..790e9d6b9 --- /dev/null +++ b/board/common/initramfs/modules.d/01infix-lib64/module-setup.sh @@ -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" +} diff --git a/board/common/initramfs/modules.d/10infix-init/init b/board/common/initramfs/modules.d/10infix-init/init new file mode 100755 index 000000000..56a1746a8 --- /dev/null +++ b/board/common/initramfs/modules.d/10infix-init/init @@ -0,0 +1,276 @@ +#!/bin/sh + +echo -e "\e[31m●\e[0m \e[33m●\e[0m \e[32m●\e[0m Infix Initramfs — @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 + + echo "Starting debug shell at $1" + exec sh -i +} + +die() +{ + step="${*:-$step}" + + echo -e "\r\e[0K[\e[31mFAIL\e[0m] $step" + 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" + 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" + echo "<5>ix-initramfs: $step" >/dev/kmsg +} + +begin() +{ + step="$*" + echo -ne "[\e[33m ⋯ \e[0m] $step" +} + +debug() +{ + echo "<7>ix-initramfs: $*" >/dev/kmsg +} + +getdisk() +{ + local _disk _uuid _kind + + case "$1" in + PARTUUID=*) + _uuid=$(echo "$1" | sed -e 's/PARTUUID=//' | tr 'A-F' 'a-f') + _disk=/dev/disk/by-partuuid/"$_uuid" + ;; + *) + _disk="$1" + ;; + esac + + _disk=$(realpath "$_disk") + _kind=$(stat -c %F "$_disk" 2>/dev/null || true) + [ "$_kind" = "block special file" ] && echo "$_disk" || true +} + +mountcore() +{ + mount -t proc proc /proc + mount -t sysfs sys /sys + mount -t devtmpfs dev /dev +} + +mountroot() +{ + local _disk=$(getdisk $(getkparam root)) + + [ -z "$_disk" ] \ + && die "Unknown root device \"$(getkparam root)\"" + + mkdir /newroot + mount "$_disk" /newroot || \ + die "Unable to mount rootfs on $_disk" + + ok "Mounted root filesystem" +} + +switchroot() +{ + local innerinit=$(getkparam init) + + begin "Switching to root filesystem" + exec switch_root /newroot ${innerinit:-/sbin/init} + die "Unable to switch to root filesystem" +} + +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 + + # Make a note to userspace that this is the first time the system + # is booting. + touch /dev/firstboot + + 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" +} + +initrd_probe() +{ + getkopt rd.noinitrd && return 0 + [ -f /initrd.image ] || return 0 + + begin "Creating initrd loop device" + + if ! losetup -P -f /initrd.image; then + warn "initrd_probe: losetup failed" + return 0 + fi + + ok "Created initrd loop device" +} + +partuuids_probe() +{ + mkdir -p /dev/disk/by-partuuid + + grep -H PARTUUID= /sys/class/block/*/uevent | \ + sed -E 's|^/sys/class/block/([^/]+)/uevent:PARTUUID=(.*)$|\1 \2|' | \ + while read name uuid; do + ln -s /dev/"$name" /dev/disk/by-partuuid/"$uuid" 2>/dev/null || true + done +} + +lvm_probe() +{ + getkopt rd.nolvm && return 0 + + begin "Activating logical volumes" + + if ! lvm vgchange -ay &>/dev/null; then + warn "lvm_probe: Failed to activate the $_vg VG" + return 0 + fi + + for _lv in $(dmsetup info -c --noheadings -o name); do + sfdisk -J /dev/mapper/"$_lv" >/tmp/lv 2>/dev/null || continue + jq -e '.partitiontable.label == "gpt"' /tmp/lv >/dev/null 2>&1 || continue + + jq -r '.partitiontable | select(.label == "gpt") | + .partitions[] | "\(.uuid | ascii_downcase) \(.node)"' /tmp/lv | \ + while read uuid node; do + ln -s "$node" /dev/disk/by-partuuid/"$uuid" 2>/dev/null || true + done + + kpartx -a -p-part /dev/mapper/"$_lv" || \ + warn "lvm_probe:: Unable to add partitions from $_lv" + done + + ok "Activated logical volumes" +} + +uuidfmt() +{ + echo $1 | \ + sed -E 's/^([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})$/\1-\2-\3-\4-\5/' +} + +dm_verity_root_probe() +{ + local _root _hash + + [ -z "$roothash" ] && return 0 + + begin "Creating verity protected root" + + _root=$(getdisk PARTUUID=$(uuidfmt $(echo "$roothash" | cut -c 1-32))) + [ "$_root" ] || die "dm_verity_probe: Unknown verity root data device" + + _hash=$(getdisk PARTUUID=$(uuidfmt $(echo "$roothash" | cut -c 33-64))) + [ "$_hash" ] || die "dm_verity_probe: Unknown verity root hash device" + + veritysetup open "$_root" root "$_hash" "$roothash" \ + || die "dm_verity_probe: veritysetup failed" + + ok "Created verity protected root" +} + +mountcore + +debug_shell internal +internal_inflate + +debug_shell probe +initrd_probe +partuuids_probe +lvm_probe +dm_verity_root_probe + +debug_shell mount +mountroot + +debug_shell switchroot +switchroot diff --git a/board/common/initramfs/modules.d/10infix-init/module-setup.sh b/board/common/initramfs/modules.d/10infix-init/module-setup.sh new file mode 100755 index 000000000..011a90fb7 --- /dev/null +++ b/board/common/initramfs/modules.d/10infix-init/module-setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +progs="dmsetup kpartx jq lvm sgdisk sfdisk veritysetup" + +check() { + require_binaries $progs || return 1 + + return 0 +} + +depends() { + return 0 +} + +install() { + inst_multiple $progs + + cp "$moddir/init" "${initdir?}/init" + sed -e "s/@VERSION@/${INFIX_VERSION}/" -i "${initdir?}/init" +} diff --git a/board/common/post-build.sh b/board/common/post-build.sh index 5a94fe885..4d4a709e0 100755 --- a/board/common/post-build.sh +++ b/board/common/post-build.sh @@ -91,6 +91,11 @@ rm -f "$TARGET_DIR/etc/os-release" fi } > "$TARGET_DIR/etc/os-release" +if [ -f "$TARGET_DIR"/loader/entries/os.conf ]; then + sed -i "s/@TITLE@/$IX_NAME $INFIX_VERSION/" \ + "$TARGET_DIR"/loader/entries/os.conf +fi + echo "$IX_TAGLINE $INFIX_VERSION -- $(date +"%b %e %H:%M %Z %Y")" > "$TARGET_DIR/etc/version" ixmsg "Creating /etc/version: $(cat "$TARGET_DIR/etc/version")" diff --git a/board/common/qemu/ddi/Config.in.in b/board/common/qemu/ddi/Config.in.in new file mode 100644 index 000000000..79a35f28f --- /dev/null +++ b/board/common/qemu/ddi/Config.in.in @@ -0,0 +1,339 @@ +mainmenu "QEMU Virtualization" + +choice + prompt "Target Architecture" + default @ARCH@ + +config IX_QEMU_aarch64 + bool "AArch64 (little endian)" + +config IX_QEMU_arm + bool "AArch32 (little endian)" + +config IX_QEMU_riscv64 + bool "riscv64" + +config IX_QEMU_x86_64 + bool "x86_64" + +endchoice + +config IX_QEMU_ARCH + string + default "aarch64" if IX_QEMU_aarch64 + default "arm" if IX_QEMU_arm + default "riscv64" if IX_QEMU_riscv64 + default "x86_64" if IX_QEMU_x86_64 + +config IX_QEMU_VIRTIO_BUS + string + default "device" if IX_QEMU_arm + default "pci" + +config IX_QEMU_BIN_DDI + string + default "@IMAGE@" + +config IX_QEMU_BIN_DISK + string + default "@DISK@" + +config IX_QEMU_BIN_KERNEL + string + depends on IX_QEMU_LOADER_QEMU + default "../zImage" if IX_QEMU_arm + default "../Image" if IX_QEMU_aarch64 + default "../bzImage" if IX_QEMU_x86_64 + +config IX_QEMU_BIN_LVM_STUB + string + default "../lvm-stub.disk" + + +choice + prompt "Loader" + default IX_QEMU_LOADER_QEMU + +config IX_QEMU_LOADER_QEMU + bool "QEMU" + +config IX_QEMU_LOADER_OVMF + bool "OVMF (UEFI)" + +endchoice + +menu "OVMF" + depends on IX_QEMU_LOADER_OVMF + +config IX_QEMU_BIN_OVMF_CODE + string "CODE binary" + default "/usr/share/OVMF/OVMF_CODE_4M.secboot.fd" if IX_QEMU_OVMF_SB + default "/usr/share/OVMF/OVMF_CODE_4M.fd" + help + Path to the OVMF executable binary, passed as parallel FLASH + 0 (read-only). + +config IX_QEMU_BIN_OVMF_VARS + string "VARS binary" + default "/usr/share/OVMF/OVMF_VARS_4M.fd" + help + Path to the binary containing the OVMF non-volatile variable + store, passed as parallel FLASH 1. + +config IX_QEMU_OVMF_SB + bool "Secure boot" + default y + +config IX_QEMU_OVMF_SB_PK + string "Platform key (PK)" + depends on IX_QEMU_OVMF_SB + default "./infix-development.crt" + +config IX_QEMU_OVMF_SB_KEK + string "Key exchange key (KEK)" + depends on IX_QEMU_OVMF_SB + default "./infix-development.crt" + +config IX_QEMU_OVMF_SB_DB + string "Signature database keys (db)" + depends on IX_QEMU_OVMF_SB + default "./infix-development.crt" + +endmenu + +menu "Machine" + +config IX_QEMU_MACHINE + string "Machine" + default "q35,smm=on" if IX_QEMU_x86_64 + default "virt" + +config IX_QEMU_CPU + string "CPU" + default "host" if IX_QEMU_x86_64 + default "cortex-a53" if IX_QEMU_aarch64 + default "max" + +config IX_QEMU_RAM + string "RAM size (k/M/G)" + default "384M" + +choice + prompt "Console" + default IX_QEMU_CONSOLE_VIRTIO + +config IX_QEMU_CONSOLE_VIRTIO + bool "Virtio (hvc0)" + +config IX_QEMU_CONSOLE_SERIAL + bool "Serial (ttyS0/ttyAMA0)" + +endchoice + +config IX_QEMU_CONSOLE + string + default "hvc0" if IX_QEMU_CONSOLE_VIRTIO + default "ttyAMA0" if IX_QEMU_arm + default "ttyAMA0" if IX_QEMU_aarch64 + default "ttyS0" + +endmenu + +menu "Disks & Filesystems" + +config IX_QEMU_DISK_INITRD + bool "Use the DDI as initrd" + depends on IX_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 IX_QEMU_DISK_SYS_IF_VIRTIO + +config IX_QEMU_DISK_SYS_IF_VIRTIO + bool "virtio" + help + Attach system disks using virtio block devices. + +endchoice + +choice + prompt "Contents" + default IX_QEMU_DISK_SYS_SPLIT + +config IX_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 IX_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 IX_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 IX_QEMU_DISK_SYS_SPLIT + bool "Split DDI/LVM" + help + Attach one disk containing the EFI System partition and an + (initially empty) LVM stub for persistent storage; and the raw + DDI image in another 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 IX_QEMU_DISK_SYS_SIZE + string "Size (k/M/G)" + default "8G" + +comment "USB mass storage" + +choice + prompt "Contents" + default IX_QEMU_DISK_USB_NONE + +config IX_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 IX_QEMU_DISK_USB_NONE + bool "None" + help + Do not attach any disk over USB. + +endchoice + +config IX_QEMU_DISK_USB_SIZE + string "Size (k/M/G)" + depends on !IX_QEMU_DISK_USB_NONE + default "4G" + +comment "Host filesystem passthrough (9P)" + +config IX_QEMU_DISK_HOST + string "Path" + default "/tmp" + +endmenu + +menu "Networking" + +comment "Host attachment" + +choice + prompt "Mode" + default IX_QEMU_NET_USER + +config IX_QEMU_NET_BRIDGE + bool "Bridged" + +config IX_QEMU_NET_NONE + bool "None" + +config IX_QEMU_NET_ROCKER + bool "Rocker" + +config IX_QEMU_NET_TAP + bool "TAP" + +config IX_QEMU_NET_USER + bool "User" + +endchoice + +config IX_QEMU_NET_MODE + string + default "bridge" if IX_QEMU_NET_BRIDGE + default "none" if IX_QEMU_NET_NONE + default "rocker" if IX_QEMU_NET_ROCKER + default "tap" if IX_QEMU_NET_TAP + default "user" if IX_QEMU_NET_USER + + +config IX_QEMU_NET_BRIDGE_DEV + string "Bridge device" + depends on IX_QEMU_NET_BRIDGE + default "virbr0" + +config IX_QEMU_NET_USER_NETBOOT + bool "Enable netbooting" + depends on IX_QEMU_NET_USER + help + Supply the DDI as the DHCP bootfile and serve the images + directory over TFTP. + +config IX_QEMU_NET_USER_OPTS + string "User mode options" + depends on IX_QEMU_NET_USER + help + Extra -nic user, + +config IX_QEMU_NET_TAP_N + int "Number of TAPs" + depends on IX_QEMU_NET_TAP + default 1 + +config IX_QEMU_NET_PORTS + int "Number of Rocker switch ports" + depends on IX_QEMU_NET_ROCKER + default 10 + + +comment "VM attachment" + +config IX_QEMU_NET_MODEL + string "Interface model" + default "virtio-net-device" if IX_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. + +endmenu + +config IX_QEMU_EXTRA + string "Extra QEMU options" + +config IX_QEMU_APPEND + string "Extra kernel options" + depends on IX_QEMU_LOADER_QEMU diff --git a/board/common/qemu/ddi/run.sh b/board/common/qemu/ddi/run.sh new file mode 100755 index 000000000..4f57ae7d8 --- /dev/null +++ b/board/common/qemu/ddi/run.sh @@ -0,0 +1,582 @@ +#!/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 <&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-$IX_QEMU_VIRTIO_BUS +} + +q_add_disk() +{ + if [ "$IX_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 [] " + ;; + esac + + echo "$_qcow" +} + +append() +{ + echo -n " $*" >>"$qdir"/append +} + +appendroot() +{ + append root=/dev/mapper/root + append roothash=$(sfdisk -J $(binpath "$IX_QEMU_BIN_DDI") \ + | jq -r '.partitiontable.partitions | "\(.[0].uuid)-\(.[1].uuid)"' \ + | tr -d '-' | tr 'A-F' 'a-f') +} + +gen_machine() +{ + q sect machine + q param type "$IX_QEMU_MACHINE" + q param accel kvm:tcg + + q sect cpu "$IX_QEMU_CPU" + + q sect m + q param size "$IX_QEMU_RAM" +} + +gen_pflash_ovmf_code() +{ + q sect drive + q param id ovmf-code + q param format raw + q param if pflash + q param unit 0 + q param file $(binpath "$IX_QEMU_BIN_OVMF_CODE") + q param readonly on +} + +gen_pflash_ovmf_vars() +{ + local _src=$(binpath "$IX_QEMU_BIN_OVMF_VARS") + local _dst="$qdir"/ovmf-vars-$(grep _OVMF_ "$qdir"/.config | sha256sum | head -c 8).fd + local _guid=$(uuidgen) + + if ! [ -f "$_dst" ]; then + [ -f "$_src" ] || die "OVMF Variable template ($_src) does not exist" + cp "$_src" "$_dst" + + if [ "$IX_QEMU_OVMF_SB" ]; then + command virt-fw-vars \ + || die "Please install virt-fw-vars to support injection of OVMF secure boot variables" + + virt-fw-vars \ + --inplace "$_dst" \ + --set-pk $_guid $(binpath "$IX_QEMU_OVMF_SB_PK") \ + --add-kek $_guid $(binpath "$IX_QEMU_OVMF_SB_KEK") \ + --add-db $_guid $(binpath "$IX_QEMU_OVMF_SB_DB") \ + --secure-boot \ + || die "Failed to inject secure boot keys to OVMF variable image" + fi + fi + + q sect drive + q param id ovmf-vars + q param format raw + q param if pflash + q param unit 1 + q param file "$_dst" +} + +gen_loader() +{ + if [ "$IX_QEMU_LOADER_QEMU" ]; then + q sect kernel $(binpath "$IX_QEMU_BIN_KERNEL") + appendroot + elif [ "$IX_QEMU_LOADER_OVMF" ]; then + gen_pflash_ovmf_code + gen_pflash_ovmf_vars + + if [ "$IX_QEMU_OVMF_SB" ]; then + q sect global + q param driver cfi.pflash01 + q param property secure + q param value on + fi + 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 "$IX_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: $IX_QEMU_CONSOLE" + ;; + esac + + append console="$IX_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 "$IX_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 "$IX_QEMU_BIN_DISK") \ + "$IX_QEMU_DISK_SYS_SIZE") + + q_add_disk disk-sys +} + +gen_disk_sys_split() +{ + # LVM stub + q sect drive + q param id disk-stub + q param format qcow2 + q param if none + q param file $(qcowed disk-stub $(binpath "$IX_QEMU_BIN_LVM_STUB") \ + "$IX_QEMU_DISK_SYS_SIZE") + + q_add_disk disk-stub + + # DDI (ro) + q sect drive + q param id disk-ddi + q param format raw + q param file $(binpath "$IX_QEMU_BIN_DDI") + q param if none + q param read-only on + + q_add_disk disk-ddi +} + +gen_disk_sys() +{ + [ "$IX_QEMU_DISK_SYS_NONE" ] && return + + if [ "$IX_QEMU_DISK_SYS_EMPTY" ]; then + gen_disk_sys_empty + elif [ "$IX_QEMU_DISK_SYS_FULL" ]; then + gen_disk_sys_full + elif [ "$IX_QEMU_DISK_SYS_SPLIT" ]; then + gen_disk_sys_split + else + die "Unknown system disk mode" + fi +} + +gen_disk_usb() +{ + [ "$IX_QEMU_DISK_USB_NONE" ] && return + + if [ "$IX_QEMU_DISK_USB_DISK" ]; then + q sect drive + q param id disk-usb + q param format qcow2 + q param file $(qcowed disk-usb \ + $(binpath "$IX_QEMU_BIN_DISK") \ + "$IX_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_host() +{ + [ -d "$IX_QEMU_DISK_HOST" ] || return + + q sect virtfs local + q param path "$IX_QEMU_DISK_HOST" + q param security_model none + q param writeout immediate + q param mount_tag host +} + +gen_disk_initrd() +{ + [ "$IX_QEMU_DISK_INITRD" ] || return + + q sect initrd $(binpath "$IX_QEMU_BIN_DDI") +} + +internal_is_available() +{ + [ "$IX_QEMU_DISK_SYS_FULL" ] || [ "$IX_QEMU_DISK_SYS_SPLIT" ] \ + || [ "$IX_QEMU_DISK_USB_DISK" ] +} + +gen_disks() +{ + gen_disk_sys + gen_disk_usb + gen_disk_host + + gen_disk_initrd + + internal_is_available || append rd.nointernal +} + +gen_net_dev() +{ + local _name="$1" + local _idx="$2" + local _mac=$(printf "02:00:00:de:ad:%02x" "$_idx") + + echo "$_name $_mac" >>"$qdir"/mactab + + q sect device "$IX_QEMU_NET_MODEL" + q param netdev "$_name" + q param mac "$_mac" +} + +gen_net_user() +{ + [ "$IX_QEMU_NET_USER" ] || return + + q sect netdev user + q param id e1 + + if [ "$IX_QEMU_NET_USER_NETBOOT" ]; then + q param tftp $(dirname $(readlink -f $(binpath "$IX_QEMU_BIN_DDI"))) + q param bootfile $(basename "$IX_QEMU_BIN_DDI") + fi + + if [ "$IX_QEMU_NET_USER_OPTS" ]; then + q option "$IX_QEMU_NET_USER_OPTS" + fi + + gen_net_dev e1 1 +} + +gen_net() +{ + :> "$qdir"/mactab + q sect fw_cfg + q param name opt/mactab + q param file "$qdir"/mactab + + gen_net_user +} + +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 virtserialport + 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 <"$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-$IX_QEMU_ARCH -nodefaults \\ +EOF + chmod +x "$qdir"/qemu.sh + + gen_machine + gen_loader + gen_serial + gen_disks + gen_net + gen_gdb + + if [ "$IX_QEMU_LOADER_QEMU" ]; then + _append=$(cat "$qdir"/append) + q sect append "\"$_append $IX_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 + rm -f "$qdir"/ovmf-vars-*.fd + 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 diff --git a/board/common/qemu/Config.in.in b/board/common/qemu/itb/Config.in.in similarity index 99% rename from board/common/qemu/Config.in.in rename to board/common/qemu/itb/Config.in.in index 411a69620..8ac7caf38 100644 --- a/board/common/qemu/Config.in.in +++ b/board/common/qemu/itb/Config.in.in @@ -149,7 +149,7 @@ config IX_QEMU_HOST config IX_QEMU_APPEND string "Extra kernel options" - depends on !IX_QEMU_ROOTFS_MMC + depends on IX_QEMU_LOADER_KERNEL config IX_QEMU_EXTRA string "Extra QEMU options" diff --git a/board/common/qemu/run.sh b/board/common/qemu/itb/run.sh similarity index 100% rename from board/common/qemu/run.sh rename to board/common/qemu/itb/run.sh diff --git a/board/common/qemu/qemu.mk b/board/common/qemu/qemu.mk index bdee41a05..694c38d6b 100644 --- a/board/common/qemu/qemu.mk +++ b/board/common/qemu/qemu.mk @@ -4,12 +4,16 @@ # ################################################################################ -QEMU_SCRIPTS_DIR := $(pkgdir) -QEMU_SCRIPTS_NAME := qemu-scripts -QEMU_SCRIPTS_TYPE := rootfs +qemu-scripts-dir := $(pkgdir)/$(if $(IX_IMAGE_DDI),ddi,itb) +qemu-image := ../$(INFIX_ARTIFACT).$(if $(IX_IMAGE_DDI),raw,qcow2) +qemu-disk := $(if $(IX_IMAGE_DDI_DISK),../$(INFIX_ARTIFACT).disk) +qemu-esp := $(if $(IX_IMAGE_BAREBOX_ESP),../barebox-esp.vfat) +qemu-extra := $(if $(IX_IMAGE_DDI),$(BINARIES_DIR)/qemu/infix-development.crt) + +qemu-kconfig-prefix := $(if $(IX_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" @@ -26,23 +30,30 @@ run-menuconfig: $(BUILD_DIR)/buildroot-config/mconf qemu-scripts: \ $(BINARIES_DIR)/qemu/run.sh \ $(BINARIES_DIR)/qemu/Config.in \ - $(BINARIES_DIR)/qemu/.config + $(BINARIES_DIR)/qemu/.config \ + $(qemu-extra) -$(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@:IX_QEMU_$(BR2_ARCH):" \ - -e "s:@DISK_IMG@:../$(INFIX_ARTIFACT).qcow2:" \ + -e "s:@ARCH@:IX_QEMU_$(BR2_ARCH):g" \ + -e "s:@IMAGE@:$(qemu-image):g" \ + -e "s:@DISK@:$(qemu-disk):g" \ < $< >$@ $(BINARIES_DIR)/qemu/.config: $(BINARIES_DIR)/qemu/Config.in @$(call qemu-kconfig,conf,--olddefconfig) @rm -f $@.old +$(BINARIES_DIR)/qemu/infix-development.crt: \ +$(BR2_EXTERNAL_INFIX_PATH)/board/common/signing-keys/development/infix.crt + @mkdir -p $(dir $@) + @cp $< $@ + TARGETS_ROOTFS += qemu-scripts endif diff --git a/board/common/rootfs/etc/partition-uuid b/board/common/rootfs/etc/partition-uuid index c7e162be7..92be42fef 100644 --- a/board/common/rootfs/etc/partition-uuid +++ b/board/common/rootfs/etc/partition-uuid @@ -1,3 +1,6 @@ AUX_UUID="78460f84-de84-4fe7-89bd-4c1f433b2230" +BAREBOX_STATE_UUID="80ca5554-c020-11f1-ba4d-b3c49d5dbf47" +ESP_UUID="82bb760e-c08e-11f1-a791-7bd312fe0c17" +ESP_BACKUP_UUID="835b97d8-c08e-11f1-a2f7-73ad4bbf39e9" PRIMARY_UUID="107ae911-a97b-4380-975c-7ce1a2dde1e0" SECONDARY_UUID="352bd9b2-2ca9-44e2-bdc7-edbc87ba1e02" diff --git a/board/common/rootfs/etc/tmpfiles.d/nginx.conf b/board/common/rootfs/etc/tmpfiles.d/nginx.conf index e10c9b19b..bec6a5bed 100644 --- a/board/common/rootfs/etc/tmpfiles.d/nginx.conf +++ b/board/common/rootfs/etc/tmpfiles.d/nginx.conf @@ -1,2 +1,2 @@ d /var/cache/nginx 0755 www-data www-data - +d /var/log/nginx 0755 www-data www-data diff --git a/board/common/rootfs/usr/bin/ddi b/board/common/rootfs/usr/bin/ddi new file mode 100755 index 000000000..39f795e07 --- /dev/null +++ b/board/common/rootfs/usr/bin/ddi @@ -0,0 +1,343 @@ +#!/bin/sh + +set -e + +capath=/etc/rauc/keys + +usage() +{ + local _ddi="$(basename $0)" + + cat <] [] + +Discoverable Disk Image manipulation + + Options: + + -A + Override the system architecture. + + -C + Source trusted signing keys from the supplied directory. + Default: $capath + + -h + Show this message and exit. + + Commands: + + inspect + + uuid + Print GPT disk identifier + + part [ is one of: + root The first root partition + + open [] + + Create a device mapping for the specified partition, called + . + + Options: + + -v + Use the associated dm-verity hash tree, whose root hash is + available in the associated signature partition. + + -V + Use the associated dm-verity hash tree, along with an + externally validated root hash. + + verify [] + + 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 + 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 diff --git a/board/x86_64/barebox_defconfig b/board/x86_64/barebox_defconfig new file mode 100644 index 000000000..b4867734c --- /dev/null +++ b/board/x86_64/barebox_defconfig @@ -0,0 +1,126 @@ +CONFIG_NAME="efi_defconfig" +CONFIG_NVVAR=y +CONFIG_MMU=y +CONFIG_HUSH_FANCY_PROMPT=y +CONFIG_CMDLINE_EDITING=y +CONFIG_AUTO_COMPLETE=y +CONFIG_MENU=y +# CONFIG_TIMESTAMP is not set +CONFIG_BOOTM_SHOW_TYPE=y +CONFIG_BOOTM_VERBOSE=y +CONFIG_BOOTM_INITRD=y +CONFIG_BLSPEC=y +CONFIG_BOOT_DDI=y +CONFIG_FLEXIBLE_BOOTARGS=y +CONFIG_CONSOLE_ACTIVATE_ALL=y +CONFIG_PARTITION_DISK_EFI=y +# CONFIG_ENV_HANDLING is not set +CONFIG_DEFAULT_ENVIRONMENT=y +CONFIG_STATE=y +CONFIG_BOOTCHOOSER=y +CONFIG_RESET_SOURCE=y +CONFIG_DEFAULT_LOGLEVEL=4 +CONFIG_DEBUG_LL=y +# CONFIG_EFI_HANDOVER_PROTOCOL is not set +CONFIG_EFI_PAYLOAD_BOOTM=y +CONFIG_EFI_PAYLOAD_STATE_LAYOUT_PATH="/env/state.dtb" +CONFIG_CMD_DMESG=y +CONFIG_LONGHELP=y +CONFIG_CMD_IOMEM=y +CONFIG_CMD_IMD=y +CONFIG_CMD_MEMINFO=y +CONFIG_CMD_BOOT=y +CONFIG_CMD_GO=y +CONFIG_CMD_LOADB=y +CONFIG_CMD_RESET=y +CONFIG_CMD_UIMAGE=y +CONFIG_CMD_BOOTCHOOSER=y +CONFIG_CMD_PARTITION=y +CONFIG_CMD_AUTOMOUNT=y +CONFIG_CMD_DMSETUP=y +CONFIG_CMD_VERITYSETUP=y +CONFIG_CMD_LVM=y +CONFIG_CMD_NV=y +CONFIG_CMD_EXPORT=y +CONFIG_CMD_GLOBAL=y +CONFIG_CMD_PRINTENV=y +CONFIG_CMD_MAGICVAR=y +CONFIG_CMD_MAGICVAR_HELP=y +CONFIG_CMD_BASENAME=y +CONFIG_CMD_DIRNAME=y +CONFIG_CMD_FILETYPE=y +CONFIG_CMD_LN=y +CONFIG_CMD_MD5SUM=y +CONFIG_CMD_READLINK=y +CONFIG_CMD_UNCOMPRESS=y +CONFIG_CMD_GETOPT=y +CONFIG_CMD_LET=y +CONFIG_CMD_MSLEEP=y +CONFIG_CMD_READF=y +CONFIG_CMD_SLEEP=y +CONFIG_CMD_DHCP=y +CONFIG_CMD_HOST=y +CONFIG_NET_CMD_IFUP=y +CONFIG_CMD_PING=y +CONFIG_CMD_TFTP=y +CONFIG_CMD_ECHO_E=y +CONFIG_CMD_EDIT=y +CONFIG_CMD_MENU=y +CONFIG_CMD_MENUTREE=y +CONFIG_CMD_READLINE=y +CONFIG_CMD_TIMEOUT=y +CONFIG_CMD_CRC=y +CONFIG_CMD_CRC_CMP=y +CONFIG_CMD_MM=y +CONFIG_CMD_DETECT=y +CONFIG_CMD_FLASH=y +CONFIG_CMD_POWEROFF=y +CONFIG_CMD_WD=y +CONFIG_CMD_KEYS=y +CONFIG_CMD_PKCS7=y +CONFIG_CMD_2048=y +CONFIG_CMD_BAREBOX_UPDATE=y +CONFIG_CMD_OF_DIFF=y +CONFIG_CMD_OF_NODE=y +CONFIG_CMD_OF_PROPERTY=y +CONFIG_CMD_OFTREE=y +CONFIG_CMD_TIME=y +CONFIG_NET=y +CONFIG_NET_NETCONSOLE=y +CONFIG_DEEP_PROBE_DEFAULT=y +CONFIG_OFDEVICE=y +CONFIG_DRIVER_SERIAL_EFI_STDIO=y +CONFIG_DRIVER_NET_EFI_SNP=y +# CONFIG_SPI is not set +CONFIG_DISK=y +CONFIG_DISK_WRITE=y +CONFIG_DM_BLK=y +CONFIG_DM_BLK_LINEAR=y +CONFIG_DM_BLK_VERITY=y +CONFIG_DM_LVM=y +CONFIG_USB_HOST=y +CONFIG_USB_EFI_IO_PROTOCOL=y +CONFIG_VIDEO=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_DRIVER_VIDEO_EFI_GOP=y +CONFIG_FINTEK_SUPERIO=y +CONFIG_SMSC_SUPERIO=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_EFI=y +CONFIG_F71808E_WDT=y +CONFIG_ITCO_WDT=y +# CONFIG_PINCTRL is not set +CONFIG_PCI_EFI=y +CONFIG_FS_EXT4=y +CONFIG_FS_TFTP=y +CONFIG_FS_TFTP_MAX_WINDOW_SIZE=128 +CONFIG_FS_NFS=y +CONFIG_FS_EFI=y +CONFIG_FS_EFIVARFS=y +CONFIG_FS_FAT=y +CONFIG_FS_FAT_WRITE=y +CONFIG_FS_SQUASHFS=y +CONFIG_CRYPTO_RSA=y +CONFIG_CRYPTO_ECDSA=y +CONFIG_CRYPTO_EFI_DB=y +CONFIG_ZLIB=y diff --git a/board/x86_64/linux_defconfig b/board/x86_64/linux_defconfig index 4b9f5d050..f4486904c 100644 --- a/board/x86_64/linux_defconfig +++ b/board/x86_64/linux_defconfig @@ -29,10 +29,14 @@ CONFIG_CGROUP_BPF=y CONFIG_USER_NS=y CONFIG_SCHED_AUTOGROUP=y CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="infix-initramfs.cpio" +CONFIG_INITRAMFS_COMPRESSION_ZSTD=y CONFIG_KALLSYMS_ALL=y CONFIG_PROFILING=y CONFIG_SMP=y CONFIG_EFI=y +CONFIG_EFI_STUB=y +# CONFIG_EFI_HANDOVER_PROTOCOL is not set CONFIG_KPROBES=y # CONFIG_GCC_PLUGINS is not set CONFIG_MODULES=y @@ -307,7 +311,6 @@ CONFIG_VIRTIO_INPUT=y CONFIG_VIRTIO_MMIO=y CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y CONFIG_EXT2_FS=y -CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT4_FS=y CONFIG_EXT4_FS_POSIX_ACL=y CONFIG_BTRFS_FS=y diff --git a/board/x86_64/rootfs/loader/entries/os.conf b/board/x86_64/rootfs/loader/entries/os.conf new file mode 100644 index 000000000..cd19524a7 --- /dev/null +++ b/board/x86_64/rootfs/loader/entries/os.conf @@ -0,0 +1,4 @@ +title @TITLE@ +linux /boot/bzImage +options console=ttyS0 console=hvc0 loglevel=4 +linux-appendroot true diff --git a/buildroot b/buildroot index 2f8fda57a..93c7c33ba 160000 --- a/buildroot +++ b/buildroot @@ -1 +1 @@ -Subproject commit 2f8fda57a6a05043cc0bc3b749514d0e46dc63e5 +Subproject commit 93c7c33ba687337e32ef973bfa38701cd77312ce diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index 6cac556ff..e39a21223 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -125,21 +125,20 @@ BR2_PACKAGE_LESS=y BR2_PACKAGE_MG=y BR2_PACKAGE_NANO=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_GIT=y +BR2_TARGET_BAREBOX_CUSTOM_GIT_REPO_URL="https://github.com/kernelkit/barebox.git" +BR2_TARGET_BAREBOX_CUSTOM_GIT_VERSION="kkit-next-ddi1" +BR2_TARGET_BAREBOX_USE_CUSTOM_CONFIG=y +BR2_TARGET_BAREBOX_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/barebox_defconfig" +BR2_TARGET_BAREBOX_IMAGE_FILE="barebox.efi" +BR2_TARGET_BAREBOX_CUSTOM_EMBEDDED_ENV_PATH="${BUILD_DIR}/infix-bareboxenv" BR2_PACKAGE_HOST_DOSFSTOOLS=y BR2_PACKAGE_HOST_E2FSPROGS=y BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y BR2_PACKAGE_HOST_GENEXT2FS=y BR2_PACKAGE_HOST_GO_BIN=y BR2_PACKAGE_HOST_MTOOLS=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y IX_VENDOR_HOME="https://www.kernelkit.org" IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers." IX_HOME="https://github.com/kernelkit/infix/" @@ -188,9 +187,8 @@ BR2_PACKAGE_WEBUI=y BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y BR2_PACKAGE_HOST_PYTHON_YANGDOC=y BR2_PACKAGE_PCIUTILS=y -IX_IMAGE_ITB_AUX=y -IX_IMAGE_ITB_QCOW=y -IX_IMAGE_ITB_RAUC=y +IX_IMAGE_DDI_DISK=y +IX_IMAGE_DDI_RAUC=y IX_IMAGE_README=y IX_TRUSTED_KEYS=y IX_TRUSTED_KEYS_DEVELOPMENT=y diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig index f3db8f305..0b1a592c6 100644 --- a/configs/x86_64_minimal_defconfig +++ b/configs/x86_64_minimal_defconfig @@ -102,20 +102,19 @@ BR2_PACKAGE_LESS=y BR2_PACKAGE_MG=y BR2_PACKAGE_NANO=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_GIT=y +BR2_TARGET_BAREBOX_CUSTOM_GIT_REPO_URL="https://github.com/kernelkit/barebox.git" +BR2_TARGET_BAREBOX_CUSTOM_GIT_VERSION="kkit-next-ddi1" +BR2_TARGET_BAREBOX_USE_CUSTOM_CONFIG=y +BR2_TARGET_BAREBOX_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/barebox_defconfig" +BR2_TARGET_BAREBOX_IMAGE_FILE="barebox.efi" +BR2_TARGET_BAREBOX_CUSTOM_EMBEDDED_ENV_PATH="${BUILD_DIR}/infix-bareboxenv" BR2_PACKAGE_HOST_DOSFSTOOLS=y BR2_PACKAGE_HOST_E2FSPROGS=y BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y BR2_PACKAGE_HOST_GENEXT2FS=y BR2_PACKAGE_HOST_MTOOLS=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y IX_VENDOR_HOME="https://www.kernelkit.org" IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers." IX_HOME="https://github.com/kernelkit/infix/" @@ -147,9 +146,8 @@ BR2_PACKAGE_MDNS_ALIAS=y BR2_PACKAGE_ONIEPROM=y BR2_PACKAGE_ROUSETTE=y BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y -IX_IMAGE_ITB_AUX=y -IX_IMAGE_ITB_QCOW=y -IX_IMAGE_ITB_RAUC=y +IX_IMAGE_DDI_DISK=y +IX_IMAGE_DDI_RAUC=y IX_IMAGE_README=y IX_TRUSTED_KEYS=y IX_TRUSTED_KEYS_DEVELOPMENT=y diff --git a/doc/developers-guide.md b/doc/developers-guide.md index b1325692c..bb44bf262 100644 --- a/doc/developers-guide.md +++ b/doc/developers-guide.md @@ -73,7 +73,7 @@ $ sudo apt install bc binutils build-essential bzip2 cpio \ libncurses-dev libssl-dev perl patch \ python3 rsync sed tar unzip wget \ autopoint bison flex autoconf automake \ - mtools + mtools sbsigntool ``` To build an Infix image; select the target and then make: diff --git a/external.mk b/external.mk index 071cfee47..ee3f41311 100644 --- a/external.mk +++ b/external.mk @@ -8,6 +8,11 @@ local.mk: @$(call IXMSG,"Installing local override for certain packages") @(cd $O && ln -s $(BR2_EXTERNAL_INFIX_PATH)/local.mk .) +# TEMPORARY +# Until the lvm series gets into a released version and we can create +# a proper release from kernelkit/barebox, we build from a GIT tag. +BAREBOX_HASH_FILES += $(BR2_EXTERNAL_INFIX_PATH)/patches/barebox/git.hash + # # Buildroot package extensions # @@ -25,6 +30,22 @@ FRR_POST_BUILD_HOOKS += FRR_POST_BUILD_HOOK # NETSNMP_CONF_OPTS += --enable-read-only +# Some of these assumes the presence of systemd, so skip them. +LVM2_CONF_OPTS += --disable-udev_rules + +# +# The multipath-tools package, which we need for kpartx, installs udev +# rules that assumes the presence of systemd. There is no option to +# skip the install, but we can pick the destination. So place them in +# the root and then remove them in the post-install hook. +MULTIPATH_TOOLS_OPTS += udevrulesdir="/.mpath-trash" + +define MPATH_POST_INSTALL_CLEANUP + rm -rf $(TARGET_DIR)/.mpath-trash +endef + +MULTIPATH_TOOLS_POST_INSTALL_TARGET_HOOKS += MPATH_POST_INSTALL_CLEANUP + # # External pre-built toolchains do not carry their own license. # diff --git a/package/confd-test-mode/confd-test-mode.mk b/package/confd-test-mode/confd-test-mode.mk index 1a9f040c9..3c991a13c 100644 --- a/package/confd-test-mode/confd-test-mode.mk +++ b/package/confd-test-mode/confd-test-mode.mk @@ -12,6 +12,7 @@ CONFD_TEST_MODE_LICENSE_FILES = LICENSE CONFD_TEST_MODE_REDISTRIBUTE = NO CONFD_TEST_MODE_DEPENDENCIES = sysrepo libite libyang confd CONFD_TEST_MODE_AUTORECONF = YES +CONFD_TEST_MODE_CONF_OPTS = --with-marker-dir=$(if $(IX_IMAGE_DDI),/boot/efi,/mnt/aux) CONFD_TEST_MODE_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_test_mode COMMON_SYSREPO_ENV = \ diff --git a/package/confd/confd.mk b/package/confd/confd.mk index 02a2249c8..2a9f5bf98 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -13,6 +13,7 @@ CONFD_REDISTRIBUTE = NO CONFD_DEPENDENCIES = host-sysrepo sysrepo rousette netopeer2 jansson libite sysrepo libsrx libglib2 libev sysklogd watchdogd CONFD_AUTORECONF = YES CONFD_CONF_OPTS += --disable-silent-rules --with-crypt=$(BR2_PACKAGE_CONFD_DEFAULT_CRYPT) +CONFD_CONF_OPTS += --with-marker-dir=$(if $(IX_IMAGE_DDI),/boot/efi,/mnt/aux) CONFD_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_confd define CONFD_CONF_ENV diff --git a/package/factory/factory.mk b/package/factory/factory.mk index 1d76d3468..bc2b6a683 100644 --- a/package/factory/factory.mk +++ b/package/factory/factory.mk @@ -11,12 +11,11 @@ FACTORY_SITE_METHOD = local FACTORY_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/factory FACTORY_REDISTRIBUTE = NO -define FACTORY_CONF_ENV -CFLAGS="$(INFIX_CFLAGS)" -endef +FACTORY_RESETME_PATH=$(if $(IX_IMAGE_DDI),/boot/efi/infix/.factory-reset,/mnt/cfg/infix.reset) define FACTORY_BUILD_CMDS $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \ + CFLAGS="$(INFIX_CFLAGS) -DRESETME_PATH=$(FACTORY_RESETME_PATH)" \ LDLIBS="$(TARGET_LDFLAGS)" endef diff --git a/patches/barebox/git.hash b/patches/barebox/git.hash new file mode 100644 index 000000000..92f7c3fcb --- /dev/null +++ b/patches/barebox/git.hash @@ -0,0 +1 @@ +sha256 b11e120c0b9d08900bdab1931e9c18f849ed76f262db6fd66f819e3312c7637d barebox-kkit-next-ddi1-git4.tar.gz diff --git a/patches/genimage/0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch b/patches/genimage/20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch similarity index 90% rename from patches/genimage/0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch rename to patches/genimage/20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch index 2714e611f..3d24ca38d 100644 --- a/patches/genimage/0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch +++ b/patches/genimage/20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch @@ -1,7 +1,7 @@ -From 7f50533b9e57f5e7d1e4dd47c3f43e2d6a9c8b1a Mon Sep 17 00:00:00 2001 +From 557bb6ebede417249e3df2a8fb7340a5cae0684e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 8 Mar 2026 04:33:56 +0100 -Subject: [PATCH] hdimage: add gpt-protective-first option for hybrid MBR +Subject: [PATCH 1/2] hdimage: add gpt-protective-first option for hybrid MBR Organization: Wires In a hybrid MBR the conventional layout (used by gdisk and others) is: @@ -31,16 +31,17 @@ The default (false) preserves the existing genimage behaviour so that platforms like Raspberry Pi continue to work without any changes. Signed-off-by: Joachim Wiberg +Signed-off-by: Tobias Waldekranz --- README.rst | 10 ++++++++++ - image-hd.c | 27 ++++++++++++++++++++++----- - 2 files changed, 32 insertions(+), 5 deletions(-) + image-hd.c | 27 +++++++++++++++++++++++++-- + 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst -index 9a95f75..f45fb93 100644 +index 770bca8..a4c1a23 100644 --- a/README.rst +++ b/README.rst -@@ -480,6 +480,16 @@ Options: +@@ -481,6 +481,16 @@ Options: placed at 512 bytes (sector 1). Defaults to 1024 bytes (sector 2). :gpt-no-backup: Boolean. If true, then the backup partition table at the end of the image is not written. @@ -58,7 +59,7 @@ index 9a95f75..f45fb93 100644 random value. :fill: If this is set to true, then the image file will be filled diff --git a/image-hd.c b/image-hd.c -index 274c6ac..610d9d1 100644 +index 97e75a3..3ecdb98 100644 --- a/image-hd.c +++ b/image-hd.c @@ -46,6 +46,7 @@ struct hdimage { @@ -72,7 +73,7 @@ index 274c6ac..610d9d1 100644 @@ -152,6 +153,26 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions) memset(&mbr, 0, sizeof(mbr)); memcpy(&mbr.disk_signature, &hd->disksig, sizeof(hd->disksig)); - + + /* + * For hybrid MBR, optionally write the GPT protective entry (0xEE) at + * slot 0 first when gpt-protective-first = true. This matches the @@ -95,32 +96,32 @@ index 274c6ac..610d9d1 100644 + list_for_each_entry(part, partitions, list) { struct mbr_partition_entry *entry; - + @@ -176,13 +197,13 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions) i++; } - + - if (hd->table_type == TYPE_HYBRID) { + /* For hybrid MBR without gpt-protective-first, append 0xEE after data partitions. */ + if (hd->table_type == TYPE_HYBRID && !hd->gpt_protective_first) { struct mbr_partition_entry *entry; - + entry = &mbr.part_entry[i]; - + entry->boot = 0x00; - entry->partition_type = 0xee; entry->relative_sectors = 1; entry->total_sectors = hd->gpt_location / 512 + GPT_SECTORS - 2; -@@ -978,6 +999,7 @@ static int hdimage_setup(struct image *image, cfg_t *cfg) +@@ -979,6 +1000,7 @@ static int hdimage_setup(struct image *image, cfg_t *cfg) table_type = cfg_getstr(cfg, "partition-table-type"); hd->gpt_location = cfg_getint_suffix(cfg, "gpt-location"); hd->gpt_no_backup = cfg_getbool(cfg, "gpt-no-backup"); + hd->gpt_protective_first = cfg_getbool(cfg, "gpt-protective-first"); hd->fill = cfg_getbool(cfg, "fill"); - + if (is_block_device(imageoutfile(image))) { -@@ -1227,6 +1249,7 @@ static cfg_opt_t hdimage_opts[] = { +@@ -1229,6 +1251,7 @@ static cfg_opt_t hdimage_opts[] = { CFG_BOOL("gpt", cfg_false, CFGF_NODEFAULT), CFG_STR("gpt-location", NULL, CFGF_NONE), CFG_BOOL("gpt-no-backup", cfg_false, CFGF_NONE), @@ -128,6 +129,6 @@ index 274c6ac..610d9d1 100644 CFG_BOOL("fill", cfg_false, CFGF_NONE), CFG_END() }; --- +-- 2.43.0 diff --git a/patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch b/patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch new file mode 100644 index 000000000..32173632d --- /dev/null +++ b/patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch @@ -0,0 +1,94 @@ +From 13ebec3f62a9ee69c3211f68a0d71ad6678b3402 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Tue, 3 Mar 2026 10:02:34 +0000 +Subject: [PATCH 2/2] image-hd: Support GROWFS flag on GPT partitions +Organization: Wires + +This is an indication to the operating system that the contained +filesystem should be expanded to fill the entire partition. For more +information: + +https://uapi-group.org/specifications/specs/discoverable_partitions_specification/#partition-attribute-flags +Signed-off-by: Tobias Waldekranz +--- + README.rst | 2 ++ + genimage.c | 2 ++ + genimage.h | 1 + + image-hd.c | 2 ++ + 4 files changed, 7 insertions(+) + +diff --git a/README.rst b/README.rst +index a4c1a23..39f7566 100644 +--- a/README.rst ++++ b/README.rst +@@ -142,6 +142,7 @@ Partition options: + For hd images this can be used for the last partition. If set + the partition will fill the remaining space of the image. + :bootable: Boolean specifying whether to set the bootable flag. ++:growfs: Boolean specifying whether to set the growfs flag (only with GPT). + :hidden: Boolean specifying whether to set the hidden flag (only with GPT). + :no-automount: Boolean specifying whether to set the no-automount flag (only with GPT). + :read-only: Boolean specifying whether to set the read-only flag (only with GPT). +@@ -509,6 +510,7 @@ genimage configuration GPT FLAG + ====================== ============================== + read-only GPT_PE_FLAG_READ_ONLY (Bit 60) + bootable GPT_PE_FLAG_BOOTABLE (Bit 2) ++growfs GPT_PE_FLAG_GROWFS (Bit 59) + hidden GPT_PE_FLAG_HIDDEN (Bit 62) + no-automount GPT_PE_FLAG_NO_AUTO (Bit 63) + ====================== ============================== +diff --git a/genimage.c b/genimage.c +index 770a8c4..f804843 100644 +--- a/genimage.c ++++ b/genimage.c +@@ -102,6 +102,7 @@ static cfg_opt_t partition_opts[] = { + CFG_INT("partition-type", 0, CFGF_NONE), + CFG_BOOL("bootable", cfg_false, CFGF_NONE), + CFG_BOOL("forced-primary", cfg_false, CFGF_NONE), ++ CFG_BOOL("growfs", cfg_false, CFGF_NONE), + CFG_BOOL("read-only", cfg_false, CFGF_NONE), + CFG_BOOL("hidden", cfg_false, CFGF_NONE), + CFG_BOOL("no-automount", cfg_false, CFGF_NONE), +@@ -405,6 +406,7 @@ static int parse_partitions(struct image *image, cfg_t *imagesec) + part->partition_type = cfg_getint(partsec, "partition-type"); + part->bootable = cfg_getbool(partsec, "bootable"); + part->forced_primary = cfg_getbool(partsec, "forced-primary"); ++ part->growfs = cfg_getbool(partsec, "growfs"); + part->read_only = cfg_getbool(partsec, "read-only"); + part->hidden = cfg_getbool(partsec, "hidden"); + part->no_automount = cfg_getbool(partsec, "no-automount"); +diff --git a/genimage.h b/genimage.h +index 2587bc6..912239e 100644 +--- a/genimage.h ++++ b/genimage.h +@@ -46,6 +46,7 @@ struct partition { + cfg_bool_t bootable; + cfg_bool_t logical; + cfg_bool_t forced_primary; ++ cfg_bool_t growfs; + cfg_bool_t read_only; + cfg_bool_t hidden; + cfg_bool_t no_automount; +diff --git a/image-hd.c b/image-hd.c +index 3ecdb98..4513322 100644 +--- a/image-hd.c ++++ b/image-hd.c +@@ -106,6 +106,7 @@ ct_assert(sizeof(struct gpt_partition_entry) == 128); + #define GPT_REVISION_1_0 0x00010000 + + #define GPT_PE_FLAG_BOOTABLE (1ULL << 2) ++#define GPT_PE_FLAG_GROWFS (1ULL << 59) + #define GPT_PE_FLAG_READ_ONLY (1ULL << 60) + #define GPT_PE_FLAG_HIDDEN (1ULL << 62) + #define GPT_PE_FLAG_NO_AUTO (1ULL << 63) +@@ -522,6 +523,7 @@ static int hdimage_insert_gpt(struct image *image, struct list_head *partitions) + table[i].last_lba = htole64((partition_end(part)) / 512 - 1); + table[i].flags = + (part->bootable ? GPT_PE_FLAG_BOOTABLE : 0) | ++ (part->growfs ? GPT_PE_FLAG_GROWFS : 0) | + (part->read_only ? GPT_PE_FLAG_READ_ONLY : 0) | + (part->hidden ? GPT_PE_FLAG_HIDDEN : 0) | + (part->no_automount ? GPT_PE_FLAG_NO_AUTO : 0); +-- +2.43.0 + diff --git a/src/confd/bin/gen-config b/src/confd/bin/gen-config.in similarity index 99% rename from src/confd/bin/gen-config rename to src/confd/bin/gen-config.in index d58e8b7b0..b2e5b1b55 100755 --- a/src/confd/bin/gen-config +++ b/src/confd/bin/gen-config.in @@ -121,7 +121,7 @@ gen_factory_cfg log "Starting up, calling gen_failure_cfg()" gen_failure_cfg -if [ -f "/mnt/aux/test-mode" ]; then +if [ -f "@MARKER_DIR@/test-mode" ]; then gen_test_cfg fi diff --git a/src/confd/configure.ac b/src/confd/configure.ac index 28f0c6e2f..3294a3209 100644 --- a/src/confd/configure.ac +++ b/src/confd/configure.ac @@ -7,6 +7,7 @@ AM_SILENT_RULES(yes) AC_CONFIG_FILES([ Makefile bin/Makefile + bin/gen-config bin/gen-version share/Makefile share/factory.d/Makefile @@ -85,6 +86,12 @@ AC_ARG_WITH(crypt, AS_HELP_STRING([--with-crypt=crypt], [Default crypt for $0$cleartext, default: yescrypt]), [crypt=$withval], [crypt="yescrypt"]) +AC_ARG_WITH([marker-dir], + [AS_HELP_STRING([--with-marker-dir=DIR], [Directory holding test-mode marker files @<:@/mnt/aux@:>@])], + [markerdir="$withval"], [markerdir=/mnt/aux]) +AC_DEFINE_UNQUOTED([MARKER_DIR], ["$markerdir"], [Directory holding test-mode marker files]) +AC_SUBST([MARKER_DIR], [$markerdir]) + AS_IF([test "x$enable_containers" = "xyes"], [ AC_DEFINE(CONTAINERS, 1, [Built with container support])]) @@ -203,6 +210,7 @@ Optional features: SNMP support .........: $enable_snmp Login shell ..........: $login_shell Default crypt algo ...: $crypt + Marker directory .....: $markerdir ------------- Compiler version -------------- $($CC --version || true) diff --git a/src/confd/src/main.c b/src/confd/src/main.c index 6e24690c4..1278bd977 100644 --- a/src/confd/src/main.c +++ b/src/confd/src/main.c @@ -54,7 +54,7 @@ #define MIGRATED_PATH "/run/confd-migrated.cfg" /* Set when startup-config fails, next boot goes to fail-secure, issue #1637 */ -#define FAILED_PATH "/mnt/aux/startup-config.failed" +#define FAILED_PATH MARKER_DIR"/startup-config.failed" /* * Set a finit condition in the usr/ namespace, e.g. @@ -603,7 +603,7 @@ static void handle_startup_failure(sr_session_ctx_t *sess, const char *failure_p */ static void maybe_enable_test_mode(void) { - if (fexist("/mnt/aux/test-mode")) { + if (fexist(MARKER_DIR "/test-mode")) { int rc; conout(3, "Enabling test mode"); @@ -635,9 +635,9 @@ static int bootstrap_config(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, } /* Test mode support */ - if (fexist("/mnt/aux/test-mode")) { - if (fexist("/mnt/aux/test-override-startup")) { - unlink("/mnt/aux/test-override-startup"); + if (fexist(MARKER_DIR "/test-mode")) { + if (fexist(MARKER_DIR "/test-override-startup")) { + unlink(MARKER_DIR "/test-override-startup"); config_path = startup_path; } else { NOTE("Test mode detected, switching to test-config"); diff --git a/src/factory/factory.c b/src/factory/factory.c index b02d90f8e..4259f1cb8 100644 --- a/src/factory/factory.c +++ b/src/factory/factory.c @@ -10,8 +10,15 @@ #include #include -#define RESETME "/mnt/cfg/infix/.reset" -#define touch(f) mknod((f), S_IFREG|0644, 0) +#define stringify(_x) #_x +#define xstringify(_x) stringify(_x) + +#ifndef RESETME_PATH +#error "RESETME_PATH must be specified" +#endif +#define RESETME xstringify(RESETME_PATH) + +#define BOARDCHECK "/usr/libexec/infix/check-factory" int rawgetch(void) { @@ -87,12 +94,39 @@ static int run(const char *cmd) return rc; } +static int mark(void) +{ + return run("mkdir -p $(dirname " RESETME ") && touch " RESETME); +} + +static int check(int clear) +{ + struct stat st; + + if (!stat(RESETME, &st) && ((st.st_mode & S_IFMT) == S_IFREG)) { + if (clear) + unlink(RESETME); + + return 0; + } + + if (!run("grep -q 'finit.cond=factory-reset' /proc/cmdline")) + return 0; + + if (!stat(BOARDCHECK, &st) && (st.st_mode & S_IXUSR)) + return run(BOARDCHECK); + + return 1; +} + int main(int argc, char *argv[]) { struct option long_opts[] = { - { "help", 0, NULL, 'h' }, - { "no-reboot", 0, NULL, 'r' }, - { "assume-yes", 0, NULL, 'y' }, + { "check", 0, NULL, 'c' }, + { "check-clear", 0, NULL, 'C' }, + { "help", 0, NULL, 'h' }, + { "no-reboot", 0, NULL, 'r' }, + { "assume-yes", 0, NULL, 'y' }, { NULL, 0, NULL, 0 } }; int reboot = 1; @@ -100,8 +134,12 @@ int main(int argc, char *argv[]) char *tty; int c; - while ((c = getopt_long(argc, argv, "h?ry", long_opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "cCh?ry", long_opts, NULL)) != EOF) { switch (c) { + case 'c': + return check(0); + case 'C': + return check(1); case 'h': case '?': return usage(0); @@ -127,7 +165,7 @@ int main(int argc, char *argv[]) errx(1, "factory reset only allowed from console login!"); if (yes || yorn("Factory reset device (y/N)? ")) { - if (touch(RESETME) && errno != EEXIST) + if (mark()) err(1, "failed"); warnx("scheduled factory reset on next boot."); diff --git a/src/test-mode/configure.ac b/src/test-mode/configure.ac index 71280daa0..769658c01 100644 --- a/src/test-mode/configure.ac +++ b/src/test-mode/configure.ac @@ -14,6 +14,11 @@ LT_INIT PKG_PROG_PKG_CONFIG +AC_ARG_WITH([marker-dir], + [AS_HELP_STRING([--with-marker-dir=DIR], [Directory holding test-mode marker files @<:@/mnt/aux@:>@])], + [markerdir="$withval"], [markerdir=/mnt/aux]) +AC_DEFINE_UNQUOTED([MARKER_DIR], ["$markerdir"], [Directory holding test-mode marker files]) + PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36]) PKG_CHECK_MODULES([libite], [libite >= 2.5.0]) @@ -53,6 +58,7 @@ cat < -o " ; exit 1 ;; - esac -done - -if [ -z "$base_img" ] || [ -z "$output_img" ]; then - echo "Both -b (base image) and -o (output image) parameters are required." - exit 1 -fi - -rm -f "$output_img" -if ! cp "$base_img" "$output_img"; then - echo "Error: Failed to copy $base_img to $output_img" - exit 1 -fi - -if ! part_table=$(fdisk -l "$output_img" 2>/dev/null); then - echo "Error: Failed to read partition table from $output_img" - exit 1 -fi - -aux_line=$(echo "$part_table" | grep 'aux') -if [ -z "$aux_line" ]; then - echo "Error: 'aux' partition not found in $output_img" - exit 1 -fi - -start=$(echo "$aux_line" | awk '{print $2}') -end=$(echo "$aux_line" | awk '{print $3}') -count=$(($end - $start + 1)) -block_size=$(echo "$part_table" | grep "Logical sector size" | awk '{print $4}') - -dd if="$output_img" of="tmpaux" skip="$start" count="$count" bs="$block_size" status=none - -touch tmp-empty-file -e2cp tmp-empty-file tmpaux:/test-mode -rm tmp-empty-file - -dd of="$output_img" if="tmpaux" seek="$start" count="$count" bs="$block_size" status=none conv=notrunc -rm tmpaux +die() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +workdir=$(mktemp -d) +trap 'rm -rf "$workdir"' EXIT INT TERM + + +baseimg="$1" +testimg="$2" + +basegpt="$workdir"/basegpt.json +sfdisk -J "$baseimg" >"$basegpt" + +lbsize=$(jq -r .partitiontable.sectorsize $basegpt) + +jq -r '.partitiontable.partitions[] + | select(.name == "aux" or .name == "esp") + | "\(.name) \(.start) \(.size)"' $basegpt | { + read part start size + + [ "$part" ] || die "Disk must contain either aux or esp partition" + + + dd status=none \ + if="$baseimg" \ + bs=$lbsize skip=$start count=$size \ + of="$workdir"/markerpart \ + || die "Unable to extract marker partition" + + touch "$workdir"/empty + + case "$part" in + aux) + e2cp "$workdir"/empty "$workdir"/markerpart:/test-mode \ + || die "Unable to inject test-mode in aux" + ;; + esp) + mcopy -i "$workdir"/markerpart "$workdir"/empty ::/test-mode \ + || die "Unable to inject test-mode in esp" + ;; + esac + + cp -f "$baseimg" "$testimg" || die "Unable to create $testimg" + dd status=none conv=notrunc \ + if="$workdir"/markerpart \ + bs=$lbsize seek=$start count=$size \ + of="$testimg" \ + || die "Unable to inject markerpartition in $testimg" +} diff --git a/test/templates/infix-bios-x86_64.mustache b/test/templates/infix-bios-x86_64.mustache index eddecec07..2666380cc 100644 --- a/test/templates/infix-bios-x86_64.mustache +++ b/test/templates/infix-bios-x86_64.mustache @@ -4,12 +4,12 @@ con=hvc0 tty -s && con=hvc2 img={{#qn_image}}{{qn_image}}{{/qn_image}}{{^qn_image}}infix-x86_64-disk.img{{/qn_image}} -bios={{#qn_bios}}{{qn_bios}}{{/qn_bios}}{{^qn_bios}}OVMF.fd{{/qn_bios}} +bios={{#qn_bios}}{{qn_bios}}{{/qn_bios}}{{^qn_bios}}/usr/share/OVMF/OVMF.fd{{/qn_bios}} imgdir=. {{> inc/infix-usb}} origimg=$(realpath $img) -qemu-img create -f qcow2 -o backing_file=$origimg -F qcow2 {{name}}.qcow2 +qemu-img create -b $origimg -F raw -f qcow2 {{name}}.qcow2 4G {{> inc/infix-mactab}} @@ -20,7 +20,7 @@ mkfifo /tmp/{{name}}-gps.in /tmp/{{name}}-gps.out 2>/dev/null mkfifo /tmp/{{name}}-gps1.in /tmp/{{name}}-gps1.out 2>/dev/null {{/qn_gps}} -exec qemu-system-x86_64 -M pc,accel=kvm:tcg -cpu max \ +exec qemu-system-x86_64 -M q35,smm=on,accel=kvm:tcg -cpu max \ -m {{#qn_mem}}{{qn_mem}}{{/qn_mem}}{{^qn_mem}}256M{{/qn_mem}} \ {{> ../qeneth/templates/inc/qemu-links}} {{> inc/infix-fwcfg}} diff --git a/test/test.mk b/test/test.mk index fdc10ff27..8973f963e 100644 --- a/test/test.mk +++ b/test/test.mk @@ -24,8 +24,7 @@ mode-run := -t $(BINARIES_DIR)/qemu.dot mode := $(mode-$(TEST_MODE)) pkg-$(ARCH) := -p $(O)/images/$(INFIX_ARTIFACT).pkg -binaries-$(ARCH) := $(INFIX_ARTIFACT).qcow2 -binaries-x86_64 += OVMF.fd +binaries-$(ARCH) := $(INFIX_ARTIFACT).disk binaries := $(foreach bin,$(binaries-$(ARCH)),-f $(BINARIES_DIR)/$(bin)) # Common transport override for minimal defconfigs diff --git a/test/virt/quad/topology.dot.in b/test/virt/quad/topology.dot.in index f42e9c22c..32305e7b3 100644 --- a/test/virt/quad/topology.dot.in +++ b/test/virt/quad/topology.dot.in @@ -24,7 +24,7 @@ graph "quad" { label="{ e1 | e2 | e3 | e4 } | dut1 | { e5 | e6 | e7 | e8 } | { radio0 | radio1 | radio2 | radio3 }", pos="10,30!", provides="infix watchdog gps", - expected_boot="primary", + expected_boot="internal-primary", qn_console=9001, qn_mem="384M", qn_usb="dut1.usb", @@ -40,7 +40,7 @@ graph "quad" { label="{ e1 | e2 | e3 | e4 } | dut2 | { e5 | e6 | e7 | e8 } | { radio0 | radio1 | radio2 }", pos="0,20!", provides="infix watchdog", - expected_boot="primary", + expected_boot="internal-primary", qn_console=9002, qn_mem="384M", qn_usb="dut2.usb" @@ -49,7 +49,7 @@ graph "quad" { label="{ e1 | e2 | e3 | e4 } | dut3 | { e5 | e6 | e7 | e8 } | { radio0 | radio1 }", pos="0,10!", provides="infix watchdog", - expected_boot="primary", + expected_boot="internal-primary", qn_console=9003, qn_mem="384M", qn_usb="dut3.usb" @@ -59,7 +59,7 @@ graph "quad" { label="{ e1 | e2 | e3 | e4 } | dut4 | { e5 | e6 | e7 | e8 } | { radio0 | radio1 }", pos="10,0!", provides="infix watchdog", - expected_boot="primary", + expected_boot="internal-primary", qn_console=9004, qn_mem="384M", qn_usb="dut4.usb" diff --git a/utils/lvm-mkinternal b/utils/lvm-mkinternal new file mode 100755 index 000000000..14fd2d812 --- /dev/null +++ b/utils/lvm-mkinternal @@ -0,0 +1,210 @@ +#!/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=(32 << 10), creation_time=0): + self.pvid = ID() + self.vgid = ID() + self.align = align + self.creation_time = creation_time + + self.lvs = [] + self.dsize = 0 + for img, path in images: + lv = Linear(self.dsize, align, path, 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() + args.images = list(zip(args.images[::2], args.images[1::2])) + + random.seed(args.creation_time) + + pv = InternalPV(args.images, creation_time=args.creation_time) + pv.write(sys.stdout.buffer) + +if __name__ == "__main__": + main()