#!/bin/sh
#
# Keeps the Foghorn APK in App Support in line with the Android apps setting,
# which foghornd signals by keeping the key file only while it is on. Runs as
# root.
#
#   foghorn-appsupport run USER     installs the APK while the setting is on and
#                                   keeps its service running, removes it once
#                                   the setting is off (foghorn-appsupport@.service)
#   foghorn-appsupport sync USER    removes the APK if the setting is off, starting
#                                   App Support for it if needed
#                                   (foghorn-appsupport-sync@.service)
#   foghorn-appsupport remove USER  the same regardless of the setting, for
#                                   uninstalling Foghorn
#
# FOGHORN_SHARE overrides where the APK and foghorn.sh are read from.
set -u

command="$1"
user="$2"
share="${FOGHORN_SHARE:-/usr/share/foghorn/appsupport}"
home="$(getent passwd "$user" | cut -d: -f6)"
key="$home/.local/share/foghorn/appsupport.key"
state=/var/lib/foghorn
marker="$state/appsupport-$user"
lock="/run/foghorn-appsupport-$user.lock"
status="/run/foghorn/appsupport-$user.status"
apk=/data/local/tmp/foghorn-appsupport.apk
script=/data/local/tmp/foghorn.sh

booted() {
    [ "$(appsupport-attach getprop sys.boot_completed 2>/dev/null | tr -d '\r\n')" = 1 ]
}

# Read as the user, so a symlink to a file only root can read gets nothing.
# Prints the key only if it looks like one: 32 bytes of base64.
read_key() {
    runuser -u "$user" -- head -n 1 "$key" 2>/dev/null \
        | grep -Ex '[A-Za-z0-9+/]{43}='
}

copy_script() {
    appsupport-attach sh -c "cat > $script" < "$share/foghorn.sh"
}

report_failure() {
    reason="$(printf '%s\n' "$1" | grep -o 'INSTALL_FAILED_[A-Z_]*' | head -n 1)"
    case "$reason" in
    INSTALL_FAILED_OLDER_SDK) message="Foghorn needs Android 11 or later in AppSupport." ;;
    "") message="Could not install Foghorn in Android." ;;
    *) message="Could not install Foghorn in Android ($reason)." ;;
    esac
    mkdir -p "$(dirname "$status")"
    chmod 755 "$(dirname "$status")"
    printf '%s\n' "$message" > "$status"
    chmod 644 "$status"
}

install_apk() {
    appsupport-attach sh -c "cat > $apk && chmod 644 $apk" < "$share/foghorn-appsupport.apk" \
        && copy_script \
        || return 1
    if ! output="$(appsupport-attach sh "$script" install "$apk" 2>&1)"; then
        echo "could not install the Foghorn APK: $output" >&2
        report_failure "$output"
        return 1
    fi
    rm -f "$status"
    mkdir -p "$state" && touch "$marker"
}

remove_apk() {
    copy_script && appsupport-attach sh "$script" remove
    rm -f "$marker" "$status"
}

# Removes the APK if it was installed, starting App Support for it if needed and
# stopping it again afterwards.
remove_through_app_support() {
    exec 9> "$lock"
    flock 9
    [ -e "$marker" ] || return 0

    unit="appsupport@$user.service"
    started=
    if ! systemctl -q is-active "$unit"; then
        echo "starting App Support to remove the Foghorn APK"
        systemctl start --no-block "$unit" || return 0
        started=1
    fi
    tries=60
    until booted; do
        tries=$((tries - 1))
        [ "$tries" -gt 0 ] || break
        sleep 2
    done
    if booted; then
        remove_apk
    else
        echo "Android did not finish starting; the Foghorn APK is still installed" >&2
    fi
    if [ -n "$started" ]; then
        systemctl stop "$unit"
    fi
}

run() {
    until booted; do
        sleep 2
    done

    # Covers APKs installed before the marker existed.
    if [ ! -e "$key" ]; then
        remove_apk > /dev/null 2>&1
    fi

    installed=
    until_check=0
    retry_at=0
    delay=30
    while :; do
        if [ -e "$key" ]; then
            if [ -z "$installed" ] && [ "$(date +%s)" -ge "$retry_at" ]; then
                if ( flock 9 && install_apk ) 9> "$lock"; then
                    installed=1
                    until_check=0
                    delay=30
                else
                    retry_at=$(($(date +%s) + delay))
                    delay=$((delay * 2))
                    if [ "$delay" -gt 1800 ]; then
                        delay=1800
                    fi
                fi
            fi
            if [ -n "$installed" ]; then
                if [ "$until_check" -le 0 ]; then
                    if ! appsupport-attach sh "$script" running; then
                        echo "starting the bridge service"
                        if value="$(read_key)"; then
                            printf '%s\n' "$value" | appsupport-attach sh "$script" start \
                                || echo "could not start the bridge service" >&2
                        else
                            echo "$key is not a key Foghorn wrote; not starting the bridge" >&2
                        fi
                    fi
                    until_check=12
                fi
                until_check=$((until_check - 1))
            fi
        else
            installed=
            retry_at=0
            delay=30
            rm -f "$status"
            if [ -e "$marker" ]; then
                echo "Android apps are off; removing the Foghorn APK"
                ( flock 9 && [ -e "$marker" ] && remove_apk ) 9> "$lock"
            fi
        fi
        sleep 5
    done
}

case "$command" in
run) run ;;
sync) [ -e "$key" ] || remove_through_app_support ;;
remove) remove_through_app_support ;;
*) echo "unknown command: $command" >&2; exit 2 ;;
esac
