# Copyright 1999-2004 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header$

# RC Dependency and misc service functions

RC_GOT_SERVICES="yes"

[ "${RC_GOT_FUNCTIONS}" != "yes" ] && source /sbin/functions.sh

if [ "${RC_GOT_DEPTREE_INFO}" != "yes" ]
then
	if ! /sbin/depscan.sh -u
	then
		echo
		eerror "Error running '/sbin/depscan.sh'!"
		eerror "Please correct any problems above."

		exit 1
	fi

	source "${svcdir}/deptree"

	if [ "${RC_GOT_DEPTREE_INFO}" != "yes" ]
	then
		echo
		eerror "Dependency info is missing!  Please run"
		echo
		eerror "  # /sbin/depscan.sh"
		echo
		eerror "to fix this."

		exit 1
	fi
fi


#####################
# Internal variables
#####################

# The name of the service whose dependency info we currently have
rc_name=
# The index of the service whose dependency info we currently have
rc_index=0
# Our dependency types ...
rc_ineed=
rc_needsme=
rc_iuse=
rc_usesme=
rc_ibefore=
rc_iafter=
rc_broken=
rc_parallel=
rc_mtime=

############
# Functions
############

# boot check_mtime(service, mtime)
#
#   Return 0 if 'service's mtime is the same as 'mtime'
#
check_mtime() {
	# This one have no 'mtime' ...
	[ "$1" = "net" ] && return 0

	[ -z "$1" -o -z "$2" ] && return 1

	# Do not fail if there is no script, as virtuals
	# will then not work ...
	if [ -e "/etc/init.d/$1" -a -x "/bin/stat" ] && \
	   [ "$(stat -c "%Y" "/etc/init.d/$1" 2>/dev/null)" -ne "$2" ]
	then
		return 1
	fi

	return 0
}

# bool get_service_index(service, index)
#
#   Print the index of 'service'.  'index' is the current index.
#
get_service_index() {
	local x=1
	local index="$2"
	local myservice="$1"

	if [ -z "$1" -o -z "$2" ]
	then
		echo "0"
		return 1
	fi

	# Do we already have the index?
	if [ -n "${index}" ] && [ "${index}" -gt 0 -a \
	     "${myservice}" = "${RC_DEPEND_TREE[${index}]}" ]
	then
		echo "${index}"
		return 0
	fi

	while [ "${x}" -le "${RC_DEPEND_TREE[0]}" ]
	do
		index=$((${x} * ${rc_index_scale}))
		
		if [ "${myservice}" = "${RC_DEPEND_TREE[${index}]}" ]
		then
			echo "${index}"
			return 0
		fi

		let "x += 1"
	done

	echo "0"
	return 1
}

# bool get_dep_info(service)
#
#   Set the Dependency variables to contain data for 'service'
#
get_dep_info() {
	local myservice="$1"

	[ -z "$1" ] && return 1

	# We already have the right stuff ...
	if [ "${myservice}" = "${rc_name}" -a -n "${rc_mtime}" ] && \
		 check_mtime "${myservice}" "${rc_mtime}"
	then
	   return 0
	fi

	rc_index="`get_service_index "${myservice}" "${rc_index}"`"
	rc_mtime="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_mtime}))]}"

	# Does the stored mtime match that of the current rc-script?
	if ! check_mtime "${myservice}" "${rc_mtime}"
	then
		# Nope, check if we already ran depscan.sh
		source "${svcdir}/deptree"
		rc_index="`get_service_index "${myservice}" "${rc_index}"`"
		rc_mtime="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_mtime}))]}"

		# Do we have it now?
		if ! check_mtime "${myservice}" "${rc_mtime}"
		then
			# Not?  So run depscan.sh ...
			einfo "Re-caching dependency info (mtimes differ)..." &>/dev/stderr
			if ! /sbin/depscan.sh &>/dev/null
			then
				return 1
			else
				# We want to check if we got the dep info later on ...
				unset RC_GOT_DEPTREE_INFO
				source "${svcdir}/deptree"
				# Everything "OK" ?
				[ "${RC_GOT_DEPTREE_INFO}" != "yes" ] && return 1
			fi

			rc_index="`get_service_index "${myservice}" "${rc_index}"`"
		fi
	fi
	
	# Verify that we have the correct index (rc_index) ...
	[ "${rc_index}" -eq 0 ] && return 1
		
	rc_name="${RC_DEPEND_TREE[${rc_index}]}"
	rc_ineed="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_ineed}))]}"
	rc_needsme="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_needsme}))]}"
	rc_iuse="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_iuse}))]}"
	rc_usesme="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_usesme}))]}"
	rc_ibefore="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_ibefore}))]}"
	rc_iafter="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_iafter}))]}"
	rc_broken="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_broken}))]}"
	rc_parallel="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_parallel}))]}"
	rc_mtime="${RC_DEPEND_TREE[$((${rc_index} + ${rc_type_mtime}))]}"
		
	return 0
}

# string check_dependency(deptype, service1)
#
#   List all the services that depend on 'service1' of dependency
#   type 'deptype'
#
# bool check_dependency(deptype, -t, service1, service2)
#
#   Returns true if 'service2' is a dependency of type 'deptype'
#   of 'service1'
#
check_dependency() {
	local x=
	local myservice=

	[ -z "$1" -o -z "$2" ] && return 1
	
	# Set the dependency variables to relate to 'service1'
	if [ "$2" = "-t" ]
	then
		[ -z "$3" -o -z "$4" ] && return 1

		myservice="$3"
	else
		myservice="$2"
	fi

	get_dep_info "${myservice}" >/dev/null || {
		eerror "Could not get dependency info for \"${myservice}\"!" > /dev/stderr
		eerror "Please run:" > /dev/stderr
		echo > /dev/stderr
		eerror "  # /sbin/depscan.sh" > /dev/stderr
		echo > /dev/stderr
		eerror "to try and fix this." > /dev/stderr
		return 1
	}

	# Do we have valid info for 'deptype' ?
	[ -z "$(eval echo \${rc_$1})" ] && return 1

	if [ "$2" = "-t" -a -n "$4" ]
	then
		# Check if 'service1' have 'deptype' dependency on 'service2'
		for x in $(eval echo \${rc_$1})
		do
			[ "${x}" = "$4" ] && return 0
		done
	else
		# Just list all services that 'service1' have 'deptype' dependency on.
		eval echo "\${rc_$1}"

		return 0
	fi

	return 1
}

# Same as for check_dependency, except 'deptype' is set to
# 'ineed'.  It will return all the services 'service1' NEED's.
ineed() {
	[ -z "$1" ] && return 1
	
	check_dependency ineed $*
	
	return $?
}

# Same as for check_dependency, except 'deptype' is set to
# 'needsme'.  It will return all the services that NEED 'service1'.
needsme() {
	[ -z "$1" ] && return 1
	
	check_dependency needsme $*

	return $?
}

# Same as for check_dependency, except 'deptype' is set to
# 'iuse'.  It will return all the services 'service1' USE's.
iuse() {
	[ -z "$1" ] && return 1
	
	check_dependency iuse $*

	return $?
}

# Same as for check_dependency, except 'deptype' is set to
# 'usesme'.  It will return all the services that USE 'service1'.
usesme() {
	[ -z "$1" ] && return 1
	
	check_dependency usesme $*

	return $?
}

# Same as for check_dependency, except 'deptype' is set to
# 'ibefore'.  It will return all the services that are started
# *after* 'service1' (iow, it will start 'service1' before the
# list of services returned).
ibefore() {
	[ -z "$1" ] && return 1
	
	check_dependency ibefore $*

	return $?
}

# Same as for check_dependency, except 'deptype' is set to
# 'iafter'.  It will return all the services that are started
# *before* 'service1' (iow, it will start 'service1' after the
# list of services returned).
iafter() {
	[ -z "$1" ] && return 1
	
	check_dependency iafter $*

	return $?
}

# Same as for check_dependency, except 'deptype' is set to
# 'broken'.  It will return all the services that 'service1'
# NEED, but are not present.
broken() {
	[ -z "$1" ] && return 1

	check_dependency broken $*
	
	return $?
}

# bool iparallel(service)
#
#   Returns true if the service can be started in parallel.
#
iparallel() {
	[ -z "$1" ] && return 1

	if check_dependency parallel -t "$1" "no"
	then
		return 1
	fi

	return 0
}

# bool is_fake_service(service, runlevel)
#
#   Returns ture if 'service' is a fake service in 'runlevel'.
#
is_fake_service() {
	local x=
	local fake_services=

	[ -z "$1" -o -z "$2" ] && return 1

	if [ "$2" != "${BOOTLEVEL}" -a \
	     -e "/etc/runlevels/${BOOTLEVEL}/.fake" ]
	then
		fake_services="$(< /etc/runlevels/${BOOTLEVEL}/.fake)"
	fi

	if [ -e "/etc/runlevels/$2/.fake" ]
	then
		fake_services="${fake_services} $(< /etc/runlevels/$2/.fake)"
	fi

	for x in ${fake_services}
	do
		if [ "$1" = "${x##*/}" ]
		then
			return 0
		fi
	done

	return 1
}

# bool in_runlevel(service, runlevel)
#
#   Returns true if 'service' is in runlevel 'runlevel'.
#
in_runlevel() {
	[ -z "$1" -o -z "$2" ] && return 1

	[ -L "/etc/runlevels/$2/$1" ] && return 0

	return 1
}

# bool is_runlevel_start()
#
#   Returns true if it is a runlevel change, and we are busy
#   starting services.
#
is_runlevel_start() {
	[ -d "${svcdir}/softscripts.old" ] && return 0

	return 1
}

# bool is_runlevel_stop()
#
#   Returns true if it is a runlevel change, and we are busy
#   stopping services.
#
is_runlevel_stop() {
	[ -d "${svcdir}/softscripts.new" ] && return 0

	return 1
}

# int start_service(service)
#
#   Start 'service' if it is not already running.
#
start_service() {
	local retval=0
	
	[ -z "$1" ] && return 1
	
	if ! service_started "$1"
	then
		splash "svc_start" "$1"
			
		if is_fake_service "$1" "${SOFTLEVEL}"
		then
			mark_service_started "$1"
			splash "svc_started" "$1" "0"
		else
			(. /sbin/runscript.sh "/etc/init.d/$1" start)
			retval="$?"
			splash "svc_started" "$1" "${retval}"
			return "${retval}"
		fi
	fi

	return 0
}

# int stop_service(service)
#
#   Stop 'service' if it is not already running.
#
stop_service() {
	local retval=0
	
	[ -z "$1" ] && return 1

	if service_started "$1"
	then
		splash "svc_stop" "$1"
			
		if is_runlevel_stop
		then
			if is_fake_service "$1" "${OLDSOFTLEVEL}"
			then
				mark_service_stopped "$1"
				splash "svc_stopped" "$1" "0"
				return 0
			fi
		else
			if is_fake_service "$1" "${SOFTLEVEL}"
			then
				mark_service_stopped "$1"
				splash "svc_stopped" "$1" "0"
				return 0
			fi
		fi

		(. /sbin/runscript.sh "/etc/init.d/$1" stop)
		retval="$?"
		splash "svc_stopped" "$1" "${retval}"
		return "${retval}"
	fi

	return 0
}

# bool mark_service_started(service)
#
#   Mark 'service' as started.
#
mark_service_started() {
	[ -z "$1" ] && return 1

	ln -snf "/etc/init.d/$1" "${svcdir}/started/$1"

	return $?
}

# bool mark_service_stopped(service)
#
#   Mark 'service' as stopped.
#
mark_service_stopped() {
	[ -z "$1" ] && return 1

	rm -f "${svcdir}/started/$1"

	return $?
}

# bool service_started(service)
#
#   Returns true if 'service' is started
#
service_started() {
	[ -z "$1" ] && return 1

	if [ -L "${svcdir}/started/$1" ]
	then
		if [ ! -e "${svcdir}/started/$1" ]
		then
			rm -f "${svcdir}/started/$1"
			
			return 1
		fi
		return 0
	fi

	return 1
}

# bool mark_service_failed(service)
#
#   Mark service as failed for current runlevel.  Note that
#   this is only valid on runlevel change ...
#
mark_service_failed() {
	[ -z "$1" ] && return 1

	if [ -d "${svcdir}/failed" ]
	then
		ln -snf "/etc/init.d/$1" "${svcdir}/failed/$1"
		return $?
	fi

	return 1
}

# bool service_failed(service)
#
#   Return true if 'service' have failed during this runlevel.
#
service_failed() {
	[ -z "$1" ] && return 1
	
	if [ -L "${svcdir}/failed/$1" ]
	then
		return 0
	fi

	return 1
}

# bool net_service(service)
#
#   Returns true if 'service' is a service controlling a network interface
#
net_service() {
	[ -z "$1" ] && return 1

	if [ "${1%%.*}" = "net" -a "${1##*.}" != "$1" ]
	then
		return 0
	fi

	return 1 
}

# bool is_net_up()
#
#    Return true if service 'net' is considered up, else false.
#
#    Notes for RC_NET_STRICT_CHECKING values:
#      none  net is up without checking anything - usefull for vservers
#      lo    Interface 'lo' is counted and if only it is up, net is up.
#      no    Interface 'lo' is not counted, and net is down even with it up,
#            so there have to be at least one other interface up.
#      yes   All interfaces must be up.
is_net_up() {
	local netcount=0

	case "${RC_NET_STRICT_CHECKING}" in
		none)
			return 0
			;;
		lo)
			netcount="$(ls -1 "${svcdir}"/started/net.* 2> /dev/null | \
			            egrep -c "\/net\..*$")"
			;;
		*)
			netcount="$(ls -1 "${svcdir}"/started/net.* 2> /dev/null | \
			            grep -v 'net\.lo' | egrep -c "\/net\..*$")"
			;;
	esac

	# Only worry about net.* services if this is the last one running,
	# or if RC_NET_STRICT_CHECKING is set ...
	if [ "${netcount}" -lt 1 -o "${RC_NET_STRICT_CHECKING}" = "yes" ]
	then
		return 1
	fi

	return 0
}

# void schedule_service_startup(service)
#
#   Schedule 'service' for startup, in parallel if possible.
#
schedule_service_startup() {
	local count=0
	local current_job=

	if [ "${RC_PARALLEL_STARTUP}" = "yes" ]
	then
		set -m +b

		if [ "$(jobs | grep -c "Running")" -gt 0 ]
		then
			if [ "$(jobs | grep -c "Running")" -eq 1 ]
			then
				if [ -n "$(jobs)" ]
				then
					current_job="$(jobs | awk '/Running/ { print $4}')"
				fi
				
				# Wait if we cannot start this service with the already running
				# one (running one might start this one ...).
				query_before "$1" "${current_job}" && wait

			elif [ "$(jobs | grep -c "Running")" -ge 2 ]
			then
				count="$(jobs | grep -c "Running")"

				# Wait until we have only one service running
				while [ "${count}" -gt 1 ]
				do
					count="$(jobs | grep -c "Running")"
				done

				if [ -n "$(jobs)" ]
				then
					current_job="$(jobs | awk '/Running/ { print $4}')"
				fi

				# Wait if we cannot start this service with the already running
				# one (running one might start this one ...).
				query_before "$1" "${current_job}" && wait
			fi
		fi

		if iparallel "$1"
		then
			eval start_service "$1" \&
		else
			# Do not start with any service running if we cannot start
			# this service in parallel ...
#			wait
			
			start_service "$1"
		fi
	else
		start_service "$1"
	fi

	# We do not need to check the return value here, as svc_{start,stop}() do
	# their own error handling ...
	return 0
}

# bool dependon(service1, service2)
#
#   Does service1 depend (NEED or USE) on service2 ?
#
dependon() {
	[ -z "$1" -o -z "$2" ] && return 1
	
	if ineed -t "$1" "$2" || iuse -t "$1" "$2"
	then
		return 0
	fi

	return 1
}

# string valid_iuse(service)
#
#   This will only give the valid use's for the service
#   (they must be in the boot or current runlevel)
#
valid_iuse() {
	local x=
	local y=

	for x in $(iuse "$1")
	do
		if [ -e "/etc/runlevels/${BOOTLEVEL}/${x}" -o \
		     -e "/etc/runlevels/${mylevel}/${x}" -o \
			 ${x} = "net" ]
		then
			echo "${x}"
		fi
	done

	return 0
}

# string valid_iafter(service)
#
#   Valid services for current or boot rc level that should start
#   before 'service'
#
valid_iafter() {
	local x=
	
	for x in $(iafter "$1")
	do
		if [ -e "/etc/runlevels/${BOOTLEVEL}/${x}" -o \
		     -e "/etc/runlevels/${mylevel}/${x}" -o \
			 ${x} = "net" ]
		then
			echo "${x}"
		fi
	done

	return 0
}

# void trace_depend(deptype, service, deplist)
#
#   Trace the dependency tree of 'service' for type 'deptype', and
#   modify 'deplist' with the info.
#
trace_depend() {
	local x=
	local y=
	local add=

	[ -z "$1" -o -z "$2" -o -z "$3" ] && return 1

	# Build the list of services that 'deptype' on this one
	for x in "$("$1" "$2")"
	do
		add="yes"
		
		for y in $(eval echo "\${$3}")
		do
			[ "${x}" = "${y}" ] && add="no"
		done

		[ "${add}" = "yes" ] && eval $(echo "$3=\"\${$3} ${x}\"")
		
		# Recurse to build a complete list ...
		trace_depend "$1" "${x}" "$3"
	done

	return 0
}

# string list_depend_trace(deptype)
#
#   Return resulting list of services for a trace of
#   type 'deptype' for $myservice
#
list_depend_trace() {
	local x=
	local list=

	[ -z "$1" ] && return 1
	
	trace_depend "$1" "${myservice}" "list"

	for x in ${list}
	do
		echo "${x}"
	done

	return 0
}

# bool query_before(service1, service2)
#
#   Return true if 'service2' should be started *before*
#   service1.
#
query_before() {
	local x=
	local list=
	local netservice="no"

	[ -z "$1" -o -z "$2" ] && return 1

	trace_depend "ineed" "$1" "list"

	for x in $1 ${list}
	do
		trace_depend "iuse" "${x}" "list"
	done

	for x in $1 ${list}
	do
		trace_depend "iafter" "${x}" "list"
	done

	net_service "$2" && netservice="yes"
	
	for x in ${list}
	do
		[ "${x}" = "$2" ] && return 0

		# Also match "net" if this is a network service ...
		[ "${netservice}" = "yes" -a "${x}" = "net" ] && return 0
	done

	return 1
}

# bool query_after(service1, service2)
#
#   Return true if 'service2' should be started *after*
#   service1.
#
query_after() {
	local x=
	local list=
	local netservice="no"

	[ -z "$1" -o -z "$2" ] && return 1

	trace_depend "needsme" "$1" "list"

	for x in $1 ${list}
	do
		trace_depend "usesme" "${x}" "list"
	done

	for x in $1 ${list}
	do
		trace_depend "ibefore" "${x}" "list"
	done

	net_service "$2" && netservice="yes"

	for x in ${list}
	do
		[ "${x}" = "$2" ] && return 0

		# Also match "net" if this is a network service ...
		[ "${netservice}" = "yes" -a "${x}" = "net" ] && return 0
	done

	return 1
}


# vim:ts=4
