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
diff --git a/prebuilt/common/bin/backuptool.functions b/prebuilt/common/bin/backuptool.functions
index 2bc821b..d180c7c 100644
--- a/prebuilt/common/bin/backuptool.functions
+++ b/prebuilt/common/bin/backuptool.functions
@@ -39,13 +39,18 @@
     if [ ! -d "$DIR" ]; then
       mkdir -p "$DIR"
     fi
-    copy_file "$C/$DIR/$FILE" "$1"
+    copy_file "$C/$DIR/$FILE" $(get_output_path "$1");
     if [ $DEBUG -eq 1 ]; then 
       echo restore_file "$C/$DIR/$FILE" "$1"
     fi
     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 3178842..34100ac 100755
--- a/prebuilt/common/bin/backuptool.sh
+++ b/prebuilt/common/bin/backuptool.sh
@@ -10,6 +10,17 @@
 
 DEBUG=0
 
+export ADDOND_VERSION=3
+
+# Partitions to mount for backup/restore in V3
+export all_V3_partitions="vendor product system_ext"
+
+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() {
   rm -rf /tmp/addon.d/
@@ -38,16 +49,32 @@
   return 0
 }
 
-# Execute /system/addon.d/*.sh scripts with $1 parameter
-run_stage() {
+# Execute /system/addon.d/*.sh scripts with each $@ parameter
+run_stages() {
   for script in $(find /tmp/addon.d/ -name '*.sh' |sort -n); do
     if [ $DEBUG -eq 1 ]; then
         echo run_stage $script $1
     fi
-    $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
 }
 
+#####################
+### Mount helpers ###
+#####################
 determine_system_mount() {
   if grep -q -e"^$SYSDEV" /proc/mounts; then
     umount $(grep -e"^$SYSDEV" /proc/mounts | cut -d" " -f2)
@@ -72,8 +99,62 @@
   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)
     # make sure we dont start with any leftovers
@@ -84,9 +165,7 @@
     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
   ;;
@@ -94,9 +173,8 @@
     cp $S/bin/backuptool.functions /tmp
     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 58105f5..770420b 100644
--- a/prebuilt/common/bin/backuptool_ab.functions
+++ b/prebuilt/common/bin/backuptool_ab.functions
@@ -39,10 +39,35 @@
 
 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 61d447a..8594af1 100755
--- a/prebuilt/common/bin/backuptool_ab.sh
+++ b/prebuilt/common/bin/backuptool_ab.sh
@@ -7,13 +7,22 @@
 export C=/postinstall/tmp/backupdir
 export V=13
 
-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
@@ -21,13 +30,9 @@
     cp -a /system/addon.d/* /postinstall/tmp/addon.d/
     rm -f /postinstall/tmp/addon.d/70-gapps.sh
 
-    # 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
@@ -59,8 +64,8 @@
 return 0
 }
 
-# Execute /system/addon.d/*.sh scripts with $1 parameter
-run_stage() {
+# Execute /system/addon.d/*.sh scripts with each $@ parameter
+run_stages() {
 log -t "update_engine" $1
 
 if [ -d /postinstall/tmp/addon.d/ ]; then
@@ -72,27 +77,110 @@
     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
     log -t "update_engine" "backuptool_ab.sh backup"
   ;;
   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