Add checkpoint & restore
authorMateusz Moscicki <m.moscicki2@partner.samsung.com>
Fri, 2 Jul 2021 14:51:30 +0000 (16:51 +0200)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Fri, 2 Jul 2021 15:05:05 +0000 (17:05 +0200)
To protect the system upgrade process in case of power failure, the
checkpoint & restore mechanism is used.

Algorithm:

  1. If there is any checkpoint on partition, the data is restored,
     because it means the previous upgrade attempt failed.
  2. When mounting a partition, a data checkpoint is created.
  3. The update is proceeding as before.
  4. If the update is successful, the data is commited.

scripts/fota-init.sh
scripts/fus_rw-init.sh

index d70b2616948b8bcd093a311ff0450d36a6bf43a1..b0e8e2507a4e3fd9a6efa5c5f7e8de0a2ea9f60c 100755 (executable)
@@ -35,6 +35,11 @@ WC="/usr/bin/wc"
 LS="/bin/ls"
 VERITYCTL="/usr/bin/verityctl"
 VERITY_HANDLER="/usr/bin/verity_handler"
+BOW_RESTORE="bow-restore"
+
+DMSETUP="dmsetup"
+FSTRIM="fstrim"
+STAT="stat"
 
 FOTA_GUI="/usr/bin/fota_gui"
 FOTA_GUI_ENABLE=
@@ -64,23 +69,116 @@ get_partition_id() {
        PART_USER=$("$BLKID" --match-token PARTLABEL=user -o device || "$BLKID" --match-token LABEL=user -o device)
 }
 
+#------------------------------------------------
+#       restore_checkpoint
+#------------------------------------------------
+restore_checkpoint() {
+       # Any existing checkpoint means that the RW update was unexpectedly
+       # aborded, so we must roll back the changes and update again.
+       echo "Bow restore"
+       PART=${1}
+
+       if [ "$(blkid -o value -s TYPE ${PART})" = "ext4" ]; then
+               "$BOW_RESTORE" "${PART}"
+       fi
+}
+
+mount_bow_partition() {
+    LABEL=${1}
+    PARTITION=${2}
+    DIRECTORY=${3}
+    BOWDEV_NAME=bowdev_${LABEL}
+    BOWDEV_PATH=/dev/mapper/${BOWDEV_NAME}
+    SECTORS=$(</sys/class/block/$(lsblk -no NAME ${PARTITION})/size)
+
+    "${DMSETUP}" create ${BOWDEV_NAME} --table "0 ${SECTORS} bow ${PARTITION}"
+    "${MOUNT}" ${BOWDEV_PATH} ${DIRECTORY}
+    "${SYNC}"
+    "${FSTRIM}" -v ${DIRECTORY}
+
+    DM_NUMBER=$(($("${STAT}" -c "0x%T" $(readlink -f ${BOWDEV_PATH}))))
+    echo 1 > /sys/block/dm-${DM_NUMBER}/bow/state
+    echo "[Debug] Mounted ${PARTITION} as DM-BOW"
+}
+
+mount_f2fs_partition() {
+    LABEL=${1}
+    PARTITION=${2}
+    DIRECTORY=${3}
+    "${MOUNT}" -o checkpoint=disable ${PARTITION} ${DIRECTORY}
+    echo "[Debug] Mounted ${PARTITION} as F2FS checkpoint=disable"
+}
+
+mount_checkpoint_partition() {
+    LABEL=${1}
+    PARTITION=${2}
+    DIRECTORY=${3}
+    FSTYPE=$(lsblk -o FSTYPE -n "${PARTITION}")
+
+    if [ "${FSTYPE}" = "ext4" ]; then
+        mount_bow_partition ${LABEL} ${PARTITION} ${DIRECTORY}
+    elif [ "${FSTYPE}" = "f2fs" ]; then
+        mount_f2fs_partition ${LABEL} ${PARTITION} ${DIRECTORY}
+    else
+        echo "[Error] Unsupported filesystem ${FSTRIM} on ${PARTITION}"
+        return 1
+    fi
+}
+
+commit_bow_partition()
+{
+    LABEL=${1}
+
+    BOWDEV_NAME=bowdev_${LABEL}
+    BOWDEV_PATH=/dev/mapper/${BOWDEV_NAME}
+    DM_NUMBER=$(($("${STAT}" -c "0x%T" $(readlink -f ${BOWDEV_PATH}))))
+    echo 2 > /sys/block/dm-${DM_NUMBER}/bow/state
+    echo "[Info] Changes on partition ${LABEL} commited (dm-bow)"
+    dmsetup remove ${BOWDEV_NAME}
+}
+
+commit_f2fs_partition()
+{
+    LABEL=${1}
+    PART_SYSTEM_DATA=$(blkid --match-token PARTLABEL="${LABEL}" -o device || blkid --match-token LABEL="${LABEL}" -o device)
+    mount -o remount,checkpoint=enable "${PART_SYSTEM_DATA}"
+    echo "[Info] Changes on partition ${LABEL} commited (f2fs)"
+}
+
+commit_partition()
+{
+    LABEL=${1}
+    TYPE=$(blkid --match-token LABEL="${LABEL}" -o value -s TYPE | tail -n 1)
+    if [ "${TYPE}" = "ext4" ]; then
+        commit_bow_partition "${LABEL}"
+    elif [ "${TYPE}" = "f2fs" ]; then
+        commit_f2fs_partition "${LABEL}"
+    else
+        echo "[Error] Cannot commit ${LABEL}: Unsupported filesystem ${TYPE}"
+    fi
+}
+
 #------------------------------------------------
 #       mount_partitions
 #------------------------------------------------
 mount_partitions() {
-       get_partition_id
+        get_partition_id
 
-       "$MOUNT" -o ro "${PART_ROOTFS}" "${FAKE_ROOT}"
-       "$MOUNT" "${PART_SYSTEM_DATA}" "${FAKE_ROOT}/${SYSTEM_DATA_MNT}"
+        restore_checkpoint "${PART_ROOTFS}"
+        mount_checkpoint_partition rootfs "${PART_ROOTFS}" "${FAKE_ROOT}"
 
-       if [ ! "z${PART_USER}" = "z" ]; then
-               "$MOUNT" "${PART_USER}" "${FAKE_ROOT}/${USER_MNT}"
-       fi
+        restore_checkpoint "${PART_SYSTEM_DATA}"
+        mount_checkpoint_partition system-data "${PART_SYSTEM_DATA}" "${FAKE_ROOT}/${SYSTEM_DATA_MNT}"
+
+        if [ ! "z${PART_USER}" = "z" ]; then
+                restore_checkpoint "${PART_USER}"
+                mount_checkpoint_partition user "${PART_USER}" "${FAKE_ROOT}/${USER_MNT}"
+        fi
 
-       "$MOUNT" -t proc none "${FAKE_ROOT}/proc"
-       "$MOUNT" -t sysfs none "${FAKE_ROOT}/sys"
-       "$MOUNT" -t devtmpfs devtmpfs "${FAKE_ROOT}/dev"
-       "$MOUNT" -t devpts devpts "${FAKE_ROOT}/dev/pts"
+        "$MOUNT" -t proc none "${FAKE_ROOT}/proc"
+        "$MOUNT" -t sysfs none "${FAKE_ROOT}/sys"
+        "$MOUNT" -t devtmpfs devtmpfs "${FAKE_ROOT}/dev"
+        "$MOUNT" -t devpts devpts "${FAKE_ROOT}/dev/pts"
 }
 
 #------------------------------------------------
@@ -95,9 +193,12 @@ umount_partitions() {
 
        if [ ! "z${PART_USER}" = "z" ]; then
                "$UMOUNT" "${FAKE_ROOT}/${USER_MNT}"
+               commit_partition user
        fi
        "$UMOUNT" "${FAKE_ROOT}/${SYSTEM_DATA_MNT}"
+       commit_partition system-data
        "$UMOUNT" "${FAKE_ROOT}"
+       commit_partition rootfs
 }
 
 #------------------------------------------------
@@ -308,6 +409,13 @@ remake_hash_table() {
        fi
 }
 
+#------------------------------------------------
+#      mark_rw_update
+#------------------------------------------------
+mark_rw_update() {
+       echo "start" > ${STATUS_FILE}
+}
+
 #------------------------------------------------
 #      Main Routine Start
 #------------------------------------------------
@@ -347,11 +455,13 @@ RO_PROGRESS_FILE=/tmp/upgrade/ro_progress
 if [ -f "${RO_PROGRESS_FILE}" ]; then
        echo -1 > "${RO_PROGRESS_FILE}"
 fi
-umount_partitions
 
 if [ "$fota_result" = "0" ] && [ "$remake_result" = "0" ]; then
+       mark_rw_update
+       umount_partitions
        exec /sbin/fus_rw-init
 else
+       umount_partitions
        log "[Error] Upgrade FAILED"
        do_reboot
 fi
index 26e6ff9a2ddc74592df94901ae72e100e6f66813..1b5f818278cda8f2c67fc97eaf5e4e0332c1be33 100755 (executable)
@@ -17,6 +17,10 @@ UMOUNT="/bin/umount"
 GREP="/bin/grep"
 BLKID="/usr/sbin/blkid"
 
+DMSETUP="/usr/sbin/dmsetup"
+FSTRIM="/usr/sbin/fstrim"
+STAT="/usr/bin/stat"
+
 SYSTEM_DATA_MNT=opt
 USER_MNT=opt/usr
 
@@ -40,6 +44,29 @@ check_user_partition() {
     return 1
 }
 
+#------------------------------------------------
+#       mount_bow_partitions
+#------------------------------------------------
+mount_bow_partition() {
+        echo "Mount BOW partition"
+        FS=${1}
+        LABEL=${2}
+        PARTITION=${3}
+        DIRECTORY=${4}
+        BOWDEV_NAME=bowdev_${LABEL}
+        BOWDEV_PATH=/dev/mapper/${BOWDEV_NAME}
+        SECTORS=$(</sys/class/block/$(lsblk -no NAME ${PARTITION})/size)
+
+        "${DMSETUP}" create ${BOWDEV_NAME} --table "0 ${SECTORS} bow ${PARTITION}"
+        "${MOUNT}" -t ${FS} ${BOWDEV_PATH} ${DIRECTORY}
+        "${SYNC}"
+        "${FSTRIM}" -v ${DIRECTORY}
+
+        DM_NUMBER=$(($("${STAT}" -c "0x%T" $(readlink -f ${BOWDEV_PATH}))))
+        echo 1 > /sys/block/dm-${DM_NUMBER}/bow/state
+        /bin/bash
+}
+
 #------------------------------------------------
 #       mount_partitions
 #------------------------------------------------
@@ -47,13 +74,14 @@ mount_partitions() {
     get_partition_id
 
     "$MOUNT" -o ro ${PART_ROOTFS} ${FAKE_ROOT}
-    "$MOUNT" ${PART_SYSTEM_DATA} ${FAKE_ROOT}/${SYSTEM_DATA_MNT}
+    "$MOUNT" ${PART_SYSTEM_DATA} ${FAKE_ROOT}/${SYSTEM_DATA_MNT}
 
     check_user_partition
-    WITH_USR_PART=$?
-    if [ "$WITH_USR_PART" = "1" ]; then
-        "$MOUNT" ${PART_USER} ${FAKE_ROOT}/${USER_MNT}
-    fi
+    # WITH_USR_PART=$?
+    # if [ "$WITH_USR_PART" = "1" ]; then
+    #     mount_bow_partition ext4 user ${PART_USER} /${USER_MNT}
+    #     # "$MOUNT" ${PART_USER} ${FAKE_ROOT}/${USER_MNT}
+    # fi
 
     "$MOUNT" -t proc none ${FAKE_ROOT}/proc
     "$MOUNT" -t sysfs none ${FAKE_ROOT}/sys