#!/usr/bin/env bash
#
# restart-services - tool to restart updated or reinstalled services
#
# Copyright 2014-2025, Marc Schiffbauer <mschiff@gentoo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License at <http://www.gnu.org/licenses/> for
# more details.

VERSION="1.5"

set -o pipefail
trap clean_exit EXIT
CNF="/etc/restart-services.conf"
CNF_AUTOLEARN="50-autolearn.conf"
SELF="${0##*/}"

function clean_exit() {
	if [[ -w /dev/tty && $- =~ .*i.* ]]; then
		stty icanon echo echok
	fi
}

function die() {
	if [[ -n $* ]]; then
		echo "$(red ERROR): $*" >&2
	fi
	exit 1
}

function include() {
	local files=( "$@" )
	local err f
	debug "called with: ${files[*]}"
	for f in "${files[@]}"; do
		debug "loading config file: $f"
		if [[ -f $f ]]; then
			err=$(bash -n "$f" 2>&1)
			if [[ -n $err ]]; then
				die "config file could not be parsed: $err"
			else
				# shellcheck disable=SC1090
				source "$f"
			fi
		else
			die "config file not found ($f)"
		fi
		if [[ $(basename "$f") == "$CNF_AUTOLEARN" ]]; then
			debug "reading autolearn info"
			# fill AUTOLEARN_ arrays from that file so
			# we know which services have been autolearned
			# before
			# shellcheck disable=SC1090
			source <(grep ^SV_ "$f" | sed 's/^/AUTOLEARN_/')
		fi
	done
}

function detect_init() {
	# openrc is default
	debug "entered function"
	INIT="openrc"

	# detect systemd
	if [[ $(basename "$(readlink /proc/1/exe)") =~ systemd.* ]]; then
		debug "detected systemd"
		INIT=systemd
	fi

	case $INIT in
		openrc)
			if selinuxenabled 2>/dev/null; then
				debug "using 'run_init' to call 'rc-service'"
				RC="run_init rc-service"
			else
				RC="rc-service"
			fi
			return 0
		;;
		systemd)
			RC="systemctl"
			return 0
		;;
	esac
}

function is_systemd() {
	#
	# return 0 if runnin on systemd
	#
	if [[ $INIT ]]; then
		if [[ $INIT == "systemd" ]]; then
			return 0
		else
			return 1
		fi
	else
		detect_init
		is_systemd
		return $?
	fi
}

function systemd_detect_require_daemon_reload() {
	# detect, if any unit files have changed on disk so that running 'systemctl daemon-reload' is required
	debug "entered function"
	local unit
	while read -r unit; do
		if systemctl status "$unit" |& grep "^Warning:.*changed on disk"; then
			debug "systemd_detect_require_daemon_reload(): unit changed on disk: $unit"
		fi
	done < <(systemctl | grep -F .service | awk '{print $1}')|grep -q "^Warning:.*changed on disk"
}

function sv_is_running() {
	local SV=$1; shift
	case $INIT in
		openrc)
			if rc-service "$SV" -- --ifstarted >/dev/null; then
				return 0
			else
				return 1
			fi
		;;
		systemd)
			if systemctl status "$SV" >/dev/null; then
				return 0
			else
				return 1
			fi
		;;
	esac
}

function sv_restart() {
	local SV=$1; shift
	local arg=$1; shift
	case $INIT in
		openrc)
			if [[ $arg == "nodeps" ]]; then
				nodeps="--nodeps"
			fi
			debug "running $RC $SV -- --ifstarted $* restart"
			# shellcheck disable=SC2086
			$RC "$SV" -- --ifstarted $nodeps restart
		;;
		systemd)
			if [[ $arg == "nodeps" ]]; then
				nodeps="--job-mode=ignore-dependencies"
			fi
			if [[ $KILL ]]; then
				debug "running $RC $SV try-restart"
				# shellcheck disable=SC2086
				$RC $nodeps kill "$SV"
			fi
			debug "running $RC $SV try-restart"
			# shellcheck disable=SC2086
			$RC $nodeps try-restart "$SV"
		;;
	esac
}

function sv_reload() {
	local SV=$1; shift
	case $INIT in
		openrc)
			debug "running $RC $SV -- --ifstarted $* reload"
			$RC "$SV" -- --ifstarted reload
		;;
		systemd)
			debug "running $RC try-reload-or-restart $SV"
			$RC try-reload-or-restart "$SV"
		;;
	esac
}

function sv_restart_critical() {
	local SV=$1; shift
	[[ $SV ]] || die "BUG: called without arg"

	if [[ $NO_COUNTDOWN ]]; then
		# -N set, restart withount countdown
		sv_restart "$SV" "$@"
	else
		# user can skip restart by pressing ctrl-c (SIGINT)
		# shellcheck disable=SC2064
		trap "echo -e '\n$(white '=>') $(yellow Skipping restart) of service $(white "$SV")'; unset SV" INT
		echo -n "$(yellow "->") Press ctrl-c within 5 seconds to skip this service "
		for i in 5 4 3 2 1; do
			if [[ -z $SV ]]; then
				break
			fi
			echo -n .
			sleep 1
		done
		trap - INT
		echo ""
		if [[ $SV ]]; then
			# restart service if user did not abort (== SV is still set)
			sv_restart "$SV" "$@"
		fi
	fi
}

function colorize() {
	local C T

	C=$1; shift
	T="$*"
	if [[ $NO_COLOR ]]; then
		echo -n "$T"
		return 0
	fi
	case $C in
		red)
			echo -en '\E[1;31;40m'"$T"
		;;
		green)
			echo -en '\E[1;32;40m'"$T"
		;;
		yellow)
			echo -en '\E[1;33;40m'"$T"
		;;
		blue)
			echo -en '\E[1;34;40m'"$T"
		;;
		white)
			echo -en '\E[1;37;40m'"$T"
		;;
	esac
	tput sgr0
}

function get_confirmation() {
	local answer
	local q=$1
	local sv
	sv=$(white "$2")
	if [[ $INTERACTIVE ]]; then
		until [[ $answer =~ [YyNn] ]]; do
			answer=""
			read -r -n1 -s -p "$(yellow "->") ${q//%1/$sv}? ($(green y)/$(red N))" answer
			if [[ -z $answer ]]; then
				answer=n
			fi
			echo ""
		done
		case $answer in
			Y|y) return 0;;
			N|n) return 1;;
		esac
	fi
	return 0
}

function top_level_ppid() {
	local pid=$1
	local stat
	local ppid_regex='^[0-9]+ \(.*\) . ([0-9]+) .*'
	# grsec denies ".... </proc/pid/stat", but "cat /proc/pid/stat" is allowed
	# iconv: see bug #726768
	# shellcheck disable=SC2002
	stat="$(cat "/proc/$pid/stat" 2>/dev/null|iconv -f UTF-8 -t UTF-8 -c)" || return 1
	if [[ $stat =~ $ppid_regex ]]; then
		local ppid=${BASH_REMATCH[1]}
	else
		echo "$(red BUG): could not determine parent pid of $pid in '/proc/$pid/stat'." >&2
		echo "$(red BUG): Please open a bugreport for this on bugs.gentoo.org" >&2
		echo "$(red BUG): /proc/$pid/stat:" >&2
		echo "$(red BUG): $stat" >&2
		ppid=1
	fi
	if [[ $ppid -le 1 ]] ; then
		echo "$pid"
	else
		top_level_ppid "$ppid"
	fi
}

red() { colorize red "$@"; }
green() { colorize green "$@"; }
yellow() { colorize yellow "$@"; }
white() { colorize white "$@"; }
blue() { colorize blue "$@"; }

function echo_sv_action() {
	local color="$1"
	local sv="$2"
	local msg="$3"
	echo "$($color "=>") $(white "$sv"): $msg"
}

function get_pid_exe() {
	# get exe of given pid
	local p="$1"
	local e
	e="$(readlink "/proc/$p/exe")"

	# remove /var/tmp/portage/*/*/image prefix, see kernel bug #10856
	# https://bugzilla.kernel.org/show_bug.cgi?id=10856
	e="${e/\/var\/tmp\/portage\/*\/*\/image/}"

	# remove " (deleted) suffix
	e="${e/ (deleted)/}"

	echo -n "$e"
}

function find_rc_service() {
	local pid=$1
	local t

	case $INIT in
		openrc)
			# easiest way: process has RC_SERVICE env set, try to find that.
			# grsec denies "xargs .... </proc/pid/environ", but "cat /proc/pid/environ" is allowed
			# processing a cleaned env with loads of \0 is slow, so check for the string we want to
			# speed things up
			if grep -aq RC_SERVICE "/proc/$pid/environ"; then
				# shellcheck disable=SC2002
				t=$(cat "/proc/$pid/environ" 2>/dev/null | xargs -0n1 | grep -a "^RC_SERVICE="|cut -d"=" -f2-)
			else
				t=""
			fi
		;;
		systemd)
			# ps may only return "-" here in some cases
			t=$(ps --no-headers -o'unit' "$pid")
			if [[ $t == "-" ]]; then
				t=""
			fi
		;;
	esac
	if [[ $t ]]; then
		echo -n "$t"
		return 0
	else
		return 1
	fi
}

function check_current_session() {
	#
	# add $pid to CURRENT_SESSION_PID and CURRENT_SESSION
	# if it belongs to our current session
	#
	local pid="$1"
	local e
	debug "called with args $*"
	if [[ $(top_level_ppid "$pid") == $(top_level_ppid $$) ]]; then
		debug "this is our session!"
		e="$(get_pid_exe "$pid")"
		CURRENT_SESSION_PID+=( "$pid" )
		# shellcheck disable=SC2191
		CURRENT_SESSION+=( ["$e"]+=",$pid" )
		CURRENT_SESSION["$e"]="${CURRENT_SESSION["$e"]#,}"
	else
		debug "we are $(top_level_ppid $$)"
	fi
}

function get_package_of_file() {
	local pkg p
	local exe="$1"
	pkg=$(qfile --nocolor -- "$exe"|awk '{print $1}')

	if [[ -z $pkg && $exe =~ ^/usr/s?bin/.* ]]; then
		# bug #926219: for merge-usr systems the registered path for a binary
		# in portage may not be its canonical path, so we try to find alternate paths
		for p in /sbin /usr/sbin /bin /usr/bin; do
			pkg=$(qfile --nocolor -- "$p/${exe##*/}"|awk '{print $1}')
			if [[ -n $pkg ]]; then
				break
			fi
		done
	fi

	if [[ -z $pkg ]]; then
		if [[ $exe =~ .*/lib(32|64)/.* ]]; then
			# sometimes file in package is just /usr/lib/foo but
			# is running as */lib(32|64)/foo
			# so test again with just /lib/ in path
			exe="${exe/\/lib32//lib}"
			exe="${exe/\/lib64//lib}"
			pkg=$(qfile --nocolor -- "$exe"|awk '{print $1}')
		fi
	fi

	# depending on portage-utils version the output differs a but, handle that
	# bug #720094
	pkg=${pkg/:/}
	if [[ $pkg ]]; then
		echo -n "$pkg"
		return 0
	else
		return 1
	fi
}

function strip_element_by_value() {
	# strip an element from an array
	# stops after first match!
	local a_name="$1"
	local a_ref="${a_name}[@]"
	local a_copy=( "${!a_ref}" )
	local e="$2"
	local i=0

	debug "stripping element $e from array $a_name (${#a_copy[*]} elements)"

	while [[ $i -lt ${#a_copy[*]} ]]; do
		if [[ "${a_copy[i]}" == "${e}" ]]; then
			unset "${a_name}[i]"
			break
		fi

		i=$((i+1))
	done
	# we need to rebuild the array because when only using
	# 'unset', bash won't update the arrays index which can
	# lead to unexpected behavior (so when unsetting [3]
	# the prior [4] will *stay* [4] and not be the new [3]
	# as one might expect...
	read -r -d "" -a "$a_name" < <(printf '%s\0' "${!a_ref}" | xargs -0n1)
}

function cleanup_array() {
	# sort elements of the given array and eliminate duplicates
	local a_name="$1"
	local a_elem="${a_name}[@]"
	debug "cleaning up array $a_name"
	read -r -d "" -a "$a_name" < <(printf '%s\0' "${!a_elem}" | sort -uz | xargs -0n1)
}

function debug() {
	if [[ $DEBUG ]]; then
		if [[ $lu_pid ]]; then
			echo "debug ${FUNCNAME[1]}(): $lu_pid: $*" >&2
		else
			echo "debug ${FUNCNAME[1]}(): $*" >&2
		fi
	fi
}

sp="/-\|"
sc=0
function spin() {
	if [[ ! $DEBUG ]]; then
		printf "\b%s" "${sp:sc++:1}"
		((sc==${#sp})) && sc=0
	fi
}
function spin_done() {
	if [[ ! $DEBUG ]]; then
		printf "\bdone\n"
	fi
}

args=$(getopt -n "$SELF" -o acdhiklNuvV -l autolearn,critical,no-color,no-countdown,debug,force-unknown,help,interactive,kill,list-only,verbose,version -- "$@") || exit 1
# shellcheck disable=SC2086
set -- $args

for i; do
	case "$i" in
		-a|--autolearn)
			shift
			AUTOLEARN=1
			if [[ ! -d /etc/restart-services.d ]]; then
				die "The autolearn feature depends on /etc/restart-services.d being a directory and *.conf file therein being included from the main config file using the include statement"
			fi
		;;
		-c|--critical)
			shift
			CRITICAL=1
		;;
		-d|--debug)
			shift
			DEBUG=1
			if [[ ! $1 =~ ^-.* ]]; then
				# use lib_users output from a file intead
				# from current system
				DEBUG_FILE="$1"
				shift
			fi

		;;
		-h|--help)
			shift
			cat<<-EOF

			  Usage: $SELF [options]

			    -l, --list-only	only list services that need to be restarted
			    -a, --autolearn	ask what to do with unknown services and write
			    			decision to the configuration
			    -c, --critical	also restart 'critical' services
			    			defined in SV_CRITICAL or SV_CRITICAL_WITH_NODEPS
			    			in $CNF
			    -i, --interactive	ask before restarting any service
			    -u, --force-unknown	enable restarting of unknown services
			    -N, --no-countdown	do not wait before restarting a critical service
			    -k, --kill		systemd only. In addition to restart a service, also
			    			send a SIGTERM to all processes belonging to the service
			    			prior to restart.
			    			This sometimes helps to prune processes that might have
			    			been started by crond and so belong to its cgroup but
			    			will not be killed/restarted by a service restart
			    			automatically. It might be worth trying this option if
			    			a service is still being listed as to be restarted even
			    			after it has been restarted already
			    -v, --verbose	Be more verbose
			    -V, --version	show version and exit
			    --no-color		do not use colors
			    -h, --help		show help and exit
			    -d, --debug		enable debug messages

			EOF
			exit 0
		;;
		-i|--interactive)
			shift
			INTERACTIVE=1
		;;
		-k|--kill)
			shift
			if is_systemd; then
				KILL=1
			else
				echo "$(yellow "-> Warning:") -k/--kill is only supported when running systemd"
			fi
		;;
		-l|--list-only)
			shift
			LIST_ONLY=1
		;;
		--no-color)
			shift
			NO_COLOR=1
		;;
		-N|--no-countdown)
			shift
			NO_COUNTDOWN=1
		;;
		-u|--force-unknown)
			shift
			FORCE_UNKNOWN=1
		;;
		-v|--verbose)
			shift
			VERBOSE=1
		;;
		-V|--version)
			shift
			echo "$SELF $VERSION"
			echo "Copyright 2014-2024 by Marc Schiffbauer <mschiff@gentoo.org>"
			exit
		;;
	esac
done

if [[ $DEBUG ]]; then
	debug "this is $SELF version $VERSION"
	debug "using $(lib_users --version)"
fi

# make sure we are running as root
if [[ $UID -ne 0 ]]; then
	die "$SELF needs to be run as root"
fi

# check /detect init system
detect_init || die "unsupported init system: $INIT"
debug "INIT is set to $INIT"

if [[ $CRITICAL ]]; then
	echo "$(white "->") Restart of critical services enabled."
fi

# before reading config
declare -A CUSTOM_PROCESS_MAP=()

# load config
include $CNF

if [[ $DEBUG ]]; then
	debug "configuration summary:"
	debug "  INITTAB_KILLALL: ${INITTAB_KILLALL[*]}"
	debug "  SV_ALWAYS: ${SV_ALWAYS[*]}"
	debug "  SV_ALWAYS_RELOAD_ONLY: ${SV_ALWAYS_RELOAD_ONLY[*]}"
	debug "  SV_ALWAYS_WITH_NODEPS: ${SV_ALWAYS_WITH_NODEPS[*]}"
	debug "  SV_CRITICAL: ${SV_CRITICAL[*]}"
	debug "  SV_CRITICAL_WITH_NODEPS: ${SV_CRITICAL_WITH_NODEPS[*]}"
	debug "  SV_IGNORE: ${SV_IGNORE[*]}"
fi

# init assoc arrays
declare -A KNOWN_PROCESSES_EXE=() UNKNOWN_PROCESSES_EXE=() REALLY_UNKNOWN_PROCESSES_EXE=() TODO_PROCESSES_EXE=()
declare -A KNOWN_PROCESSES_PROC=() UNKNOWN_PROCESSES_PROC=() REALLY_UNKNOWN_PROCESSES_PROC=() TODO_PROCESSES_PROC=()
declare -A INITTAB_PROCESSES=() FOUND_INITTAB_PROCESSES=() FOUND_INITTAB_PROCESSES_UNKNOWN=()
declare -A CURRENT_SESSION=() UNKNOWN_PROCESSES_ROOT=() REALLY_UNKNOWN_PROCESSES_ROOT=()

ROOT=/
FOUND_SV=()
FOUND_SV_CRITICAL=()
FOUND_SV_CRITICAL_WITH_NODEPS=()
FOUND_SV_ALWAYS_RELOAD_ONLY=()
FOUND_SV_ALWAYS_WITH_NODEPS=()
FOUND_SV_ALWAYS=()
FOUND_SV_IGNORE=()
FOUND_SV_UNKNOWN=()
CURRENT_SESSION_PID=()
FOUND_SYSTEMD_SCOPES=()

echo -n "$(white "->") Searching for services that need to be restarted ...  "

debug "analyzing lib_users output ..."
while IFS=";" read -r lu_pids lu_libs lu_proc; do
	unset lu_pid
	debug "analyzing $lu_proc (pids: $lu_pids)"
	# bug #678500: ignore tmp-files (lib_users -i '/tmp/*' will not match anything ... lib_users bug? "*/tmp/*" *will* match
	# but also will match /var/tmp/portage for example which we need ...
	# so for now we look at the deleted libs ourself for false positive filtering instead of relying on the lib_users filter
	#
	# deleted lib should never be just "/"
	if [[ $lu_libs =~ ^/(,.*)?$ ]]; then
		# ex. bug #678820: ignore bpfilter_umh
		debug "ignoring false positive (proc=$lu_proc with pid(s) $lu_pids): seems to be a kernel process"
		continue
	fi

	# ignore current proc if *all* lu_libs reside in /tmp
	is_tmp=1
	for lu_lib in ${lu_libs//,/ }; do
		spin
		if ! [[ $lu_lib =~ ^/tmp/.* ]]; then
			unset is_tmp
		fi
	done
	if [[ $is_tmp ]]; then
		debug "ignoring false positive (proc=$lu_proc with pid(s) $lu_pids): all deleted files reside in /tmp"
		continue
	fi

	# check for custom process mappings vis CUSTOM_PROCESS_MAP
	debug "checking for custom process mapping ..."
	for custom_proc in "${!CUSTOM_PROCESS_MAP[@]}"; do
		spin
		debug "checking $custom_proc"
		unset match
		if [[ ${custom_proc:0:2} == "E@" ]]; then
			debug "trying ere match on ${custom_proc:2}"
			if [[ $lu_proc =~ ${custom_proc:2} ]]; then
				match=1
			fi
		else
			debug "trying pattern match on $custom_proc"
			if [[ $lu_proc == "$custom_proc" ]]; then
				match=1
			fi
		fi
		if [[ $match ]]; then
			debug "custom proc match ($custom_proc): $lu_proc"
			if [[ -e /etc/init.d/${CUSTOM_PROCESS_MAP[$custom_proc]} ]]; then
				debug "adding defined init script: ${CUSTOM_PROCESS_MAP[$custom_proc]}"
				FOUND_SV+=( "${CUSTOM_PROCESS_MAP[$custom_proc]}" )
				continue 2
			else
				debug "defined service '${CUSTOM_PROCESS_MAP[$custom_proc]}' not found in /etc/init.d"
				debug "trying to find service automatically"
				echo "$(yellow "=> WARNING")"": defined custom service '${CUSTOM_PROCESS_MAP[$custom_proc]}' not found. File /etc/init.d/${CUSTOM_PROCESS_MAP[$custom_proc]} does not exist. Switching to autodetection."

			fi
		else
			debug "no match found"
		fi
	done

	debug "checking processes of $lu_proc (pids: $lu_pids)"
	for lu_pid in ${lu_pids//,/ }; do
		spin
		debug "checking pid"
		orig_lu_pid=$lu_pid

		# always look at parent pid
		lu_pid=$(top_level_ppid "$lu_pid")

		# sometimes pids disappear:
		if ! [[ $lu_pid ]]; then
			debug "process disappeared, skipping"
			continue
		fi

		if [[ $orig_lu_pid != "$lu_pid" ]]; then
			debug "lu_pid set to its ppid (was $orig_lu_pid)"
		fi

		check_current_session "$orig_lu_pid"

		unset lu_exe
		lu_exe="$(get_pid_exe "$lu_pid")"

		R="$(readlink "/proc/$lu_pid/root")"

		# normally we do not want to try to restart services that run within another root
		# but some exceptions exist, where config and/or init script will put a service into
		# a chroot. So we will consider these well known chroots.
		# The regex CHROOT_EXCEPTIONS_RE matches all those well known chroots
		CHROOT_EXCEPTIONS_RE='^(/etc/unbound'
		# shellcheck disable=SC1091
		CHROOT_EXCEPTIONS_RE+="$(test -f /etc/conf.d/named && { . /etc/conf.d/named; echo "|$CHROOT"; })"
		CHROOT_EXCEPTIONS_RE+='|/run/lldpd'
		CHROOT_EXCEPTIONS_RE+='|/var/empty)/?$'

		# skip processes that are already known
		for i in "${!KNOWN_PROCESSES_EXE[@]}"; do
			spin
			if [[ $i == "$lu_pid" ]]; then
				debug "skipping $lu_exe (pid $orig_lu_pid): we already found the service for that ppid ($lu_pid)"
				if [[ $INIT == "openrc" ]]; then
					debug "WARNING: Including child-process environment. This might contain sensitive data. Please revise carefully before including it in bugreports."
					debug "/proc/$orig_lu_pid/environ: $(xargs -0n1 < "/proc/$orig_lu_pid/environ" 2>/dev/null | paste -s -d ' ')"
				fi
				continue 2
			fi
		done
		# skip processes that are already known within another root
		for i in "${!UNKNOWN_PROCESSES_EXE[@]}"; do
			spin
			if [[ $i == "$lu_pid" ]]; then
				debug "skipping $lu_exe (pid $orig_lu_pid): we already found the ppid ($lu_pid) in another root"
				if [[ $INIT == "openrc" ]]; then
					debug "WARNING: Including child-process environment. This might contain sensitive data. Please revise carefully before including it in bugreports."
					debug "/proc/$orig_lu_pid/environ: $(xargs -0n1 < "/proc/$orig_lu_pid/environ" 2>/dev/null | paste -s -d ' ')"
				fi
				continue 2
			fi
		done

		# in some cases a child process may be the 'real' parent process of a service and thus having RC_SERVICE set in its env
		for i in "${!TODO_PROCESSES_EXE[@]}"; do
			spin
			if [[ $i == "$lu_pid" ]]; then
				# process already in TODO_PROCESSES_*, lets check this child
				debug "checking $lu_exe (pid $orig_lu_pid): we already found the ppid ($lu_pid) but did not find an init script yet"
				debug "checking env of pid $orig_lu_pid"
				t=$(find_rc_service "$orig_lu_pid")
				# skip systend scopes as they are no "service" as we think of it
				if [[ $t =~ .*\.scope$ ]]; then
					debug "skipping scope $t, adding to FOUND_SYSTEMD_SCOPES"
					FOUND_SYSTEMD_SCOPES+=( "$t" )
				elif [[ $t ]]; then
					debug "child process hit: found $t for $lu_exe (pid $orig_lu_pid)"
					debug "adding to FOUND_SV: $t"
					FOUND_SV+=( "$t" )
					debug "moving from TODO_PROCESSES to KNOWN_PROCESSES: $lu_exe ($lu_pid)"
					KNOWN_PROCESSES_EXE+=( ["$lu_pid"]="$lu_exe" )
					KNOWN_PROCESSES_PROC+=( ["$lu_pid"]="$lu_proc" )
					unset "TODO_PROCESSES_EXE[$lu_pid]"
					unset "TODO_PROCESSES_PROC[$lu_pid]"
				else
					debug "no luck, forgetting $orig_lu_pid"
				fi
				continue 2
			fi
		done

		debug "process root is $R"

		if [[ $R == "$ROOT" || $R =~ $CHROOT_EXCEPTIONS_RE ]]; then
			t=$(find_rc_service "$lu_pid")
			# skip systend scopes as they are no "service" as we think of it
			if [[ $t =~ .*\.scope$ ]]; then
				debug "skipping scope $t, adding to FOUND_SYSTEMD_SCOPES"
				FOUND_SYSTEMD_SCOPES+=( "$t" )
			elif [[ $t ]]; then
				debug "direct hit: found $t for $lu_exe"
				debug "adding to FOUND_SV: $t"
				FOUND_SV+=( "$t" )
				debug "adding to KNOWN_PROCESSES: $lu_exe ($lu_pid)"
				KNOWN_PROCESSES_EXE+=( ["$lu_pid"]="$lu_exe" )
				KNOWN_PROCESSES_PROC+=( ["$lu_pid"]="$lu_proc" )
				continue
			else
				debug "no direct hit: process $lu_proc has no RC_SERVICE env set, adding to TODO_PROCESSES"
				if [[ $INIT == "openrc" ]]; then
					debug "WARNING: Including process environment. This might contain sensitive data. Please revise carefully before including it in bugreports."
					debug "/proc/$lu_pid/environ: $(xargs -0n1 < "/proc/$lu_pid/environ" 2>/dev/null | paste -s -d ' ')"
				fi
				TODO_PROCESSES_EXE+=( ["$lu_pid"]="$lu_exe" )
				TODO_PROCESSES_PROC+=( ["$lu_pid"]="$lu_proc" )
				continue
			fi
		else
			debug "pid $orig_lu_pid not considered because it runs in a different root (root=$R)"
			UNKNOWN_PROCESSES_EXE+=( ["$lu_pid"]="$lu_exe" )
			UNKNOWN_PROCESSES_PROC+=( ["$lu_pid"]="$lu_proc" )
			UNKNOWN_PROCESSES_ROOT+=( ["$lu_pid"]="$R" )
		fi
	done
# lib_users <= 0.12 reports postgres' "/anon_hugepage" as deleted lib, so tell it to ignore this manually (see bug #648356)
# bug #678500: ignore tmp-files
done < <(if [[ $DEBUG && $DEBUG_FILE ]]; then cat "$DEBUG_FILE"; else lib_users -m -I /anon_hugepage -i '/tmp/*' 2>/dev/null; fi)
unset lu_pid

if [[ ${#TODO_PROCESSES_EXE[*]} -gt 0 ]]; then
	debug "analyzing ${#TODO_PROCESSES_EXE[*]} remaining processes (not direct hits): ${TODO_PROCESSES_EXE[*]}"
else
	debug "no remaining processes"
fi
# analyze remaining procs that do not have any RC_SERVICE
while read -r lu_pid; do
	spin
	lu_exe="${TODO_PROCESSES_EXE[$lu_pid]}"
	lu_proc="${TODO_PROCESSES_PROC[$lu_pid]}"

	debug "checking exe $lu_exe"
	unset PKG

	# DO NOT manipulate lu_exe here because
	# they have to match known processes so that we can
	# find false positives at the end
	# if we need to do that: the prior loop is the right place

	# try to find out whether lu_pid is a script
	SCRIPT_RE="^(/usr/bin/(perl|ruby)([0-9.]+)?|/usr/bin/python([0-9.]+[a-z]*)?|(/usr)?/bin/(bash|ksh|zsh))$"

	if [[ "$lu_exe" =~ $SCRIPT_RE ]]; then
		debug "process looks like a script"
		# $lu_pid is propably a script
		# try to determine script name

		# shellcheck disable=SC2206
		script_candidates=( $lu_proc )
		if [[ ${#script_candidates[@]} -ge 2 ]]; then
			# swap index 0<->1 because second part is more likely
			# to be the script name
			tmp=${script_candidates[0]}
			script_candidates[0]=${script_candidates[1]}
			script_candidates[1]=$tmp

			unset lu_exe_script
			for s in "${script_candidates[@]}"; do
				s=${s//\"/}
				debug "checking proc part '$s'"
				EXEC_PATH_RE="/(usr/)?((s)?bin|lib(32|64|exec)?)?/"
				if [[ $s =~ $EXEC_PATH_RE ]]; then
					debug "'$s' may be script, looking for package"
					if PKG=$(get_package_of_file "$s"); then
						debug "bingo. '$s' belongs to a package"
						lu_exe_script="$s"
						break
					fi
				fi
			done
		fi
	else
		debug "process does not look like a script"
		PKG=$(get_package_of_file "$lu_exe")
	fi

	if [[ $PKG ]]; then
		debug "found package: $PKG"

		# some processes have their init scripts in a seperate package :-/
		# so as a last resort we add hardcoded well known cases
		case $PKG in
			dev-lang/php) PKG="app-admin/eselect-php" ;;
		esac

		case $INIT in
			openrc)
				# shellcheck disable=SC2207
				_init_scripts=( $(qlist -e "$PKG"|grep /etc/init.d/|paste -s -d" ") )
			;;
			systemd)
				# shellcheck disable=SC2207
				_init_scripts=( $(qlist -e "$PKG"|grep -E "^/usr/lib/systemd/system/.*\.service$"|sed 's,/.*/,,'|paste -s -d" ") )
			;;
		esac

		if [[ ${#_init_scripts} -gt 0 ]]; then
			debug "found $INIT services: ${_init_scripts[*]}"
			for i in "${_init_scripts[@]}"; do
				if sv_is_running "$i"; then
					debug "found started service, adding $INIT service: $i"
					FOUND_SV+=( "$i" )
					continue 2
				else
					debug "no started service found."
				fi
			done
		elif [[ -e /etc/inittab ]] && grep -Eq ":respawn:$lu_exe([[:space:]]+|$)" /etc/inittab; then
			debug "inittab process found: $lu_exe"
			debug "adding to INITTAB_PROCESSES: $lu_pid:$lu_exe"
			INITTAB_PROCESSES+=( ["$lu_pid"]="$lu_exe" )
			continue
		elif [[ -e /etc/inittab ]] && grep -Eq ":respawn:(/usr)?/s?bin/${lu_exe##*/}([[:space:]]+|$)" /etc/inittab; then
			lu_exe=$(grep -E ":respawn:(/usr)?/s?bin/${lu_exe##*/}([[:space:]]+|$)" /etc/inittab|grep -oEm1 "(/usr)?/s?bin/${lu_exe##*/}")
			debug "inittab process found with alternate (symlinked) path: $lu_exe"
			debug "adding to INITTAB_PROCESSES: $lu_pid:$lu_exe"
			INITTAB_PROCESSES+=( ["$lu_pid"]="$lu_exe" )
			continue
		fi

		# try package/exe/script name as init script names
		debug "no $INIT services found. trying some kind of fuzzy search"

		case $INIT in
			openrc)
				# shellcheck disable=SC2207
				init_candidates=( $(find /etc/init.d -regex "/etc/init.d/\(${PKG#*/}\|$(basename "${lu_exe_script:-$lu_exe}")\)\(-[0-9.-]*\)?\(d\)?" -printf '%P') )
			;;
			systemd)
				# shellcheck disable=SC2207
				init_candidates=( $(find /etc/systemd/system -regex "/etc/systemd/system/\(${PKG#*/}\|$(basename "${lu_exe_script:-$lu_exe}")\)\(-[0-9.-]*\)?\(d\)?" -printf '%P') )
			;;
		esac

		debug "other candidates: ${init_candidates[*]}"
		for i in "${init_candidates[@]}"; do
			debug "checking if $i is started"
			if sv_is_running "$i"; then
				debug "found started service, adding $INIT service: $i"
				FOUND_SV+=( "$i" )
			else
				debug "no other started $INIT services found for that process"
			fi
		done
	else
		debug "does not belong to any package: $lu_exe"
	fi
	debug "adding to UNKNOWN_PROCESSES: $lu_exe"
	UNKNOWN_PROCESSES_EXE+=( ["$lu_pid"]="$lu_exe" )
	UNKNOWN_PROCESSES_PROC+=( ["$lu_pid"]="$lu_proc" )
	UNKNOWN_PROCESSES_ROOT+=( ["$lu_pid"]="$(readlink "/proc/$lu_pid/root")" )
done < <([[ ${#TODO_PROCESSES_EXE[@]} -gt 0 ]] && xargs -n1 <<<"${!TODO_PROCESSES_EXE[@]}")

# cleanup list of init scripts
cleanup_array FOUND_SV

# map services in FOUND_SV to different user defined classes
for I in "${FOUND_SV[@]}"; do
	spin
	SV="${I##*/}"
	debug "mapping service $SV:"
	# for systemd, we allow .service units to be specified in config without suffix (without .service)
	# so we add optional (\.service)? to every regex to support this
	case "$SV" in
		"$(for re in "${SV_IGNORE[@]}"; do grep -E "^$re(\.service)?$" <<<"$SV" && break;done)")
			debug "  SV_IGNORE"
			FOUND_SV_IGNORE+=( "$SV" )
		;;
		"$(for re in "${SV_CRITICAL_WITH_NODEPS[@]}"; do grep -E "^$re(\.service)?$" <<<"$SV" && break;done)")
			debug "  SV_CRITICAL_WITH_NODEPS"
			FOUND_SV_CRITICAL_WITH_NODEPS+=( "$SV" )
		;;
		"$(for re in "${SV_CRITICAL[@]}"; do grep -E "^$re(\.service)?$" <<<"$SV" && break;done)")
			debug "  SV_CRITICAL"
			FOUND_SV_CRITICAL+=( "$SV" )
		;;
		"$(for re in "${SV_ALWAYS_RELOAD_ONLY[@]}"; do grep -E "^$re(\.service)?$" <<<"$SV" && break;done)")
			debug "  SV_ALWAYS_RELOAD_ONLY"
			FOUND_SV_ALWAYS_RELOAD_ONLY+=( "$SV" )
		;;
		"$(for re in "${SV_ALWAYS_WITH_NODEPS[@]}"; do grep -E "^$re(\.service)?$" <<<"$SV" && break;done)")
			debug "  SV_ALWAYS_WITH_NODEPS"
			FOUND_SV_ALWAYS_WITH_NODEPS+=( "$SV" )
		;;
		"$(for re in "${SV_ALWAYS[@]}"; do grep -E "^$re(\.service)?$" <<<"$SV" && break;done)")
			debug "  SV_ALWAYS"
			FOUND_SV_ALWAYS+=( "$SV" )
		;;
		*)
			debug "  SV_UNKNOWN"
			FOUND_SV_UNKNOWN+=( "$SV" )
		;;
	esac
done
while read -r lu_pid; do
	spin
	lu_exe=${INITTAB_PROCESSES[$lu_pid]}
	debug "checking if $lu_exe is a known inittab process"
	case $lu_exe in
		"$(for re in "${INITTAB_KILLALL[@]}"; do grep -E "^$re$" <<<"$lu_exe" && break;done)")
			FOUND_INITTAB_PROCESSES+=( ["$lu_pid"]="$lu_exe" )
			debug "'$lu_exe' added to FOUND_INITTAB_PROCESSES"
		;;
		*)
			FOUND_INITTAB_PROCESSES_UNKNOWN+=( ["$lu_pid"]="$lu_exe" )
			debug "'$lu_exe' added to FOUND_INITTAB_PROCESSES_UNKNOWN"
		;;
	esac
done < <([[ ${#INITTAB_PROCESSES[@]} -gt 0 ]] && printf '%s\0' "${!INITTAB_PROCESSES[@]}" | sort -uz | xargs -0n1)

spin_done

if [[ $INIT == systemd ]]; then
	if systemd_detect_require_daemon_reload; then
		debug "systemd: daemon-reload required"
		if [[ $LIST_ONLY ]]; then
			echo "$(white "=>") systemd: detected changed unit files, daemon-reload will be executed"
		else
			echo_sv_action green systemd "Reloading systemd daemon"
			systemctl daemon-reload
		fi
	else
		debug "systemd: daemon-reload NOT required"
	fi
fi

if [[ $LIST_ONLY && ${#FOUND_SV[*]} -gt 0 ]]; then
	echo "$(white "=>") Found $(white ${#FOUND_SV[*]}) services that need to be restarted or reloaded:"
fi

# see, if we need to autolearn unknown services
if [[ $AUTOLEARN ]]; then
	echo "$(white "->") Now trying to learn $(yellow unknown) services ..."
	if [[ ${#FOUND_SV_UNKNOWN[@]} -gt 0 ]]; then
		for SV in "${FOUND_SV_UNKNOWN[@]}"; do
			unset REPLY
			until [[ $REPLY =~ ^[1-7]$ ]]; do
				if [[ $REPLY ]]; then
					echo "Please enter a number from 1 to 7 to choose"
				fi
				cat <<-EOF
				   How do you want the service '$(white "$SV")' to be handled?
				   $(yellow '1)') always $(white reload) (SV_ALWAYS_RELOAD_ONLY)
				   $(yellow '2)') always $(white restart) (SV_ALWAYS)
				   $(yellow '3)') always $(white 'restart without deps') (SV_ALWAYS_WITH_NODEPS)
				   $(yellow '4)') restart $(white 'only with -c/--critical') (SV_CRITICAL)
				   $(yellow '5)') restart $(white 'without deps only with -c/--critical') (SV_CRITICAL_WITH_NODEPS)
				   $(yellow '6)') $(white ignore) service (SV_IGNORE)
				   $(yellow '7)') $(white skip), do not configure
				EOF
				read -rp "[1-7]> " REPLY
			done
			case $REPLY in
				1)
					echo_sv_action yellow "$SV" "Configuring as SV_ALWAYS_RELOAD_ONLY"
					SV_ALWAYS_RELOAD_ONLY+=( "$SV" )
					AUTOLEARN_SV_ALWAYS_RELOAD_ONLY+=( "$SV" )
					FOUND_SV_ALWAYS_RELOAD_ONLY+=( "$SV" )
				;;
				2)
					echo_sv_action yellow "$SV" "Configuring as SV_ALWAYS"
					SV_ALWAYS+=( "$SV" )
					AUTOLEARN_SV_ALWAYS+=( "$SV" )
					FOUND_SV_ALWAYS+=( "$SV" )
				;;
				3)
					echo_sv_action yellow "$SV" "Configuring as SV_ALWAYS_WITH_NODEPS"
					SV_ALWAYS_WITH_NODEPS+=( "$SV" )
					AUTOLEARN_SV_ALWAYS_WITH_NODEPS+=( "$SV" )
					FOUND_SV_ALWAYS_WITH_NODEPS+=( "$SV" )
				;;
				4)
					echo_sv_action yellow "$SV" "Configuring as SV_CRITICAL"
					SV_CRITICAL+=( "$SV" )
					AUTOLEARN_SV_CRITICAL+=( "$SV" )
					FOUND_SV_CRITICAL+=( "$SV" )
				;;
				5)
					echo_sv_action yellow "$SV" "Configuring as SV_CRITICAL_WITH_NODEPS"
					SV_CRITICAL_WITH_NODEPS+=( "$SV" )
					AUTOLEARN_SV_CRITICAL_WITH_NODEPS+=( "$SV" )
					FOUND_SV_CRITICAL_WITH_NODEPS+=( "$SV" )
				;;
				6)
					echo_sv_action yellow "$SV" "Configuring as SV_IGNORE"
					SV_IGNORE+=( "$SV" )
					AUTOLEARN_SV_IGNORE+=( "$SV" )
					FOUND_SV_IGNORE+=( "$SV" )
				;;
				7)
					echo_sv_action yellow "$SV" "Skipping"
				;;
			esac
			if [[ $REPLY -lt 7 ]]; then
					strip_element_by_value FOUND_SV_UNKNOWN "$SV"
			fi

			cat >/etc/restart-services.d/"$CNF_AUTOLEARN" <<-EOF
			#
			# THIS FILE IS AUTOGENERATED BY -a / --autolearn COMMAND LINE OPTION
			#
			EOF
			for v in SV_ALWAYS SV_ALWAYS_RELOAD_ONLY SV_ALWAYS_WITH_NODEPS SV_CRITICAL SV_CRITICAL_WITH_NODEPS SV_IGNORE; do
				av="AUTOLEARN_${v}[@]"
				if [[ -n ${!av} ]]; then
					echo "$v+=( ""${!av}"" )" >> /etc/restart-services.d/"$CNF_AUTOLEARN"
				fi
			done
		done
	else
		echo "   none found."
	fi
fi

# process different classes of services we found
if [[ ${#FOUND_SV_ALWAYS_RELOAD_ONLY[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") class $(green SV_ALWAYS_RELOAD_ONLY)(${#FOUND_SV_ALWAYS_RELOAD_ONLY[*]}): ${FOUND_SV_ALWAYS_RELOAD_ONLY[*]}"
	else
		for SV in "${FOUND_SV_ALWAYS_RELOAD_ONLY[@]}"; do
			if get_confirmation "Reload %1" "$SV"; then
				echo_sv_action green "$SV" "Reloading..."
				sv_reload "$SV"
			fi
		done
	fi
fi
if [[ ${#FOUND_SV_ALWAYS_WITH_NODEPS[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") class $(green SV_ALWAYS_WITH_NODEPS)(${#FOUND_SV_ALWAYS_WITH_NODEPS[*]}): ${FOUND_SV_ALWAYS_WITH_NODEPS[*]}"
	else
		for SV in "${FOUND_SV_ALWAYS_WITH_NODEPS[@]}"; do
		if get_confirmation "Restart %1 without dependencies" "$SV"; then
			echo_sv_action green "$SV" "Restarting $(yellow without dependencies) ..."
			sv_restart "$SV" "nodeps"
		fi
		done
	fi
fi
if [[ ${#FOUND_SV_ALWAYS[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") class $(green SV_ALWAYS)(${#FOUND_SV_ALWAYS[*]}): ${FOUND_SV_ALWAYS[*]}"
	else
		for SV in "${FOUND_SV_ALWAYS[@]}"; do
			if get_confirmation "Restart %1" "$SV"; then
				echo_sv_action green "$SV" "Restarting..."
				sv_restart "$SV"
			fi
		done
	fi
fi
if [[ ${#FOUND_SV_CRITICAL[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") class $(red SV_CRITICAL)(${#FOUND_SV_CRITICAL[*]}): ${FOUND_SV_CRITICAL[*]}"
	elif [[ $CRITICAL ]]; then
		# restart only if forced by user (critical services)
		for SV in "${FOUND_SV_CRITICAL[@]}"; do
			if get_confirmation "Restart $(red critical) service %1" "$SV"; then
				echo_sv_action green "$SV" "$(yellow Restarting...)"
				sv_restart_critical "$SV"
			fi
		done
	else
		for SV in "${FOUND_SV_CRITICAL[@]}"; do
			echo_sv_action red "$SV" "Not restarting $(red critical) service (requires -c) ..."
		done
	fi
fi
if [[ ${#FOUND_SV_CRITICAL_WITH_NODEPS[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") class $(red SV_CRITICAL_WITH_NODEPS)(${#FOUND_SV_CRITICAL_WITH_NODEPS[*]}): ${FOUND_SV_CRITICAL_WITH_NODEPS[*]}"
	elif [[ $CRITICAL ]]; then
		# restart only if forced by user (critical services)
		for SV in "${FOUND_SV_CRITICAL_WITH_NODEPS[@]}"; do
			if get_confirmation "Restart $(red critical) service %1 without dependencies" "$SV"; then
				echo_sv_action green "$SV" "$(yellow Restarting without dependencies ...)"
				sv_restart_critical "$SV" "nodeps"
			fi
		done
	else
		for SV in "${FOUND_SV_CRITICAL_WITH_NODEPS[@]}"; do
			echo_sv_action red "$SV" "Not restarting $(red critical) service (requires -c) ..."
		done
	fi
fi
if [[ ${#FOUND_SV_IGNORE[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") class $(yellow SV_IGNORE) (${#FOUND_SV_IGNORE[*]}): ${FOUND_SV_IGNORE[*]}"
	fi
fi
if [[ ${#FOUND_SV_UNKNOWN[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=->") $(yellow unknown)(${#FOUND_SV_UNKNOWN[*]}): ${FOUND_SV_UNKNOWN[*]}"
	elif [[ $FORCE_UNKNOWN ]]; then
		for SV in "${FOUND_SV_UNKNOWN[@]}"; do
			echo "$(yellow "-> Hint:") Classify service '$SV' by adding it to a service class in '$CNF'"
			if get_confirmation "Restart $(yellow unknown) service %1" "$SV"; then
				echo_sv_action green "$SV" "$(yellow Restarting...)"
				sv_restart_critical "$SV"
			fi
		done
	else
		for SV in "${FOUND_SV_UNKNOWN[@]}"; do
			echo_sv_action red "$SV" "Not restarting $(yellow unknown) service (requires -u) ..."
		done
	fi
fi

if [[ ${#FOUND_SV} -eq 0 ]]; then
	echo "No known services need to be restarted."
fi

# process different classes of services we found
if [[ ${#FOUND_INITTAB_PROCESSES[@]} -gt 0 ]]; then
	debug "processing inittab processes"
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=>") Found $((${#FOUND_INITTAB_PROCESSES[*]}+${#FOUND_INITTAB_PROCESSES_UNKNOWN[*]})) inittab processes that need to be restarted"
		echo -n "$(white "=->") class $(green INITTAB_KILLALL) (${#FOUND_INITTAB_PROCESSES[*]}): "
		for pid in "${!FOUND_INITTAB_PROCESSES[@]}"; do
			echo -n "${FOUND_INITTAB_PROCESSES[$pid]}($pid) "
		done
		echo ""
	else
		for pid in "${!FOUND_INITTAB_PROCESSES[@]}"; do
			exe="${FOUND_INITTAB_PROCESSES[$pid]} ($pid)"
			if get_confirmation "Kill respawn init processes %1" "$exe"; then
				echo_sv_action green "$exe" "Restarting..."
				if kill "$pid"; then
					echo_sv_action green "$exe" "OK"
				else
					echo_sv_action red "$exe" "Failed"
				fi
			fi
		done
	fi
fi
if [[ ${#FOUND_INITTAB_PROCESSES_UNKNOWN[@]} -gt 0 ]]; then
	if [[ $LIST_ONLY ]]; then
		echo -n "$(white "=->") $(yellow unknown) (${#FOUND_INITTAB_PROCESSES_UNKNOWN[*]}): "
		for pid in "${!FOUND_INITTAB_PROCESSES_UNKNOWN[@]}"; do
			echo -n "${FOUND_INITTAB_PROCESSES_UNKNOWN[$pid]}($pid) "
		done
		echo ""
	elif [[ $FORCE_UNKNOWN ]]; then
		for pid in "${!FOUND_INITTAB_PROCESSES_UNKNOWN[@]}"; do
			exe="${FOUND_INITTAB_PROCESSES_UNKNOWN[$pid]} ($pid)"
			if get_confirmation "Kill respawn init processes %1" "$exe"; then
				echo_sv_action green "$exe" "Killing..."
				if kill "$pid"; then
					echo_sv_action green "$exe" "OK"
				else
					echo_sv_action red "$exe" "failed"
				fi
			fi
		done
	else
		for pid in "${!FOUND_INITTAB_PROCESSES_UNKNOWN[@]}"; do
			exe="${FOUND_INITTAB_PROCESSES_UNKNOWN[$pid]} ($pid)"
			echo_sv_action red "$exe" "Not killing $(yellow unknown) inittab process (requires -u) ..."
		done
	fi
fi

# cleanup UNKNOWN_PROCESSES: remove pids where we have the same process name in KNOWN_PROCESSES
debug "looking for false positives in unknown processes"
for pid in "${!UNKNOWN_PROCESSES_EXE[@]}"; do
	# skip unknown process if we know another pid of it
	uex="${UNKNOWN_PROCESSES_EXE[$pid]}"
	uep="${UNKNOWN_PROCESSES_PROC[$pid]}"
	debug "looking at unknown process $uex ($pid)"
	for k in "${KNOWN_PROCESSES_EXE[@]}"; do
		if [[ $k == "$uex" ]]; then
			debug "$uex is not unknown, removing from list"
			continue 2
		fi
	done
	REALLY_UNKNOWN_PROCESSES_PROC+=( ["$pid"]="$uep" )
	REALLY_UNKNOWN_PROCESSES_EXE+=( ["$pid"]="$uex" )
	REALLY_UNKNOWN_PROCESSES_ROOT+=( ["$pid"]="$(readlink "/proc/$pid/root")" )
done

# handle pid 1
if [[ ${REALLY_UNKNOWN_PROCESSES_EXE[1]} ]]; then
	debug "pid 1 needs to be restarted"
	if [[ $LIST_ONLY ]]; then
		echo "$(white "=>") Found pid 1 (${REALLY_UNKNOWN_PROCESSES_EXE[1]}) that needs to be restarted."
	else
		if get_confirmation "Restart %1" "${REALLY_UNKNOWN_PROCESSES_EXE[1]}"; then
			echo_sv_action green "${REALLY_UNKNOWN_PROCESSES_EXE[1]}" "Restarting..."
			telinit u
		fi
	fi
	unset "REALLY_UNKNOWN_PROCESSES_EXE[1]"
fi

if [[ ${#CURRENT_SESSION_PID[@]} -gt 0 ]]; then
	debug "CURRENT_SESSION_PID array is set: ${CURRENT_SESSION_PID[*]}"
	debug "we are $$"
	debug "our top level ppid is $(top_level_ppid $$)"
	cleanup_array CURRENT_SESSION_PID
	debug "programs of current user session that need to be restarted:"
	if [[ $VERBOSE ]]; then
		echo "$(white "=>")" "Found $(white "${#CURRENT_SESSION_PID[@]}")" "processes in your current session that might need to be restarted:"
		for e in ${!CURRENT_SESSION[*]}; do
			debug "  ${CURRENT_SESSION[$e]}"
			echo "$(white "=->")" "[${CURRENT_SESSION[$e]}]: $e"
		done
	else
		echo "$(white "=>")" "Found $(white "${#CURRENT_SESSION_PID[@]}")" "processes in your current session that might need to be restarted. Use --verbose to show details"
	fi
fi

cleanup_array FOUND_SYSTEMD_SCOPES
if [[ ${#FOUND_SYSTEMD_SCOPES[@]} -gt 0 ]]; then
	debug "found SCOPES: ${FOUND_SYSTEMD_SCOPES[*]}"
fi

if [[ ${#REALLY_UNKNOWN_PROCESSES_EXE[@]} -gt 0 ]]; then
	echo ""
	if [[ $VERBOSE ]]; then
		echo "$(yellow "-> Warning:") The following running processes did not match any known service but have been updated or deleted (or some deps)"
		echo "$(yellow "-> ") If any of those match a custom service you can configure a service manually using 'CUSTOM_PROCESS_MAP'"
		for pid in "${!REALLY_UNKNOWN_PROCESSES_EXE[@]}"; do
			if [[ $pid -eq 1 ]]; then
				continue
			fi
			owner="$(ps -o user= -p "$pid")"
			echo "$(yellow "=> $pid"): " "$(white "${REALLY_UNKNOWN_PROCESSES_PROC[$pid]}" "[")""exe=${REALLY_UNKNOWN_PROCESSES_EXE[$pid]}""$(white "]")"" (owner: $(white "$owner"), root: $(white "${REALLY_UNKNOWN_PROCESSES_ROOT[$pid]}"))"
			if [[ $pid == $(top_level_ppid $$) ]]; then
				echo "  $(yellow "-> This is you! You may need to relogin/reconnect and/or restart your screen/tmux session to get rid of this")"
			fi
		done
	else
		echo "$(yellow "-> Warning:") Found ${#REALLY_UNKNOWN_PROCESSES_EXE[@]} running processes that did not match any known service but have been updated or deleted (or some deps). Use --verbose to show details"
	fi
fi
# EOF
