init: Add features to preserve paths on clobbering stateful

Clobbering stateful wipes the stateful partition on reboot, this
provides a way to preserve paths from being wiped.

BUG=b:170883046
TEST=# Verify paths are preserved after clobbering stateful

Change-Id: I3be23efaa6cc340243cc1a6aed37d014dca4b96d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2485908
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Commit-Queue: Jae Hoon Kim <kimjae@chromium.org>
diff --git a/init/chromeos_shutdown b/init/chromeos_shutdown
index 2ff4c54..31d7114 100755
--- a/init/chromeos_shutdown
+++ b/init/chromeos_shutdown
@@ -30,6 +30,7 @@
 CROS_DEBUG="$((! $?))"
 
 dev_unmount_packages() { true; }
+dev_push_paths_to_preserve() { true; }
 
 collect_shutdown_umount_failure_logs() {
   (
@@ -79,7 +80,8 @@
   . /usr/share/cros/dev_utils.sh
 fi
 
-STATEFUL_UPDATE="/mnt/stateful_partition/.update_available"
+STATEFUL_PARTITION="/mnt/stateful_partition"
+STATEFUL_UPDATE="${STATEFUL_PARTITION}/.update_available"
 
 # target_version should only be created for test lab DUTs.
 TARGET_VERSION="/run/update_target_version"
@@ -92,12 +94,12 @@
   if [ -r "${TARGET_VERSION}" ] && [ ! -L "${TARGET_VERSION}" ]; then
     # Used later to clear Quota parameters from stateful.
     UPDATE_TARGET="$(cut -d '.' -f 1 "${TARGET_VERSION}")"
-    STATE_DEV="$(findmnt -n -o SOURCE -M /mnt/stateful_partition)"
+    STATE_DEV="$(findmnt -n -o SOURCE -M ${STATEFUL_PARTITION})"
     rm -f "${TARGET_VERSION}"
   fi
 
   if [ "${STATEFUL_UPDATE_ARGS}" = "clobber" ]; then
-    PRESERVE_DIR="/mnt/stateful_partition/unencrypted/preserve"
+    PRESERVE_DIR="${STATEFUL_PARTITION}/unencrypted/preserve"
 
     # Measure shutdown time up to this point.
     bootstat before_preserve
@@ -106,6 +108,7 @@
     rm -rf "${PRESERVE_DIR}/log"
     mkdir -p -m 0755 "${PRESERVE_DIR}"
     cp -a "${MNTS}/var/log" "${PRESERVE_DIR}"
+    dev_push_paths_to_preserve
 
     # We are about to put this into a directory that will shortly be wiped
     # out. Keep a timestamp where it will be preserved as well.
@@ -162,7 +165,7 @@
 
 # Unmount /mnt/stateful_partition only if the previous unmounts succeeded.
 if [ "${rc}" -eq 0 ]; then
-  umount -n /mnt/stateful_partition
+  umount -n "${STATEFUL_PARTITION}"
 fi
 exit "$(( rc | $? ))"
 ) >/run/mount_encrypted/umount-encrypted.log 2>&1
@@ -175,8 +178,8 @@
     --mount_device="stateful"
   crash_reporter --early --log_to_stderr --preserve_across_clobber \
     --ephemeral_collect
-  mv /run/mount_encrypted/umount-encrypted.log /mnt/stateful_partition/
-  mv /run/shutdown_umount_failure.log /mnt/stateful_partition/
+  mv /run/mount_encrypted/umount-encrypted.log "${STATEFUL_PARTITION}/"
+  mv /run/shutdown_umount_failure.log "${STATEFUL_PARTITION}/"
 else
   if [ -n "${UPDATE_TARGET}" ] && [ -n "${STATE_DEV}" ]; then
     # 10756.0.0 is the first build to turn on ext4 quota.
diff --git a/init/chromeos_startup b/init/chromeos_startup
index ebca67f..76857a1 100755
--- a/init/chromeos_startup
+++ b/init/chromeos_startup
@@ -165,6 +165,7 @@
 dev_gather_logs() { true; }
 dev_mount_packages() { true; }
 dev_is_debug_build() { false; }
+dev_pop_paths_to_preserve() { true; }
 
 # do_* are wrapper functions that may be redefined in developer mode or test
 # images. Find more implementation in {dev,test,factory}_utils.sh.
@@ -692,6 +693,7 @@
 
 # Mount dev packages.
 dev_mount_packages
+dev_pop_paths_to_preserve
 
 if [ "${DISABLE_STATEFUL_SECURITY_HARDENING}" = "false" ]; then
   # Unmount securityfs so that further modifications to inode security policies
diff --git a/init/dev_utils.sh b/init/dev_utils.sh
index 52a6341..07aac7c 100644
--- a/init/dev_utils.sh
+++ b/init/dev_utils.sh
@@ -7,6 +7,14 @@
 
 STATEFUL_PARTITION="/mnt/stateful_partition"
 
+PRESERVE_DIR="${STATEFUL_PARTITION}/unencrypted/preserve"
+
+# These paths will be preserved through clobbering.
+PATHS_TO_PRESERVE=""
+PATHS_TO_PRESERVE="${PATHS_TO_PRESERVE} /var/lib/servod"
+PATHS_TO_PRESERVE="${PATHS_TO_PRESERVE} /usr/local/servod"
+PATHS_TO_PRESERVE="${PATHS_TO_PRESERVE} /var/lib/device_health_profile"
+
 # Returns if we are running on a debug build.
 dev_is_debug_build() {
   crossystem 'debug_build?1'
@@ -93,7 +101,6 @@
 
   # Check for clobber.
   if [ "${stateful_update_args}" = "clobber" ]; then
-    local preserve_dir="${STATEFUL_PARTITION}/unencrypted/preserve"
 
     # Find everything in stateful and delete it, except for protected paths, and
     # non-empty directories. The non-empty directories contain protected content
@@ -103,13 +110,13 @@
         -not -path "${STATEFUL_PARTITION}/.labmachine" \
         -not -path "${developer_target}/*" \
         -not -path "${var_target}/*" \
-        -not -path "${preserve_dir}/*" \
+        -not -path "${PRESERVE_DIR}/*" \
         -not -type d -print0 | xargs -0 -r rm -f
 
     find "${STATEFUL_PARTITION}" -depth -mindepth 1 \
         -not -path "${developer_target}/*" \
         -not -path "${var_target}/*" \
-        -not -path "${preserve_dir}/*" \
+        -not -path "${PRESERVE_DIR}/*" \
         -type d -print0 | xargs -0 -r rmdir --ignore-fail-on-non-empty
 
     # Let's really be done before coming back.
@@ -227,6 +234,34 @@
   umount -n /usr/local
 }
 
+# Copy contents in src path to dst path if it exists.
+copy_path() {
+  local src_path="$1"
+  local dst_path="$2"
+  if [ -d "${src_path}" ]; then
+    mkdir -p "${dst_path}"
+    cp -a "${src_path}/"* "${dst_path}"
+  fi
+}
+
+# Pushes the array of paths to preserve to protected path.
+dev_push_paths_to_preserve() {
+  local path
+  for path in ${PATHS_TO_PRESERVE}; do
+    copy_path "${path}" "${PRESERVE_DIR}/${path}"
+  done
+}
+
+# Pops the array of paths to preserve from protected path.
+dev_pop_paths_to_preserve() {
+  local path
+  for path in ${PATHS_TO_PRESERVE}; do
+    local src_path="${PRESERVE_DIR}/${path}"
+    copy_path "${src_path}" "${path}"
+    rm -rf "${src_path}"
+  done
+}
+
 # Load more utilities on test image.
 if [ -f /usr/share/cros/test_utils.sh ]; then
   . /usr/share/cros/test_utils.sh