#!/bin/sh

set -e

readonly PACKAGE_NAME=utrans-rc
TS="/var/tmp/${PACKAGE_NAME}.stamp"

# args: exit status
usage() {
    cat <<EOF
Usage: $(basename "$0") [-h] [-b <BACKEND>..] [ -n | -o <OUTPUT-DIR>] [ -u | <SOURCE-DIR>.. ]
  Options:-
    -b <BACKEND>:	specify output backend, see utrans(1)
    -h:			show this usage
    -o <OUTPUT-DIR>:    save ouput to subdirectories of this directory
    -n:			no install, just show what would be done
    -u:			update mode
EOF
    exit $(($1+0))
}

# Return status
dpkg_known_files() {
    ret=1 # False
    # Some equivalent LSB scripts have .sh appended to their name.
    for file do
	[ -z "${file##/etc/init.d/*}" ] && set -- "$@" "${file}.sh"
    done
    for file do
	while read -r owner conffile ; do
	    [ -z "$owner" ] || [ -z "$conffile" ] ||
		[ "$file" != "$conffile" ] && continue
	    echo "Skipping: $conffile owned by ${owner%:}" >&2
	    ret=0 # True
	done <<EOF
${DPKG_KNOWN:=$(dpkg-query -S /etc/init.d/* /etc/conf.d/* /etc/cron.d/* /etc/xinetd.d/* 2>/dev/null)}
EOF
    done
    return $ret
}

do_install() {
    for target do
	source="${OUTPUT_DIR}${target#/etc}"
	owner=$(ucfq -w "$target" | awk -F: '{print $2}')
	case  "$owner" in
	    '') ;;
	    "$PACKAGE_NAME")
		cmp --quiet "$source" "$target" &&
		    continue ;;
	    *) echo "Skipping $target already registered to $owner" >&2
	       continue ;;
	esac
	echo "Considering new $target" >&2
	if [ "$NO_ACT" ] ; then
	    echo "DRYRUN: try install $target" >&2
	    continue
	fi
	if [ -f "$target" ] ; then
	    sha256=$(sha256sum "$target")
	    mode=$(stat -c '%a' "$target")
	fi
	sudo chown root:root "$source"
	target_dir=$(dirname "$target")
	sudo mkdir -p "${target_dir}"
	sudo --preserve-env=UCF_FORCE_CONFFNEW,UCF_FORCE_CONFFOLD,UCF_FORCE_CONFFMISS,DEBIAN_FRONTEND \
	     ucf "$source" "$target"
	if  [ -z "$owner" ] && [ -f "$target" ] &&
		sha256sum --status --check 2>/dev/null <<EOF ; then
$sha256
EOF
	    # No changes made by ucf: continue to avoid taking ownership.
       	    continue
	fi
	sudo ucfr "$PACKAGE_NAME" "$target"
	case "$target_dir" in
	    /etc/init.d)
		rc_name=${target#"${target_dir}"/}
		# Make ucf droppings non-executable
		find /etc/init.d -name "${rc_name}.ucf-*" -exec sudo chmod a-x '{}' ';'
		if [ -f "$target" ] ; then
		    if ! sha256sum --status --check 2>/dev/null <<EOF || [ "$mode" != "755" ] ; then
$sha256
EOF
			echo "Setting up new $target" >&2
			sudo chmod 755 "$target"
			# awk could be mawk with incomplete POSIX regex support
			origin=$(awk '/^#  [0-9a-f]+  .*\.service$/ {print $3}' "$target")
			awk -F= '/^WantedBy=.*\.target$/ {print $2}' "$origin" |
			    while IFS= read -r wb; do
				if binary_in_path rc-update ; then
				    case "$wb" in
					'') echo "WARNING: no WantedBy in $origin, skipping runlevel setup" >&2 && continue ;;
					poweroff.target|reboot.target) orl=off ;;
					rescue.target) orl=recovery ;;
					shutdown.target) orl=shutdown ;;
					sysinit.target) orl=sysinit ;;
					*.target) orl=default ;;
				    esac
				    sudo rc-update show "$orl" | grep -q "$rc_name | $orl" && continue
				    sudo rc-update add "$rc_name" "$orl"
				else
				    sudo update-rc.d "$rc_name" defaults
				fi
			    done
		    fi
		fi
		;;
	esac
    done
}

# Verify source units for files we have registered with ucf
verify_installed() {
    # Reverse sort: process /etc/init.d before /etc/conf.d
    for owned in $(ucfq -w "$PACKAGE_NAME" | awk -F: '{if ($3) print $1}' | sort -r); do
	for f in $(awk '/^#  [0-9a-f]+  .+\..+$/ {print $2, $3}' "$owned" |
		       sha256sum -c 2>/dev/null | awk -F: '/FAILED/ {print $1}') ; do
	    if [ -f "$f" ] ; then
		echo "Modified source unit: $f" >&2
		translate_try_install "$f" || continue
	    else
		echo "Deleted source unit: $f" >&2
		case "$owned" in
		    /etc/init.d/*)
			echo "Disabling $owned" >&2
			rc_name=$(basename "$owned")
			sudo invoke-rc.d "$rc_name" stop || true
			sudo update-rc.d "$rc_name" remove || true
			sudo chmod -x "$owned"
		esac
		if [ "$(ucfq -w "$owned")" = "$owned:$PACKAGE_NAME:Yes:No" ] ; then
		    echo "Removing unmodified $owned" >&2
		    sudo rm -f "$owned" "$owned.ucf-"*
		fi
		echo "Disowning $owned" >&2
		sudo ucfr --purge "$PACKAGE_NAME" "$owned"
		sudo ucf --purge "$owned"
	    fi
	    continue 2 # Next $owned
	done
    done
}

binary_in_path() {
    PATH=/usr/sbin:/usr/bin:/sbin:/bin: command -v "$1" >/dev/null
}

detect_backends() {
    binary_in_path openrc || set -- lsb
    binary_in_path xinetd || set -- "$@" inetd
    if [ $# -gt 0 ] ; then
	echo "Using auto backends: $*" >&2
	echo "$@" | sed 's/^\| / -b /g'
    fi
}

try_install() {
    unit=$1
    pkg=$2
    if [ "$pkg" ] && [ "$last_pkg" != "$pkg" ] ; then
	# This is expensive, so reuse if possible.
	pkg_list=$(dpkg-query -L "$pkg")
	last_pkg="$pkg"
    fi
    PKG_HAS_SUPPORT=
    # Handle all files generated by unit together
    set --
    while read -r file  ; do
	if [ -z "$file" ] ; then
	    echo "WARNING: no output files, skipping." >&2
	    return
	fi
	target="/etc${file#"$OUTPUT_DIR"}"
	if grep -q "^$(dirname "$target")" <<EOF
$pkg_list
EOF
	then
	    echo "$pkg already provides files in $(dirname "$target"), skipping" >&2
	    PKG_HAS_SUPPORT=1
	    break
	fi
	set -- "$target" "$@"
    done <<EOF
$(find "$OUTPUT_DIR" ! -type d -print)
EOF
    # Skip if package already provides support or any files are owned by
    # a package.
    [ "$PKG_HAS_SUPPORT" ] || dpkg_known_files "$@" || do_install "$@"

    # Only runs when OUTPUT_DIR is a temporary directory.
    # Clean, ensure error if OUTPUT_DIR unset.
    find "${OUTPUT_DIR:?}" ! -type d -delete
}

translate_try_install() {
    unit=$1
    binsrc=$2 # Optional, not set when updating existing
    # Split BACKENDS on whitepsace:
    # shellcheck disable=SC2086
    "${UNIT_TRANSLATOR}" $BACKENDS "$unit" "${OUTPUT_DIR}"

    if [ "$TRY_INSTALL" ] ; then
	try_install "$unit" "$binsrc"
    fi
}

: "${UNIT_TRANSLATOR:=utrans}"

# Ensure other internal variables are not inherited
unset NO_ACT TRY_INSTALL PKG_HAS_SUPPORT OP_MODE OUTPUT_DIR BACKENDS

while getopts b:hno:u-: OPT; do
    case "$OPT" in
	b) BACKENDS="$BACKENDS -b $OPTARG" ;;
	h) usage ;;
	o) OUTPUT_DIR=$OPTARG ;;
	n) NO_ACT=1; unset TS ;;
	u)
	    if [ -f "$TS" ] ; then
		OP_MODE=update
	    else
		echo "WARNING: $TS not found, processing all units" >&2
	    fi
	    ;;
	-*)
	   echo "ERROR: long options not supported" >&2
	   usage 1
	   ;;
	\?) usage 1 ;; # Bad short option from getopts
    esac
done
shift $((OPTIND-1))

if [ -z "$BACKENDS" ] ; then
    BACKENDS=$(detect_backends)
fi

if [ -z "$OUTPUT_DIR" ]; then
    OUTPUT_DIR=$(mktemp -d)
    TRY_INSTALL=1
    trap 'rm -rf "$OUTPUT_DIR"' EXIT
    verify_installed
else
    unset TS OP_MODE
fi

if [ $# -eq 0 ] ; then
     set -- /usr/lib/systemd/system /lib/systemd/system
else
     if [ "$OP_MODE" ] ; then
	 echo "WARNING: ignoring update mode for non-default SOURCE_DIR" >&2
	 unset OP_MODE
     fi
     unset TS
fi

# Sigh, dpkg-query can't be sure to find the package with an
# absolute path after usrmerge moving. So use a dpkg-query
# pattern-match and anchor with awk at beginning (directory) and
# end.
unit_owners() {
    for src_path do
	shift
	set "$@" "${src_path#/}"
    done
    dpkg-query -S "$@" | sort -u 2>/dev/null
}
DPKG_SOURCE=$(unit_owners "$@")
unit_binsrc() {
    unit=$1
    awk -F: -v regex="${unit#/usr}\$" '$2 ~ regex {print $1}' <<EOF
${DPKG_SOURCE}
EOF
}

find -L "$@" ! -type d -size +0 \( -name '*.service'  -o -name '*.timer' \) ! -name '*@*' ${OP_MODE:+-cnewer "$TS"} \
     -exec readlink --canonicalize-existing '{}' ';' | sort -u |
    while read -r u; do
	binsrc=$(unit_binsrc "$u")
	if [ -z "$binsrc" ] ; then
	    echo "WARNING: unit is not known to dpkg. Install translated files manually." >&2
	else
	    echo "Processing $u" >&2
	    translate_try_install "$u" "$binsrc"
	fi
    done

# Update timestamp
[ -z "$TS" ] || sudo sh -c ":>$TS"
