diff --git a/meta-digi-dey/classes/dey-swupdate-common.bbclass b/meta-digi-dey/classes/dey-swupdate-common.bbclass index ee96e2d97..7e3031d3e 100644 --- a/meta-digi-dey/classes/dey-swupdate-common.bbclass +++ b/meta-digi-dey/classes/dey-swupdate-common.bbclass @@ -48,6 +48,26 @@ def update_based_on_files(d): # Variable that determines if SWU update is based on files or not. SWUPDATE_IS_FILES_UPDATE = "${@update_based_on_files(d)}" +####################################### +###### SWU Update based on RDIFF ###### +####################################### + +# Variable used to generate the 'rootfs' RDIFF file. Do not modify. +SWUPDATE_RDIFF_ROOTFS_DELTA_FILE_NAME = "swupdate-rootfs.rdiff" + +# Initialize variable to provide the base file from which to generate the 'rootfs' RDIFF file. +SWUPDATE_RDIFF_ROOTFS_SOURCE_FILE ?= "" + +# Rdiff image template based on storage type. +SWUPDATE_RDIFF_IMAGE_TEMPLATE_FILE = "${@bb.utils.contains('STORAGE_MEDIA', 'mmc', 'image_template_rdiff_mmc', 'image_template_rdiff_nand', d)}" + +# Checks whether SWU update is based on RDIFF or not. +def update_based_on_rdiff(d): + return str("read-only-rootfs" in d.getVar('IMAGE_FEATURES') and d.getVar('SWUPDATE_IS_FILES_UPDATE') != "true" and d.getVar('SWUPDATE_RDIFF_ROOTFS_SOURCE_FILE') != "").lower() + +# Variable that determines if SWU update is based on RDIFF or not. +SWUPDATE_IS_RDIFF_UPDATE = "${@update_based_on_rdiff(d)}" + ####################################### ##### SWU Update based on images ###### ####################################### @@ -57,7 +77,7 @@ SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE = "${@bb.utils.contains('STORAGE_MEDIA', 'mm # Checks whether SWU update is based on images or not. def update_based_on_images(d): - return str(d.getVar('SWUPDATE_IS_FILES_UPDATE') != "true").lower() + return str(d.getVar('SWUPDATE_IS_FILES_UPDATE') != "true" and d.getVar('SWUPDATE_IS_RDIFF_UPDATE') != "true").lower() # Variable that determines if SWU update is based on images or not. SWUPDATE_IS_IMAGES_UPDATE = "${@update_based_on_images(d)}" @@ -77,8 +97,16 @@ UBOOTIMG_OFFSET ?= "${BOOTLOADER_SEEK_BOOT}" ########## SWU Update Script ########## ####################################### +# Retrieve the correct update script name based on the SWU update type. +def get_update_script_name(d): + if d.getVar('SWUPDATE_IS_FILES_UPDATE') == "true": + return "update_files.sh" + if d.getVar('SWUPDATE_IS_RDIFF_UPDATE') == "true": + return "update_rdiff.sh" + return "update_images.sh" + # Initialize variable that configures the update script to use. -SWUPDATE_SCRIPT ?= "${@oe.utils.vartrue('SWUPDATE_IS_FILES_UPDATE', 'update_files.sh', 'update_images.sh', d)}" +SWUPDATE_SCRIPT ?= "${@get_update_script_name(d)}" # Name of the update script to include in the SWU package. SWUPDATE_SCRIPT_NAME = "${@os.path.basename(d.getVar('SWUPDATE_SCRIPT'))}" diff --git a/meta-digi-dey/classes/dey-swupdate.bbclass b/meta-digi-dey/classes/dey-swupdate.bbclass index eb351c1fd..15e0e3c29 100644 --- a/meta-digi-dey/classes/dey-swupdate.bbclass +++ b/meta-digi-dey/classes/dey-swupdate.bbclass @@ -14,6 +14,8 @@ # Load commmon variables. inherit dey-swupdate-common +DEPENDS += "${@oe.utils.ifelse(d.getVar('SWUPDATE_IS_RDIFF_UPDATE') == 'true', 'librsync-native', '')}" + ####################################### ###### SWU Update based on files ###### ####################################### @@ -60,3 +62,26 @@ create_swupdate_targz_file() { gzip "${targzfile%.*}" } ROOTFS_POSTPROCESS_COMMAND:append = "${@oe.utils.conditional('SWUPDATE_IS_FILES_UPDATE', 'true', ' create_swupdate_targz_file;', '', d)}" + +####################################### +###### SWU Update based on RDIFF ###### +####################################### + +create_swupdate_rdiff_file() { + local signature_file="${DEPLOY_DIR_IMAGE}/swupdate_rootfs_rdiff.sig" + local rootfs_rdiff_file="${DEPLOY_DIR_IMAGE}/${SWUPDATE_RDIFF_ROOTFS_DELTA_FILE_NAME}" + local rootfs_file="${IMGDEPLOYDIR}/${IMAGE_LINK_NAME}.squashfs" + + # Clean previous versions of the files. + rm -f "${signature_file}" "${rootfs_rdiff_file}" + + # Create signature file. + rdiff signature "${SWUPDATE_RDIFF_ROOTFS_SOURCE_FILE}" "${signature_file}" + + # Create the delta file. + rdiff delta "${signature_file}" "${rootfs_file}" "${rootfs_rdiff_file}" + + # Clean intermediates. + rm -f "${signature_file}" +} +IMAGE_POSTPROCESS_COMMAND:append = "${@oe.utils.conditional('SWUPDATE_IS_RDIFF_UPDATE', 'true', ' create_swupdate_rdiff_file;', '', d)}" diff --git a/meta-digi-dey/recipes-digi/swu-images/files/image_template_rdiff_mmc b/meta-digi-dey/recipes-digi/swu-images/files/image_template_rdiff_mmc new file mode 100644 index 000000000..6ca186650 --- /dev/null +++ b/meta-digi-dey/recipes-digi/swu-images/files/image_template_rdiff_mmc @@ -0,0 +1,8 @@ + { + type = "rdiff_image"; + filename = "@@SWUPDATE_RDIFF_ROOTFS_DELTA_FILE_NAME@@"; + device = "##DEV##"; + properties: { + rdiffbase = ["/dev/rdiff_source_rootfs"]; + }; + } diff --git a/meta-digi-dey/recipes-digi/swu-images/files/image_template_rdiff_nand b/meta-digi-dey/recipes-digi/swu-images/files/image_template_rdiff_nand new file mode 100644 index 000000000..88585fa5b --- /dev/null +++ b/meta-digi-dey/recipes-digi/swu-images/files/image_template_rdiff_nand @@ -0,0 +1,9 @@ + { + type = "ubivol_rdiff_image"; + filename = "@@SWUPDATE_RDIFF_ROOTFS_DELTA_FILE_NAME@@"; + volume = "##DEV##"; + properties: { + rdiffbase = ["/dev/rdiff_source_rootfs"]; + rdiffnewsize = "$swupdate_get_size(@@DEPLOY_DIR_IMAGE@@/@@IMG_NAME@@-@@MACHINE@@.squashfs)"; + }; + } diff --git a/meta-digi-dey/recipes-digi/swu-images/files/sw-description-rdiff_template b/meta-digi-dey/recipes-digi/swu-images/files/sw-description-rdiff_template new file mode 100644 index 000000000..ad06f926d --- /dev/null +++ b/meta-digi-dey/recipes-digi/swu-images/files/sw-description-rdiff_template @@ -0,0 +1,49 @@ +software = +{ + version = "@@DEY_FIRMWARE_VERSION@@"; + description = "@@SWUPDATE_DESCRIPTION@@"; + + @@SWUPDATE_STORAGE_TYPE@@ = { + primary: { + images: ( + ##IMAGES_PRIMARY## + ); + scripts: ( + { + filename = "@@SWUPDATE_SCRIPT_NAME@@"; + type = "shellscript"; + data = "$swupdate_get_sha256(@@SWUPDATE_RDIFF_ROOTFS_SOURCE_FILE@@)" + sha256 = "$swupdate_get_sha256(@@SWUPDATE_SCRIPT_NAME@@)"; + } + ); + uboot: ( + { + name = "upgrade_available"; + value = "1"; + } + ); + } + secondary: { + images: ( + ##IMAGES_SECONDARY## + ); + scripts: ( + { + filename = "@@SWUPDATE_SCRIPT_NAME@@"; + type = "shellscript"; + data = "$swupdate_get_sha256(@@SWUPDATE_RDIFF_ROOTFS_SOURCE_FILE@@)" + sha256 = "$swupdate_get_sha256(@@SWUPDATE_SCRIPT_NAME@@)"; + } + ); + uboot: ( + { + name = "upgrade_available"; + value = "1"; + } + ); + } + platform = { + ref = "#./primary"; + } + }; +} diff --git a/meta-digi-dey/recipes-digi/swu-images/files/update_rdiff.sh b/meta-digi-dey/recipes-digi/swu-images/files/update_rdiff.sh new file mode 100755 index 000000000..aca86d7f5 --- /dev/null +++ b/meta-digi-dey/recipes-digi/swu-images/files/update_rdiff.sh @@ -0,0 +1,206 @@ +#!/bin/sh +#=============================================================================== +# +# update_rdiff +# +# Copyright (C) 2023 by Digi International Inc. +# All rights reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# +# !Description: SWU update rdiff script +# +#=============================================================================== + +# Sanity check. This script should be always executed with at least one argument. +if [ $# -lt 1 ]; then + exit 1; +fi + +# Sanity check. Do not run in single-boot systems. +[ "$(fw_printenv -n dualboot)" = "yes" ] || { echo "This update cannot be applied to single-boot systems, aborting..."; exit 1; } + +# Sanity check. Do not run in R/W systems. +if ! grep "/proc/mounts" -qe "squashfs"; then + echo "This update cannot be applied to R/W systems, aborting..." + exit 1 +fi + +# Variables. +BLOCK_SIZE=4096 +ROOTFS_NAME="rootfs" +ROOTFS_SOURCE_ENDPOINT="/dev/rdiff_source_rootfs" +ROOTFS_DEV_BLOCK="mmcblk0p3" +ROOTFS_DEV_BLOCK_A="mmcblk0p3" +ROOTFS_DEV_BLOCK_B="mmcblk0p4" + +# Determines whether the file system type is UBIFS or not. +is_ubifs() { + [ -c "/dev/ubi0" ] +} + +# Determines whether the system is dualboot or not. +is_dualboot() { + [ "$(fw_printenv -n dualboot)" = "yes" ] +} + +# Retrieves the dualboot active system letter. +# +# Returns: +# The dualboot active system letter: 'a' for primary, 'b' for secondary. +get_active_system() { + local active_system="$(fw_printenv -n active_system)" + echo "${active_system}" | cut -d_ -f2 +} + +# Retrieves the MTD partition number corresponding to the given partition name. +# +# Args: +# $1: partition name. +# +# Returns: +# The MTD partition number corresponding to the given partition name, -1 if +# not found. +get_mtd_number() { + local mtd_line="$(sed -ne "/${1}/s,^mtd\([0-9]\+\).*,\1,g;T;p" /proc/mtd)" + echo "${mtd_line:--1}" +} + +# Creates the UBI device for the given MTD partition number. +# +# Args: +# $1: the MTD partition number to create the UBI device for. +# +# Returns: +# The created UBI device number for the given MTD partition number, -1 if error. +create_ubi_device() { + local dev_number="$(ubiattach -m "${1}" 2>/dev/null | sed -ne 's,.*device number \([0-9]\).*,\1,g;T;p' 2>/dev/null)" + echo "${dev_number:--1}" +} + +# Retrieves the UBI device number containing the given partition name. If the +# device does not exist, the method attempts to create it based on the MTD dev +# number containing the desired partition. +# +# Args: +# $1: partition name. +# +# Returns: +# The UBI device number containing the given partition name, -1 if not found. +get_ubi_device() { + local ubi_devices="$(ubinfo | grep "Present UBI devices:" | cut -d ":" -f2 | xargs | sed -e 's/,//g')" + for ubi_device in ${ubi_devices}; do + if ubinfo "/dev/${ubi_device}" -a | grep -qe "Name:.*$1"; then + echo "${ubi_device}" | tr -dc '0-9' + return 0 + fi + done + + # Look for the MTD number containing the given partition name. + local mtd_num="$(get_mtd_number "${1}")" + if [ "${mtd_num}" = "-1" ]; then + echo "-1" + return 1 + else + # Create the UBI device. + ubi_device_number="$(create_ubi_device "${mtd_num}")" + echo "${ubi_device_number}" + fi +} + +# Retrieves the UBI volume containing the given partition name. +# +# Args: +# $1: partition name. +# +# Returns: +# The UBI volume containing the given partition name, -1 if not found. +get_ubi_volume() { + # Look for the UBI device containing given partition. + local ubi_device="$(get_ubi_device "${1}")" + if [ "${ubi_device}" = "-1" ]; then + echo "-1" + return 1 + fi + # Look for the UBI volume containing given partition. + local ubi_volume="$(ubinfo -d "${ubi_device}" -N "${1}" | grep "Volume ID" | cut -d ":" -f2 | xargs | cut -d " " -f1)" + if [ -z "${ubi_volume}" ]; then + echo "-1" + return 1 + fi + echo "ubi${ubi_device}_${ubi_volume}" +} + +# Creates the 'rootfs' source endpoint. +# +# Update source for the 'rootfs' partition cannot be determined at build time. For MTD +# devices, it depends on whether system is based on single or multiple MTD partitions. +# For this reason, hook the source update to a well known endpoint and just create the +# required link from the running system once all the information is available. +create_source_endpoint() { + # Initialize vars. Assume system is MMC based. + local rootfs_source_partiton="${ROOTFS_NAME}" + local rootfs_source_dev="${ROOTFS_DEV_BLOCK}" + + # Remove previous link. + [ -L "${ROOTFS_SOURCE_ENDPOINT}" ] && unlink "${ROOTFS_SOURCE_ENDPOINT}" + + # Update variables for dualboot systems. + if is_dualboot; then + local active_part="$(get_active_system)" + rootfs_source_partiton="${rootfs_source_partiton}_${active_part}" + if [ "${active_part}" = "a" ]; then + rootfs_source_dev=${ROOTFS_DEV_BLOCK_A} + else + rootfs_source_dev=${ROOTFS_DEV_BLOCK_B} + fi + fi + + # Update variables for MTD systems. + if is_ubifs; then + # Look for 'rootfs' source UBI volume. + rootfs_source_dev="$(get_ubi_volume "${rootfs_source_partiton}")" + [ "${rootfs_source_dev}" = "-1" ] && { echo "Unable to find UBI volume containing '${rootfs_source_partiton}' partition."; exit 1; } + fi + + # Create link. + ln -s "${rootfs_source_dev}" "${ROOTFS_SOURCE_ENDPOINT}" +} + +# Validates the base image before applying the RDIFF patch. +# +# Args: +# $1: Checksum of original base image. +validate_base_image() { + local fs_size="$(hexdump -s 0x28 -n 4 -e '1/4 "%d"' "${ROOTFS_SOURCE_ENDPOINT}")" + fs_size=$(( (fs_size + 0xfff) & 0xfffff000 )) + local n_blocks=$(( fs_size/BLOCK_SIZE )) + local checksum="$(dd if="${ROOTFS_SOURCE_ENDPOINT}" bs="${BLOCK_SIZE}" count="${n_blocks}" 2> /dev/null | sha256sum | cut -d " " -f1)" + + if [ "${checksum}" != "${1}" ]; then + echo "[ERROR] Base image is not the expected one or has been modified. Aborting update..." + exit 1 + fi +} + +# Called just before installation process starts. +if [ "${1}" = "preinst" ]; then + create_source_endpoint + validate_base_image "${2}" + + # TODO: Execute custom code here. For example: + # - Mount additional devices/partitions. + # - Stop services/process before installation. +fi + +# Called just after installation process ends. +if [ "${1}" = "postinst" ]; then + : + + # TODO: Execute custom code here. For example: + # - Clean files/directories. + # - Post-process files. +fi diff --git a/meta-digi-dey/recipes-digi/swu-images/swu.inc b/meta-digi-dey/recipes-digi/swu-images/swu.inc index 1332116de..94199300f 100644 --- a/meta-digi-dey/recipes-digi/swu-images/swu.inc +++ b/meta-digi-dey/recipes-digi/swu-images/swu.inc @@ -8,13 +8,17 @@ LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-only;md5=801f80980d171d SRC_URI = " \ file://sw-description-images_template \ file://sw-description-files_template \ + file://sw-description-rdiff_template \ file://sw-description-uboot \ file://swupdate_uboot_nand.sh \ file://swupdate_uboot_mmc.sh \ file://image_template_mmc \ file://image_template_nand \ + file://image_template_rdiff_mmc \ + file://image_template_rdiff_nand \ file://update_images.sh \ file://update_files.sh \ + file://update_rdiff.sh \ " inherit swupdate dey-swupdate-common @@ -26,6 +30,7 @@ INHIBIT_SWUPDATE_ADD_SRC_URI = "true" SWUPDATE_IMAGES = " \ ${@oe.utils.ifelse(d.getVar('SWUPDATE_IS_IMAGES_UPDATE') == 'true', '${IMG_NAME}', '')} \ ${@oe.utils.ifelse(d.getVar('SWUPDATE_IS_FILES_UPDATE') == 'true', '${SWUPDATE_FILES_TARGZ_FILE_NAME}', '')} \ + ${@oe.utils.ifelse(d.getVar('SWUPDATE_IS_RDIFF_UPDATE') == 'true', '${IMG_NAME} ${SWUPDATE_RDIFF_ROOTFS_DELTA_FILE_NAME}', '')} \ ${@oe.utils.ifelse(d.getVar('SWUPDATE_UBOOTIMG') == 'true', '${UBOOT_PREFIX}', '')} \ ${@oe.utils.ifelse(d.getVar('SWUPDATE_UBOOTIMG') == 'true', '${SWUPDATE_UBOOT_SCRIPT}', '')} \ ${SWUPDATE_SCRIPT_NAME} \ @@ -33,7 +38,9 @@ SWUPDATE_IMAGES = " \ # Associate images and file types. python () { - img_fstypes = d.getVar('BOOTFS_EXT') + " " + d.getVar('ROOTFS_EXT') + img_fstypes = d.getVar('BOOTFS_EXT') + if d.getVar('SWUPDATE_IS_IMAGES_UPDATE') == "true": + img_fstypes = img_fstypes + " " + d.getVar('ROOTFS_EXT') d.setVarFlag("SWUPDATE_IMAGES_FSTYPES", d.getVar('IMG_NAME'), img_fstypes) if (d.getVar('SWUPDATE_UBOOTIMG') == "true"): uboot_fstypes = d.getVar('UBOOT_EXT') @@ -75,6 +82,8 @@ fill_description() { sed -i -e "s,##UBOOTIMG_OFFSET##,${UBOOTIMG_OFFSET},g" "${WORKDIR}/sw-description" elif [ "${SWUPDATE_IS_FILES_UPDATE}" = "true" ]; then cp ${WORKDIR}/sw-description-files_template ${WORKDIR}/sw-description + elif [ "${SWUPDATE_IS_RDIFF_UPDATE}" = "true" ]; then + cp ${WORKDIR}/sw-description-rdiff_template ${WORKDIR}/sw-description else cp ${WORKDIR}/sw-description-images_template ${WORKDIR}/sw-description fi @@ -87,26 +96,35 @@ fill_description() { BOOT_IMAGE_NAME="${IMG_NAME}-${MACHINE}${BOOTFS_EXT}" ROOTFS_IMAGE_NAME="${IMG_NAME}-${MACHINE}${ROOTFS_EXT}" + # Set correct image templates. + BOOT_IMAGE_TEMPLATE="${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}" + ROOTFS_IMAGE_TEMPLATE="${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}" + if [ "${SWUPDATE_IS_RDIFF_UPDATE}" = "true" ]; then + ROOTFS_IMAGE_TEMPLATE="${SWUPDATE_RDIFF_IMAGE_TEMPLATE_FILE}" + fi + # Add primary bank images section for dual boot systems. printf "%s,\n%s\n" \ - "$(sed -e "s,##IMG_NAME##,${BOOT_IMAGE_NAME},g" -e "s,##DEV##,${BOOT_DEV_NAME_A},g" -e "/compressed/d" "${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}")" \ - "$(sed -e "s,##IMG_NAME##,${ROOTFS_IMAGE_NAME},g" -e "s,##DEV##,${ROOTFS_DEV_NAME_A},g" "${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}")" \ + "$(sed -e "s,##IMG_NAME##,${BOOT_IMAGE_NAME},g" -e "s,##DEV##,${BOOT_DEV_NAME_A},g" -e "/compressed/d" "${BOOT_IMAGE_TEMPLATE}")" \ + "$(sed -e "s,##IMG_NAME##,${ROOTFS_IMAGE_NAME},g" -e "s,##DEV##,${ROOTFS_DEV_NAME_A},g" "${ROOTFS_IMAGE_TEMPLATE}")" \ > images_temp.txt sed -i -e "/##IMAGES_PRIMARY##/r images_temp.txt" -e "/##IMAGES_PRIMARY##/d" "${WORKDIR}/sw-description" # Add secondary bank images section for dual boot systems. printf "%s,\n%s\n" \ - "$(sed -e "s,##IMG_NAME##,${BOOT_IMAGE_NAME},g" -e "s,##DEV##,${BOOT_DEV_NAME_B},g" -e "/compressed/d" "${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}")" \ - "$(sed -e "s,##IMG_NAME##,${ROOTFS_IMAGE_NAME},g" -e "s,##DEV##,${ROOTFS_DEV_NAME_B},g" "${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}")" \ + "$(sed -e "s,##IMG_NAME##,${BOOT_IMAGE_NAME},g" -e "s,##DEV##,${BOOT_DEV_NAME_B},g" -e "/compressed/d" "${BOOT_IMAGE_TEMPLATE}")" \ + "$(sed -e "s,##IMG_NAME##,${ROOTFS_IMAGE_NAME},g" -e "s,##DEV##,${ROOTFS_DEV_NAME_B},g" "${ROOTFS_IMAGE_TEMPLATE}")" \ > images_temp.txt sed -i -e "/##IMAGES_SECONDARY##/r images_temp.txt" -e "/##IMAGES_SECONDARY##/d" "${WORKDIR}/sw-description" # Add images section for single boot systems. - printf "%s,\n%s\n" \ - "$(sed -e "s,##IMG_NAME##,${BOOT_IMAGE_NAME},g" -e "s,##DEV##,${BOOT_DEV_NAME},g" -e "/compressed/d" "${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}")" \ - "$(sed -e "s,##IMG_NAME##,${ROOTFS_IMAGE_NAME},g" -e "s,##DEV##,${ROOTFS_DEV_NAME_FINAL},g" "${SWUPDATE_IMAGES_IMAGE_TEMPLATE_FILE}")" \ - > images_temp.txt - sed -i -e "/##IMAGES_SINGLE##/r images_temp.txt" -e "/##IMAGES_SINGLE##/d" "${WORKDIR}/sw-description" + if [ "${SWUPDATE_IS_RDIFF_UPDATE}" != "true" ]; then + printf "%s,\n%s\n" \ + "$(sed -e "s,##IMG_NAME##,${BOOT_IMAGE_NAME},g" -e "s,##DEV##,${BOOT_DEV_NAME},g" -e "/compressed/d" "${BOOT_IMAGE_TEMPLATE}")" \ + "$(sed -e "s,##IMG_NAME##,${ROOTFS_IMAGE_NAME},g" -e "s,##DEV##,${ROOTFS_DEV_NAME_FINAL},g" "${ROOTFS_IMAGE_TEMPLATE}")" \ + > images_temp.txt + sed -i -e "/##IMAGES_SINGLE##/r images_temp.txt" -e "/##IMAGES_SINGLE##/d" "${WORKDIR}/sw-description" + fi # Remove 'compressed' flag for read-only file systems as they use 'squashfs' images. if [ -n "${@bb.utils.contains('IMAGE_FEATURES', 'read-only-rootfs', '1', '', d)}" ]; then