1#!/system/bin/sh 2 3# 4# Copyright (C) 2016 The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19# This script runs as a postinstall step to drive otapreopt. It comes with the 20# OTA package, but runs /system/bin/otapreopt_chroot in the (old) active system 21# image. See system/extras/postinst/postinst.sh for some docs. 22 23TARGET_SLOT="$1" 24STATUS_FD="$2" 25 26# "1" if the script is triggered by the `UpdateEngine.triggerPostinstall` API. Empty otherwise. 27TRIGGERED_BY_API="$3" 28 29# Maximum number of packages/steps. 30MAXIMUM_PACKAGES=1000 31 32# First ensure the system is booted. This is to work around issues when cmd would 33# infinitely loop trying to get a service manager (which will never come up in that 34# mode). b/30797145 35BOOT_PROPERTY_NAME="dev.bootcomplete" 36 37BOOT_COMPLETE=$(getprop $BOOT_PROPERTY_NAME) 38if [ "$BOOT_COMPLETE" != "1" ] ; then 39 echo "$0: Error: boot-complete not detected." 40 # We must return 0 to not block sideload. 41 exit 0 42fi 43 44# Compute target slot suffix. 45# TODO: Once bootctl is not restricted, we should query from there. Or get this from 46# update_engine as a parameter. 47if [ "$TARGET_SLOT" = "0" ] ; then 48 TARGET_SLOT_SUFFIX="_a" 49elif [ "$TARGET_SLOT" = "1" ] ; then 50 TARGET_SLOT_SUFFIX="_b" 51else 52 echo "$0: Unknown target slot $TARGET_SLOT" 53 exit 1 54fi 55 56# A source that infinitely emits arbitrary lines. 57# When connected to STDIN of another process, this source keeps STDIN open until 58# the consumer process closes STDIN or this script dies. 59# In practice, the pm command keeps consuming STDIN, so we don't need to worry 60# about running out of buffer space. 61function infinite_source { 62 while echo .; do 63 sleep 1 64 done 65} 66 67if [[ "$TRIGGERED_BY_API" = "1" ]]; then 68 # During OTA installation, the script is called the first time, and 69 # `TRIGGERED_BY_API` can never be "1". `TRIGGERED_BY_API` being "1" means this 70 # is the second call to this script, through the 71 # `UpdateEngine.triggerPostinstall` API. 72 # When we reach here, it means Pre-reboot Dexopt is enabled in asynchronous 73 # mode and the job scheduler determined that it's the time to run the job. 74 # Start Pre-reboot Dexopt now and wait for it to finish. 75 infinite_source | pm art on-ota-staged --start 76 exit $? 77fi 78 79PR_DEXOPT_JOB_VERSION="$(pm art pr-dexopt-job --version)" 80if (( $? == 0 )) && (( $PR_DEXOPT_JOB_VERSION >= 3 )); then 81 # Delegate to Pre-reboot Dexopt, a feature of ART Service. 82 # ART Service decides what to do with this request: 83 # - If Pre-reboot Dexopt is disabled or unsupported, the command returns 84 # non-zero. 85 # This is always the case if the current system is Android 14 or earlier. 86 # - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks 87 # until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds 88 # or not. 89 # This is the default behavior if the current system is Android 15. 90 # - If Pre-reboot Dexopt is enabled in asynchronous mode, the command 91 # schedules an asynchronous job and returns 0 immediately. 92 # Later, when the device is idle and charging, the job will be run by the 93 # job scheduler. It will call this script again through the 94 # `UpdateEngine.triggerPostinstall` API, with `TRIGGERED_BY_API` being "1". 95 # This is always the case if the current system is Android 16 or later. 96 if infinite_source | pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then 97 # Handled by Pre-reboot Dexopt. 98 exit 0 99 fi 100 echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt." 101else 102 echo "Pre-reboot Dexopt is too old. Fall back to otapreopt." 103fi 104 105if [ "$(/system/bin/otapreopt_chroot --version)" != 2 ]; then 106 # We require an updated chroot wrapper that reads dexopt commands from stdin. 107 # Even if we kept compat with the old binary, the OTA preopt wouldn't work due 108 # to missing sepolicy rules, so there's no use spending time trying to dexopt 109 # (b/291974157). 110 echo "$0: Current system image is too old to work with OTA preopt - skipping." 111 exit 0 112fi 113 114PREPARE=$(cmd otadexopt prepare) 115# Note: Ignore preparation failures. Step and done will fail and exit this. 116# This is necessary to support suspends - the OTA service will keep 117# the state around for us. 118 119# Create an array with all dexopt commands in advance, to know how many there are. 120otadexopt_cmds=() 121while (( ${#otadexopt_cmds[@]} < MAXIMUM_PACKAGES )) ; do 122 DONE=$(cmd otadexopt done) 123 if [ "$DONE" = "OTA complete." ] ; then 124 break 125 fi 126 otadexopt_cmds+=("$(cmd otadexopt next)") 127done 128 129DONE=$(cmd otadexopt done) 130cmd otadexopt cleanup 131 132echo "$0: Using streaming otapreopt_chroot on ${#otadexopt_cmds[@]} packages" 133 134function print_otadexopt_cmds { 135 for cmd in "${otadexopt_cmds[@]}" ; do 136 print "$cmd" 137 done 138} 139 140function report_progress { 141 while read count ; do 142 # mksh can't do floating point arithmetic, so emulate a fixed point calculation. 143 (( permilles = 1000 * count / ${#otadexopt_cmds[@]} )) 144 printf 'global_progress %d.%03d\n' $((permilles / 1000)) $((permilles % 1000)) >&${STATUS_FD} 145 done 146} 147 148print_otadexopt_cmds | \ 149 /system/bin/otapreopt_chroot $STATUS_FD $TARGET_SLOT_SUFFIX | \ 150 report_progress 151 152if [ "$DONE" = "OTA incomplete." ] ; then 153 echo "$0: Incomplete." 154else 155 echo "$0: Complete or error." 156fi 157 158print -u${STATUS_FD} "global_progress 1.0" 159 160exit 0 161