#!/bin/sh
#
# Licensed under the GPLv2
#
# Copyright 2011, Red Hat, Inc.
# Harald Hoyer <harald@redhat.com>
ACTION="$1"

# Before trying to use /dev/console, verify that it exists,
# and that it can actually be used. When console=null is used,
# echo will fail. We do the check in a subshell, because otherwise
# the process will be killed when when running as PID 1.
# shellcheck disable=SC2217
[ -w /dev/console ] \
    && (echo < /dev/console > /dev/null 2> /dev/null) \
    && exec < /dev/console >> /dev/console 2>> /dev/console

export TERM=linux
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
. /lib/dracut-lib.sh

if [ "$(stat -c '%T' -f /)" = "tmpfs" ]; then
    mount -o remount,rw /
fi

mkdir -p /oldsys
for i in sys proc run dev; do
    mkdir -p /oldsys/$i
    mount --move /oldroot/$i /oldsys/$i
done

# if "kexec" was installed after creating the initramfs, we try to copy it from the real root
# libz normally is pulled in via kmod/modprobe and udevadm
if [ "$ACTION" = "kexec" ] && ! command -v kexec > /dev/null 2>&1; then
    for p in /usr/sbin /usr/bin /sbin /bin; do
        cp -a /oldroot/${p}/kexec $p > /dev/null 2>&1 && break
    done
    hash kexec
fi

trap "emergency_shell --shutdown shutdown Signal caught!" 0
getarg 'rd.break=pre-shutdown' && emergency_shell --shutdown pre-shutdown "Break before pre-shutdown"

source_hook pre-shutdown

warn "Killing all remaining processes"

killall_proc_mountpoint /oldroot || sleep 0.2

# Timeout for umount calls. The value can be set to 0 to wait forever.
_umount_timeout=$(getarg rd.shutdown.timeout.umount)
_umount_timeout=${_umount_timeout:-90s}
_timed_out_umounts=""

umount_a() {
    local _verbose="n"
    if [ "$1" = "-v" ]; then
        _verbose="y"
        shift
        exec 7>&2
    else
        exec 7> /dev/null
    fi

    local _did_umount="n"
    while read -r _ mp _ || [ -n "$mp" ]; do
        strstr "$mp" oldroot || continue
        strstr "$_timed_out_umounts" " $mp " && continue

        # Unmount the file system. The operation uses a timeout to avoid waiting
        # indefinitely if this is e.g. a stuck NFS mount. The command is
        # invoked in a subshell to silence also the "Killed" message that might
        # be produced by the shell.
        (
            set +m
            timeout --signal=KILL "$_umount_timeout" umount "$mp"
        ) 2>&7
        local ret=$?
        if [ $ret -eq 0 ]; then
            _did_umount="y"
            warn "Unmounted $mp."
        elif [ $ret -eq 137 ]; then
            _timed_out_umounts="$_timed_out_umounts $mp "
            warn "Unmounting $mp timed out."
        elif [ "$_verbose" = "y" ]; then
            warn "Unmounting $mp failed with status $ret."
        fi
    done < /proc/mounts

    losetup -D 2>&7

    exec 7>&-
    [ "$_did_umount" = "y" ] && return 0
    return 1
}

_cnt=0
while [ $_cnt -le 40 ]; do
    umount_a || break
    _cnt=$((_cnt + 1))
done

[ $_cnt -ge 40 ] && umount_a -v

if strstr "$(cat /proc/mounts)" "/oldroot"; then
    warn "Cannot umount /oldroot"
    for _pid in /proc/*; do
        _pid=${_pid##/proc/}
        case $_pid in
            *[!0-9]*) continue ;;
        esac
        [ "$_pid" -eq $$ ] && continue

        [ -e "/proc/$_pid/exe" ] || continue
        [ -e "/proc/$_pid/root" ] || continue

        if strstr "$(ls -l /proc/"$_pid" /proc/"$_pid"/fd 2> /dev/null)" "oldroot"; then
            warn "Blocking umount of /oldroot [$_pid] $(cat /proc/"$_pid"/cmdline)"
        else
            warn "Still running [$_pid] $(cat /proc/"$_pid"/cmdline)"
        fi

        # shellcheck disable=SC2012
        ls -l "/proc/$_pid/exe" 2>&1 | vwarn
        # shellcheck disable=SC2012
        ls -l "/proc/$_pid/fd" 2>&1 | vwarn
    done
fi

_check_shutdown() {
    local __f
    local __s=0
    for __f in "$hookdir"/shutdown/*.sh; do
        [ -e "$__f" ] || continue
        # shellcheck disable=SC1090 disable=SC2240
        if (final="$1" . "$__f" "$1"); then
            rm -f -- "$__f"
        else
            __s=1
        fi
    done
    return $__s
}

_cnt=0
while [ $_cnt -le 40 ]; do
    _check_shutdown && break
    _cnt=$((_cnt + 1))
done
[ $_cnt -ge 40 ] && _check_shutdown final

getarg 'rd.break=shutdown' && emergency_shell --shutdown shutdown "Break before shutdown"

case "$ACTION" in
    reboot | poweroff | halt)
        $ACTION -f -n
        warn "$ACTION failed!"
        ;;
    kexec)
        kexec -e
        warn "$ACTION failed!"
        reboot -f -n
        ;;
    *)
        warn "Shutdown called with argument '$ACTION'. Rebooting!"
        reboot -f -n
        ;;
esac

emergency_shell --shutdown shutdown
