#!/bin/bash
#
# License: Copyright 2015 SpinetiX. This file is licensed
#          under the terms of the GNU General Public License version 2.
#          This program is licensed "as is" without any warranty of any
#          kind, whether express or implied.
#
# Copyright 1999-2003 MontaVista Software, Inc.
# Copyright 2002, 2003, 2004 Sony Corporation
# Copyright 2002, 2003, 2004 Matsushita Electric Industrial Co., Ltd.
#
### BEGIN INIT INFO
# Required-Start:
# Required-Stop:
# Should-Start:
# Should-Stop:
# Default-Start: 2 3 5
# Default-Stop:
# Short-Description: Sets up updater service
# Description: Sets up updater service
### END INIT INFO
# NOTE: this should be started shortly after syslog and stopped before the
# watchdog.

# Init script information
NAME=updater
DESC="system updater setup"

# The base name of the updater executable
DAEMON="updater"

# The cache dir
CACHEDIR=/var/cache/updater

# The status file
STATUS_FILE=/var/lib/updater/status

# The lock file
LOCK_FILE=/var/run/lock/updater

# The file indicating update availability
UPDATE_STATE_FILE=/var/lib/updater/update-state

# file to prevent double initialization after boot
INITED_FILE=/var/run/updater.inited
# The file used to prevent updater from running on shutdown
NORUN_FILE=/var/run/updater.norun

# Where moved files are stored
MOVEDFILESDIR=/var/cache/updater/moved-files

# The maximum wait for a running updater to exit (in seconds)
# Note that we do not want a running update to be interrupted
# in the middle, so we set a relatively long timeout here (while
# checking the updater process will exit rapidly, only when
# installing updates through rpm will it take time to exit).
# Note that the watchdog daemon can also have a maximum timeout
# that can reboot a system even if held off by the repair binary.
MAX_WAIT=3600

# The init script for splash screens
SPLASH=/etc/init.d/splash

# Load init script configuration
[ -f /etc/default/$NAME ] && . /etc/default/$NAME

# Source the init script functions
. /etc/init.d/functions

# Ignore SIGHUP, as we may receive it as a side effect of
# running this script (should not but better be safe)
trap '' SIGHUP

# Each init script action is defined below...

clean_packages() {
    find "$CACHEDIR" -mindepth 2 -maxdepth 2 -type d -name packages -print0 | \
	xargs -0r -i find '{}' -mindepth 1 -delete
}

seconds_since_boot() {
    local t="$(< /proc/uptime)"
    t="${t%% *}"
    echo "${t%%.*}"
}

# returns the pid of updater processes
pidof_updater() {
    local pids lockpid p

    # NOTE: pidof does not return running scripts named "$DAEMON" (which
    # could be us) and returns zero if processes found, non-zero otherwise.
    # But pidof may not return all processes, for instance sysvinit's pidof
    # skips processes in D state.
    pids="$(pidof updater)"

    # check PID from lockfile if it is a live process and if it is the updater
    [ -f "$LOCK_FILE" ] && lockpid="$(tr -d '[:space:]' < "$LOCK_FILE")"
    if [ -n "$lockpid" ]; then
        if ! [ -e /proc/"$lockpid" ] || [ "$(sed -e 's|\x00.*||;s|.*/||' /proc/"$lockpid"/cmdline 2>/dev/null)" != "updater" ]; then
            lockpid=
        fi
    fi
    for p in $pids; do
        [ "$p" = "$lockpid" ] && lockpid= && break
    done

    if [ -n "$pids" -o -n "$lockpid" ]; then
        echo $pids $lockpid
    else
        return 1
    fi
}

# returns 0 if the deamon is running, non-zero otherwise
is_daemon_running() {
    pidof_updater > /dev/null
    return
}

# signals $DAEMON to stop and returns 0 if any processes found to signal
# non-zero otherwise
signal_daemon_stop() {
    pids="$(pidof_updater)" || return 1
    # we do not care if a process returned by pidof has just gone
    # so we ignore the return status of kill
    kill -s SIGHUP -- $pids > /dev/null 2>&1
    return 0
}

# returns zero if signal_daemon_stop returns zero or if the status
# file indicates that we are rebooting
rebooting_or_signal_daemon_stop() {
    # Must signal first and check after to avoid race conditions
    signal_daemon_stop && return 0
    if [ -f "$STATUS_FILE" ]; then
	STATUS="$(< "$STATUS_FILE")"
	case "$STATUS" in
	    REBOOTING*)
		return 0
		;;
	esac
    fi
    return 1
}

start() {
    local RET

    echo -n "Starting $DESC: "

    if [ -e "$INITED_FILE" ]; then
	echo -n "already initialized "
	success; echo
	return 0
    fi

    touch "$INITED_FILE"

    if [ -f "$STATUS_FILE" ]; then
	STATUS="$(< "$STATUS_FILE")"
	case "$STATUS" in
	    UPDATING*|CORRUPTED*)
		echo -n \
		    "previous update interrupted - SYSTEM CORRUPTED, "
		if [ "$STATUS" != CORRUPTED ]; then
		    echo CORRUPTED > "$STATUS_FILE".tmp
		    fsync "$STATUS_FILE".tmp 2>/dev/null || sync
		    mv "$STATUS_FILE".tmp "$STATUS_FILE"
		fi
		echo CORRUPTED > "$UPDATE_STATE_FILE"
		;;
	    READY*)
		;;
	    REBOOTING*)
		echo READY > "$STATUS_FILE"
		;;
	    *)
		echo -n \
		    "previous run interrupted in safe place, "
		echo READY > "$STATUS_FILE"
		;;
	esac
    fi

    if [ -e "$MOVEDFILESDIR"/restore ]; then
        echo -n "restoring moved files "
        "$MOVEDFILESDIR"/restore
        RET=$?
        if [ $RET -eq 0 ]; then
            success; echo
        else
            failure; echo
        fi
    fi
    rm -rf "$MOVEDFILESDIR"

    echo -n "cleaning cached packages "
    clean_packages
    RET=$?
    if [ $RET -eq 0 ]; then
	success; echo
    else
	failure; echo
	return 1
    fi

    return 0
}

# Signals any running updater to stop but does not wait for it to stop
# Returns 0 if no updater running, 2 if one still running but the MAX_WAIT
# time has not yet elapsed since originally signalling, and 1 if one still
# running but MAX_WAIT has expired
stop_nowait() {
    local timediff timenow timefile

    # Make sure updater cannot start again
    if [ ! -f "$NORUN_FILE" ]; then
	seconds_since_boot > "$NORUN_FILE" || return
	timediff=0
    fi

    rebooting_or_signal_daemon_stop || return 0

    # If this is the first time we signal it see if it exited quickly
    if [ -n "$timediff" ]; then
	sleep 1
	rebooting_or_signal_daemon_stop || return 0
    fi

    # An updater is still running, check for MAX_WAIT
    if [ -z "$timediff" ]; then
	timenow="$(seconds_since_boot)"
	timefile="$(< "$NORUN_FILE")"
	if [ -z "$timefile" ]; then
	    # NORUN_FILE might have been created empty by previous version of this script
	    echo "$timenow" > "$NORUN_FILE" || return
	    timediff=0
	else
	    timediff=$(( timenow - timefile ))
	fi
    else
	[ -x "$SPLASH" ] && "$SPLASH" updating > /dev/null 2>&1
    fi
    if [ "$timediff" -le "$MAX_WAIT" ]; then
	return 2
    else
	return 1
    fi
}

stop () {
    local RET

    echo -n "Stopping $DESC: "

    echo "preventing updater from starting"
    if [ ! -f "$NORUN_FILE" ]; then
	seconds_since_boot > "$NORUN_FILE"
	RET=$?
    else
	RET=0
    fi
    if [ $RET -eq 0 ]; then
	echo -n ", "
    else
	echo " failed, " -n
    fi

    echo -n "terminating current updaters"
    signal_daemon_stop
    if [ $? -eq 0 ]; then # an updater is running
	usleep 250000
	RET=1
	for (( i=0; i< "$MAX_WAIT"; i++ )); do
	    if ! is_daemon_running; then
		if [ $i -ne 0 ]; then
		    [ -x "$SPLASH" ] && "$SPLASH" stop > /dev/null 2>&1
		fi
		RET=0
		break
	    fi
	    if [ $i -eq 0 ]; then
		[ -x "$SPLASH" ] && "$SPLASH" updating > /dev/null 2>&1
	    fi
	    echo -n "."
	    sleep 1
	done
    else
	RET=0 # no updater running
    fi
    if [ $RET -eq 0 ]; then
	echo -n ", "
    else
	echo -n " failed (timed out waiting for updater to exit) "
    fi
    RETF=$RET

    echo -n "cleaning cached packages"
    clean_packages
    RET=$?
    if [ $RET -eq 0 ]; then
	success; echo
    else
	failure; echo
    fi

    return $RETF
}

parse() {
    case "$1" in
	start)
	    start
	    return $?
	    ;;
	stop)
	    stop
	    return $?
	    ;;
	stop-nowait)
	    stop_nowait
	    return $?
	    ;;
	restart|condrestart)
	    # for update-rc.d compatibility which may call "restart",
	    # no daemon started by this script, only initialization,
	    # so nothing to restart
	    echo "Nothing to restart for $DESC"
	    return 0
	    ;;
	*)
	    echo "Usage: $NAME {start|stop|restart|condrestart|stop-nowait}" >&2
	    ;;
    esac

    return 1
}

parse $@



