#!/bin/sh
#
# Author: Petter Reinholdtsen
# Date: 2005-09-03
#
# Update the boot order based on init.d script dependency info

set -e

now=`date +%Y%m%dT%H%M`
logdir=/var/lib/insserv
backupfile="$logdir/bootscripts-$now.tar.gz"
listfile="$logdir/bootscripts-$now-after.list"
logfile="$logdir/run-$now.log"
flagfile="$logdir/using-insserv"

# Make sure insserv is in path
PATH=/sbin:$PATH

convert_rc_s_to_k() {
  runlevel=$1
  for link in $(cd $target/etc/rc$runlevel.d; ls S* || true); do
      set `echo $link|sed "s%S\(..\)\(.*\)%\1 \2%"`
      seq=$1
      service=$2
      mv $target/etc/rc$runlevel.d/$link $target/etc/rc$runlevel.d/K$seq$service
  done
}

# Recreate sysv boot sequence by calling the postinst for all packages
# with init.d scripts, to get them to call update-rc.d again.
regenerate_sysv_sequence() {
    packages="$(dpkg -S $(find /etc/init.d -type f -perm /+x) 2>/dev/null | cut -d: -f1 | sort -u)"

    for p in $packages ; do
        # Make sure it is installed
	if dpkg --get-selections $p | grep -qw install ; then
            # Only fix packages with init.d scripts
	    if dpkg -L $p | grep -q /etc/init.d/ ; then
		brokenpackages="$brokenpackages $p"
	    fi
	fi
    done

    # Remove the old sequence
    find /etc/rc[S0123456].d -type l -print0 | xargs -0 rm

    # As the sysv-rc update-rc.d script do not check dependencies, it
    # is enough to run through these scripts once.
    for p in $brokenpackages ; do
	if [ -x /var/lib/dpkg/info/$p.postinst ] ; then
	    echo "info: running package $p postinst"
	    DEBCONF_FRONTEND=noninteractive	\
		/var/lib/dpkg/info/$p.postinst configure < /dev/null || true
	else
	    echo "error: package $p got init.d script but no postinst"
	fi
    done

    # Using postinst directly might mess up the terminal.  Try to recover
    reset || true
}

# Based on code from dash postinst
check_divert() {
    package=insserv
    div=$(dpkg-divert --list $2)
    distrib=${4:-$2.distrib}
    case "$1" in
    true)
        if [ -z "$div" ]; then
	    dpkg-divert --package $package --divert $distrib --add $2
	    cp -dp $2 $distrib
	    ln -sf $3 $2
	fi
	;;
    false)
        if [ -n "$div" ] && [ -z "${div%%*by $package}" ]; then
	    mv $distrib $2
	    dpkg-divert --remove $2
	fi
	;;
    status) # Return true if the divert is in effect
        if [ -n "$div" ] && [ -z "${div%%*by $package}" ]; then
	    :
	else
	    false
	fi
    esac
}

deactivate_inserv() {
    if check_divert status /usr/sbin/update-rc.d \
	/usr/sbin/update-rc.d-insserv ; then
        # Undo divert
	check_divert false /usr/sbin/update-rc.d \
	    /usr/sbin/update-rc.d-insserv
    fi

    if [ ! -f $flagfile ] ; then
	echo "error: The boot system is not currently converted to insserv."
	echo "error: Flag file $flagfile is missing."
	echo "error: Unable to remove the use of insserv."
	exit 1
    fi

    # Check if there has been changes, or if it is safe to restore
    # from backup

    # Pick the last list of symlinks.
    for file in $logdir/bootscripts-*-after.list ; do
	listfile=$file
    done
    ls /etc/init.d /etc/rc*.d > "$logdir/current.list"

    # If the rc.d scripts order look like it did when we converted, it
    # is safe to restore.
    if [ -f $listfile ] && cmp $logdir/current.list $listfile ; then
	rm "$logdir/current.list"
	echo "info: Restoring using backed up copy of init.d/ and rc*.d/."
	for backup in $logdir/bootscripts-*.tar.gz ; do
	    backupfile=$backup
	done
	if [ -f "$backupfile" ] ; then
	    (cd /etc; rm -rf init.d rc*.d; tar zxf $backupfile)
	    echo "info: successfully restored backup of init.d scripts"
	    rm $flagfile
	else
	    echo "error: Unable to locate backup file"
	    exit 1
	fi
    else
	rm "$logdir/current.list"
	echo "error: Unable to restore the boot sequence.  Invalid backup."
	echo "error: Trying to recover by reconfiguring all packages with init.d scripts."
	regenerate_sysv_sequence
	rm -f $flagfile
    fi

    # Remove files generated by insserv
    rm -f /etc/init.d/.depend.boot
    rm -f /etc/init.d/.depend.start
    rm -f /etc/init.d/.depend.stop
}

is_unsafe_to_activate() {
    # Refuse to convert when there are obsolete init.d scripts left
    # behind, as these tend to confuse the boot sequence.
    echo "info: Checking if it is safe to convert to dependency based boot."
    retval=1
    for package in $(dpkg -S $(find /etc/init.d -type f -perm /+x) \
                     2>/dev/null | cut -d: -f1 | sort -u); do
        obsolete_initscripts=$(dpkg-query -W -f='${Conffiles}\n' $package | \
	    grep 'obsolete$' | grep -o '/etc/init.d/[^ ]\+') || :
        if [ "$obsolete_initscripts" ]; then
            for initscript in $obsolete_initscripts; do
                if [ -e "$initscript" ]; then
                    echo "error: Obsolete conffile $initscript left behind by package $package"
                    retval=0
                fi
            done
        fi
    done

    insserv -nv > $logfile 2>&1 || true
    if egrep -q 'There is a loop between|already provided!|provides system facility' $logfile ; then
	echo "error: Problems running insserv:"
	egrep 'There is a loop between|loop involving service|already provided!|provides system facility' $logfile | sed 's/^/  /'
	rm $logfile
	retval=0
    fi
    return $retval
}

activate_insserv() {
    if is_unsafe_to_activate ; then
	cat <<EOF
info: Please check out this manually.
info: Refusing to convert boot sequence until this is fixed
info: See http://wiki.debian.org/LSBInitScripts/DependencyBasedBoot
info: for more information on how to solve these issues.  Most likely,
info: it is a bug in the binary package with the init.d script in
info: question, and not with insserv.
EOF
	exit 1
    fi

    echo "info: Backing up existing boot scripts in $backupfile"
    (cd /etc; tar zcf $backupfile init.d rc*.d)

    echo "info: Reordering boot system, log to $logfile"
    (
	echo "info: Converting rc0.d/S* and rc6.d/S* to K*."
	convert_rc_s_to_k 0
	convert_rc_s_to_k 6
	echo "info: running insserv"
	insserv -v
    ) > $logfile 2>&1

    echo "info: Recording new boot sequence in $listfile"
    ls /etc/init.d /etc/rc*.d > $listfile

    echo "info: Use '$0 restore' to restore the old boot sequence."
    touch $flagfile

    # Divert update-rc.d to update-rc.d-insserv
    check_divert true /usr/sbin/update-rc.d \
	/usr/sbin/update-rc.d-insserv
}

case "$1" in
    enable|'')
	activate_insserv
	;;
    disable|restore)
	deactivate_inserv
	;;
    check)
	if is_unsafe_to_activate ; then
	    echo "warning: It is not safe to convert to dependency based boot."
	    exit 1
	else
	    echo "info: It is safe to convert to dependency based boot."
	    exit 0
	fi
	;;
    *)
	echo "error: Unknown argument '$1'"
	exit 1
	;;
esac

exit 0
