From c63fa8441bb5ad1fc4fb68155a5e5166aa25cac0 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Tue, 29 Dec 2020 18:38:28 +0100 Subject: [PATCH] backuptool: Support seamless backup and restore to extra partitions For scripts declaring ADDOND_VERSION=3 automatically mount vendor, product, system_ext and others (when they're dedicated partitions). Also expose the get_output_path() function to get the path to where a file is mounted in case it lives in a dedicated partition. ab exapmles: get_output_path "system/product/priv-app/MyApp.apk" = "/postinstall/product/priv-app/MyApk.apk" get_output_path "system/app/MySystemApp.apk" = "/postinstall/system/app/MySystemApp.apk" a-only examples: get_output_path "/mnt/system/system/product/priv-app/MyApp.apk" = "/mnt/system/system/product/priv-app/MyApp.apk" ****************************************************************** Instead of cycling all scripts for each stage, run pre-backup -> backup -> post-backup in quick succession (and likewise for restore), to ensure backwards compatibility with scripts that wrongly assumed their environment not to change between steps. This is needed because we want to undo any mounting done for V3 scripts when executing V2 scripts. If a V2 script did mounting in pre-restore and expected things to still be mounted in restore, we would break their (yes incorrect) assumption. Change-Id: I73fbad6f45824fed99e4482128769435348588f5 --- prebuilt/common/bin/backuptool.functions | 9 +- prebuilt/common/bin/backuptool.sh | 96 ++++++++++++++-- prebuilt/common/bin/backuptool_ab.functions | 29 ++++- prebuilt/common/bin/backuptool_ab.sh | 121 +++++++++++++++++--- 4 files changed, 226 insertions(+), 29 deletions(-) diff --git a/prebuilt/common/bin/backuptool.functions b/prebuilt/common/bin/backuptool.functions index 4319b783..7a915072 100644 --- a/prebuilt/common/bin/backuptool.functions +++ b/prebuilt/common/bin/backuptool.functions @@ -34,10 +34,15 @@ restore_file() { if [ ! -d "$DIR" ]; then mkdir -p "$DIR"; fi - copy_file "$C/$DIR/$FILE" "$1"; + copy_file "$C/$DIR/$FILE" $(get_output_path "$1"); if [ -n "$2" ]; then echo "Deleting obsolete file $2" - rm "$2"; + rm $(get_output_path "$2"); fi fi } + +get_output_path() { + # In recovery we mounted all partitions in the right place, so we can rely on symlinks + echo "$1" +} diff --git a/prebuilt/common/bin/backuptool.sh b/prebuilt/common/bin/backuptool.sh index e0020925..878187f7 100755 --- a/prebuilt/common/bin/backuptool.sh +++ b/prebuilt/common/bin/backuptool.sh @@ -8,9 +8,20 @@ export SYSDEV="$(readlink -nf "$2")" export SYSFS="$3" export V=18.1 +export ADDOND_VERSION=3 + +# Partitions to mount for backup/restore in V3 +export all_V3_partitions="vendor product system_ext" + # Scripts in /system/addon.d expect to find backuptool.functions in /tmp cp -f /tmp/install/bin/backuptool.functions /tmp +get_script_version() { + version=$(grep "^# ADDOND_VERSION=" $1 | cut -d= -f2) + [ -z "$version" ] && version=1 + echo $version +} + # Preserve /system/addon.d in /tmp/addon.d preserve_addon_d() { if [ -d $S/addon.d/ ]; then @@ -43,15 +54,31 @@ fi return 0 } -# Execute /system/addon.d/*.sh scripts with $1 parameter -run_stage() { +# Execute /system/addon.d/*.sh scripts with each $@ parameter +run_stages() { if [ -d /tmp/addon.d/ ]; then for script in $(find /tmp/addon.d/ -name '*.sh' |sort -n); do - $script $1 + v=$(get_script_version $script) + if [ $v -ge 3 ]; then + mount_extra $all_V3_partitions + else + umount_extra $all_V3_partitions + fi + + for stage in $@; do + if [ $v -ge 3 ]; then + $script $stage + else + ADDOND_VERSION=2 $script $stage + fi + done done fi } +##################### +### Mount helpers ### +##################### determine_system_mount() { if grep -q -e"^$SYSDEV" /proc/mounts; then umount $(grep -e"^$SYSDEV" /proc/mounts | cut -d" " -f2) @@ -76,26 +103,77 @@ unmount_system() { umount $SYSMOUNT } +get_block_for_mount_point() { + grep -v "^#" /etc/recovery.fstab | grep " $1 " | tail -n1 | tr -s ' ' | cut -d' ' -f1 +} + +find_block() { + local name="$1" + local fstab_entry=$(get_block_for_mount_point "/$name") + # P-SAR hacks + [ -z "$fstab_entry" ] && [ "$name" = "system" ] && fstab_entry=$(get_block_for_mount_point "/") + [ -z "$fstab_entry" ] && [ "$name" = "system" ] && fstab_entry=$(get_block_for_mount_point "/system_root") + + local dev + if [ "$DYNAMIC_PARTITIONS" = "true" ]; then + if [ -n "$fstab_entry" ]; then + dev="${BLK_PATH}/${fstab_entry}" + else + dev="${BLK_PATH}/${name}" + fi + else + if [ -n "$fstab_entry" ]; then + dev="$fstab_entry" + else + dev="${BLK_PATH}/${name}" + fi + fi + + if [ -b "$dev" ]; then + echo "$dev" + fi +} + determine_system_mount +DYNAMIC_PARTITIONS=$(getprop ro.boot.dynamic_partitions) +BLK_PATH=$(dirname "$SYSDEV") + +mount_extra() { + for partition in $@; do + mnt_point="/$partition" + mountpoint "$mnt_point" >/dev/null 2>&1 && break + + blk_dev=$(find_block "$partition") + if [ -e "$blk_dev" ]; then + [ "$DYNAMIC_PARTITIONS" = "true" ] && blockdev --setrw "$blk_dev" + mkdir -p "$mnt_point" + mount -o rw "$blk_dev" "$mnt_point" + fi + done +} + +umount_extra() { + for partition in $@; do + umount -l "/$partition" 2>/dev/null + done +} + case "$1" in backup) mount_system if check_prereq; then mkdir -p $C preserve_addon_d - run_stage pre-backup - run_stage backup - run_stage post-backup + run_stages pre-backup backup post-backup fi unmount_system ;; restore) mount_system if check_prereq; then - run_stage pre-restore - run_stage restore - run_stage post-restore + run_stages pre-restore restore post-restore + umount_extra $all_V3_partitions restore_addon_d rm -rf $C sync diff --git a/prebuilt/common/bin/backuptool_ab.functions b/prebuilt/common/bin/backuptool_ab.functions index a5103f6f..11869c9e 100644 --- a/prebuilt/common/bin/backuptool_ab.functions +++ b/prebuilt/common/bin/backuptool_ab.functions @@ -39,10 +39,35 @@ backup_file() { restore_file() { if [ -e "$C/$1" -o -L "$C/$1" ]; then - move_file "$C/$1" "/postinstall/$1"; + move_file "$C/$1" $(get_output_path "$1"); if [ -n "$2" ]; then echo "Deleting obsolete file $2" - rm "$2"; + rm $(get_output_path "$2"); fi fi } + +get_output_path() { + if [ $ADDOND_VERSION -lt 3 ]; then + echo "/postinstall/$1" + return + fi + + file=$(echo "$1" | sed "s|^$S/||") + if __is_on_mounted_partition "$file"; then + echo "/postinstall/$file" + else + echo "/postinstall/$1" + fi +} + +__is_on_mounted_partition() { + for p in $all_V3_partitions; do + mnt_point="/postinstall/$p" + if echo "$1" | grep -q "^$p/" && [ ! -L "$mnt_point" ] && mountpoint >/dev/null 2>&1 "$mnt_point"; then + return 0 + fi + done + + return 1 +} diff --git a/prebuilt/common/bin/backuptool_ab.sh b/prebuilt/common/bin/backuptool_ab.sh index 727a8cc2..b4cdba2f 100755 --- a/prebuilt/common/bin/backuptool_ab.sh +++ b/prebuilt/common/bin/backuptool_ab.sh @@ -7,26 +7,31 @@ export S=/system export C=/postinstall/tmp/backupdir export V=18.1 -export ADDOND_VERSION=2 +export ADDOND_VERSION=3 + +# Partitions to mount for backup/restore in V3 +export all_V3_partitions="vendor product system_ext odm oem" # Scripts in /system/addon.d expect to find backuptool.functions in /tmp mkdir -p /postinstall/tmp/ mountpoint /postinstall/tmp >/dev/null 2>&1 || mount -t tmpfs tmpfs /postinstall/tmp cp -f /postinstall/system/bin/backuptool_ab.functions /postinstall/tmp/backuptool.functions +get_script_version() { + version=$(grep "^# ADDOND_VERSION=" $1 | cut -d= -f2) + [ -z "$version" ] && version=1 + echo $version +} + # Preserve /system/addon.d in /tmp/addon.d preserve_addon_d() { if [ -d /system/addon.d/ ]; then mkdir -p /postinstall/tmp/addon.d/ cp -a /system/addon.d/* /postinstall/tmp/addon.d/ - # Discard any scripts that aren't at least our version level + # Discard any version 1 script, as it is not compatible with a/b for f in /postinstall/tmp/addon.d/*sh; do - SCRIPT_VERSION=$(grep "^# ADDOND_VERSION=" $f | cut -d= -f2) - if [ -z "$SCRIPT_VERSION" ]; then - SCRIPT_VERSION=1 - fi - if [ $SCRIPT_VERSION -lt $ADDOND_VERSION ]; then + if [ $(get_script_version $f) = 1 ]; then rm $f fi done @@ -58,8 +63,8 @@ fi return 0 } -# Execute /system/addon.d/*.sh scripts with $1 parameter -run_stage() { +# Execute /system/addon.d/*.sh scripts with each $@ parameter +run_stages() { if [ -d /postinstall/tmp/addon.d/ ]; then for script in $(find /postinstall/tmp/addon.d/ -name '*.sh' |sort -n); do # we have no /sbin/sh in android, only recovery @@ -67,26 +72,110 @@ if [ -d /postinstall/tmp/addon.d/ ]; then sed -i '0,/#!\/sbin\/sh/{s|#!/sbin/sh|#!/system/bin/sh|}' $script # we can't count on /tmp existing on an A/B device, so utilize /postinstall/tmp as tmpfs sed -i 's|. /tmp/backuptool.functions|. /postinstall/tmp/backuptool.functions|g' $script - $script $1 + + v=$(get_script_version $script) + if [ $v -ge 3 ]; then + mount_extra $all_V3_partitions + else + umount_extra $all_V3_partitions + fi + + for stage in $@; do + if [ $v -ge 3 ]; then + $script $stage + else + ADDOND_VERSION=2 $script $stage + fi + done done fi } +##################### +### Mount helpers ### +##################### +get_block_for_mount_point() { + grep -v "^#" /vendor/etc/fstab.$(getprop ro.boot.hardware) | grep " $1 " | tail -n1 | tr -s ' ' | cut -d' ' -f1 +} + +find_block() { + local name="$1" + local fstab_entry=$(get_block_for_mount_point "/$name") + # P-SAR hacks + [ -z "$fstab_entry" ] && [ "$name" = "system" ] && fstab_entry=$(get_block_for_mount_point "/") + [ -z "$fstab_entry" ] && [ "$name" = "system" ] && fstab_entry=$(get_block_for_mount_point "/system_root") + + local dev + if [ "$DYNAMIC_PARTITIONS" = "true" ]; then + if [ -n "$fstab_entry" ]; then + dev="${BLK_PATH}/${fstab_entry}${SLOT_SUFFIX}" + else + dev="${BLK_PATH}/${name}${SLOT_SUFFIX}" + fi + else + if [ -n "$fstab_entry" ]; then + dev="${fstab_entry}${SLOT_SUFFIX}" + else + dev="${BLK_PATH}/${name}${SLOT_SUFFIX}" + fi + fi + + if [ -b "$dev" ]; then + echo "$dev" + fi +} + +DYNAMIC_PARTITIONS=$(getprop ro.boot.dynamic_partitions) +if [ "$DYNAMIC_PARTITIONS" = "true" ]; then + BLK_PATH="/dev/block/mapper" +else + BLK_PATH=/dev/block/bootdevice/by-name +fi + +CURRENTSLOT=$(getprop ro.boot.slot_suffix) +if [ ! -z "$CURRENTSLOT" ]; then + if [ "$CURRENTSLOT" = "_a" ]; then + # Opposite slot + SLOT_SUFFIX="_b" + else + SLOT_SUFFIX="_a" + fi +fi + +mount_extra() { + for partition in $@; do + mnt_point="/postinstall/$partition" + mountpoint "$mnt_point" >/dev/null 2>&1 && break + + blk_dev=$(find_block "$partition") + if [ -n "$blk_dev" ]; then + [ "$DYNAMIC_PARTITIONS" = "true" ] && blockdev --setrw "$blk_dev" + mount -o rw "$blk_dev" "$mnt_point" + fi + done +} + +umount_extra() { + for partition in $@; do + # Careful with unmounting. If the update has a partition less than the current system, + # /postinstall/$partition is a symlink to /system/$partition, which on the active slot + # is a symlink to /$partition which is a mountpoint we would end up unmounting! + [ ! -L "/postinstall/$partition" ] && umount -l "/postinstall/$partition" 2>/dev/null + done +} + case "$1" in backup) if check_prereq; then mkdir -p $C preserve_addon_d - run_stage pre-backup - run_stage backup - run_stage post-backup + run_stages pre-backup backup post-backup fi ;; restore) if check_prereq; then - run_stage pre-restore - run_stage restore - run_stage post-restore + run_stages pre-restore restore post-restore + umount_extra $all_V3_partitions restore_addon_d rm -rf $C umount /postinstall/tmp