I am currently working on the software update framework swupdate. As an update scheme, I chose the dual copy approach. I have installed Ubuntu 20.04 on my target device (desktop pc -->i5,32GB SSD,BIOS,..) to test and partitioned it accordingly.
/dev/sda1 --/boot
/dev/sda2 --/
/dev/sda3 --/root2
/dev/sda4 --/data
For testing purposes I created a root file system image from the host system (/dev/sda2) using the "dd" command. I assigned a new UUID and label to the image using "tune2fs" and "e2label" commands.
dd if=/dev/sda2 of=rootfs.img status=progress
e2label rootfs.img root2
e2fsck -fy rootfs.img
tune2fs -U random rootfs.img
It is necessary to have a mechanism for coordination between the boot loader and the update agent in order to boot the system with the secondary rootfs partition after a successful update. The bootloader should be assure which rootfs partition (A or B) is loaded.
Ubuntu uses GRUB as the standard boot loader. GRUB provides an "environment block" which can be used to save a small amount of state (/boot/grub/grubenv). The update agent "SWUpdate" has a bootloader handler to manage this file. SWupdate is able to add environment variables to this file. SWupdate can call scripts before and after installing the images (pre-postinstall).
I have written a bash script that reads the current rootfs partition from the kernel command line (/proc/cmdline) and the environment variables from the GRUB environment block (/boot/grub/grubenv) in order to update the rootfs partition after the update process through the default bootloader configuration file (/etc/default/grub) with which the system is booted.
CONFIG_FILE=/etc/default/grub
ROOTFS1_UUID=$(blkid -o value -s UUID /dev/sda2)
ROOTFS2_UUID=$(blkid -o value -s UUID /dev/sda3)
function get_UUID_of_current_boot_device() {
for i in `cat /proc/cmdline`; do
case "$i" in
root=*)
ROOT="${i#root=UUID=}"
;;
esac
done
}
function get_partition_number_of_current_boot_device() {
if [[ $ROOT = $ROOTFS1_UUID ]]; then
CURRENT_PARTITION="2";
elif [[ $ROOT = $ROOTFS2_UUID ]]; then
CURRENT_PARTITION="3";
else
echo "Error by Partitionshema!!"
fi
}
function get_bootloader_env() {
for i in `cat /boot/grub/grubenv`; do
case "$i" in
bootpart=*)
BOOT_DEVICE="${i#bootpart=}"
;;
esac
done
}
function set_boot_device_UUID() {
if [ $BOOT_DEVICE = "2" ]; then
BOOT_DEVICE_UUID="$ROOTFS1_UUID";
elif [ $BOOT_DEVICE = "3" ]; then
BOOT_DEVICE_UUID="$ROOTFS2_UUID";
else
echo "";
BOOT_DEVICE_UUID="$ROOT";
fi
}
As far as I have read so far, in order to permanently modify the kernel command line parameters, the required parameters must be configured in the GRUB_CMDLINE_LINUX_DEFAULT line or in the GRUB_CMDLINE_LINUX line. In my script I modified this line so that the $BOOT_DEVICE_UUID variable should always be the rootfs partition that should be booted after the reboot.
sed -i "s/\(GRUB_CMDLINE_LINUX *= *\).*/\1\"$BOOT_DEVICE_UUID\"/" $CONFIG_FILE;
Unfortunately that didn't work, I thought that the value of the existing variable "root" in the command line would be replaced with the value that I passed.
The boot loader should somehow select via environment variables which Rootfs partition, which contains the Linux system, should be loaded. Could someone please explain to me where my mistake is and could someone give me a concrete example about this topic?