new file mode 100644
@@ -0,0 +1,90 @@
+#
+# CIP Core, generic profile
+#
+# Copyright (c) Siemens AG, 2020
+#
+# Authors:
+# Christian Storm <christian.storm@siemens.com>
+#
+# SPDX-License-Identifier: MIT
+
+KCONFIG_SNIPPETS = ""
+
+# The following function defines the kconfig snippet system
+# with automatich debian dependency injection
+#
+# To define a feature set, the user has to define the following
+# variable to an empty string:
+#
+# KFEATURE_featurename = ""
+#
+# Then, required additions to the variables can be defined:
+#
+# KFEATURE_featurename[KCONFIG_SNIPPETS] = "file://snippet-file-name.snippet"
+# KFEATURE_featurename[SRC_URI] = "file://required-file.txt"
+# KFEATURE_featurename[DEPENDS] = "deb-pkg1 deb-pkg2 deb-pkg3"
+# KFEATURE_featurename[DEBIAN_DEPENDS] = "deb-pkg1"
+# KFEATURE_featurename[BUILD_DEB_DEPENDS] = "deb-pkg1,deb-pkg2,deb-pkg3"
+
+# The 'KCONFIG_SNIPPETS' flag gives a list of URI entries, where only
+# file:// is supported. These snippets are appended to the DEFCONFIG file.
+#
+# Features can depend on other features via the following mechanism:
+#
+# KFEATURE_DEPS[feature1] = "feature2"
+
+python () {
+ requested_features = d.getVar("KFEATURES", True) or ""
+
+ features = set(requested_features.split())
+ old_features = set()
+ feature_deps = d.getVarFlags("KFEATURE_DEPS") or {}
+ while old_features != features:
+ diff_features = old_features.symmetric_difference(features)
+ old_features = features.copy()
+ for i in diff_features:
+ features.update(feature_deps.get(i, "").split())
+
+ for f in sorted(features):
+ bb.debug(2, "Feature: " + f)
+ varname = "KFEATURE_" + f
+ dummyvar = d.getVar(varname, False)
+ if dummyvar == None:
+ bb.error("Feature var " + f + " must be defined with needed flags.")
+ else:
+ feature_flags = d.getVarFlags(varname)
+ for feature_varname in sorted(feature_flags):
+ if feature_flags.get(feature_varname, "") != "":
+ sep = " "
+
+ # Required to add KCONFIG_SNIPPETS to SRC_URI here,
+ # because 'SRC_URI += "${KCONFIG_SNIPPETS}"' would
+ # conflict with SRC_APT feature.
+ if feature_varname == "KCONFIG_SNIPPETS":
+ d.appendVar('SRC_URI',
+ " " + feature_flags[feature_varname].strip())
+
+ # BUILD_DEP_DEPENDS and DEBIAN_DEPENDS is ',' separated
+ # Only add ',' if there is already something there
+ if feature_varname in ["BUILD_DEB_DEPENDS",
+ "DEBIAN_DEPENDS"]:
+ sep = "," if d.getVar(feature_varname) else ""
+
+ d.appendVar(feature_varname,
+ sep + feature_flags[feature_varname].strip())
+}
+
+# DEFCONFIG must be a predefined bitbake variable and the corresponding file
+# must exist in the WORKDIR.
+# The resulting generated config is the same file suffixed with ".gen"
+
+do_prepare_build_prepend() {
+ sh -x
+ GENCONFIG="${WORKDIR}/${DEFCONFIG}".gen
+ rm -f "$GENCONFIG"
+ cp "${WORKDIR}/${DEFCONFIG}" "$GENCONFIG"
+ for CONFIG_SNIPPET in $(echo "${KCONFIG_SNIPPETS}" | sed 's#file://##g')
+ do
+ cat ${WORKDIR}/$CONFIG_SNIPPET >> "$GENCONFIG"
+ done
+}
new file mode 100644
@@ -0,0 +1,76 @@
+#
+# CIP Core, generic profile
+#
+# Copyright (c) Siemens AG, 2020
+#
+# Authors:
+# Christian Storm <christian.storm@siemens.com>
+#
+# SPDX-License-Identifier: MIT
+
+# This class manages the config snippets together with their dependencies
+# to build SWUpdate
+
+inherit kconfig-snippets
+
+BUILD_DEB_DEPENDS = " \
+ zlib1g-dev, debhelper, libconfig-dev, libarchive-dev, \
+ python-sphinx:native, dh-systemd, libsystemd-dev"
+
+KFEATURE_lua = ""
+KFEATURE_lua[BUILD_DEB_DEPENDS] = "liblua5.3-dev"
+KFEATURE_lua[KCONFIG_SNIPPETS] = "file://swupdate_defconfig_lua.snippet"
+
+KFEATURE_luahandler = ""
+KFEATURE_luahandler[KCONFIG_SNIPPETS] = "file://swupdate_defconfig_luahandler.snippet"
+KFEATURE_luahandler[SRC_URI] = "file://${SWUPDATE_LUASCRIPT}"
+
+KFEATURE_DEPS = ""
+KFEATURE_DEPS[luahandler] = "lua"
+
+KFEATURE_efibootguard = ""
+KFEATURE_efibootguard[BUILD_DEB_DEPENDS] = "efibootguard-dev"
+KFEATURE_efibootguard[DEBIAN_DEPENDS] = "efibootguard-dev"
+KFEATURE_efibootguard[DEPENDS] = "efibootguard-dev"
+KFEATURE_efibootguard[KCONFIG_SNIPPETS] = "file://swupdate_defconfig_efibootguard.snippet"
+
+KFEATURE_mtd = ""
+KFEATURE_mtd[BUILD_DEB_DEPENDS] = "libmtd-dev"
+KFEATURE_mtd[DEPENDS] = "mtd-utils"
+KFEATURE_mtd[KCONFIG_SNIPPETS] = "file://swupdate_defconfig_mtd.snippet"
+
+KFEATURE_ubi = ""
+KFEATURE_ubi[BUILD_DEB_DEPENDS] = "libubi-dev"
+KFEATURE_ubi[KCONFIG_SNIPPETS] = "file://swupdate_defconfig_ubi.snippet"
+
+KFEATURE_DEPS[ubi] = "mtd"
+
+KFEATURE_u-boot = ""
+KFEATURE_u-boot[BUILD_DEB_DEPENDS] = "u-boot-${MACHINE}-dev"
+KFEATURE_u-boot[DEBIAN_DEPENDS] = "u-boot-tools"
+KFEATURE_u-boot[DEPENDS] = "${U_BOOT}"
+KFEATURE_u-boot[KCONFIG_SNIPPETS] = "file://swupdate_defconfig_u-boot.snippet"
+
+SWUPDATE_LUASCRIPT ?= "swupdate_handlers.lua"
+
+def get_bootloader_featureset(d):
+ bootloader = d.getVar("BOOTLOADER", True) or ""
+ if bootloader == "efibootguard":
+ return "efibootguard"
+ if bootloader == "u-boot":
+ return "u-boot"
+ return ""
+
+SWUPDATE_KFEATURES ??= ""
+KFEATURES = "${SWUPDATE_KFEATURES}"
+KFEATURES += "${@get_bootloader_featureset(d)}"
+
+# Astonishingly, as an anonymous python function, BOOTLOADER is always None
+# one time before it gets set. So the following must be a task.
+python do_check_bootloader () {
+ bootloader = d.getVar("BOOTLOADER", True) or "None"
+ if not bootloader in ["efibootguard", "u-boot"]:
+ bb.warn("swupdate: BOOTLOADER set to incompatible value: " + bootloader)
+}
+addtask check_bootloader before do_fetch
+
new file mode 100644
@@ -0,0 +1,75 @@
+#
+# CIP Core, generic profile
+#
+# Copyright (c) Siemens AG, 2020
+#
+# Authors:
+# Christian Storm <christian.storm@siemens.com>
+# Quirin Gylstorff <quirin.gylstorff@siemens.com>
+#
+# SPDX-License-Identifier: MIT
+
+SWU_IMAGE_FILE ?= "${PN}-${DISTRO}-${MACHINE}.swu"
+SWU_DESCRIPTION_FILE ?= "sw-description"
+SWU_ADDITIONAL_FILES ?= ""
+SWU_SIGNED ?= ""
+SWU_SIGNATURE_EXT ?= "sig"
+SWU_SIGNATURE_TYPE ?= "rsa"
+
+IMAGER_INSTALL += "${@'openssl' if bb.utils.to_boolean(d.getVar('SWU_SIGNED')) else ''}"
+
+do_swupdate_image[stamp-extra-info] = "${DISTRO}-${MACHINE}"
+do_swupdate_image[cleandirs] += "${WORKDIR}/swu"
+do_swupdate_image() {
+ rm -f '${DEPLOY_DIR_IMAGE}/${SWU_IMAGE_FILE}'
+ cp '${WORKDIR}/${SWU_DESCRIPTION_FILE}' '${WORKDIR}/swu/${SWU_DESCRIPTION_FILE}'
+
+ # Create symlinks for files used in the update image
+ for file in ${SWU_ADDITIONAL_FILES}; do
+ if [ -e "${WORKDIR}/$file" ]; then
+ ln -s "${WORKDIR}/$file" "${WORKDIR}/swu/$file"
+ else
+ ln -s "${DEPLOY_DIR_IMAGE}/$file" "${WORKDIR}/swu/$file"
+ fi
+ done
+
+ # Prepare for signing
+ sign='${@'x' if bb.utils.to_boolean(d.getVar('SWU_SIGNED')) else ''}'
+ if [ -n "$sign" ]; then
+ image_do_mounts
+ cp -f '${SIGN_KEY}' '${WORKDIR}/dev.key'
+ test -e '${SIGN_CRT}' && cp -f '${SIGN_CRT}' '${WORKDIR}/dev.crt'
+
+ # Fill in file check sums
+ for file in ${SWU_ADDITIONAL_FILES}; do
+ sed -i "s:$file-sha256:$(sha256sum '${WORKDIR}/swu/'$file | cut -f 1 -d ' '):g" \
+ '${WORKDIR}/swu/${SWU_DESCRIPTION_FILE}'
+ done
+ fi
+
+ cd "${WORKDIR}/swu"
+ for file in '${SWU_DESCRIPTION_FILE}' ${SWU_ADDITIONAL_FILES}; do
+ echo "$file"
+ if [ -n "$sign" -a \
+ '${SWU_DESCRIPTION_FILE}' = "$file" ]; then
+ if [ "${SWU_SIGNATURE_TYPE}" = "rsa" ]; then
+ sudo chroot ${BUILDCHROOT_DIR} /usr/bin/openssl dgst \
+ -sha256 -sign '${PP_WORK}/dev.key' \
+ '${PP_WORK}/swu/'"$file" \
+ > '${WORKDIR}/swu/'"$file".'${SWU_SIGNATURE_EXT}'
+ elif [ "${SWU_SIGNATURE_TYPE}" = "cms" ]; then
+ sudo chroot ${BUILDCHROOT_DIR} /usr/bin/openssl cms \
+ -sign -in '${PP_WORK}/swu/'"$file" \
+ -out '${WORKDIR}/swu/'"$file".'${SWU_SIGNATURE_EXT}' \
+ -signer '${PP_WORK}/dev.crt' \
+ -inkey '${PP_WORK}/dev.key' \
+ -outform DER -nosmimecap -binary
+ fi
+ echo "$file".'${SWU_SIGNATURE_EXT}'
+ fi
+ done | cpio -ovL -H crc \
+ > '${DEPLOY_DIR_IMAGE}/${SWU_IMAGE_FILE}'
+ cd -
+}
+
+addtask swupdate_image before do_build after do_copy_boot_files do_install_imager_deps do_transform_template
new file mode 100644
@@ -0,0 +1,6 @@
+swupdate (${PV}) unstable; urgency=medium
+
+ * SWUpdate
+
+ -- Christian Storm <christian.storm@siemens.com> Thu, 31 Jan 2019 15:23:56 +0100
+
new file mode 100644
@@ -0,0 +1 @@
+11
new file mode 100644
@@ -0,0 +1,15 @@
+Source: swupdate
+Section: embedded
+Priority: optional
+Maintainer: Stefano Babic <sbabic@denx.de>
+Build-Depends: ${BUILD_DEB_DEPENDS}
+Standards-Version: 4.2.1
+Homepage: http://sbabic.github.io/swupdate
+
+Package: swupdate
+Architecture: any
+Depends: ${DEBIAN_DEPENDS}
+Description: reliable way to update an embedded system
+ This project is thought to help to update an embedded system from a storage media or from network.
+ However, it should be mainly considered as a framework, where further protocols or installers
+ (in SWUpdate they are called handlers) can be easily added to the application.
new file mode 100644
@@ -0,0 +1,36 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: swupdate
+Maintainer: Stefano Babic <sbabic@denx.de>
+Source: http://github.com/sbabic/swupdate
+
+Files: *
+Copyright: 2014-2017 Stefano Babic <sbabic@denx.de>
+
+License: GPL-2 with OpenSSL exception
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ In addition, as a special exception, the author of this
+ program gives permission to link the code of its
+ release with the OpenSSL project's "OpenSSL" library (or
+ with modified versions of it that use the same license as
+ the "OpenSSL" library), and distribute the linked
+ executables. You must obey the GNU General Public
+ License in all respects for all of the code used other
+ than "OpenSSL". If you modify this file, you may extend
+ this exception to your version of the file, but you are
+ not obligated to do so. If you do not wish to do so,
+ delete this exception statement from your version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
new file mode 100755
@@ -0,0 +1,30 @@
+#!/usr/bin/make -f
+
+ifneq ($(DEB_BUILD_GNU_TYPE),$(DEB_HOST_GNU_TYPE))
+export CROSS_COMPILE=$(DEB_HOST_GNU_TYPE)-
+export CC=$(DEB_HOST_GNU_TYPE)-gcc
+export LD=$(DEB_HOST_GNU_TYPE)-gcc
+endif
+
+export DH_VERBOSE = 1
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+bindnow
+
+documentation: configure
+ make man
+
+configure:
+ make ${DEFCONFIG}
+
+build: documentation configure
+ dh $@
+
+%:
+ echo $@
+ dh $@
+
+override_dh_installchangelogs:
+ true
+
+override_dh_installdocs:
+ true
new file mode 100644
@@ -0,0 +1,2 @@
+examples/configuration
+examples/description
new file mode 100644
@@ -0,0 +1,2 @@
+swupdate usr/bin
+swupdate.cfg /etc
new file mode 100644
@@ -0,0 +1,5 @@
+doc/build/man/swupdate.1
+doc/build/man/client.1
+doc/build/man/sendtohawkbit.1
+doc/build/man/hawkbitcfg.1
+doc/build/man/progress.1
new file mode 100644
@@ -0,0 +1,2 @@
+X /tmp/datadst
+X /tmp/scripts
new file mode 100644
@@ -0,0 +1,12 @@
+# Example watch control file for uscan
+# Rename this file to "watch" and then you can run the "uscan" command
+# to check for upstream updates and more.
+# See uscan(1) for format
+
+# Compulsory line, this is a version 4 file
+version=4
+
+# GitHub hosted projects
+opts="filenamemangle="s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%<project>-$1.tar.gz%" \
+ https://github.com/<user>/swupdate/tags \
+ (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate
new file mode 100644
@@ -0,0 +1,2 @@
+#!/bin/sh
+deb-systemd-helper enable swupdate.socket || true
new file mode 100644
@@ -0,0 +1,6 @@
+globals :
+{
+ verbose = true;
+ loglevel = 10;
+ syslog = false;
+};
new file mode 100644
@@ -0,0 +1,11 @@
+[Unit]
+Description=SWUpdate daemon
+Documentation=https://github.com/sbabic/swupdate
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/swupdate -f /etc/swupdate.cfg
+KillMode=mixed
+
+[Install]
+WantedBy=multi-user.target
new file mode 100644
@@ -0,0 +1,11 @@
+[Unit]
+Description=SWUpdate socket listener
+Documentation=https://github.com/sbabic/swupdate
+Documentation=https://sbabic.github.io/swupdate
+
+[Socket]
+ListenStream=/tmp/sockinstctrl
+ListenStream=/tmp/swupdateprog
+
+[Install]
+WantedBy=sockets.target
new file mode 100644
@@ -0,0 +1,13 @@
+[Unit]
+Description=SWUpdate socket listener
+Documentation=https://github.com/sbabic/swupdate
+Documentation=https://sbabic.github.io/swupdate
+
+[Socket]
+SocketUser=${SWUPDATE_SOCKET_OWNER}
+SocketGroup=root
+ListenStream=/tmp/sockinstctrl
+ListenStream=/tmp/swupdateprog
+
+[Install]
+WantedBy=sockets.target
new file mode 100644
@@ -0,0 +1,83 @@
+#
+# Automatically generated file; DO NOT EDIT.
+# Swupdate Configuration
+#
+CONFIG_HAVE_DOT_CONFIG=y
+
+#
+# Swupdate Settings
+#
+
+#
+# General Configuration
+#
+# CONFIG_CURL is not set
+# CONFIG_CURL_SSL is not set
+CONFIG_SYSTEMD=y
+CONFIG_SCRIPTS=y
+# CONFIG_HW_COMPATIBILITY is not set
+CONFIG_SW_VERSIONS_FILE="/etc/sw-versions"
+
+#
+# Socket Paths
+#
+CONFIG_SOCKET_CTRL_PATH="/tmp/sockinstctrl"
+CONFIG_SOCKET_PROGRESS_PATH="/tmp/swupdateprog"
+CONFIG_SOCKET_REMOTE_HANDLER_DIRECTORY="/tmp/"
+# CONFIG_MTD is not set
+# CONFIG_LUA is not set
+# CONFIG_LUAPKG is not set
+# CONFIG_FEATURE_SYSLOG is not set
+
+#
+# Build Options
+#
+CONFIG_CROSS_COMPILE=""
+CONFIG_SYSROOT=""
+CONFIG_EXTRA_CFLAGS=""
+CONFIG_EXTRA_LDFLAGS=""
+CONFIG_EXTRA_LDLIBS=""
+
+#
+# Debugging Options
+#
+# CONFIG_DEBUG is not set
+# CONFIG_WERROR is not set
+# CONFIG_NOCLEANUP is not set
+# CONFIG_BOOTLOADER_EBG is not set
+# CONFIG_UBOOT is not set
+# CONFIG_BOOTLOADER_NONE is not set
+# CONFIG_BOOTLOADER_GRUB is not set
+# CONFIG_DOWNLOAD is not set
+# CONFIG_DOWNLOAD_SSL is not set
+# CONFIG_CHANNEL_CURL is not set
+# CONFIG_HASH_VERIFY=y
+# CONFIG_SIGNED_IMAGES is not set
+# CONFIG_ENCRYPTED_IMAGES is not set
+# CONFIG_SURICATTA is not set
+# CONFIG_WEBSERVER is not set
+CONFIG_GUNZIP=y
+
+#
+# Parser Features
+#
+CONFIG_LIBCONFIG=y
+CONFIG_PARSERROOT=""
+# CONFIG_JSON is not set
+# CONFIG_LUAEXTERNAL is not set
+# CONFIG_SETEXTPARSERNAME is not set
+# CONFIG_SETSWDESCRIPTION is not set
+
+#
+# Image Handlers
+#
+CONFIG_RAW=y
+# CONFIG_LUASCRIPTHANDLER is not set
+# CONFIG_SHELLSCRIPTHANDLER is not set
+# CONFIG_HANDLER_IN_LUA is not set
+# CONFIG_EMBEDDED_LUA_HANDLER is not set
+# CONFIG_EMBEDDED_LUA_HANDLER_SOURCE is not set
+CONFIG_ARCHIVE=y
+# CONFIG_REMOTE_HANDLER is not set
+# CONFIG_SWUFORWARDER_HANDLER is not set
+# CONFIG_BOOTLOADERHANDLER is not set
new file mode 100644
@@ -0,0 +1,3 @@
+CONFIG_BOOTLOADER_NONE=n
+CONFIG_BOOTLOADER_EBG=y
+CONFIG_BOOTLOADERHANDLER=y
new file mode 100644
@@ -0,0 +1,2 @@
+CONFIG_LUA=y
+CONFIG_LUAPKG="lua53"
new file mode 100644
@@ -0,0 +1,4 @@
+CONFIG_LUASCRIPTHANDLER=y
+CONFIG_HANDLER_IN_LUA=y
+CONFIG_EMBEDDED_LUA_HANDLER=y
+CONFIG_EMBEDDED_LUA_HANDLER_SOURCE="swupdate_handlers.lua"
new file mode 100644
@@ -0,0 +1 @@
+CONFIG_MTD=y
new file mode 100644
@@ -0,0 +1,3 @@
+CONFIG_UBOOT=y
+CONFIG_UBOOT_FWENV="/etc/fw_env.config"
+CONFIG_BOOTLOADERHANDLER=y
new file mode 100644
@@ -0,0 +1,6 @@
+CONFIG_UBIVOL=y
+CONFIG_UBIATTACH=y
+CONFIG_UBIBLACKLIST=""
+CONFIG_UBIWHITELIST=""
+CONFIG_UBIVIDOFFSET=0
+CONFIG_CFI=y
new file mode 100644
@@ -0,0 +1,453 @@
+--[[
+
+ Round-robin Image and File Handler.
+
+ Copyright (C) 2019, Siemens AG
+
+ Author: Christian Storm <christian.storm@siemens.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+
+ An `sw-description` file using these handlers may look like:
+ software =
+ {
+ version = "0.1.0";
+ images: ({
+ filename = "rootfs.ext4";
+ device = "sda4,sda5";
+ type = "roundrobin";
+ compressed = false;
+ });
+ files: ({
+ filename = "vmlinuz";
+ path = "vmlinuz";
+ type = "kernelfile";
+ device = "sda2,sda3";
+ filesystem = "vfat";
+ },
+ {
+ filename = "initrd.img";
+ path = "initrd.img";
+ type = "kernelfile";
+ device = "sda2,sda3";
+ filesystem = "vfat";
+ });
+ }
+
+ The semantics is as follows: Instead of having a fixed target device,
+ the 'roundrobin' image handler calculates the target device by parsing
+ /proc/cmdline, matching the root=<device> kernel parameter against its
+ 'device' attribute's list of devices, and sets the actual target
+ device to the next 'device' attribute list entry in a round-robin
+ manner. The actual flashing is done via chain-calling another handler,
+ defaulting to the "raw" handler.
+
+ The 'kernelfile' file handler reuses the 'roundrobin' handler's target
+ device calculation by reading the actual target device from the same
+ index into its 'device' attribute's list of devices. The actual placing
+ of files into this partition is done via chain-calling another handler,
+ defaulting to the "rawfile" handler.
+
+ In the above example, if /dev/sda4 is currently booted according to
+ /proc/cmdline, /dev/sda5 will be flashed and the vmlinuz and initrd.img
+ files will be placed on /dev/sda3. If /dev/sda5 is booted, /dev/sda4
+ will be flashed and the vmlinuz and initrd.img files are placed on
+ /dev/sda2.
+ In addition to "classical" device nodes as in this example, partition
+ UUIDs as reported, e.g., by `blkid -s PARTUUID` are also supported.
+ UBI volumes are supported as well by specifying a CSV list of
+ ubi<number>:<label> items.
+
+ Configuration is done via an INI-style configuration file located at
+ /etc/swupdate.handler.ini or via compiled-in configuration (by
+ embedding the Lua handler script into the SWUpdate binary via using
+ CONFIG_EMBEDDED_LUA_HANDLER), the latter having precedence over the
+ former. See the example configuration below.
+ If uncommenting this example block, it will take precedence over any
+ /etc/swupdate.handler.ini configuration file.
+
+ The chain-called handlers can either be specified in the configuration,
+ i.e., a static run-time setting, or via the 'chainhandler' property of
+ an 'image' or 'file' section in the sw-description, with the latter
+ taking precedence over the former, e.g.,
+ ...
+ images: ({
+ filename = "rootfs.ext4";
+ device = "sda4,sda5";
+ type = "roundrobin";
+ properties: {
+ chainhandler = "myraw";
+ };
+ });
+ ...
+ Such a sw-description fragment will chain-call the imaginary "myraw"
+ handler regardless of what's been configured in the compiled-in or the
+ configuration file.
+ When chain-calling the "rdiff_image" handler, its 'rdiffbase' property
+ is subject to round-robin as well, i.e., the 'rdiffbase' property is
+ expected to be a CSV list as for the 'device' property, and the actual
+ 'rdiffbase' property value is calculated following the same round-robin
+ calculation mechanism stated above prior to chain-calling the actual
+ "rdiff_image" handler, e.g.,
+ images: ({
+ filename = "rootfs.ext4";
+ type = "roundrobin";
+ device = "sda4,sda5";
+ properties: {
+ chainhandler = "rdiff_image";
+ rdiffbase="sda1,sda2";
+ };
+ });
+ will set the 'rdiffbase' property to /dev/sda2 (/dev/sda1) if /dev/sda4
+ (/dev/sda5) is the currently booted root file system according to
+ /proc/cmdline parsing.
+
+]]
+
+
+local configuration = [[
+[bootloader]
+# Required: bootloader name, uboot and ebg currently supported.
+name=ebg
+# Required: bootloader-specific key-value pairs, e.g., for ebg:
+kernelname=linux.signed.efi
+# For relying on FAT labels, prefix bootlabels with 'L:', e.g., L:BOOT0.
+# For using custom labels, i.e., relying on the contents of an EFILABEL
+# file within the partition, prefix it with 'C:', e.g., C:BOOT0.
+bootlabel={ "C:BOOT0:", "C:BOOT1:" }
+
+# Optional: handler to chain-call for the 'roundrobin' handler,
+# defaulting to 'raw'
+[roundrobin]
+chainhandler=raw
+
+# Optional: handler to chain-call for the 'kernelfile' handler,
+# defaulting to 'rawfile'
+[kernelfile]
+chainhandler=rawfile
+]]
+
+-- Default configuration file, tried if no compiled-in config is available.
+local cfgfile = "/etc/swupdate.handler.ini"
+
+-- Table holding the configuration.
+local config = {}
+
+-- Mandatory configuration [section] and keys
+local BOOTLOADERCFG = {
+ ebg = {
+ bootloader = {"name", "bootlabel", "kernelname"}
+ },
+ -- TODO fill with mandatory U-Boot configuration
+ uboot = {
+ bootloader = {"name"}
+ }
+}
+
+-- enum-alikes to make code more readable
+local BOOTLOADER = { EBG = "ebg", UBOOT = "uboot" }
+local PARTTYPE = { UUID = 1, PLAIN = 2, UBI = 3 }
+
+-- Target table describing the target device the image is to be/has been flashed to.
+local rrtarget = {
+ size = function(self)
+ local _size = 0
+ for index in pairs(self) do _size = _size + 1 end
+ return _size - 1
+ end
+}
+
+-- Helper function parsing CSV fields of a struct img_type such as
+-- the "device" fields or the "rdiffbase" property.
+local get_device_list = function(device_node_csv_list)
+ local device_list = {}
+ for item in device_node_csv_list:gmatch("([^,]+)") do
+ local device_node = item:gsub("/dev/", "")
+ device_list[#device_list+1] = device_node
+ device_list[device_node] = #device_list
+ end
+ return device_list
+end
+
+-- Helper function to determine device node location.
+local get_device_path = function(device_node)
+ if device_node:match("ubi%d+:%S+") then
+ return 0, device_node, PARTTYPE.UBI
+ end
+ local device_path = string.format("/dev/disk/by-partuuid/%s", device_node)
+ local file = io.open(device_path, "rb" )
+ if file then
+ file:close()
+ return 0, device_path, PARTTYPE.UUID
+ end
+ device_path = string.format("/dev/%s", device_node)
+ file = io.open(device_path, "rb" )
+ if file then
+ file:close()
+ return 0, device_path, PARTTYPE.PLAIN
+ end
+ swupdate.error(string.format("Cannot access target device node /dev/{,disk/by-partuuid}/%s", device_node))
+ return 1, nil, nil
+end
+
+-- Helper function parsing the INI-style configuration.
+local get_config = function()
+ -- Return configuration right away if it's already parsed.
+ if config ~= nil and #config > 0 then
+ return config
+ end
+
+ -- Get configuration INI-style string.
+ if not configuration then
+ swupdate.trace(string.format("No compiled-in config found, trying %s", cfgfile))
+ local file = io.open(cfgfile, "r" )
+ if not file then
+ swupdate.error(string.format("Cannot open config file %s", cfgfile))
+ return nil
+ end
+ configuration = file:read("*a")
+ file:close()
+ end
+ if configuration:sub(-1) ~= "\n" then
+ configuration=configuration.."\n"
+ end
+
+ -- Parse INI-style contents into config table.
+ local sec, key, value
+ for line in configuration:gmatch("(.-)\n") do
+ if line:match("^%[([%w%p]+)%][%s]*") then
+ sec = line:match("^%[([%w%p]+)%][%s]*")
+ config[sec] = {}
+ elseif sec then
+ key, value = line:match("^([%w%p]-)=(.*)$")
+ if key and value then
+ if tonumber(value) then value = tonumber(value) end
+ if value == "true" then value = true end
+ if value == "false" then value = false end
+ if value:sub(1,1) == "{" then
+ local _value = {}
+ for _key, _ in value:gmatch("\"(%S+)\"") do
+ table.insert(_value, _key)
+ end
+ value = _value
+ end
+ config[sec][key] = value
+ else
+ if not line:match("^$") and not line:match("^#") then
+ swupdate.warn(string.format("Syntax error, skipping '%s'", line))
+ end
+ end
+ else
+ swupdate.error(string.format("Syntax error. no [section] encountered."))
+ return nil
+ end
+ end
+
+ -- Check config table for mandatory key existence.
+ if config["bootloader"] == nil or config["bootloader"]["name"] == nil then
+ swupdate.error(string.format("Syntax error. no [bootloader] encountered or name= missing therein."))
+ return nil
+ end
+ local bcfg = BOOTLOADERCFG[config.bootloader.name]
+ if not bcfg then
+ swupdate.error(string.format("Bootloader unsupported, name=uboot|ebg missing in [bootloader]?."))
+ return nil
+ end
+ for sec, _ in pairs(bcfg) do
+ for _, key in pairs(bcfg[sec]) do
+ if config[sec] == nil or config[sec][key] == nil then
+ swupdate.error(string.format("Mandatory config key %s= in [%s] not found.", key, sec))
+ end
+ end
+ end
+
+ return config
+end
+
+-- Round-robin image handler for updating the root partition.
+function handler_roundrobin(image)
+ -- Read configuration.
+ if not get_config() then
+ swupdate.error("Cannot read configuration.")
+ return 1
+ end
+
+ -- Check if we can chain-call the handler.
+ local chained_handler = "raw"
+ if image.properties ~= nil and image.properties["chainhandler"] ~= nil then
+ chained_handler = image.properties["chainhandler"]
+ elseif config["roundrobin"] ~= nil and config["roundrobin"]["chainhandler"] ~= nil then
+ chained_handler = config["roundrobin"]["chainhandler"]
+ end
+ if not swupdate.handler[chained_handler] then
+ swupdate.error(string.format("'%s' handler not available in SWUpdate distribution.", chained_handler))
+ return 1
+ end
+
+ -- Get device list for round-robin.
+ local devices = get_device_list(image.device)
+ if #devices < 2 then
+ swupdate.error("Specify at least 2 devices in the device= property for 'roundrobin'.")
+ return 1
+ end
+
+ -- Check that rrtarget is unset, else a reboot may be pending.
+ if rrtarget:size() > 0 then
+ swupdate.warn("The 'roundrobin' handler has been run. Is a reboot pending?")
+ end
+
+ -- Determine current root device.
+ local file = io.open("/proc/cmdline", "r")
+ if not file then
+ swupdate.error("Cannot open /proc/cmdline.")
+ return 1
+ end
+ local cmdline = file:read("*l")
+ file:close()
+
+ local rootparam, rootdevice
+ for item in cmdline:gmatch("%S+") do
+ rootparam, rootdevice = item:match("(root=[%u=]*[/dev/]*(%S+))")
+ if rootparam and rootdevice then break end
+ end
+ if not rootdevice then
+ -- Use findmnt to get the rootdev
+ rootdevice = io.popen('findmnt -nl / -o PARTUUID'):read("*l")
+ if not rootdevice then
+ swupdate.error("Cannot determine current root device.")
+ return 1
+ end
+ end
+ swupdate.info(string.format("Current root device is: %s", rootdevice))
+
+ if not devices[rootdevice] then
+ swupdate.error(string.format("Current root device '%s' is not in round-robin root devices list: %s", rootdevice, image.device:gsub("/dev/", "")))
+ return 1
+ end
+
+ -- Perform round-robin calculation for target.
+ local err
+ rrtarget.index = devices[rootdevice] % #devices + 1
+ rrtarget.device_node = devices[rrtarget.index]
+ err, rrtarget.device_path, rrtarget.parttype = get_device_path(devices[rrtarget.index])
+ if err ~= 0 then
+ return 1
+ end
+ swupdate.info(string.format("Using '%s' as 'roundrobin' target via '%s' handler.", rrtarget.device_path, chained_handler))
+
+ -- If the chain-called handler is rdiff_image, adapt the rdiffbase property
+ if chained_handler == "rdiff_image" then
+ if image.properties ~= nil and image.properties["rdiffbase"] ~= nil then
+ local rdiffbase_devices = get_device_list(image.properties["rdiffbase"])
+ if #rdiffbase_devices < 2 then
+ swupdate.error("Specify at least 2 devices in the rdiffbase= property for 'roundrobin'.")
+ return 1
+ end
+ err, image.propierties["rdiffbase"], _ = get_device_path(rdiffbase_devices[rrtarget.index])
+ if err ~= 0 then
+ return 1
+ end
+ swupdate.info(string.format("Using device %s as rdiffbase.", image.properties["rdiffbase"]))
+ else
+ swupdate.error("Property 'rdiffbase' is missing in sw-description.")
+ return 1
+ end
+ end
+
+ -- Actually flash the partition.
+ local msg
+ image.type = chained_handler
+ image.device = rrtarget.device_path
+ err, msg = swupdate.call_handler(chained_handler, image)
+ if err ~= 0 then
+ swupdate.error(string.format("Error chain-calling '%s' handler: %s", chained_handler, (msg or "")))
+ return 1
+ end
+
+ if config.bootloader.name == BOOTLOADER.EBG then
+ if rootparam then
+ local value = cmdline:gsub(
+ rootparam:gsub("%-", "%%-"),
+ string.format("root=%s%s",
+ (rrtarget.parttype == PARTTYPE.PLAIN and "") or (rrtarget.parttype == PARTTYPE.UBI and "") or "PARTUUID=",
+ rrtarget.parttype == PARTTYPE.PLAIN and rrtarget.device_path or devices[rrtarget.index]
+ )
+ )
+ swupdate.info(string.format("Setting EFI Bootguard environment: kernelparams=%s", value))
+ swupdate.set_bootenv("kernelparams", value)
+ end
+ elseif config.bootloader.name == BOOTLOADER.UBOOT then
+ -- Update U-Boot environment.
+ swupdate.info(string.format("Setting U-Boot environment"))
+ local value = rrtarget.index
+ swupdate.set_bootenv("swupdpart", value);
+ end
+
+ return 0
+end
+
+-- File handler for updating kernel files.
+function handler_kernelfile(image)
+ -- Check if we can chain-call the handler.
+ local chained_handler = "rawfile"
+ if image.properties ~= nil and image.properties["chainhandler"] ~= nil then
+ chained_handler = image.properties["chainhandler"]
+ elseif config["kernelfile"] ~= nil and config["kernelfile"]["chainhandler"] ~= nil then
+ chained_handler = config["kernelfile"]["chainhandler"]
+ end
+ if not swupdate.handler[chained_handler] then
+ swupdate.error(string.format("'%s' handler not available in SWUpdate distribution."), chained_handler)
+ return 1
+ end
+
+ -- Check that rrtarget is set, else the 'roundrobin' handler hasn't been run.
+ if rrtarget:size() == 0 then
+ swupdate.error("The 'roundrobin' handler hasn't been run.")
+ swupdate.info("Place 'roundrobin' above 'kernelfile' in sw-description.")
+ return 1
+ end
+
+ -- Get device list for round-robin.
+ local devices = get_device_list(image.device)
+ if #devices < 2 then
+ swupdate.error("Specify at least 2 devices in the device= property for 'kernelfile'.")
+ return 1
+ end
+ if rrtarget.index > #devices then
+ swupdate.error("Cannot map kernel partition to root partition.")
+ return 1
+ end
+
+ -- Perform round-robin indexing for target.
+ local err
+ err, image.device, _ = get_device_path(devices[rrtarget.index])
+ if err ~= 0 then
+ return 1
+ end
+ swupdate.info(string.format("Using '%s' as 'kernelfile' target via '%s' handler.", image.device, chained_handler))
+
+ -- Actually copy the 'kernelfile' files.
+ local msg
+ image.type = chained_handler
+ err, msg = swupdate.call_handler(chained_handler, image)
+ if err ~= 0 then
+ swupdate.error(string.format("Error chain-calling '%s' handler: %s", chained_handler, (msg or "")))
+ return 1
+ end
+
+ if config.bootloader.name == BOOTLOADER.EBG then
+ -- Update EFI Boot Guard environment: kernelfile
+ local value = string.format("%s%s", config.bootloader.bootlabel[rrtarget.index], config.bootloader.kernelname)
+ swupdate.info(string.format("Setting EFI Bootguard environment: kernelfile=%s", value))
+ swupdate.set_bootenv("kernelfile", value)
+ elseif config.bootloader.name == BOOTLOADER.UBOOT then
+ -- Update U-Boot environment.
+ swupdate.info(string.format("Setting U-Boot environment"))
+ -- TODO
+ end
+
+ return 0
+end
+
+swupdate.register_handler("roundrobin", handler_roundrobin, swupdate.HANDLER_MASK.IMAGE_HANDLER)
+swupdate.register_handler("kernelfile", handler_kernelfile, swupdate.HANDLER_MASK.FILE_HANDLER)
new file mode 100644
@@ -0,0 +1,54 @@
+#
+# CIP Core, generic profile
+#
+# Copyright (c) Siemens AG, 2020
+#
+# Authors:
+# Quirin Gylstorff <quirin.gylstorff@siemens.com>
+#
+# SPDX-License-Identifier: MIT
+
+hDESCRIPTION = "swupdate utility for software updates"
+HOMEPAGE= "https://github.com/sbabic/swupdate"
+LICENSE = "GPL-2.0"
+LIC_FILES_CHKSUM = "file://${LAYERDIR_isar}/licenses/COPYING.GPLv2;md5=751419260aa954499f7abaabaa882bbe"
+
+SRC_URI = "git://github.com/sbabic/swupdate.git;branch=master;protocol=https"
+
+SRCREV = "1a6dfbb5a0be978ac1a159758e278ab4d44167e2"
+PV = "2020.4-git+isar"
+
+DEFCONFIG := "swupdate_defconfig"
+
+SRC_URI += "file://debian \
+ file://${DEFCONFIG} \
+ file://${PN}.cfg"
+
+DEPENDS += "libubootenv"
+
+DEBIAN_DEPENDS = "${shlibs:Depends}, ${misc:Depends}"
+
+inherit dpkg
+inherit swupdate-config
+
+KFEATURES += "luahandler"
+
+S = "${WORKDIR}/git"
+
+TEMPLATE_FILES = "debian/changelog.tmpl debian/control.tmpl debian/rules.tmpl"
+TEMPLATE_VARS += "BUILD_DEB_DEPENDS DEFCONFIG DEBIAN_DEPENDS"
+
+do_prepare_build() {
+ DEBDIR=${S}/debian
+ cp -R ${WORKDIR}/debian ${S}
+
+ install -m 0644 ${WORKDIR}/${PN}.cfg ${S}/swupdate.cfg
+ install -m 0644 ${WORKDIR}/${DEFCONFIG}.gen ${S}/configs/${DEFCONFIG}
+
+ if ! grep -q "configs/${DEFCONFIG}" ${S}/.gitignore
+ then
+ echo "configs/${DEFCONFIG}" >> ${S}/.gitignore
+ fi
+ # luahandler
+ install -m 0644 ${WORKDIR}/${SWUPDATE_LUASCRIPT} ${S}
+}