Merge "remount: Simplify fs_mgr_overlayfs_setup."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index c15146b..1c89472 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -14,9 +14,15 @@
         "-Wno-nullability-completeness",
         "-Os",
         "-fno-finite-loops",
+        "-DANDROID_DEBUGGABLE=0",
     ],
 
     local_include_dirs: ["include"],
+    product_variables: {
+        debuggable: {
+            cflags: ["-UANDROID_DEBUGGABLE", "-DANDROID_DEBUGGABLE=1"],
+        }
+    },
 }
 
 cc_library_headers {
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index bc08327..68b2e67 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -62,10 +62,11 @@
 #define DEBUGGER_SIGNAL BIONIC_SIGNAL_DEBUGGER
 
 static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) {
+  bool enabled = true;
+#if ANDROID_DEBUGGABLE
   char value[PROP_VALUE_MAX] = "";
-  bool enabled =
-      !(__system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1") &&
-        __system_property_get("debug.debuggerd.disable", value) > 0 && !strcmp(value, "1"));
+  enabled = !(__system_property_get("debug.debuggerd.disable", value) > 0 && !strcmp(value, "1"));
+#endif
   if (enabled) {
     sigaction(SIGABRT, action, nullptr);
     sigaction(SIGBUS, action, nullptr);
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 11e4790..949cec6 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -55,10 +55,10 @@
 YELLOW="${ESCAPE}[33m"
 BLUE="${ESCAPE}[34m"
 NORMAL="${ESCAPE}[0m"
-TMPDIR=${TMPDIR:-/tmp}
 print_time=false
 start_time=`date +%s`
 ACTIVE_SLOT=
+OVERLAYFS_BACKING="cache mnt/scratch"
 
 ADB_WAIT=4m
 FASTBOOT_WAIT=2m
@@ -68,6 +68,43 @@
 ##  Helper Functions
 ##
 
+[ "USAGE: LOG [RUN|OK|PASSED|WARNING|ERROR|FAILED|INFO] [message]..." ]
+LOG() {
+  case "${1}" in
+    R*)
+      shift
+      echo "${GREEN}[ RUN      ]${NORMAL}" "${@}"
+      ;;
+    OK)
+      shift
+      echo "${GREEN}[       OK ]${NORMAL}" "${@}"
+      ;;
+    P*)
+      shift
+      echo "${GREEN}[  PASSED  ]${NORMAL}" "${@}"
+      ;;
+    W*)
+      shift
+      echo "${YELLOW}[  WARNING ]${NORMAL}" "${@}"
+      ;;
+    E*)
+      shift
+      echo "${RED}[    ERROR ]${NORMAL}" "${@}"
+      ;;
+    F*)
+      shift
+      echo "${RED}[  FAILED  ]${NORMAL}" "${@}"
+      ;;
+    I*)
+      shift
+      echo "${BLUE}[     INFO ]${NORMAL}" "${@}"
+      ;;
+    *)
+      echo "${BLUE}[     INFO ]${NORMAL}" "${@}"
+      ;;
+  esac >&2
+}
+
 [ "USAGE: inFastboot
 
 Returns: true if device is in fastboot mode" ]
@@ -143,7 +180,7 @@
 
 Returns: the logcat output" ]
 adb_logcat() {
-  echo "${RED}[     INFO ]${NORMAL} logcat ${@}" >&2 &&
+  LOG INFO "logcat ${*}"
   adb logcat "${@}" </dev/null |
     tr -d '\r' |
     grep -v 'logd    : logdr: UID=' |
@@ -164,7 +201,7 @@
   if [ -z "${L}" ]; then
     return
   fi
-  echo "${YELLOW}[  WARNING ]${NORMAL} unlabeled sepolicy violations:" >&2
+  LOG WARNING "unlabeled sepolicy violations:"
   echo "${L}" | sed "s/^/${INDENT}/" >&2
 }
 
@@ -175,15 +212,6 @@
   adb_sh getprop ${1} </dev/null
 }
 
-[ "USAGE: isDebuggable
-
-Returns: true if device is (likely) a debug build" ]
-isDebuggable() {
-  if inAdb && [ 1 != "`get_property ro.debuggable`" ]; then
-    false
-  fi
-}
-
 [ "USAGE: adb_su <commands> </dev/stdin >/dev/stdout 2>/dev/stderr
 
 Returns: true if the command running as root succeeded" ]
@@ -202,17 +230,6 @@
     return ${ret}
 }
 
-[ "USAGE: adb_ls <dirfile> >stdout
-
-Returns: filename or directoru content to stdout with carriage returns skipped,
-         true if the ls had no errors" ]
-adb_ls() {
-    local OUTPUT="`adb_sh ls ${1} </dev/null 2>/dev/null`"
-    local ret=${?}
-    echo "${OUTPUT}" | tr -d '\r'
-    return ${ret}
-}
-
 [ "USAGE: adb_test <expression>
 
 Returns: exit status of the test expression" ]
@@ -241,15 +258,15 @@
   if [ X"${duration}" != X"${duration%s}" ]; then
     duration=${duration%s}
   elif [ X"${duration}" != X"${duration%m}" ]; then
-    duration=`expr ${duration%m} \* 60`
+    duration=$(( ${duration%m} * 60 ))
   elif [ X"${duration}" != X"${duration%h}" ]; then
-    duration=`expr ${duration%h} \* 3600`
+    duration=$(( ${duration%h} * 3600 ))
   elif [ X"${duration}" != X"${duration%d}" ]; then
-    duration=`expr ${duration%d} \* 86400`
+    duration=$(( ${duration%d} * 86400 ))
   fi
-  local seconds=`expr ${duration} % 60`
-  local minutes=`expr \( ${duration} / 60 \) % 60`
-  local hours=`expr ${duration} / 3600`
+  local seconds=$(( ${duration} % 60 ))
+  local minutes=$(( ( ${duration} / 60 ) % 60 ))
+  local hours=$(( ${duration} / 3600 ))
   if [ 0 -eq ${minutes} -a 0 -eq ${hours} ]; then
     if [ 1 -eq ${duration} ]; then
       echo 1 second
@@ -265,10 +282,10 @@
     return
   fi
   if [ 0 -eq ${hours} ]; then
-    echo ${minutes}:`expr ${seconds} / 10``expr ${seconds} % 10`
+    echo ${minutes}:$(( ${seconds} / 10 ))$(( ${seconds} % 10 ))
     return
   fi
-  echo ${hours}:`expr ${minutes} / 10``expr ${minutes} % 10`:`expr ${seconds} / 10``expr ${seconds} % 10`
+  echo ${hours}:$(( ${minutes} / 10 ))$(( ${minutes} % 10 )):$(( ${seconds} / 10 ))$(( ${seconds} % 10))
 }
 
 [ "USAGE: USB_DEVICE=\`usb_devnum [--next]\`
@@ -282,7 +299,7 @@
     if [ -n "${usb_device}" ]; then
       USB_DEVICE=dev${usb_device}
     elif [ -n "${USB_DEVICE}" -a "${1}" ]; then
-      USB_DEVICE=dev`expr ${USB_DEVICE#dev} + 1`
+      USB_DEVICE=dev$(( ${USB_DEVICE#dev} + 1 ))
     fi
     echo "${USB_DEVICE}"
   fi
@@ -298,10 +315,10 @@
   if [ -n "${1}" -a -n "`which timeout`" ]; then
     USB_DEVICE=`usb_devnum --next`
     duration=`format_duration ${1}`
-    echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
+    echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" >&2
     timeout --preserve-status --signal=KILL ${1} adb wait-for-device 2>/dev/null
     ret=${?}
-    echo -n "                                                                             ${CR}"
+    echo -n "                                                                             ${CR}" >&2
   else
     adb wait-for-device
     ret=${?}
@@ -310,11 +327,11 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" >&2
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     fi
   fi
   local end=`date +%s`
-  local diff_time=`expr ${end} - ${start}`
+  local diff_time=$(( ${end} - ${start} ))
   local _print_time=${print_time}
   if [ ${diff_time} -lt 15 ]; then
     _print_time=false
@@ -339,8 +356,8 @@
       ;;
   esac
   if ${_print_time} || [ -n "${reason}" ]; then
-    echo "${BLUE}[     INFO ]${NORMAL} adb wait duration ${diff_time}${reason}"
-  fi >&2
+    LOG INFO "adb wait duration ${diff_time}${reason}"
+  fi
 
   return ${ret}
 }
@@ -413,8 +430,8 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
-    fi >&2
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+    fi
   fi
   return ${ret}
 }
@@ -438,8 +455,8 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
-    fi >&2
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+    fi
   fi
   return ${ret}
 }
@@ -494,10 +511,10 @@
         break
       fi
     fi
-    counter=`expr ${counter} + 1`
+    counter=$(( ${counter} + 1 ))
     if [ ${counter} -gt ${timeout} ]; then
       ${exit_function}
-      echo "ERROR: wait_for_screen() timed out (`format_duration ${timeout}`)" >&2
+      LOG ERROR "wait_for_screen() timed out ($(format_duration ${timeout}))"
       return 1
     fi
     sleep 1
@@ -570,22 +587,13 @@
 
 [ "USAGE: restore
 
-Do nothing: should be redefined when necessary.  Called after cleanup.
+Do nothing: should be redefined when necessary.
 
 Returns: reverses configurations" ]
 restore() {
   true
 }
 
-[ "USAGE: cleanup
-
-Do nothing: should be redefined when necessary
-
-Returns: cleans up any latent resources" ]
-cleanup() {
-  true
-}
-
 [ "USAGE: test_duration >/dev/stderr
 
 Prints the duration of the test
@@ -593,12 +601,12 @@
 Returns: reports duration" ]
 test_duration() {
   if ${print_time}; then
-    echo "${BLUE}[     INFO ]${NORMAL} end `date`"
+    LOG INFO "end $(date)"
     [ -n "${start_time}" ] || return
     end_time=`date +%s`
-    local diff_time=`expr ${end_time} - ${start_time}`
-    echo "${BLUE}[     INFO ]${NORMAL} duration `format_duration ${diff_time}`"
-  fi >&2
+    local diff_time=$(( ${end_time} - ${start_time} ))
+    LOG INFO "duration $(format_duration ${diff_time})"
+  fi
 }
 
 [ "USAGE: die [-d|-t <epoch>] [message] >/dev/stderr
@@ -618,121 +626,65 @@
     fi
     shift 2
   fi >&2
-  echo "${RED}[  FAILED  ]${NORMAL} ${@}" >&2
-  cleanup
-  restore
-  test_duration
+  LOG FAILED "${@}"
   exit 1
 }
 
-[ "USAGE: EXPECT_EQ <lval> <rval> [--warning [message]]
-
-Returns true if (regex) lval matches rval" ]
-EXPECT_EQ() {
-  local lval="${1}"
-  local rval="${2}"
-  shift 2
-  local error=1
-  local prefix="${RED}[    ERROR ]${NORMAL}"
-  if [ X"${1}" = X"--warning" ]; then
-      prefix="${RED}[  WARNING ]${NORMAL}"
-      error=0
-      shift 1
-  fi
-  if ! ( echo X"${rval}" | grep '^X'"${lval}"'$' >/dev/null 2>/dev/null ); then
-    if [ `echo ${lval}${rval}${*} | wc -c` -gt 50 -o "${rval}" != "${rval%
-*}" ]; then
-      echo "${prefix} expected \"${lval}\""
-      echo "${prefix} got \"${rval}\"" |
-        sed ": again
-             N
-             s/\(\n\)\([^ ]\)/\1${INDENT}\2/
-             t again"
-      if [ -n "${*}" ] ; then
-        echo "${prefix} ${*}"
-      fi
-    else
-      echo "${prefix} expected \"${lval}\" got \"${rval}\" ${*}"
-    fi >&2
-    return ${error}
-  fi
-  if [ -n "${*}" ] ; then
-    prefix="${GREEN}[     INFO ]${NORMAL}"
-    if [ X"${lval}" != X"${rval}" ]; then  # we were supplied a regex?
-      if [ `echo ${lval}${rval}${*} | wc -c` -gt 60 -o "${rval}" != "${rval% *}" ]; then
-        echo "${prefix} ok \"${lval}\""
-        echo "       = \"${rval}\"" |
-          sed ": again
-               N
-               s/\(\n\)\([^ ]\)/\1${INDENT}\2/
-               t again"
-        if [ -n "${*}" ] ; then
-          echo "${prefix} ${*}"
-        fi
-      else
-        echo "${prefix} ok \"${lval}\" = \"${rval}\" ${*}"
-      fi
-    else
-      echo "${prefix} ok \"${lval}\" ${*}"
-    fi >&2
-  fi
-  return 0
-}
-
-[ "USAGE: EXPECT_NE <lval> <rval> [--warning [message]]
-
-Returns true if lval matches rval" ]
-EXPECT_NE() {
-  local lval="${1}"
-  local rval="${2}"
-  shift 2
-  local error=1
-  local prefix="${RED}[    ERROR ]${NORMAL}"
-  if [ X"${1}" = X"--warning" ]; then
-      prefix="${RED}[  WARNING ]${NORMAL}"
-      error=0
-      shift 1
-  fi
-  if [ X"${rval}" = X"${lval}" ]; then
-    echo "${prefix} did not expect \"${lval}\" ${*}" >&2
-    return ${error}
-  fi
-  if [ -n "${*}" ] ; then
-    echo "${prefix} ok \"${lval}\" not \"${rval}\" ${*}" >&2
-  fi
-  return 0
-}
-
 [ "USAGE: check_eq <lval> <rval> [--warning [message]]
 
-Exits if (regex) lval mismatches rval" ]
+Exits if (regex) lval mismatches rval.
+
+Returns: true if lval matches rval" ]
 check_eq() {
   local lval="${1}"
   local rval="${2}"
   shift 2
+  if [[ "${rval}" =~ ^${lval}$ ]]; then
+    return 0
+  fi
+
+  local error=true
+  local logt=ERROR
   if [ X"${1}" = X"--warning" ]; then
-      EXPECT_EQ "${lval}" "${rval}" ${*}
-      return
+    shift 1
+    error=false
+    logt=WARNING
   fi
-  if ! EXPECT_EQ "${lval}" "${rval}"; then
-    die "${@}"
+  if [ $(( ${#lval} + ${#rval} )) -gt 40 ]; then
+    LOG "${logt}" "expected \"${lval}\"
+${INDENT}got      \"${rval}\""
+  else
+    LOG "${logt}" "expected \"${lval}\" got \"${rval}\""
   fi
+  ${error} && die "${*}"
+  [ -n "${*}" ] && LOG "${logt}" "${*}"
+  return 1
 }
 
 [ "USAGE: check_ne <lval> <rval> [--warning [message]]
 
-Exits if lval matches rval" ]
+Exits if (regex) lval matches rval.
+
+Returns: true if lval mismatches rval" ]
 check_ne() {
   local lval="${1}"
   local rval="${2}"
   shift 2
+  if ! [[ "${rval}" =~ ^${lval}$ ]]; then
+    return 0
+  fi
+
+  local error=true
+  local logt=ERROR
   if [ X"${1}" = X"--warning" ]; then
-      EXPECT_NE "${lval}" "${rval}" ${*}
-      return
+      shift 1
+      error=false
+      logt=WARNING
   fi
-  if ! EXPECT_NE "${lval}" "${rval}"; then
-    die "${@}"
-  fi
+  LOG "${logt}" "unexpected \"${rval}\""
+  ${error} && die "${*}"
+  [ -n "${*}" ] && LOG "${logt}" "${*}"
+  return 1
 }
 
 [ "USAGE: join_with <delimiter> <strings>
@@ -759,10 +711,11 @@
   local exclude_filesystems=(
     "overlay" "tmpfs" "none" "sysfs" "proc" "selinuxfs" "debugfs" "bpf"
     "binfmt_misc" "cg2_bpf" "pstore" "tracefs" "adb" "mtp" "ptp" "devpts"
-    "ramdumpfs" "binder" "securityfs" "functionfs" "rootfs"
+    "ramdumpfs" "binder" "securityfs" "functionfs" "rootfs" "fuse"
   )
   local exclude_devices=(
     "\/sys\/kernel\/debug" "\/data\/media" "\/dev\/block\/loop[0-9]*"
+    "\/dev\/block\/vold\/[^ ]+"
     "${exclude_filesystems[@]}"
   )
   local exclude_mount_points=(
@@ -784,8 +737,45 @@
 Filters out all apex and vendor override administrative overlay mounts
 uninteresting to the test" ]
 skip_unrelated_mounts() {
-    grep -v "^overlay.* /\(apex\|bionic\|system\|vendor\)/[^ ]" |
-      grep -v "[%] /\(data_mirror\|apex\|bionic\|system\|vendor\)/[^ ][^ ]*$"
+  grep -vE \
+      -e "^overlay.* /(apex|bionic|system|vendor)/[^ ]" \
+      -e "^[^ ]+ /apex/[^ ]" \
+      -e "[%] /(data_mirror|apex|bionic|system|vendor)/[^ ]+$"
+}
+
+[ "USAGE: surgically_wipe_overlayfs
+
+Surgically wipe any mounted overlayfs scratch files.
+
+Returns: true if wiped anything" ]
+surgically_wipe_overlayfs() {
+  local wiped_anything=false
+  for d in ${OVERLAYFS_BACKING}; do
+    if adb_su test -d "/${d}/overlay" </dev/null; then
+      LOG INFO "/${d}/overlay is setup, surgically wiping"
+      adb_su rm -rf "/${d}/overlay" </dev/null
+      wiped_anything=true
+    fi
+  done
+  ${wiped_anything}
+}
+
+[ "USAGE: is_overlayfs_mounted
+
+Returns: true if overlayfs is mounted" ]
+is_overlayfs_mounted() {
+  local df_output=$(adb_su df -k </dev/null)
+  local df_header_line=$(echo "${df_output}" | head -1)
+  local overlay_mounts=$(echo "${df_output}" | tail +2 |
+                         skip_unrelated_mounts |
+                         awk '$1 == "overlay" || $6 == "/mnt/scratch"')
+  if ! echo "${overlay_mounts}" | grep -q '^overlay '; then
+    return 1
+  fi >/dev/null 2>/dev/null
+  ( echo "${df_header_line}"
+    echo "${overlay_mounts}"
+  ) >&2
+  return 0
 }
 
 ##
@@ -874,24 +864,53 @@
   NORMAL=""
 fi
 
-# Set an ERR trap handler to report any unhandled error
-trap 'die "line ${LINENO}: unhandled error"' ERR
+TMPDIR=
+
+exit_handler() {
+  [ -n "${TMPDIR}" ] && rm -rf "${TMPDIR}"
+  local err=0
+  if ! restore; then
+    LOG ERROR "restore failed"
+    err=1
+  fi >&2
+  test_duration || true
+  if [ "${err}" != 0 ]; then
+    exit "${err}"
+  fi
+}
+trap 'exit_handler' EXIT
+
+TMPDIR=$(mktemp -d)
 
 if ${print_time}; then
-  echo "${BLUE}[     INFO ]${NORMAL}" start `date` >&2
+  LOG INFO "start $(date)"
 fi
 
+if [ -z "${ANDROID_SERIAL}" ]; then
+  inAdb || die "no device or more than one device in adb mode"
+  D=$(adb devices | awk '$2 == "device" { print $1; exit }')
+  [ -n "${D}" ] || die "cannot get device serial"
+  ANDROID_SERIAL="${D}"
+fi
+export ANDROID_SERIAL
+
 inFastboot && die "device in fastboot mode"
 inRecovery && die "device in recovery mode"
 if ! inAdb; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} device not in adb mode" >&2
+  LOG WARNING "device not in adb mode"
   adb_wait ${ADB_WAIT}
 fi
 inAdb || die "specified device not in adb mode"
-isDebuggable || die "device not a debug build"
+[ "1" = "$(get_property ro.debuggable)" ] || die "device not a debug build"
+[ "orange" = "$(get_property ro.boot.verifiedbootstate)" ] || die "device not bootloader unlocked"
+can_restore_verity=true
+if [ "2" != "$(get_property partition.system.verified)" ]; then
+  LOG WARNING "device might not support verity"
+  can_restore_verity=false
+fi
 enforcing=true
 if ! adb_su getenforce </dev/null | grep 'Enforcing' >/dev/null; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} device does not have sepolicy in enforcing mode" >&2
+  LOG WARNING "device does not have sepolicy in enforcing mode"
   enforcing=false
 fi
 
@@ -899,9 +918,6 @@
 
 # Collect characteristics of the device and report.
 
-D=`get_property ro.serialno`
-[ -n "${D}" ] || D=`get_property ro.boot.serialno`
-[ -z "${D}" -o -n "${ANDROID_SERIAL}" ] || ANDROID_SERIAL=${D}
 USB_SERIAL=
 if [ -n "${ANDROID_SERIAL}" -a "Darwin" != "${HOSTOS}" ]; then
   USB_SERIAL="`find /sys/devices -name serial | grep usb || true`"
@@ -915,35 +931,44 @@
   USB_ADDRESS=${USB_SERIAL%/serial}
   USB_ADDRESS=usb${USB_ADDRESS##*/}
 fi
-[ -z "${ANDROID_SERIAL}${USB_ADDRESS}" ] ||
-  USB_DEVICE=`usb_devnum`
-  echo "${BLUE}[     INFO ]${NORMAL}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} >&2
+USB_DEVICE=$(usb_devnum)
+[ -z "${ANDROID_SERIAL}${USB_ADDRESS}${USB_DEVICE}" ] ||
+  LOG INFO "${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE}"
 BUILD_DESCRIPTION=`get_property ro.build.description`
 [ -z "${BUILD_DESCRIPTION}" ] ||
-  echo "${BLUE}[     INFO ]${NORMAL} ${BUILD_DESCRIPTION}" >&2
+  LOG INFO "${BUILD_DESCRIPTION}"
 KERNEL_VERSION="`adb_su cat /proc/version </dev/null 2>/dev/null`"
 [ -z "${KERNEL_VERSION}" ] ||
-  echo "${BLUE}[     INFO ]${NORMAL} ${KERNEL_VERSION}" >&2
+  LOG INFO "${KERNEL_VERSION}"
 ACTIVE_SLOT=`get_active_slot`
 [ -z "${ACTIVE_SLOT}" ] ||
-  echo "${BLUE}[     INFO ]${NORMAL} active slot is ${ACTIVE_SLOT}" >&2
+  LOG INFO "active slot is ${ACTIVE_SLOT}"
 
 # Acquire list of system partitions
+FSTAB_SUFFIXES=(
+  "$(get_property ro.boot.fstab_suffix)"
+  "$(get_property ro.boot.hardware)"
+  "$(get_property ro.boot.hardware.platform)"
+)
+FSTAB_PATTERN='\.('"$(join_with "|" "${FSTAB_SUFFIXES[@]}")"')$'
+FSTAB_FILE=$(adb_su ls -1 '/vendor/etc/fstab*' </dev/null |
+             grep -E "${FSTAB_PATTERN}" |
+             head -1)
 
 # KISS (assume system partition mount point is "/<partition name>")
-PARTITIONS=`adb_su cat /vendor/etc/fstab* </dev/null |
-              grep -v "^[#${SPACE}${TAB}]" |
-              skip_administrative_mounts |
-              awk '$1 ~ /^[^\/]+$/ && "/"$1 == $2 && $4 ~ /(^|,)ro(,|$)/ { print $1 }' |
-              sort -u |
-              tr '\n' ' '`
+[ -n "${FSTAB_FILE}" ] &&
+  PARTITIONS=$(adb_su grep -v "^[#${SPACE}${TAB}]" "${FSTAB_FILE}" |
+               skip_administrative_mounts |
+               awk '$1 ~ /^[^\/]+$/ && "/"$1 == $2 && $4 ~ /(^|,)ro(,|$)/ { print $1 }' |
+               sort -u |
+               tr '\n' ' ')
 PARTITIONS="${PARTITIONS:-system vendor}"
 # KISS (we do not support sub-mounts for system partitions currently)
 MOUNTS="`for i in ${PARTITIONS}; do
            echo /${i}
          done |
          tr '\n' ' '`"
-echo "${BLUE}[     INFO ]${NORMAL} System Partitions list: ${PARTITIONS}" >&2
+LOG INFO "System Partitions list: ${PARTITIONS}"
 
 # Report existing partition sizes
 adb_sh ls -l /dev/block/by-name/ /dev/block/mapper/ </dev/null 2>/dev/null |
@@ -964,287 +989,278 @@
         ;;
     esac
     size=`adb_su cat /sys/block/${device}/size 2>/dev/null </dev/null` &&
-      size=`expr ${size} / 2` &&
-      echo "${BLUE}[     INFO ]${NORMAL} partition ${name} device ${device} size ${size}K" >&2
+      size=$(( ${size} / 2 )) &&
+      LOG INFO "partition ${name} device ${device} size ${size}K"
   done
 
-# If reboot too soon after fresh flash, could trip device update failure logic
-if ${screen_wait}; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} waiting for screen to come up. Consider --no-wait-screen option" >&2
-fi
-if ! wait_for_screen && ${screen_wait}; then
-  screen_wait=false
-  echo "${YELLOW}[  WARNING ]${NORMAL} not healthy, no launcher, skipping wait for screen" >&2
-fi
+LOG RUN "Checking kernel support for overlayfs"
 
-# Can we test remount -R command?
-OVERLAYFS_BACKING="cache mnt/scratch"
 overlayfs_supported=true
-if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
-     "2" != "`get_property partition.system.verified`" ]; then
-  restore() {
-    ${overlayfs_supported} || return 0
-    inFastboot &&
-      fastboot reboot &&
-      adb_wait ${ADB_WAIT} ||
-      true
-    if inAdb; then
-      reboot=false
-      for d in ${OVERLAYFS_BACKING}; do
-        if adb_test -d /${d}/overlay; then
-          adb_su rm -rf /${d}/overlay </dev/null
-          reboot=true
-        fi
-      done
-      if ${reboot}; then
-        adb_reboot &&
-        adb_wait ${ADB_WAIT}
-      fi
-    fi
-  }
-else
-  restore() {
-    ${overlayfs_supported} || return 0
-    inFastboot &&
-      fastboot reboot &&
-      adb_wait ${ADB_WAIT} ||
-      true
-    inAdb &&
-      adb_root &&
-      adb enable-verity >/dev/null 2>/dev/null &&
-      adb_reboot &&
-      adb_wait ${ADB_WAIT}
-  }
-
-  echo "${GREEN}[ RUN      ]${NORMAL} Testing adb shell su root remount -R command" >&2
-
-  avc_check
-  T=`adb_date`
-  adb_su remount -R system </dev/null
-  err=${?}
-  if [ "${err}" != 0 ]; then
-    echo "${YELLOW}[  WARNING ]${NORMAL} adb shell su root remount -R system = ${err}, likely did not reboot!" >&2
-    T="-t ${T}"
+adb_root || die "becoming root to mine kernel information"
+if ! adb_test -d /sys/module/overlay; then
+  if adb_sh grep -q "nodev${TAB}overlay" /proc/filesystems; then
+    LOG OK "overlay module present"
   else
-    # Rebooted, logcat will be meaningless, and last logcat will likely be clear
-    T=""
+    LOG WARNING "overlay module not present"
+    overlayfs_supported=false
   fi
-  sleep 2
-  adb_wait ${ADB_WAIT} ||
-    die "waiting for device after adb shell su root remount -R system `usb_status`"
-  if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
-       "2" = "`get_property partition.system.verified`" ]; then
-    die ${T} "remount -R command failed
-${INDENT}ro.boot.verifiedbootstate=\"`get_property ro.boot.verifiedbootstate`\"
-${INDENT}partition.system.verified=\"`get_property partition.system.verified`\""
-  fi
-
-  echo "${GREEN}[       OK ]${NORMAL} adb shell su root remount -R command" >&2
-fi
-
-echo "${GREEN}[ RUN      ]${NORMAL} Testing kernel support for overlayfs" >&2
-
-adb_wait || die "wait for device failed"
-adb_root ||
-  die "initial setup"
-
-adb_test -d /sys/module/overlay ||
-  adb_sh grep "nodev${TAB}overlay" /proc/filesystems </dev/null >/dev/null 2>/dev/null &&
-  echo "${GREEN}[       OK ]${NORMAL} overlay module present" >&2 ||
-  (
-    echo "${YELLOW}[  WARNING ]${NORMAL} overlay module not present" >&2 &&
-      false
-  ) ||
-  overlayfs_supported=false
+fi >&2
 if ${overlayfs_supported}; then
-  adb_test -f /sys/module/overlay/parameters/override_creds &&
-    echo "${GREEN}[       OK ]${NORMAL} overlay module supports override_creds" >&2 ||
-    case `adb_sh uname -r </dev/null` in
+  if adb_test -f /sys/module/overlay/parameters/override_creds; then
+    LOG OK "overlay module supports override_creds"
+  else
+    case "$(adb_sh uname -r </dev/null)" in
       4.[456789].* | 4.[1-9][0-9]* | [56789].*)
-        echo "${YELLOW}[  WARNING ]${NORMAL} overlay module does not support override_creds" >&2 &&
+        LOG WARNING "overlay module does not support override_creds"
         overlayfs_supported=false
         ;;
       *)
-        echo "${GREEN}[       OK ]${NORMAL} overlay module uses caller's creds" >&2
+        LOG OK "overlay module uses caller's creds"
         ;;
     esac
+  fi
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} Checking current overlayfs status" >&2
+restore() {
+  LOG INFO "restoring device"
+  ${overlayfs_supported} || return 0
+  inFastboot &&
+    fastboot reboot &&
+    adb_wait "${ADB_WAIT}" ||
+    true
+  if ! inAdb; then
+    LOG ERROR "expect adb device"
+    return 1
+  fi
+  adb_root || true
+  local reboot=false
+  if surgically_wipe_overlayfs; then
+    reboot=true
+  fi
+  if ${can_restore_verity}; then
+    if ! adb enable-verity; then
+      LOG ERROR "adb enable-verity"
+      return 1
+    fi
+    LOG INFO "restored verity"
+    reboot=true
+  fi >&2
+  if ${reboot}; then
+    adb_reboot &&
+      adb_wait "${ADB_WAIT}"
+  fi
+}
+
+# If reboot too soon after fresh flash, could trip device update failure logic
+if ${screen_wait}; then
+  LOG INFO "waiting for screen to come up. Consider --no-wait-screen option"
+fi
+if ! wait_for_screen && ${screen_wait}; then
+  screen_wait=false
+  LOG WARNING "not healthy, no launcher, skipping wait for screen"
+fi
+
+################################################################################
+LOG RUN "Checking current overlayfs status"
+
+adb_wait || die "wait for device failed"
+adb_root || die "adb root failed"
 
 # We can not universally use adb enable-verity to ensure device is
 # in a overlayfs disabled state since it can prevent reboot on
 # devices that remount the physical content rather than overlayfs.
 # So lets do our best to surgically wipe the overlayfs state without
 # having to go through enable-verity transition.
-reboot=false
-for d in ${OVERLAYFS_BACKING}; do
-  if adb_test -d /${d}/overlay; then
-    echo "${YELLOW}[  WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
-    adb_sh rm -rf /${d}/overlay </dev/null ||
-      die "/${d}/overlay wipe"
-    reboot=true
-  fi
-done
-if ${reboot}; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} rebooting before test" >&2
+if surgically_wipe_overlayfs; then
+  LOG WARNING "rebooting before test"
   adb_reboot &&
     adb_wait ${ADB_WAIT} ||
-    die "lost device after reboot after wipe `usb_status`"
+    die "lost device after reboot after overlay wipe $(usb_status)"
   adb_root ||
     die "lost device after elevation to root after wipe `usb_status`"
 fi
-D=`adb_sh df -k </dev/null` &&
-  H=`echo "${D}" | head -1` &&
-  D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay "` &&
-  echo "${H}" &&
-  echo "${D}" &&
+is_overlayfs_mounted &&
   die "overlay takeover unexpected at this phase"
-echo "${GREEN}[       OK ]${NORMAL} no overlay present before setup" >&2
+
 overlayfs_needed=true
-D=`adb_sh cat /proc/mounts </dev/null |
-   skip_administrative_mounts data`
-if echo "${D}" | grep /dev/root >/dev/null; then
-  D=`echo / /
-     echo "${D}" | grep -v /dev/root`
-fi
-D=`echo "${D}" | cut -s -d' ' -f1 | sort -u`
+D=$(adb_sh grep " ro," /proc/mounts </dev/null |
+    skip_administrative_mounts data |
+    skip_unrelated_mounts |
+    awk '{ print $1 }' |
+    sed 's|/dev/root|/|' |
+    sort -u)
 no_dedupe=true
 for d in ${D}; do
   adb_sh tune2fs -l $d </dev/null 2>&1 |
     grep "Filesystem features:.*shared_blocks" >/dev/null &&
   no_dedupe=false
 done
-D=`adb_sh df -k ${D} </dev/null |
-   sed 's@\([%] /\)\(apex\|bionic\|system\|vendor\)/[^ ][^ ]*$@\1@'`
-echo "${D}"
+D=$(adb_sh df -k ${D} </dev/null)
+echo "${D}" >&2
 if [ X"${D}" = X"${D##* 100[%] }" ] && ${no_dedupe} ; then
   overlayfs_needed=false
   # if device does not need overlays, then adb enable-verity will brick device
-  restore() {
-    ${overlayfs_supported} || return 0
-    inFastboot &&
-      fastboot reboot &&
-      adb_wait ${ADB_WAIT}
-    inAdb &&
-      adb_wait ${ADB_WAIT}
-  }
+  can_restore_verity=false
 elif ! ${overlayfs_supported}; then
   die "need overlayfs, but do not have it"
 fi
+LOG OK "no overlay present before setup"
 
-echo "${GREEN}[ RUN      ]${NORMAL} disable-verity -R" >&2
+################################################################################
+# Precondition is overlayfs *not* setup.
+LOG RUN "Testing adb disable-verity -R"
 
-L=
 T=$(adb_date)
-H=$(adb_su disable-verity -R 2>&1)
-err="${?}"
-echo "${H}"
+adb_su disable-verity -R >&2 ||
+  die -t "${T}" "disable-verity -R failed"
+sleep 2
+adb_wait "${ADB_WAIT}" ||
+  die "lost device after adb disable-verity -R $(usb_status)"
 
-if [ "${err}" != 0 ]; then
-  die -t "${T}" "disable-verity -R"
+if [ "2" = "$(get_property partition.system.verified)" ]; then
+  LOG ERROR "partition.system.verified=$(get_property partition.system.verified)"
+  die "verity not disabled after adb disable-verity -R"
 fi
-
-# Fuzzy search for a line that contains "overlay" and "fail". Informational only.
-if echo "${H}" | grep -i "overlay" | grep -iq "fail"; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} overlayfs setup whined" >&2
-fi
-
-adb_wait "${ADB_WAIT}" &&
-  adb_root ||
-  die "lost device after adb shell su root disable-verity -R $(usb_status)"
-
 if ${overlayfs_needed}; then
-  has_overlayfs_setup=false
-  for d in ${OVERLAYFS_BACKING}; do
-    if adb_test -d "/${d}/overlay"; then
-      has_overlayfs_setup=true
-      echo "${GREEN}[       OK ]${NORMAL} /${d}/overlay is setup" >&2
-    fi
-  done
-  if ! ${has_overlayfs_setup}; then
-    die "no overlay being setup after disable-verity -R"
-  fi
+  is_overlayfs_mounted ||
+    die "no overlay takeover after adb disable-verity -R"
+  LOG OK "overlay takeover after adb disable-verity -R"
 fi
+LOG OK "adb disable-verity -R"
 
-echo "${GREEN}[ RUN      ]${NORMAL} remount" >&2
+
+LOG RUN "Testing adb remount -R"
+
+if surgically_wipe_overlayfs; then
+  adb_reboot &&
+    adb_wait "${ADB_WAIT}" ||
+    die "lost device after reboot after overlay wipe $(usb_status)"
+fi
+is_overlayfs_mounted &&
+  die "overlay takeover unexpected at this phase"
+
+T=$(adb_date)
+adb_su remount -R </dev/null >&2 ||
+  die -t "${T}" "adb remount -R failed"
+sleep 2
+adb_wait "${ADB_WAIT}" ||
+  die "lost device after adb remount -R $(usb_status)"
+
+if [ "2" = "$(get_property partition.system.verified)" ]; then
+  LOG ERROR "partition.system.verified=$(get_property partition.system.verified)"
+  die "verity not disabled after adb remount -R"
+fi
+if ${overlayfs_needed}; then
+  is_overlayfs_mounted ||
+    die "no overlay takeover after adb remount -R"
+  LOG OK "overlay takeover after adb remount -R"
+fi
+LOG OK "adb remount -R"
+
+################################################################################
+# Precondition is a verity-disabled device with overlayfs already setup.
+LOG RUN "Testing adb remount RW"
+
+if ! ${overlayfs_needed}; then
+  LOG WARNING "Reboot to RO (device doesn't use overlayfs)"
+  adb_reboot &&
+    adb_wait "${ADB_WAIT}" ||
+    die "lost device after reboot to RO $(usb_status)"
+fi
 
 # Feed log with selinux denials as baseline before overlays
 adb_unroot
 adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
 adb_root
 
-D=`adb remount 2>&1`
-ret=${?}
-echo "${D}"
-[ ${ret} != 0 ] ||
-  [ X"${D}" = X"${D##*remount failed}" ] ||
-  ( [ -n "${L}" ] && echo "${L}" && false ) ||
-  die -t "${T}" "adb remount failed"
-D=`adb_sh df -k </dev/null` &&
-  H=`echo "${D}" | head -1` &&
-  D=`echo "${D}" | skip_unrelated_mounts | grep "^overlay "` ||
-  ( [ -n "${L}" ] && echo "${L}" && false )
-ret=${?}
+adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts </dev/null &&
+  die "/vendor is not RO"
+T=$(adb_date)
+adb remount vendor >&2 ||
+  die -t "${T}" "adb remount vendor"
+adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts </dev/null ||
+  die "/vendor is not RW"
+
+adb_sh grep -qE " (/system|/) [^ ]* rw," /proc/mounts </dev/null &&
+  die "/system is not RO"
+T=$(adb_date)
+adb remount >&2 ||
+  die -t "${T}" "adb remount"
+adb_sh grep -qE " (/system|/) [^ ]* rw," /proc/mounts </dev/null ||
+  die "/system is not RW"
+
+D=$(adb_sh df -k </dev/null)
+H=$(echo "${D}" | head -1)
+D=$(echo "${D}" | skip_unrelated_mounts | grep "^overlay ")
+if [ -n "${D}" ] && ! ${overlayfs_needed}; then
+  die -t "${T}" "unexpected overlay takeover"
+fi
+if [ -z "${D}" ] && ${overlayfs_needed}; then
+  die -t "${T}" "expected overlay takeover"
+fi
+
+# If scratch_partition && uses_dynamic_scratch, then scratch is on super.
+# If scratch_partition && !uses_dynamic_scratch, then scratch is super_other, system_other.
+# If !scratch_partition, then scratch is on /data via image_manager.
 uses_dynamic_scratch=false
 scratch_partition=
-virtual_ab=`get_property ro.virtual_ab.enabled`
+virtual_ab=$(get_property ro.virtual_ab.enabled)
 if ${overlayfs_needed}; then
-  if [ ${ret} != 0 ]; then
-    die -t ${T} "overlay takeover failed"
-  fi
-  echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
-   echo "${YELLOW}[  WARNING ]${NORMAL} overlay takeover not complete" >&2
-  if [ -z "${virtual_ab}" ]; then
+  M=$(adb_sh cat /proc/mounts </dev/null |
+      awk '$2 == "/mnt/scratch" { print $1, $3; exit }')
+  [ -z "${M}" ] && die "cannot find any scratch device mounted on /mnt/scratch"
+
+  scratch_device=$(echo "${M}" | awk '{ print $1 }')
+  scratch_filesystem=$(echo "${M}" | awk '{ print $2 }')
+  scratch_size=$(adb_sh df -k "${scratch_device}" </dev/null |
+                 tail +2 | head -1 | awk '{ print $2 }')
+  [ -z "${scratch_size}" ] && die "cannot get size of scratch device (${scratch_device})"
+
+  if [ -n "${virtual_ab}" ]; then
+    LOG INFO "using dynamic scratch partition on /data (VAB device)"
+  elif [[ "${scratch_device}" == /dev/block/by-name/* ]]; then
+    scratch_partition="${scratch_device##/dev/block/by-name/}"
+    LOG INFO "using physical scratch partition ${scratch_partition}"
+  else
+    uses_dynamic_scratch=true
     scratch_partition=scratch
+    LOG INFO "using dynamic scratch partition on super"
   fi
-  if echo "${D}" | grep " /mnt/scratch" >/dev/null; then
-    echo "${BLUE}[     INFO ]${NORMAL} using ${scratch_partition} dynamic partition for overrides" >&2
-  fi
-  M=`adb_sh cat /proc/mounts </dev/null |
-     sed -n 's@\([^ ]*\) /mnt/scratch \([^ ]*\) .*@\2 on \1@p'`
-  [ -n "${M}" ] &&
-    echo "${BLUE}[     INFO ]${NORMAL} scratch filesystem ${M}"
-  uses_dynamic_scratch=true
-  if [ "${M}" != "${M##*/dev/block/by-name/}" ]; then
-    uses_dynamic_scratch=false
-    scratch_partition="${M##*/dev/block/by-name/}"
-  fi
-  scratch_size=`adb_sh df -k /mnt/scratch </dev/null 2>/dev/null |
-                while read device kblocks used available use mounted on; do
-                  if [ "/mnt/scratch" = "\${mounted}" ]; then
-                    echo \${kblocks}
-                  fi
-                done` &&
-    [ -n "${scratch_size}" ] ||
-    die "scratch size"
-  echo "${BLUE}[     INFO ]${NORMAL} scratch size ${scratch_size}KB" >&2
+  LOG INFO "scratch device ${scratch_device} filesystem ${scratch_filesystem} size ${scratch_size}KiB"
+
   for d in ${OVERLAYFS_BACKING}; do
     if adb_test -d /${d}/overlay/system/upper; then
-      echo "${BLUE}[     INFO ]${NORMAL} /${d}/overlay is setup" >&2
+      LOG INFO "/${d}/overlay is setup"
     fi
   done
 
-  echo "${H}" &&
-    echo "${D}" &&
-    echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
-    die  "overlay takeover after remount"
-  !(adb_sh grep "^overlay " /proc/mounts </dev/null |
+  ( echo "${H}"
+    echo "${D}"
+  ) >&2
+  echo "${D}" | grep ' /system$' >/dev/null ||
+    die -t "${T}" "expected overlay to takeover /system after remount"
+  adb_sh grep "^overlay " /proc/mounts </dev/null |
     skip_unrelated_mounts |
-    grep " overlay ro,") ||
-    die "remount overlayfs missed a spot (ro)"
-  !(adb_sh grep -v noatime /proc/mounts </dev/null |
+    grep " overlay ro," &&
+    die "expected overlay to be RW after remount"
+  adb_sh grep -v noatime /proc/mounts </dev/null |
     skip_administrative_mounts data |
     skip_unrelated_mounts |
-    grep -v ' ro,') ||
+    grep -v ' ro,' &&
     die "mounts are not noatime"
-  D=`adb_sh grep " rw," /proc/mounts </dev/null |
-     skip_administrative_mounts data`
-  if echo "${D}" | grep /dev/root >/dev/null; then
-    D=`echo / /
-       echo "${D}" | grep -v /dev/root`
-  fi
-  D=`echo "${D}" | cut -s -d' ' -f1 | sort -u`
+
+  data_device=$(adb_sh cat /proc/mounts </dev/null | awk '$2 == "/data" { print $1; exit }')
+  D=$(adb_sh grep " rw," /proc/mounts </dev/null |
+      skip_administrative_mounts data |
+      skip_unrelated_mounts |
+      awk '{ print $1 }' |
+      grep -v "${data_device}" |
+      sed 's|/dev/root|/|' |
+      sort -u)
+  if [ -n "${D}" ]; then
+    adb_sh df -k ${D} </dev/null |
+      sed -e 's/^Filesystem      /Filesystem (rw) /'
+  fi >&2
   bad_rw=false
   for d in ${D}; do
     if adb_sh tune2fs -l $d </dev/null 2>&1 |
@@ -1257,21 +1273,14 @@
         bad_rw=true
     fi
   done
-  [ -z "${D}" ] ||
-    D=`adb_sh df -k ${D} </dev/null |
-       sed -e 's@\([%] /\)\(apex\|bionic\|system\|vendor\)/[^ ][^ ]*$@\1@' \
-           -e 's/^Filesystem      /Filesystem (rw) /'`
-  [ -z "${D}" ] || echo "${D}"
   ${bad_rw} && die "remount overlayfs missed a spot (rw)"
-else
-  if [ ${ret} = 0 ]; then
-    die -t ${T} "unexpected overlay takeover"
-  fi
 fi
 
+LOG OK "adb remount RW"
+
 # Check something.
 
-echo "${GREEN}[ RUN      ]${NORMAL} push content to ${MOUNTS}" >&2
+LOG RUN "push content to ${MOUNTS}"
 
 A="Hello World! $(date)"
 for i in ${MOUNTS}; do
@@ -1290,26 +1299,22 @@
 
 # Download libc.so, append some garbage, push back, and check if the file
 # is updated.
-tempdir="`mktemp -d`"
-cleanup() {
-  rm -rf ${tempdir}
-}
-adb pull /system/lib/bootstrap/libc.so ${tempdir} >/dev/null ||
+adb pull /system/lib/bootstrap/libc.so "${TMPDIR}/libc.so" >/dev/null ||
   die "pull libc.so from device"
 garbage="D105225BBFCB1EB8AB8EBDB7094646F0"
-echo "${garbage}" >> ${tempdir}/libc.so
-adb push ${tempdir}/libc.so /system/lib/bootstrap/libc.so >/dev/null ||
+echo "${garbage}" >>"${TMPDIR}/libc.so"
+adb push "${TMPDIR}/libc.so" /system/lib/bootstrap/libc.so >/dev/null ||
   die "push libc.so to device"
-adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice >/dev/null ||
+adb pull /system/lib/bootstrap/libc.so "${TMPDIR}/libc.so.fromdevice" >/dev/null ||
   die "pull libc.so from device"
-diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null ||
+diff "${TMPDIR}/libc.so" "${TMPDIR}/libc.so.fromdevice" > /dev/null ||
   die "libc.so differ"
 
-echo "${GREEN}[ RUN      ]${NORMAL} reboot to confirm content persistent" >&2
+LOG RUN "reboot to confirm content persistent"
 
 fixup_from_recovery() {
   inRecovery || return 1
-  echo "${YELLOW}[    ERROR ]${NORMAL} Device in recovery" >&2
+  LOG ERROR "Device in recovery"
   adb reboot </dev/null
   adb_wait ${ADB_WAIT}
 }
@@ -1323,14 +1328,13 @@
   D=`adb_su df -k </dev/null` &&
     H=`echo "${D}" | head -1` &&
     D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay "` ||
-    ( echo "${L}" && false ) ||
     die -d "overlay takeover failed after reboot"
 
   adb_su sed -n '1,/overlay \/system/p' /proc/mounts </dev/null |
     skip_administrative_mounts |
     grep -v ' \(erofs\|squashfs\|ext4\|f2fs\|vfat\) ' &&
-    echo "${YELLOW}[  WARNING ]${NORMAL} overlay takeover after first stage init" >&2 ||
-    echo "${GREEN}[       OK ]${NORMAL} overlay takeover in first stage init" >&2
+    LOG WARNING "overlay takeover after first stage init" ||
+    LOG OK "overlay takeover in first stage init"
 fi
 
 if ${enforcing}; then
@@ -1338,16 +1342,14 @@
     die "device not in unroot'd state"
   B="`adb_cat /vendor/hello 2>&1`"
   check_eq "cat: /vendor/hello: Permission denied" "${B}" vendor after reboot w/o root
-  echo "${GREEN}[       OK ]${NORMAL} /vendor content correct MAC after reboot" >&2
+  LOG OK "/vendor content correct MAC after reboot"
   # Feed unprivileged log with selinux denials as a result of overlays
   wait_for_screen
   adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
 fi
 # If overlayfs has a nested security problem, this will fail.
-B="`adb_ls /system/`" ||
-  die "adb ls /system"
-[ X"${B}" != X"${B#*priv-app}" ] ||
-  die "adb ls /system/priv-app"
+adb_sh ls /system >/dev/null || die "ls /system"
+adb_test -d /system/priv-app || die "[ -d /system/priv-app ]"
 B="`adb_cat /system/priv-app/hello`"
 check_eq "${A}" "${B}" /system/priv-app after reboot
 # Only root can read vendor if sepolicy permissions are as expected.
@@ -1356,7 +1358,7 @@
 for i in ${MOUNTS}; do
   B="`adb_cat ${i}/hello`"
   check_eq "${A}" "${B}" ${i#/} after reboot
-  echo "${GREEN}[       OK ]${NORMAL} ${i} content remains after reboot" >&2
+  LOG OK "${i} content remains after reboot"
 done
 
 check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
@@ -1367,130 +1369,124 @@
 
 # Check if the updated libc.so is persistent after reboot.
 adb_root &&
-  adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice >/dev/null ||
+  adb pull /system/lib/bootstrap/libc.so "${TMPDIR}/libc.so.fromdevice" >/dev/null ||
   die "pull libc.so from device"
-diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null || die "libc.so differ"
-rm -rf ${tempdir}
-cleanup() {
-  true
-}
-echo "${GREEN}[       OK ]${NORMAL} /system/lib/bootstrap/libc.so content remains after reboot" >&2
+diff "${TMPDIR}/libc.so" "${TMPDIR}/libc.so.fromdevice" > /dev/null || die "libc.so differ"
+LOG OK "/system/lib/bootstrap/libc.so content remains after reboot"
 
-echo "${GREEN}[ RUN      ]${NORMAL} flash vendor, confirm its content disappears" >&2
+################################################################################
+LOG RUN "flash vendor, and confirm vendor override disappears"
 
-H=`adb_sh echo '${HOSTNAME}' </dev/null 2>/dev/null`
-is_bootloader_fastboot=false
+is_bootloader_fastboot=true
 # cuttlefish?
-[ X"${H}" != X"${H#vsoc}" ] || is_bootloader_fastboot=true
+[[ "$(get_property ro.product.device)" == vsoc* ]] &&
+  is_bootloader_fastboot=false
 is_userspace_fastboot=false
 
 if ! ${is_bootloader_fastboot}; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} does not support fastboot, skipping"
-elif [ -z "${ANDROID_PRODUCT_OUT}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} build tree not setup, skipping"
-elif [ ! -s "${ANDROID_PRODUCT_OUT}/vendor.img" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} vendor image missing, skipping"
-elif [ "${ANDROID_PRODUCT_OUT}" = "${ANDROID_PRODUCT_OUT%*/${H}}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} wrong vendor image, skipping"
-elif [ -z "${ANDROID_HOST_OUT}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} please run lunch, skipping"
-elif ! (
-          adb_cat /vendor/build.prop |
-          cmp -s ${ANDROID_PRODUCT_OUT}/vendor/build.prop
-       ) >/dev/null 2>/dev/null; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} vendor image signature mismatch, skipping"
+  LOG WARNING "does not support fastboot flash, skipping"
 else
   wait_for_screen
+  adb_root || die "adb root"
+
+  VENDOR_DEVICE_CANDIDATES=(
+    "/dev/block/mapper/vendor"{_${ACTIVE_SLOT},}
+    "/dev/block/by-name/vendor"{_${ACTIVE_SLOT},}
+  )
+  for b in "${VENDOR_DEVICE_CANDIDATES[@]}"; do
+    if adb_test -e "${b}"; then
+      adb pull "${b}" "${TMPDIR}/vendor.img" || die "adb pull ${b}"
+      LOG INFO "pulled ${b} from device as vendor.img"
+      break
+    fi
+  done
+  [ -f "${TMPDIR}/vendor.img" ] ||
+    die "cannot find block device of vendor partition"
+
   avc_check
   adb reboot fastboot </dev/null ||
     die "fastbootd not supported (wrong adb in path?)"
   any_wait ${ADB_WAIT} &&
     inFastboot ||
     die "reboot into fastboot to flash vendor `usb_status` (bad bootloader?)"
-  fastboot flash vendor ||
+  fastboot flash vendor "${TMPDIR}/vendor.img" ||
     ( fastboot reboot && false) ||
     die "fastboot flash vendor"
+  LOG OK "flashed vendor"
+
   fastboot_getvar is-userspace yes &&
     is_userspace_fastboot=true
-  if [ -n "${scratch_paritition}" ]; then
+  # check ${scratch_partition} via fastboot
+  if [ -n "${scratch_partition}" ]; then
     fastboot_getvar partition-type:${scratch_partition} raw ||
       ( fastboot reboot && false) ||
       die "fastboot can not see ${scratch_partition} parameters"
     if ${uses_dynamic_scratch}; then
-      # check ${scratch_partition} via fastboot
       fastboot_getvar has-slot:${scratch_partition} no &&
         fastboot_getvar is-logical:${scratch_partition} yes ||
         ( fastboot reboot && false) ||
         die "fastboot can not see ${scratch_partition} parameters"
+      LOG INFO "expect fastboot erase ${scratch_partition} to fail"
+      fastboot erase ${scratch_partition} &&
+        ( fastboot reboot || true) &&
+        die "fastboot can erase ${scratch_partition}"
     else
       fastboot_getvar is-logical:${scratch_partition} no ||
         ( fastboot reboot && false) ||
         die "fastboot can not see ${scratch_partition} parameters"
-    fi
-    if ! ${uses_dynamic_scratch}; then
       fastboot reboot-bootloader ||
-        die "Reboot into fastboot"
+        die "fastboot reboot bootloader"
     fi
-    if ${uses_dynamic_scratch}; then
-      echo "${BLUE}[     INFO ]${NORMAL} expect fastboot erase ${scratch_partition} to fail" >&2
-      fastboot erase ${scratch_partition} &&
-        ( fastboot reboot || true) &&
-        die "fastboot can erase ${scratch_partition}"
-    fi
-    echo "${BLUE}[     INFO ]${NORMAL} expect fastboot format ${scratch_partition} to fail" >&2
+    LOG INFO "expect fastboot format ${scratch_partition} to fail"
     fastboot format ${scratch_partition} &&
       ( fastboot reboot || true) &&
       die "fastboot can format ${scratch_partition}"
   fi
-  fastboot reboot ||
-    die "can not reboot out of fastboot"
-  echo "${YELLOW}[  WARNING ]${NORMAL} adb after fastboot"
+
+  fastboot reboot || die "cannot reboot out of fastboot"
+  LOG INFO "reboot from fastboot"
   adb_wait ${ADB_WAIT} ||
     fixup_from_recovery ||
-    die "did not reboot after formatting ${scratch_partition} `usb_status`"
+    die "cannot reboot after flash vendor $(usb_status)"
   if ${overlayfs_needed}; then
     adb_root &&
       D=`adb_sh df -k </dev/null` &&
       H=`echo "${D}" | head -1` &&
       D=`echo "${D}" | skip_unrelated_mounts | grep "^overlay "` &&
-      echo "${H}" &&
-      echo "${D}" &&
+      ( echo "${H}" &&
+        echo "${D}"
+      ) >&2 &&
       echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
       die  "overlay /system takeover after flash vendor"
     echo "${D}" | grep "^overlay .* /vendor\$" >/dev/null &&
       if ${is_userspace_fastboot}; then
         die  "overlay supposed to be minus /vendor takeover after flash vendor"
       else
-        echo "${YELLOW}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
-        echo "${YELLOW}[  WARNING ]${NORMAL} overlay supposed to be minus /vendor takeover after flash vendor" >&2
+        LOG WARNING "fastbootd missing required to invalidate, ignoring a failure"
+        LOG WARNING "overlay supposed to be minus /vendor takeover after flash vendor"
       fi
   fi
-  B="`adb_cat /system/hello`"
-  check_eq "${A}" "${B}" system after flash vendor
-  B="`adb_ls /system/`" ||
-    die "adb ls /system"
-  [ X"${B}" != X"${B#*priv-app}" ] ||
-    die "adb ls /system/priv-app"
-  B="`adb_cat /system/priv-app/hello`"
-  check_eq "${A}" "${B}" system/priv-app after flash vendor
-  adb_root ||
-    die "adb root"
-  B="`adb_cat /vendor/hello`"
-  if ${is_userspace_fastboot} || ! ${overlayfs_needed}; then
-    check_eq "cat: /vendor/hello: No such file or directory" "${B}" \
-             vendor content after flash vendor
-  else
-    echo "${YELLOW}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
-    check_eq "cat: /vendor/hello: No such file or directory" "${B}" \
-             --warning vendor content after flash vendor
+  check_eq "${A}" "$(adb_cat /system/hello)" "/system content after flash vendor"
+  check_eq "${SYSTEM_INO}" "$(adb_sh stat --format=%i /system/hello </dev/null)" "system inode after flash vendor"
+  adb_sh ls /system >/dev/null || die "ls /system"
+  adb_test -d /system/priv-app || die "[ -d /system/priv-app ]"
+  check_eq "${A}" "$(adb_cat /system/priv-app/hello)" "/system/priv-app content after flash vendor"
+  adb_root || die "adb root"
+  if adb_test -e /vendor/hello; then
+    if ${is_userspace_fastboot} || ! ${overlayfs_needed}; then
+      die "vendor content after flash vendor"
+    else
+      LOG WARNING "fastbootd missing required to invalidate, ignoring a failure"
+      LOG WARNING "vendor content after flash vendor"
+    fi
   fi
-
-  check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
-
-fi
+  LOG OK "vendor override destroyed after flash verdor"
+fi >&2
 
 wait_for_screen
-echo "${GREEN}[ RUN      ]${NORMAL} remove test content (cleanup)" >&2
+
+################################################################################
+LOG RUN "remove test content (cleanup)"
 
 T=`adb_date`
 H=`adb remount 2>&1`
@@ -1498,7 +1494,7 @@
 L=
 D="${H%?Now reboot your device for settings to take effect*}"
 if [ X"${H}" != X"${D}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} adb remount requires a reboot after partial flash (legacy avb)"
+  LOG WARNING "adb remount requires a reboot after partial flash (legacy avb)"
   L=`adb_logcat -b all -v nsec -t ${T} 2>&1`
   adb_reboot &&
     adb_wait ${ADB_WAIT} &&
@@ -1508,42 +1504,35 @@
   H=`adb remount 2>&1`
   err=${?}
 fi
-echo "${H}"
+echo "${H}" >&2
 [ ${err} = 0 ] &&
   ( adb_sh rm /vendor/hello </dev/null 2>/dev/null || true ) &&
   adb_sh rm /system/hello /system/priv-app/hello </dev/null ||
-  ( [ -n "${L}" ] && echo "${L}" && false ) ||
+  ( [ -n "${L}" ] && echo "${L}" && false ) >&2 ||
   die -t ${T} "cleanup hello"
-B="`adb_cat /system/hello`"
-check_eq "cat: /system/hello: No such file or directory" "${B}" after rm
-B="`adb_cat /system/priv-app/hello`"
-check_eq "cat: /system/priv-app/hello: No such file or directory" "${B}" after rm
-B="`adb_cat /vendor/hello`"
-check_eq "cat: /vendor/hello: No such file or directory" "${B}" after rm
+adb_test -e /system/hello &&
+  die "/system/hello lingers after rm"
+adb_test -e /system/priv-app/hello &&
+  die "/system/priv-app/hello lingers after rm"
+adb_test -e /vendor/hello &&
+  die "/vendor/hello lingers after rm"
 for i in ${MOUNTS}; do
   adb_sh rm ${i}/hello </dev/null 2>/dev/null || true
 done
 
 if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
 
-  echo "${GREEN}[ RUN      ]${NORMAL} test fastboot flash to ${scratch_partition} recovery" >&2
+  LOG RUN "test fastboot flash to ${scratch_partition} recovery"
 
   avc_check
   adb reboot fastboot </dev/null ||
     die "Reboot into fastbootd"
-  img=${TMPDIR}/adb-remount-test-${$}.img
-  cleanup() {
-    rm ${img}
-  }
+  img="${TMPDIR}/adb-remount-test-${$}.img"
   dd if=/dev/zero of=${img} bs=4096 count=16 2>/dev/null &&
     fastboot_wait ${FASTBOOT_WAIT} ||
     die "reboot into fastboot to flash scratch `usb_status`"
   fastboot flash --force ${scratch_partition} ${img}
   err=${?}
-  cleanup
-  cleanup() {
-    true
-  }
   fastboot reboot ||
     die "can not reboot out of fastboot"
   [ 0 -eq ${err} ] ||
@@ -1556,7 +1545,7 @@
   err=${?}
   if [ X"${D}" != "${D%?Now reboot your device for settings to take effect*}" ]
   then
-    echo "${YELLOW}[  WARNING ]${NORMAL} adb disable-verity requires a reboot after partial flash"
+    LOG WARNING "adb disable-verity requires a reboot after partial flash"
     adb_reboot &&
       adb_wait ${ADB_WAIT} &&
       adb_root ||
@@ -1567,32 +1556,27 @@
     err=${?}
   fi
 
-  echo "${D}"
+  echo "${D}" >&2
   [ ${err} = 0 ] &&
     [ X"${D}" = X"${D##*setup failed}" ] &&
     [ X"${D}" != X"${D##*[Uu]sing overlayfs}" ] &&
-    echo "${GREEN}[       OK ]${NORMAL} ${scratch_partition} recreated" >&2 ||
+    LOG OK "${scratch_partition} recreated" ||
     die -t ${T} "setup for overlayfs"
-  D=`adb remount 2>&1`
-  err=${?}
-  echo "${D}"
-  [ ${err} != 0 ] ||
-    [ X"${D}" = X"${D##*remount failed}" ] ||
-    ( echo "${D}" && false ) ||
+  adb remount >&2 ||
     die -t ${T} "remount failed"
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} test raw remount commands" >&2
+LOG RUN "test raw remount commands"
 
 fixup_from_fastboot() {
   inFastboot || return 1
   if [ -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[    ERROR ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     else
-      echo "${YELLOW}[    ERROR ]${NORMAL} Active slot to be set to ${ACTIVE_SLOT}"
-    fi >&2
+      LOG WARNING "Active slot to be set to ${ACTIVE_SLOT}"
+    fi
     fastboot --set-active=${ACTIVE_SLOT}
   fi
   fastboot reboot
@@ -1610,32 +1594,12 @@
   die "remount command"
 adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null </dev/null ||
   die "/vendor is not read-write"
-echo "${GREEN}[       OK ]${NORMAL} mount -o rw,remount command works" >&2
-
-# Prerequisite is a prepped device from above.
-adb_reboot &&
-  adb_wait ${ADB_WAIT} ||
-  fixup_from_fastboot ||
-  die "lost device after reboot to ro state `usb_status`"
-adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null </dev/null &&
-  die "/vendor is not read-only"
-adb_su remount vendor </dev/null ||
-  die "remount command"
-adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null </dev/null ||
-  die "/vendor is not read-write"
-adb_sh grep " /system .* rw," /proc/mounts >/dev/null </dev/null &&
-  die "/vendor is not read-only"
-echo "${GREEN}[       OK ]${NORMAL} remount command works from setup" >&2
+LOG OK "mount -o rw,remount command works"
 
 # Prerequisite is an overlayfs deconstructed device but with verity disabled.
 # This also saves a lot of 'noise' from the command doing a mkfs on backing
 # storage and all the related tuning and adjustment.
-for d in ${OVERLAYFS_BACKING}; do
-  if adb_test -d /${d}/overlay; then
-    adb_su rm -rf /${d}/overlay </dev/null ||
-      die "/${d}/overlay wipe"
-  fi
-done
+surgically_wipe_overlayfs || true
 adb_reboot &&
   adb_wait ${ADB_WAIT} ||
   fixup_from_fastboot ||
@@ -1644,58 +1608,12 @@
   die "/vendor is not read-only"
 adb_su remount vendor </dev/null ||
   die "remount command"
-adb_su df -k </dev/null | skip_unrelated_mounts
+adb_su df -k </dev/null | skip_unrelated_mounts >&2
 adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null </dev/null ||
   die "/vendor is not read-write"
 adb_sh grep " \(/system\|/\) .* rw," /proc/mounts >/dev/null </dev/null &&
   die "/system is not read-only"
-echo "${GREEN}[       OK ]${NORMAL} remount command works from scratch" >&2
+LOG OK "remount command works from scratch"
 
-if ! restore; then
-  restore() {
-    true
-  }
-  die "failed to restore verity after remount from scratch test"
-fi
 
-err=0
-
-if ${overlayfs_supported}; then
-  echo "${GREEN}[ RUN      ]${NORMAL} test 'adb remount -R'" >&2
-  avc_check
-  adb_root ||
-    die "adb root in preparation for adb remount -R"
-  T=`adb_date`
-  adb remount -R
-  err=${?}
-  if [ "${err}" != 0 ]; then
-    die -t ${T} "adb remount -R = ${err}"
-  fi
-  sleep 2
-  adb_wait ${ADB_WAIT} ||
-    die "waiting for device after adb remount -R `usb_status`"
-  if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
-       "2" = "`get_property partition.system.verified`" ] &&
-     [ -n "`get_property ro.boot.verifiedbootstate`" -o \
-       -n "`get_property partition.system.verified`" ]; then
-    die "remount -R command failed to disable verity
-${INDENT}ro.boot.verifiedbootstate=\"`get_property ro.boot.verifiedbootstate`\"
-${INDENT}partition.system.verified=\"`get_property partition.system.verified`\""
-  fi
-
-  echo "${GREEN}[       OK ]${NORMAL} 'adb remount -R' command" >&2
-
-  restore
-  err=${?}
-fi
-
-restore() {
-  true
-}
-
-[ ${err} = 0 ] ||
-  die "failed to restore verity"
-
-echo "${GREEN}[  PASSED  ]${NORMAL} adb remount" >&2
-
-test_duration
+LOG PASSED "adb remount test"
diff --git a/init/Android.bp b/init/Android.bp
index 856fe3e..dfc90da 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -53,6 +53,7 @@
     "util.cpp",
 ]
 init_device_sources = [
+    "apex_init_util.cpp",
     "block_dev_initializer.cpp",
     "bootchart.cpp",
     "builtins.cpp",
@@ -217,6 +218,7 @@
         "selinux_policy_version",
     ],
     srcs: init_common_sources + init_device_sources,
+    export_include_dirs: ["."],
     generated_sources: [
         "apex-info-list",
     ],
@@ -246,6 +248,10 @@
             ],
         },
     },
+    visibility: [
+        "//system/apex/apexd",
+        "//frameworks/native/cmds/installd",
+    ],
 }
 
 phony {
diff --git a/init/action_manager.h b/init/action_manager.h
index 2746a7c..68912a8 100644
--- a/init/action_manager.h
+++ b/init/action_manager.h
@@ -49,6 +49,7 @@
     bool HasMoreCommands() const;
     void DumpState() const;
     void ClearQueue();
+    auto size() const { return actions_.size(); }
 
   private:
     ActionManager(ActionManager const&) = delete;
diff --git a/init/apex_init_util.cpp b/init/apex_init_util.cpp
new file mode 100644
index 0000000..d618a6e
--- /dev/null
+++ b/init/apex_init_util.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apex_init_util.h"
+
+#include <glob.h>
+
+#include <map>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+
+#include "action_manager.h"
+#include "init.h"
+#include "parser.h"
+#include "service_list.h"
+#include "util.h"
+
+namespace android {
+namespace init {
+
+static Result<std::vector<std::string>> CollectApexConfigs(const std::string& apex_name) {
+    glob_t glob_result;
+    std::string glob_pattern = apex_name.empty() ?
+            "/apex/*/etc/*rc" : "/apex/" + apex_name + "/etc/*rc";
+
+    const int ret = glob(glob_pattern.c_str(), GLOB_MARK, nullptr, &glob_result);
+    if (ret != 0 && ret != GLOB_NOMATCH) {
+        globfree(&glob_result);
+        return Error() << "Glob pattern '" << glob_pattern << "' failed";
+    }
+    std::vector<std::string> configs;
+    for (size_t i = 0; i < glob_result.gl_pathc; i++) {
+        std::string path = glob_result.gl_pathv[i];
+        // Filter-out /apex/<name>@<ver> paths. The paths are bind-mounted to
+        // /apex/<name> paths, so unless we filter them out, we will parse the
+        // same file twice.
+        std::vector<std::string> paths = android::base::Split(path, "/");
+        if (paths.size() >= 3 && paths[2].find('@') != std::string::npos) {
+            continue;
+        }
+        // Filter directories
+        if (path.back() == '/') {
+            continue;
+        }
+        configs.push_back(path);
+    }
+    globfree(&glob_result);
+    return configs;
+}
+
+static Result<void> ParseConfigs(const std::vector<std::string>& configs) {
+    Parser parser = CreateApexConfigParser(ActionManager::GetInstance(),
+                     ServiceList::GetInstance());
+    bool success = true;
+    for (const auto& c : configs) {
+        success &= parser.ParseConfigFile(c);
+    }
+
+    if (success) {
+        return {};
+    } else {
+        return Error() << "Unable to parse apex configs";
+    }
+}
+
+Result<void> ParseApexConfigs(const std::string& apex_name) {
+    auto configs = OR_RETURN(CollectApexConfigs(apex_name));
+
+    if (configs.empty()) {
+        return {};
+    }
+
+    auto filtered_configs = FilterVersionedConfigs(configs,
+                                    android::base::GetIntProperty("ro.build.version.sdk", INT_MAX));
+    return ParseConfigs(filtered_configs);
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/apex_init_util.h b/init/apex_init_util.h
new file mode 100644
index 0000000..43f8ad5
--- /dev/null
+++ b/init/apex_init_util.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+// Parse all config files for a given apex.
+// If apex name is empty(""), config files for all apexes will be parsed.
+Result<void> ParseApexConfigs(const std::string& apex_name);
+
+}  // namespace init
+}  // namespace android
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 38f6f39..c8cb253 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -69,6 +69,7 @@
 #include <system/thread_defs.h>
 
 #include "action_manager.h"
+#include "apex_init_util.h"
 #include "bootchart.h"
 #include "builtin_arguments.h"
 #include "fscrypt_init_extensions.h"
@@ -1279,48 +1280,6 @@
     return GenerateLinkerConfiguration();
 }
 
-static Result<void> parse_apex_configs() {
-    glob_t glob_result;
-    static constexpr char glob_pattern[] = "/apex/*/etc/*rc";
-    const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
-    if (ret != 0 && ret != GLOB_NOMATCH) {
-        globfree(&glob_result);
-        return Error() << "glob pattern '" << glob_pattern << "' failed";
-    }
-    std::vector<std::string> configs;
-    Parser parser =
-            CreateApexConfigParser(ActionManager::GetInstance(), ServiceList::GetInstance());
-    for (size_t i = 0; i < glob_result.gl_pathc; i++) {
-        std::string path = glob_result.gl_pathv[i];
-        // Filter-out /apex/<name>@<ver> paths. The paths are bind-mounted to
-        // /apex/<name> paths, so unless we filter them out, we will parse the
-        // same file twice.
-        std::vector<std::string> paths = android::base::Split(path, "/");
-        if (paths.size() >= 3 && paths[2].find('@') != std::string::npos) {
-            continue;
-        }
-        // Filter directories
-        if (path.back() == '/') {
-            continue;
-        }
-        configs.push_back(path);
-    }
-    globfree(&glob_result);
-
-    int active_sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
-
-    bool success = true;
-    for (const auto& c : parser.FilterVersionedConfigs(configs, active_sdk)) {
-        success &= parser.ParseConfigFile(c);
-    }
-    ServiceList::GetInstance().MarkServicesUpdate();
-    if (success) {
-        return {};
-    } else {
-        return Error() << "Could not parse apex configs";
-    }
-}
-
 /*
  * Creates a directory under /data/misc/apexdata/ for each APEX.
  */
@@ -1351,7 +1310,8 @@
     if (!create_dirs.ok()) {
         return create_dirs.error();
     }
-    auto parse_configs = parse_apex_configs();
+    auto parse_configs = ParseApexConfigs(/*apex_name=*/"");
+    ServiceList::GetInstance().MarkServicesUpdate();
     if (!parse_configs.ok()) {
         return parse_configs.error();
     }
diff --git a/init/init.cpp b/init/init.cpp
index be99a1c..ce668d7 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -63,8 +63,10 @@
 #include <selinux/android.h>
 #include <unwindstack/AndroidUnwinder.h>
 
+#include "action.h"
+#include "action_manager.h"
 #include "action_parser.h"
-#include "builtins.h"
+#include "apex_init_util.h"
 #include "epoll.h"
 #include "first_stage_init.h"
 #include "first_stage_mount.h"
@@ -82,6 +84,7 @@
 #include "selabel.h"
 #include "selinux.h"
 #include "service.h"
+#include "service_list.h"
 #include "service_parser.h"
 #include "sigchld_handler.h"
 #include "snapuserd_transition.h"
@@ -446,11 +449,47 @@
     return {};
 }
 
+int StopServicesFromApex(const std::string& apex_name) {
+    auto services = ServiceList::GetInstance().FindServicesByApexName(apex_name);
+    if (services.empty()) {
+        LOG(INFO) << "No service found for APEX: " << apex_name;
+        return 0;
+    }
+    std::set<std::string> service_names;
+    for (const auto& service : services) {
+        service_names.emplace(service->name());
+    }
+    constexpr std::chrono::milliseconds kServiceStopTimeout = 10s;
+    int still_running = StopServicesAndLogViolations(service_names, kServiceStopTimeout,
+                        true /*SIGTERM*/);
+    // Send SIGKILL to ones that didn't terminate cleanly.
+    if (still_running > 0) {
+        still_running = StopServicesAndLogViolations(service_names, 0ms, false /*SIGKILL*/);
+    }
+    return still_running;
+}
+
+void RemoveServiceAndActionFromApex(const std::string& apex_name) {
+    // Remove services and actions that match apex name
+    ActionManager::GetInstance().RemoveActionIf([&](const std::unique_ptr<Action>& action) -> bool {
+        if (GetApexNameFromFileName(action->filename()) == apex_name) {
+            return true;
+        }
+        return false;
+    });
+    ServiceList::GetInstance().RemoveServiceIf([&](const std::unique_ptr<Service>& s) -> bool {
+        if (GetApexNameFromFileName(s->filename()) == apex_name) {
+            return true;
+        }
+        return false;
+    });
+}
+
 static Result<void> DoUnloadApex(const std::string& apex_name) {
-    std::string prop_name = "init.apex." + apex_name;
-    // TODO(b/232114573) remove services and actions read from the apex
-    // TODO(b/232799709) kill services from the apex
-    SetProperty(prop_name, "unloaded");
+    if (StopServicesFromApex(apex_name) > 0) {
+        return Error() << "Unable to stop all service from " << apex_name;
+    }
+    RemoveServiceAndActionFromApex(apex_name);
     return {};
 }
 
@@ -474,14 +513,14 @@
 }
 
 static Result<void> DoLoadApex(const std::string& apex_name) {
-    std::string prop_name = "init.apex." + apex_name;
-    // TODO(b/232799709) read .rc files from the apex
+    if(auto result = ParseApexConfigs(apex_name); !result.ok()) {
+        return result.error();
+    }
 
     if (auto result = UpdateApexLinkerConfig(apex_name); !result.ok()) {
         return result.error();
     }
 
-    SetProperty(prop_name, "loaded");
     return {};
 }
 
diff --git a/init/init.h b/init/init.h
index 5220535..063632a 100644
--- a/init/init.h
+++ b/init/init.h
@@ -46,5 +46,9 @@
 
 int SecondStageMain(int argc, char** argv);
 
+int StopServicesFromApex(const std::string& apex_name);
+
+void RemoveServiceAndActionFromApex(const std::string& apex_name);
+
 }  // namespace init
 }  // namespace android
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 5651a83..05cf3fd 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -15,11 +15,14 @@
  */
 
 #include <functional>
+#include <string_view>
+#include <type_traits>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <gtest/gtest.h>
+#include <selinux/selinux.h>
 
 #include "action.h"
 #include "action_manager.h"
@@ -27,6 +30,7 @@
 #include "builtin_arguments.h"
 #include "builtins.h"
 #include "import_parser.h"
+#include "init.h"
 #include "keyword_map.h"
 #include "parser.h"
 #include "service.h"
@@ -37,6 +41,7 @@
 using android::base::GetIntProperty;
 using android::base::GetProperty;
 using android::base::SetProperty;
+using android::base::StringReplace;
 using android::base::WaitForProperty;
 using namespace std::literals;
 
@@ -188,6 +193,198 @@
     EXPECT_TRUE(service->is_override());
 }
 
+static std::string GetSecurityContext() {
+    char* ctx;
+    if (getcon(&ctx) == -1) {
+        ADD_FAILURE() << "Failed to call getcon : " << strerror(errno);
+    }
+    std::string result = std::string(ctx);
+    freecon(ctx);
+    return result;
+}
+
+void TestStartApexServices(const std::vector<std::string>& service_names,
+        const std::string& apex_name) {
+    for (auto const& svc : service_names) {
+        auto service = ServiceList::GetInstance().FindService(svc);
+        ASSERT_NE(nullptr, service);
+        ASSERT_RESULT_OK(service->Start());
+        ASSERT_TRUE(service->IsRunning());
+        LOG(INFO) << "Service " << svc << " is running";
+        if (!apex_name.empty()) {
+            service->set_filename("/apex/" + apex_name + "/init_test.rc");
+        } else {
+            service->set_filename("");
+        }
+    }
+    if (!apex_name.empty()) {
+        auto apex_services = ServiceList::GetInstance().FindServicesByApexName(apex_name);
+        EXPECT_EQ(service_names.size(), apex_services.size());
+    }
+}
+
+void TestStopApexServices(const std::vector<std::string>& service_names, bool expect_to_run) {
+    for (auto const& svc : service_names) {
+        auto service = ServiceList::GetInstance().FindService(svc);
+        ASSERT_NE(nullptr, service);
+        EXPECT_EQ(expect_to_run, service->IsRunning());
+    }
+}
+
+void TestRemoveApexService(const std::vector<std::string>& service_names, bool exist) {
+    for (auto const& svc : service_names) {
+        auto service = ServiceList::GetInstance().FindService(svc);
+        ASSERT_EQ(exist, service != nullptr);
+    }
+}
+
+void InitApexService(const std::string_view& init_template) {
+    std::string init_script = StringReplace(init_template, "$selabel",
+                                    GetSecurityContext(), true);
+
+    TestInitText(init_script, BuiltinFunctionMap(), {}, &ActionManager::GetInstance(),
+            &ServiceList::GetInstance());
+}
+
+void TestApexServicesInit(const std::vector<std::string>& apex_services,
+            const std::vector<std::string>& other_apex_services,
+            const std::vector<std::string> non_apex_services) {
+    auto num_svc = apex_services.size() + other_apex_services.size() + non_apex_services.size();
+    ASSERT_EQ(num_svc, ServiceList::GetInstance().size());
+
+    TestStartApexServices(apex_services, "com.android.apex.test_service");
+    TestStartApexServices(other_apex_services, "com.android.other_apex.test_service");
+    TestStartApexServices(non_apex_services, /*apex_anme=*/ "");
+
+    StopServicesFromApex("com.android.apex.test_service");
+    TestStopApexServices(apex_services, /*expect_to_run=*/ false);
+    TestStopApexServices(other_apex_services, /*expect_to_run=*/ true);
+    TestStopApexServices(non_apex_services, /*expect_to_run=*/ true);
+
+    RemoveServiceAndActionFromApex("com.android.apex.test_service");
+    ASSERT_EQ(other_apex_services.size() + non_apex_services.size(),
+        ServiceList::GetInstance().size());
+
+    // TODO(b/244232142): Add test to check if actions are removed
+    TestRemoveApexService(apex_services, /*exist*/ false);
+    TestRemoveApexService(other_apex_services, /*exist*/ true);
+    TestRemoveApexService(non_apex_services, /*exist*/ true);
+
+    ServiceList::GetInstance().RemoveServiceIf([&](const std::unique_ptr<Service>& s) -> bool {
+        return true;
+    });
+
+    ActionManager::GetInstance().RemoveActionIf([&](const std::unique_ptr<Action>& s) -> bool {
+        return true;
+    });
+}
+
+TEST(init, StopServiceByApexName) {
+    std::string_view script_template = R"init(
+service apex_test_service /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(script_template);
+    TestApexServicesInit({"apex_test_service"}, {}, {});
+}
+
+TEST(init, StopMultipleServicesByApexName) {
+    std::string_view script_template = R"init(
+service apex_test_service_multiple_a /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+service apex_test_service_multiple_b /system/bin/id
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(script_template);
+    TestApexServicesInit({"apex_test_service_multiple_a",
+            "apex_test_service_multiple_b"}, {}, {});
+}
+
+TEST(init, StopServicesFromMultipleApexes) {
+    std::string_view apex_script_template = R"init(
+service apex_test_service_multi_apex_a /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+service apex_test_service_multi_apex_b /system/bin/id
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(apex_script_template);
+
+    std::string_view other_apex_script_template = R"init(
+service apex_test_service_multi_apex_c /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(other_apex_script_template);
+
+    TestApexServicesInit({"apex_test_service_multi_apex_a",
+            "apex_test_service_multi_apex_b"}, {"apex_test_service_multi_apex_c"}, {});
+}
+
+TEST(init, StopServicesFromApexAndNonApex) {
+    std::string_view apex_script_template = R"init(
+service apex_test_service_apex_a /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+service apex_test_service_apex_b /system/bin/id
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(apex_script_template);
+
+    std::string_view non_apex_script_template = R"init(
+service apex_test_service_non_apex /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(non_apex_script_template);
+
+    TestApexServicesInit({"apex_test_service_apex_a",
+            "apex_test_service_apex_b"}, {}, {"apex_test_service_non_apex"});
+}
+
+TEST(init, StopServicesFromApexMixed) {
+    std::string_view script_template = R"init(
+service apex_test_service_mixed_a /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(script_template);
+
+    std::string_view other_apex_script_template = R"init(
+service apex_test_service_mixed_b /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(other_apex_script_template);
+
+    std::string_view non_apex_script_template = R"init(
+service apex_test_service_mixed_c /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+    InitApexService(non_apex_script_template);
+
+    TestApexServicesInit({"apex_test_service_mixed_a"},
+            {"apex_test_service_mixed_b"}, {"apex_test_service_mixed_c"});
+}
+
 TEST(init, EventTriggerOrderMultipleFiles) {
     // 6 total files, which should have their triggers executed in the following order:
     // 1: start - original script parsed
@@ -338,20 +535,6 @@
     EXPECT_EQ(2, num_executed);
 }
 
-TEST(init, RespondToCtlApexMessages) {
-    if (getuid() != 0) {
-        GTEST_SKIP() << "Skipping test, must be run as root.";
-        return;
-    }
-
-    std::string apex_name = "com.android.apex.cts.shim";
-    SetProperty("ctl.apex_unload", apex_name);
-    EXPECT_TRUE(WaitForProperty("init.apex." + apex_name, "unloaded", 10s));
-
-    SetProperty("ctl.apex_load", apex_name);
-    EXPECT_TRUE(WaitForProperty("init.apex." + apex_name, "loaded", 10s));
-}
-
 TEST(init, RejectsCriticalAndOneshotService) {
     if (GetIntProperty("ro.product.first_api_level", 10000) < 30) {
         GTEST_SKIP() << "Test only valid for devices launching with R or later";
diff --git a/init/parser.cpp b/init/parser.cpp
index abc2017..0a388db 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -156,58 +156,6 @@
     return true;
 }
 
-std::vector<std::string> Parser::FilterVersionedConfigs(const std::vector<std::string>& configs,
-                                                        int active_sdk) {
-    std::vector<std::string> filtered_configs;
-
-    std::map<std::string, std::pair<std::string, int>> script_map;
-    for (const auto& c : configs) {
-        int sdk = 0;
-        const std::vector<std::string> parts = android::base::Split(c, ".");
-        std::string base;
-        if (parts.size() < 2) {
-            continue;
-        }
-
-        // parts[size()-1], aka the suffix, should be "rc" or "#rc"
-        // any other pattern gets discarded
-
-        const auto& suffix = parts[parts.size() - 1];
-        if (suffix == "rc") {
-            sdk = 0;
-        } else {
-            char trailer[9] = {0};
-            int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
-            if (r != 2) {
-                continue;
-            }
-            if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
-                continue;
-            }
-        }
-
-        if (sdk < 0 || sdk > active_sdk) {
-            continue;
-        }
-
-        base = parts[0];
-        for (unsigned int i = 1; i < parts.size() - 1; i++) {
-            base = base + "." + parts[i];
-        }
-
-        // is this preferred over what we already have
-        auto it = script_map.find(base);
-        if (it == script_map.end() || it->second.second < sdk) {
-            script_map[base] = std::make_pair(c, sdk);
-        }
-    }
-
-    for (const auto& m : script_map) {
-        filtered_configs.push_back(m.second.first);
-    }
-    return filtered_configs;
-}
-
 bool Parser::ParseConfigDir(const std::string& path) {
     LOG(INFO) << "Parsing directory " << path << "...";
     std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
diff --git a/init/parser.h b/init/parser.h
index 2f4108f..95b0cd7 100644
--- a/init/parser.h
+++ b/init/parser.h
@@ -76,12 +76,6 @@
     void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
     void AddSingleLineParser(const std::string& prefix, LineCallback callback);
 
-    // Compare all files */path.#rc and */path.rc with the same path prefix.
-    // Keep the one with the highest # that doesn't exceed the system's SDK.
-    // (.rc == .0rc for ranking purposes)
-    std::vector<std::string> FilterVersionedConfigs(const std::vector<std::string>& configs,
-                                                    int active_sdk);
-
     // Host init verifier check file permissions.
     bool ParseConfigFileInsecure(const std::string& path);
 
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 4e4bfd8..880674c 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -491,7 +491,7 @@
         return ErrnoError() << "zram_backing_dev: swapoff (" << backing_dev << ")"
                             << " failed";
     }
-    LOG(INFO) << "swapoff() took " << swap_timer;;
+    LOG(INFO) << "swapoff() took " << swap_timer;
 
     if (!WriteStringToFile("1", ZRAM_RESET)) {
         return Error() << "zram_backing_dev: reset (" << backing_dev << ")"
diff --git a/init/service.h b/init/service.h
index c14b312..6d9a0ca 100644
--- a/init/service.h
+++ b/init/service.h
@@ -143,6 +143,8 @@
         }
     }
     Subcontext* subcontext() const { return subcontext_; }
+    const std::string& filename() const { return filename_; }
+    void set_filename(const std::string& name) { filename_ = name; }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
diff --git a/init/service_list.h b/init/service_list.h
index 555da25..f858bc3 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -16,10 +16,14 @@
 
 #pragma once
 
+#include <iterator>
 #include <memory>
 #include <vector>
 
+#include <android-base/logging.h>
+
 #include "service.h"
+#include "util.h"
 
 namespace android {
 namespace init {
@@ -52,6 +56,17 @@
         return nullptr;
     }
 
+    std::vector<Service*> FindServicesByApexName(const std::string& apex_name) const {
+        CHECK(!apex_name.empty()) << "APEX name cannot be empty";
+        std::vector<Service*> matches;
+        for (const auto& svc : services_) {
+            if (GetApexNameFromFileName(svc->filename()) == apex_name) {
+                matches.emplace_back(svc.get());
+            }
+        }
+        return matches;
+    }
+
     Service* FindInterface(const std::string& interface_name) {
         for (const auto& svc : services_) {
             if (svc->interfaces().count(interface_name) > 0) {
@@ -79,6 +94,8 @@
         services_update_finished_ = false;
     }
 
+    auto size() const { return services_.size(); }
+
   private:
     std::vector<std::unique_ptr<Service>> services_;
 
diff --git a/init/util.cpp b/init/util.cpp
index bfc3fb6..2d40142 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -30,6 +30,7 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <map>
 #include <thread>
 
 #include <android-base/file.h>
@@ -748,5 +749,57 @@
     return "";
 }
 
+std::vector<std::string> FilterVersionedConfigs(const std::vector<std::string>& configs,
+                                                int active_sdk) {
+    std::vector<std::string> filtered_configs;
+
+    std::map<std::string, std::pair<std::string, int>> script_map;
+    for (const auto& c : configs) {
+        int sdk = 0;
+        const std::vector<std::string> parts = android::base::Split(c, ".");
+        std::string base;
+        if (parts.size() < 2) {
+            continue;
+        }
+
+        // parts[size()-1], aka the suffix, should be "rc" or "#rc"
+        // any other pattern gets discarded
+
+        const auto& suffix = parts[parts.size() - 1];
+        if (suffix == "rc") {
+            sdk = 0;
+        } else {
+            char trailer[9] = {0};
+            int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
+            if (r != 2) {
+                continue;
+            }
+            if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
+                continue;
+            }
+        }
+
+        if (sdk < 0 || sdk > active_sdk) {
+            continue;
+        }
+
+        base = parts[0];
+        for (unsigned int i = 1; i < parts.size() - 1; i++) {
+            base = base + "." + parts[i];
+        }
+
+        // is this preferred over what we already have
+        auto it = script_map.find(base);
+        if (it == script_map.end() || it->second.second < sdk) {
+            script_map[base] = std::make_pair(c, sdk);
+        }
+    }
+
+    for (const auto& m : script_map) {
+        filtered_configs.push_back(m.second.first);
+    }
+    return filtered_configs;
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/util.h b/init/util.h
index daec470..0181bf0 100644
--- a/init/util.h
+++ b/init/util.h
@@ -109,5 +109,11 @@
 bool Has32BitAbi();
 
 std::string GetApexNameFromFileName(const std::string& path);
+
+// Compare all files */path.#rc and */path.rc with the same path prefix.
+// Keep the one with the highest # that doesn't exceed the system's SDK.
+// (.rc == .0rc for ranking purposes)
+std::vector<std::string> FilterVersionedConfigs(const std::vector<std::string>& configs,
+                                                  int active_sdk);
 }  // namespace init
 }  // namespace android
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index bdb8075..da5005c 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -41,9 +41,11 @@
  */
 
 #define AID_ROOT 0 /* traditional unix root user */
-/* The following are for LTP and should only be used for testing */
-#define AID_DAEMON 1 /* traditional unix daemon owner */
-#define AID_BIN 2    /* traditional unix binaries owner */
+
+/* The following are for tests like LTP and should only be used for testing. */
+#define AID_DAEMON 1 /* Traditional unix daemon owner. */
+#define AID_BIN 2    /* Traditional unix binaries owner. */
+#define AID_SYS 3    /* A group with the same gid on Linux/macOS/Android. */
 
 #define AID_SYSTEM 1000 /* system server */