From: Daniel Drake Implement per-init-script locking/serialization in baselayout Index: baselayout-1.12.9/etc/conf.d/rc =================================================================== --- baselayout-1.12.9.orig/etc/conf.d/rc +++ baselayout-1.12.9/etc/conf.d/rc @@ -244,3 +244,6 @@ svcfstype="tmpfs" # due to kernel limitations. svcsize=2048 + +svclock="/var/lib/init.d/locks" + Index: baselayout-1.12.9/sbin/functions.sh =================================================================== --- baselayout-1.12.9.orig/sbin/functions.sh +++ baselayout-1.12.9/sbin/functions.sh @@ -9,6 +9,7 @@ RC_GOT_FUNCTIONS="yes" # Check /etc/conf.d/rc for a description of these ... declare -r svclib="/lib/rcscripts" declare -r svcdir="${svcdir:-/var/lib/init.d}" +svclock=${svclock:-/var/lib/init.d/locks} svcmount="${svcmount:-no}" svcfstype="${svcfstype:-tmpfs}" svcsize="${svcsize:-1024}" Index: baselayout-1.12.9/sbin/rc =================================================================== --- baselayout-1.12.9.orig/sbin/rc +++ baselayout-1.12.9/sbin/rc @@ -517,6 +517,14 @@ then # Update the dependency cache /sbin/depscan.sh + # Create locking directory + if [[ ! -d "${svclock}" ]] ; then + if ! mkdir -p -m 0755 "${svclock}" 2>/dev/null ; then + eerror "Could not create needed directory '${svclock}'!" + fi + fi + rm -rf "${svclock}"/* 2>/dev/null + # Now that the dependency cache are up to date, make sure these # are marked as started ... ( @@ -689,11 +697,13 @@ else [[ -L ${x} ]] && COLDPLUG_SERVICES="${COLDPLUG_SERVICES} ${x##*/}" done for x in ${COLDPLUG_SERVICES} ; do + lock "${x}" if [[ ! -e /etc/runlevels/"${BOOTLEVEL}"/"${x}" \ && ! -e /etc/runlevels/"${DEFAULTLEVEL}"/"${x}" ]] ; then myscripts="${myscripts} ${x}" mark_service_coldplugged "${x}" fi + unlock "${x}" done if [[ -n ${myscripts} ]] ; then einfo "Device initiated services:${HILITE}${myscripts}${NORMAL}" @@ -737,6 +747,7 @@ get_stop_services() { reverse_list $(trace_dependencies ${list}) } +# Callers should hold lock dep_stop() { local dep needsme service="${1##*/}" @@ -776,7 +787,9 @@ if [[ ${SOFTLEVEL} != "single" && \ ${SOFTLEVEL} != "shutdown" ]] then for i in $(get_stop_services) ; do + lock "${i}" dep_stop "${i}" + unlock "${i}" done # Wait for any services that may still be stopping ... @@ -797,7 +810,11 @@ else # First stop non critical services for i in $(get_stop_services) ; do - is_critical_service "${i}" || dep_stop "${i}" + if ! is_critical_service "${i}"; then + lock "${i}" + dep_stop "${i}" + unlock "${i}" + fi done # Wait for any services that may still be stopping ... @@ -806,7 +823,9 @@ else export STOP_CRITICAL="yes" # Now stop the rest for i in $(get_stop_services) ; do + lock "${i}" dep_stop "${i}" + unlock "${i}" done unset STOP_CRITICAL fi @@ -861,9 +880,11 @@ get_start_services() { # Start scripts for i in $(get_start_services) ; do + lock "${i}" if service_stopped "${i}" ; then do_interactive start_service "${i}" fi + unlock "${i}" done # Wait for any services that may still be running ... Index: baselayout-1.12.9/sbin/rc-services.sh =================================================================== --- baselayout-1.12.9.orig/sbin/rc-services.sh +++ baselayout-1.12.9/sbin/rc-services.sh @@ -5,6 +5,7 @@ RC_GOT_SERVICES="yes" [[ ${RC_GOT_FUNCTIONS} != "yes" ]] && source /sbin/functions.sh +[[ ${RC_GOT_LOCKING} != "yes" ]] && source "${svclib}/sh/rc-locking.sh" if [[ ${RC_GOT_DEPTREE_INFO} != "yes" ]] ; then # Only try and update if we are root @@ -409,7 +410,11 @@ start_service() { # then just start it and return the exit status ( profiling name "/etc/init.d/${service} start" - "/etc/init.d/${service}" start + + SVCNAME="${service}" + source "${svclib}/sh/runscript-shared.sh" + source /etc/init.d/${service} + svc_start ) service_started "${service}" || service_inactive "${service}" \ @@ -470,7 +475,15 @@ stop_service() { ${STOP_CRITICAL} == "yes" ]] ; then # if we can not start the services in parallel # then just start it and return the exit status - ( "/etc/init.d/${service}" stop ) + ( + SVCNAME="${service}" + if [[ -e "${svcdir}/scheduled/${SVCNAME}" ]] ; then + rm -Rf "${svcdir}/scheduled/${SVCNAME}" + fi + source "${svclib}/sh/runscript-shared.sh" + source /etc/init.d/${service} + svc_stop + ) service_stopped "${service}" retval=$? end_service "${service}" "${retval}" @@ -741,15 +754,22 @@ is_net_up() { return 0 ;; lo) + lock "net.lo" service_started "net.lo" - return $? + retval="$?" + unlock "net.lo" + return ${retval} ;; yes) for x in $(dolisting "/etc/runlevels/${BOOTLEVEL}/net.*") \ $(dolisting "/etc/runlevels/${SOFTLEVEL}/net.*") ; do local y="${x##*/}" [[ ${y} == "$1" ]] && return 1 - service_started "${y}" || return 1 + lock "${y}" + service_started "${y}" + retval="$?" + unlock "${y}" + [[ ${retval} == 0 ]] || return 1 done return 0 ;; Index: baselayout-1.12.9/sbin/rc-locking.sh =================================================================== --- /dev/null +++ baselayout-1.12.9/sbin/rc-locking.sh @@ -0,0 +1,25 @@ +# Distributed under the terms of the GNU General Public License v2 + +RC_GOT_LOCKING="yes" + +[[ ${RC_GOT_FUNCTIONS} != "yes" ]] && source /sbin/functions.sh + +lock() { + [[ ! -w ${svclock} ]] && return 0 + local lock="${svclock}/$1" + + until mkfifo "${lock}" 2>/dev/null; do + cat "${lock}" 2>/dev/null + done +} + +unlock() { + [[ ! -w ${svclock} ]] && return 0 + local lock="${svclock}/$1" + local tmp="${lock}.$$" + + mv "${lock}" "${tmp}" + touch "${tmp}" + rm -f "${tmp}" +} + Index: baselayout-1.12.9/bin/rc-status =================================================================== --- baselayout-1.12.9.orig/bin/rc-status +++ baselayout-1.12.9/bin/rc-status @@ -32,6 +32,8 @@ runleveldir=/etc/runlevels # grab settings from conf.d/rc source "${svclib}/sh/rc-daemon.sh" +[[ ${RC_GOT_LOCKING} != "yes" ]] && source "${svclib}/sh/rc-locking.sh" + ################################################################################ # Parse command line options # ################################################################################ @@ -163,7 +165,9 @@ if [[ -x ${svcdir}/started ]]; then # stopped running without our say so if [[ ${EUID} == 0 ]]; then for service in ${started}; do + lock "${service}" update_service_status "${service}" + unlock "${service}" done started=$(ls ${svcdir}/started) fi Index: baselayout-1.12.9/sbin/runscript.sh =================================================================== --- baselayout-1.12.9.orig/sbin/runscript.sh +++ baselayout-1.12.9/sbin/runscript.sh @@ -20,19 +20,11 @@ else SVCNAME="$1" fi -declare -r SVCNAME="${SVCNAME##*/}" +SVCNAME="${SVCNAME##*/}" export SVCNAME # Support deprecated myservice variable myservice="${SVCNAME}" -svc_trap() { - trap 'eerror "ERROR: ${SVCNAME} caught an interrupt"; exit 1' \ - INT QUIT TSTP -} - -# Setup a default trap -svc_trap - # coldplug events can trigger init scripts, but we don't want to run them # until after rc sysinit has completed so we punt them to the boot runlevel if [[ -e /dev/.rcsysinit ]] ; then @@ -69,51 +61,7 @@ if [[ ${IN_HOTPLUG} == "1" ]] ; then set +f fi -# State variables -svcpause="no" - -# Functions to handle dependencies and services -[[ ${RC_GOT_SERVICES} != "yes" ]] && source "${svclib}/sh/rc-services.sh" -# Functions to control daemons -[[ ${RC_GOT_DAEMON} != "yes" ]] && source "${svclib}/sh/rc-daemon.sh" - -# Source configuration files. -# (1) Source /etc/conf.d/net if it is a net.* service -# (2) Source /etc/conf.d/${SVCNAME} to get initscript-specific -# configuration (if it exists). -# (3) Source /etc/rc.conf to pick up potentially overriding -# configuration, if the system administrator chose to put it -# there (if it exists). -if net_service "${SVCNAME}" ; then - conf="$(add_suffix /etc/conf.d/net)" - [[ -e ${conf} ]] && source "${conf}" -fi -conf="$(add_suffix "/etc/conf.d/${SVCNAME}")" -[[ -e ${conf} ]] && source "${conf}" -conf="$(add_suffix /etc/rc.conf)" -[[ -e ${conf} ]] && source "${conf}" - -mylevel="${SOFTLEVEL}" -[[ ${SOFTLEVEL} == "${BOOTLEVEL}" \ - || ${SOFTLEVEL} == "reboot" || ${SOFTLEVEL} == "shutdown" ]] \ - && mylevel="${DEFAULTLEVEL}" - -# Call svc_quit if we abort AND we have obtained a lock -service_started "${SVCNAME}" -svcstarted="$?" -service_inactive "${SVCNAME}" -svcinactive="$?" -svc_quit() { - eerror "ERROR: ${SVCNAME} caught an interrupt" - if service_inactive "${SVCNAME}" || [[ ${svcinactive} == "0" ]] ; then - mark_service_inactive "${SVCNAME}" - elif [[ ${svcstarted} == "0" ]] ; then - mark_service_started "${SVCNAME}" - else - mark_service_stopped "${SVCNAME}" - fi - exit 1 -} +source "${svclib}/sh/runscript-shared.sh" usage() { local IFS="|" @@ -123,461 +71,6 @@ usage() { eerror " ${SVCNAME} without arguments for full help" } -stop() { - # Return success so the symlink gets removed - return 0 -} - -start() { - eerror "ERROR: ${SVCNAME} does not have a start function." - # Return failure so the symlink doesn't get created - return 1 -} - -restart() { - if ! service_stopped "${SVCNAME}" ; then - svc_stop || return "$?" - fi - svc_start -} - -status() { - # Dummy function - return 0 -} - -svc_schedule_start() { - local service="$1" start="$2" - [[ ! -d "${svcdir}/scheduled/${service}" ]] \ - && mkdir -p "${svcdir}/scheduled/${service}" - ln -snf "/etc/init.d/${service}" \ - "${svcdir}/scheduled/${service}/${start}" -} - -svc_start_scheduled() { - [[ ! -d "${svcdir}/scheduled/${SVCNAME}" ]] && return - local x= services= - - # If we're being started in the background, then don't - # tie up the daemon that called us starting our scheduled services - if [[ ${IN_BACKGROUND} == "true" || ${IN_BACKGROUND} == "1" ]] ; then - unset IN_BACKGROUND - svc_start_scheduled & - export IN_BACKGROUND=true - return - fi - - for x in $(dolisting "${svcdir}/scheduled/${SVCNAME}/") ; do - services="${services} ${x##*/}" - done - - for x in $(trace_dependencies ${services}) ; do - service_stopped "${x}" && start_service "${x}" - rm -f "${svcdir}/scheduled/${SVCNAME}/${x}" - done - - rmdir "${svcdir}/scheduled/${SVCNAME}" -} - -svc_stop() { - local x= mydep= mydeps= retval=0 - local -a servicelist=() - - # Do not try to stop if it had already failed to do so - if is_runlevel_stop && service_failed "${SVCNAME}" ; then - return 1 - elif service_stopped "${SVCNAME}" ; then - if [[ ${IN_HOTPLUG} != "1" ]] ; then - ewarn "WARNING: ${SVCNAME} has not yet been started." - fi - return 0 - fi - if ! mark_service_stopping "${SVCNAME}" ; then - if [[ ${IN_HOTPLUG} != "1" ]] ; then - eerror "ERROR: ${SVCNAME} is already stopping." - fi - return 1 - fi - - # Ensure that we clean up if we abort for any reason - trap "svc_quit" INT QUIT TSTP - - mark_service_starting "${SVCNAME}" - service_message "Service ${SVCNAME} stopping" - - if in_runlevel "${SVCNAME}" "${BOOTLEVEL}" && \ - [[ ${SOFTLEVEL} != "reboot" && ${SOFTLEVEL} != "shutdown" && \ - ${SOFTLEVEL} != "single" ]] ; then - ewarn "WARNING: you are stopping a boot service." - fi - - if [[ ${svcpause} != "yes" && ${RC_NO_DEPS} != "yes" ]] \ - && ! service_wasinactive "${SVCNAME}" ; then - if net_service "${SVCNAME}" ; then - if is_runlevel_stop || ! is_net_up "${SVCNAME}" ; then - mydeps="net" - fi - fi - mydeps="${mydeps} ${SVCNAME}" - fi - - # Save the IN_BACKGROUND var as we need to clear it for stopping depends - local ib_save="${IN_BACKGROUND}" - unset IN_BACKGROUND - - for mydep in ${mydeps} ; do - for x in $(needsme "${mydep}") ; do - if service_started "${x}" || service_inactive "${x}" ; then - stop_service "${x}" - fi - service_list=( "${service_list[@]}" "${x}" ) - done - done - - for x in "${service_list[@]}" ; do - service_stopped "${x}" && continue - wait_service "${x}" - if ! service_stopped "${x}" ; then - eerror "ERROR: cannot stop ${SVCNAME} as ${x} is still up." - retval=1 - break - fi - done - - IN_BACKGROUND="${ib_save}" - - if [[ ${retval} == "0" ]] ; then - # Now that deps are stopped, stop our service - ( - exit() { - RC_QUIET_STDOUT="no" - eerror "DO NOT USE EXIT IN INIT.D SCRIPTS" - eerror "This IS a bug, please fix your broken init.d" - unset -f exit - exit "$@" - } - # Stop einfo/ebegin/eend from working as parallel messes us up - if [[ ${RC_PARALLEL_STARTUP} == "yes" ]] ; then - [[ ${RC_VERBOSE} != "yes" \ - || -e ${svcdir}/exclusive/${SVCNAME} ]] \ - && RC_QUIET_STDOUT="yes" - fi - stop - ) - retval="$?" - - # If a service has been marked inactive, exit now as something - # may attempt to start it again later - if [[ ${retval} == "0" ]] && service_inactive "${SVCNAME}" ; then - svcinactive=0 - return 0 - fi - fi - - if [[ ${retval} != 0 ]] ; then - # Did we fail to stop? create symlink to stop multible attempts at - # runlevel change. Note this is only used at runlevel change ... - is_runlevel_stop && mark_service_failed "${SVCNAME}" - - # If we are halting the system, do it as cleanly as possible - if [[ ${SOFTLEVEL} == "reboot" || ${SOFTLEVEL} == "shutdown" ]] ; then - mark_service_stopped "${SVCNAME}" - else - if [[ ${svcinactive} == "0" ]] ; then - mark_service_inactive "${SVCNAME}" - else - mark_service_started "${SVCNAME}" - fi - fi - - service_message "eerror" "ERROR: ${SVCNAME} failed to stop" - else - svcstarted=1 - if service_inactive "${SVCNAME}" ; then - svcinactive=0 - else - mark_service_stopped "${SVCNAME}" - fi - service_message "Service ${SVCNAME} stopped" - fi - - # Reset the trap - svc_trap - - return "${retval}" -} - -svc_start() { - local x= y= retval=0 startinactive= - - # Do not try to start if i have done so already on runlevel change - if is_runlevel_start && service_failed "${SVCNAME}" ; then - return 1 - elif service_started "${SVCNAME}" ; then - if [[ ${IN_HOTPLUG} != "1" ]] ; then - ewarn "WARNING: ${SVCNAME} has already been started." - fi - return 0 - elif service_inactive "${SVCNAME}" ; then - if [[ ${IN_BACKGROUND} != "true" ]] ; then - if [[ ${IN_HOTPLUG} != "1" ]] ; then - ewarn "WARNING: ${SVCNAME} has already been started." - fi - return 0 - fi - fi - - if ! mark_service_starting "${SVCNAME}" ; then - if service_stopping "${SVCNAME}" ; then - eerror "ERROR: ${SVCNAME} is already stopping." - else - eerror "ERROR: ${SVCNAME} is already starting." - fi - return 1 - fi - - # Ensure that we clean up if we abort for any reason - trap "svc_quit" INT QUIT TSTP - - service_message "Service ${SVCNAME} starting" - - if broken "${SVCNAME}" ; then - eerror "ERROR: Some services needed are missing. Run" - eerror " './${SVCNAME} broken' for a list of those" - eerror " services. ${SVCNAME} was not started." - retval=1 - fi - - # Save the IN_BACKGROUND var as we need to clear it for starting depends - local ib_save="${IN_BACKGROUND}" - unset IN_BACKGROUND - - if [[ ${retval} == "0" && ${RC_NO_DEPS} != "yes" ]] ; then - local startupservices="$(ineed "${SVCNAME}") $(valid_iuse "${SVCNAME}")" - local netservices= - for x in $(dolisting "/etc/runlevels/${BOOTLEVEL}/net.*") \ - $(dolisting "/etc/runlevels/${mylevel}/net.*") \ - $(dolisting "/var/lib/init.d/coldplugged/net.*") ; do - netservices="${netservices} ${x##*/}" - done - - # Start dependencies, if any. - if ! is_runlevel_start ; then - for x in ${startupservices} ; do - if [[ ${x} == "net" ]] && ! net_service "${SVCNAME}" \ - && ! is_net_up ; then - for y in ${netservices} ; do - service_stopped "${y}" && start_service "${y}" - done - elif [[ ${x} != "net" ]] ; then - service_stopped "${x}" && start_service "${x}" - fi - done - fi - - # We also wait for any services we're after to finish incase they - # have a "before" dep but we don't dep on them. - if is_runlevel_start ; then - startupservices="${startupservices} $(valid_iafter "${SVCNAME}")" - if net_service "${SVCNAME}" ; then - startupservices="${startupservices} $(valid_iafter "net")" - fi - fi - - if [[ " ${startupservices} " == *" net "* ]] ; then - startupservices=" ${startupservices} " - startupservices="${startupservices/ net / ${netservices} }" - startupservices="${startupservices// net /}" - fi - - # Wait for dependencies to finish. - for x in ${startupservices} ; do - service_started "${x}" && continue - wait_service "${x}" - if ! service_started "${x}" ; then - # A 'need' dependency is critical for startup - if ineed -t "${SVCNAME}" "${x}" >/dev/null \ - || ( net_service "${x}" && ineed -t "${SVCNAME}" net \ - && ! is_net_up ) ; then - if service_inactive "${x}" || service_wasinactive "${x}" || \ - [[ -n $(dolisting "${svcdir}"/scheduled/*/"${x}") ]] ; then - svc_schedule_start "${x}" "${SVCNAME}" - [[ -n ${startinactive} ]] && startinactive="${startinactive}, " - startinactive="${startinactive}${x}" - else - eerror "ERROR: cannot start ${SVCNAME} as ${x} could not start" - retval=1 - break - fi - fi - fi - done - - if [[ -n ${startinactive} && ${retval} == "0" ]] ; then - # Change the last , to or for correct grammar. - x="${startinactive##*, }" - startinactive="${startinactive/%, ${x}/ or ${x}}" - ewarn "WARNING: ${SVCNAME} is scheduled to start when ${startinactive} has started." - retval=1 - fi - fi - - if [[ ${retval} == "0" ]] ; then - IN_BACKGROUND="${ib_save}" - ( - exit() { - RC_QUIET_STDOUT="no" - eerror "DO NOT USE EXIT IN INIT.D SCRIPTS" - eerror "This IS a bug, please fix your broken init.d" - unset -f exit - exit "$@" - } - - # Apply any ulimits if defined - [[ -n ${RC_ULIMIT} ]] && ulimit ${RC_ULIMIT} - - # Stop einfo/ebegin/eend from working as parallel messes us up - if [[ ${RC_PARALLEL_STARTUP} == "yes" ]] ; then - [[ ${RC_VERBOSE} != "yes" \ - || -e ${svcdir}/exclusive/${SVCNAME} ]] \ - && RC_QUIET_STDOUT="yes" - fi - - start - ) - retval="$?" - - # If a service has been marked inactive, exit now as something - # may attempt to start it again later - if [[ ${retval} == "0" ]] && service_inactive "${SVCNAME}" ; then - svcinactive=0 - service_message "ewarn" "WARNING: ${SVCNAME} has started but is inactive" - return 1 - fi - fi - - if [[ ${retval} != "0" ]] ; then - if [[ ${svcinactive} == "0" ]] ; then - mark_service_inactive "${SVCNAME}" - else - mark_service_stopped "${SVCNAME}" - fi - - if [[ -z ${startinactive} ]] ; then - is_runlevel_start && mark_service_failed "${SVCNAME}" - service_message "eerror" "ERROR: ${SVCNAME} failed to start" - fi - else - svcstarted=0 - mark_service_started "${SVCNAME}" - service_message "Service ${SVCNAME} started" - fi - - # Reset the trap - svc_trap - - return "${retval}" -} - -svc_restart() { - # We don't kill child processes if we're restarting - # This is especically important for sshd .... - RC_KILL_CHILDREN="no" - - # Create a snapshot of started services - rm -rf "${svcdir}/snapshot/$$" - mkdir -p "${svcdir}/snapshot/$$" - cp -pPR "${svcdir}"/started/* "${svcdir}"/inactive/* \ - "${svcdir}/snapshot/$$/" 2>/dev/null - rm -f "${svcdir}/snapshot/$$/${SVCNAME}" - - # Simple way to try and detect if the service use svc_{start,stop} - # to restart if it have a custom restart() funtion. - if [[ -n $(egrep '^[[:space:]]*restart[[:space:]]*()' "/etc/init.d/${SVCNAME}") ]] ; then - if [[ -z $(egrep 'svc_stop' "/etc/init.d/${SVCNAME}") || \ - -z $(egrep 'svc_start' "/etc/init.d/${SVCNAME}") ]] ; then - echo - ewarn "Please use 'svc_stop; svc_start' and not 'stop; start' to" - ewarn "restart the service in its custom 'restart()' function." - ewarn "Run ${SVCNAME} without arguments for more info." - echo - if ! service_stopped "${SVCNAME}" ; then - svc_stop || return "$?" - fi - svc_start - else - restart - fi - else - restart - fi - retval="$?" - - [[ -e "${svcdir}/scheduled/${SVCNAME}" ]] \ - && rm -Rf "${svcdir}/scheduled/${SVCNAME}" - - # Restart dependencies as well - for x in $(dolisting "${svcdir}/snapshot/$$/") ; do - x="${x##*/}" - if [[ -x /etc/init.d/"${x}" ]] && service_stopped "${x}" ; then - if service_inactive "${SVCNAME}" \ - || service_wasinactive "${SVCNAME}" ; then - svc_schedule_start "${SVCNAME}" "${x}" - ewarn "WARNING: ${x} is scheduled to start when ${SVCNAME} has started." - elif service_started "${SVCNAME}" ; then - start_service "${x}" - fi - fi - done - rm -rf "${svcdir}/snapshot/$$" - - service_started "${SVCNAME}" && svc_start_scheduled - - # Wait for services to come up - if [[ ${IN_BACKGROUND} != "true" \ - && ${IN_BACKGROUND} != "1" ]] ; then - [[ ${RC_PARALLEL_STARTUP} == "yes" ]] && wait - fi - - return ${retval} -} - -svc_status() { - # The basic idea here is to have some sort of consistent - # output in the status() function which scripts can use - # as an generic means to detect status. Any other output - # should thus be formatted in the custom status() function - # to work with the printed " * status: foo". - local efunc="" state="" - - # If we are effectively root, check to see if required daemons are running - # and update our status accordingly - [[ ${EUID} == 0 ]] && update_service_status "${SVCNAME}" - - if service_stopping "${SVCNAME}" ; then - efunc="eerror" - state="stopping" - elif service_starting "${SVCNAME}" ; then - efunc="einfo" - state="starting" - elif service_inactive "${SVCNAME}" ; then - efunc="ewarn" - state="inactive" - elif service_started "${SVCNAME}" ; then - efunc="einfo" - state="started" - else - efunc="eerror" - state="stopped" - fi - [[ ${RC_QUIET_STDOUT} != "yes" ]] \ - && ${efunc} "status: ${state}" - - status - # Return 0 if started, otherwise 1 - [[ ${state} == "started" ]] -} - rcscript_errors="$(bash -n "${myscript}" 2>&1)" || { [[ -n ${rcscript_errors} ]] && echo "${rcscript_errors}" >&2 eerror "ERROR: ${myscript} has syntax errors in it; aborting ..." @@ -649,6 +142,7 @@ retval=0 for arg in "$@" ; do case "${arg}" in stop) + lock "${SVCNAME}" if [[ -e "${svcdir}/scheduled/${SVCNAME}" ]] ; then rm -Rf "${svcdir}/scheduled/${SVCNAME}" fi @@ -669,46 +163,63 @@ for arg in "$@" ; do if [[ ${IN_BACKGROUND} == "true" ]] ; then for x in $(dolisting "${svcdir}/snapshot/$$/") ; do x="${x##*/}" + lock "${x}" if [[ -x /etc/init.d/"${x}" ]] && service_stopped "${x}" ; then svc_schedule_start "${SVCNAME}" "${x}" fi + unlock "${x}" done rm -rf "${svcdir}/snapshot/$$" else rm -f "${svcdir}"/scheduled/*/"${SVCNAME}" fi + unlock "${SVCNAME}" ;; start) + lock "${SVCNAME}" svc_start retval="$?" - service_started "${SVCNAME}" && svc_start_scheduled + service_started "${SVCNAME}" + started="$?" + unlock "${SVCNAME}" + [[ ${started} == 0 ]] && svc_start_scheduled ;; needsme|ineed|usesme|iuse|broken) trace_dependencies "-${arg}" ;; status) + lock "${SVCNAME}" svc_status retval="$?" + unlock "${SVCNAME}" ;; zap) einfo "Manually resetting ${SVCNAME} to stopped state." + lock "${SVCNAME}" mark_service_stopped "${SVCNAME}" + unlock "${SVCNAME}" ;; restart) + lock "${SVCNAME}" svc_restart retval="$?" + unlock "${SVCNAME}" ;; condrestart|conditionalrestart) + lock "${SVCNAME}" if service_started "${SVCNAME}" ; then svc_restart fi retval="$?" + unlock "${SVCNAME}" ;; pause) svcpause="yes" + lock "${SVCNAME}" svc_stop retval="$?" + unlock "${SVCNAME}" svcpause="no" ;; --quiet|--nocolor|--nodeps|--verbose|--debug) Index: baselayout-1.12.9/sbin/runscript-shared.sh =================================================================== --- /dev/null +++ baselayout-1.12.9/sbin/runscript-shared.sh @@ -0,0 +1,534 @@ +[[ ${RC_GOT_FUNCTIONS} != "yes" ]] && source /sbin/functions.sh + +# State variables +svcpause="no" + +# Functions to handle dependencies and services +[[ ${RC_GOT_SERVICES} != "yes" ]] && source "${svclib}/sh/rc-services.sh" +# Functions to control daemons +[[ ${RC_GOT_DAEMON} != "yes" ]] && source "${svclib}/sh/rc-daemon.sh" + +# Source configuration files. +# (1) Source /etc/conf.d/net if it is a net.* service +# (2) Source /etc/conf.d/${SVCNAME} to get initscript-specific +# configuration (if it exists). +# (3) Source /etc/rc.conf to pick up potentially overriding +# configuration, if the system administrator chose to put it +# there (if it exists). +if net_service "${SVCNAME}" ; then + conf="$(add_suffix /etc/conf.d/net)" + [[ -e ${conf} ]] && source "${conf}" +fi +conf="$(add_suffix "/etc/conf.d/${SVCNAME}")" +[[ -e ${conf} ]] && source "${conf}" +conf="$(add_suffix /etc/rc.conf)" +[[ -e ${conf} ]] && source "${conf}" + +mylevel="${SOFTLEVEL}" +[[ ${SOFTLEVEL} == "${BOOTLEVEL}" \ + || ${SOFTLEVEL} == "reboot" || ${SOFTLEVEL} == "shutdown" ]] \ + && mylevel="${DEFAULTLEVEL}" + +# Call svc_quit if we abort AND we have obtained a lock +#lock "${SVCNAME}" +#service_started "${SVCNAME}" +#svcstarted="$?" +#service_inactive "${SVCNAME}" +#svcinactive="$?" +#unlock "${SVCNAME}" + +svc_trap() { + trap 'eerror "ERROR: ${SVCNAME} caught an interrupt"; exit 1' \ + INT QUIT TSTP +} + +# Setup a default trap +svc_trap + +svc_quit() { + eerror "ERROR: ${SVCNAME} caught an interrupt" + if service_inactive "${SVCNAME}" || [[ ${svcinactive} == "0" ]] ; then + mark_service_inactive "${SVCNAME}" + elif [[ ${svcstarted} == "0" ]] ; then + mark_service_started "${SVCNAME}" + else + mark_service_stopped "${SVCNAME}" + fi + exit 1 +} + +stop() { + # Return success so the symlink gets removed + return 0 +} + +start() { + eerror "ERROR: ${SVCNAME} does not have a start function." + # Return failure so the symlink doesn't get created + return 1 +} + +restart() { + if ! service_stopped "${SVCNAME}" ; then + svc_stop || return "$?" + fi + svc_start +} + +status() { + # Dummy function + return 0 +} + +svc_schedule_start() { + local service="$1" start="$2" + + [[ ! -d "${svcdir}/scheduled/${service}" ]] \ + && mkdir -p "${svcdir}/scheduled/${service}" + ln -snf "/etc/init.d/${service}" \ + "${svcdir}/scheduled/${service}/${start}" +} + +svc_start_scheduled() { + [[ ! -d "${svcdir}/scheduled/${SVCNAME}" ]] && return + local x= services= + + # If we're being started in the background, then don't + # tie up the daemon that called us starting our scheduled services + if [[ ${IN_BACKGROUND} == "true" || ${IN_BACKGROUND} == "1" ]] ; then + unset IN_BACKGROUND + svc_start_scheduled & + export IN_BACKGROUND=true + return + fi + + lock "${SVCNAME}" + for x in $(dolisting "${svcdir}/scheduled/${SVCNAME}/") ; do + services="${services} ${x##*/}" + done + unlock "${SVCNAME}" + + for x in $(trace_dependencies ${services}) ; do + lock "${x}" + service_stopped "${x}" && start_service "${x}" + unlock "${x}" + rm -f "${svcdir}/scheduled/${SVCNAME}/${x}" + done + + rmdir "${svcdir}/scheduled/${SVCNAME}" +} + +svc_stop() { + local x= mydep= mydeps= retval=0 + local -a servicelist=() + + # Do not try to stop if it had already failed to do so + if is_runlevel_stop && service_failed "${SVCNAME}" ; then + return 1 + elif service_stopped "${SVCNAME}" ; then + if [[ ${IN_HOTPLUG} != "1" ]] ; then + ewarn "WARNING: ${SVCNAME} has not yet been started." + fi + return 0 + fi + if ! mark_service_stopping "${SVCNAME}" ; then + if [[ ${IN_HOTPLUG} != "1" ]] ; then + eerror "ERROR: ${SVCNAME} is already stopping." + fi + return 1 + fi + + # Ensure that we clean up if we abort for any reason + trap "svc_quit" INT QUIT TSTP + + mark_service_starting "${SVCNAME}" + service_message "Service ${SVCNAME} stopping" + + if in_runlevel "${SVCNAME}" "${BOOTLEVEL}" && \ + [[ ${SOFTLEVEL} != "reboot" && ${SOFTLEVEL} != "shutdown" && \ + ${SOFTLEVEL} != "single" ]] ; then + ewarn "WARNING: you are stopping a boot service." + fi + + if [[ ${svcpause} != "yes" && ${RC_NO_DEPS} != "yes" ]] \ + && ! service_wasinactive "${SVCNAME}" ; then + if net_service "${SVCNAME}" ; then + if is_runlevel_stop || ! is_net_up "${SVCNAME}" ; then + mydeps="net" + fi + fi + mydeps="${mydeps} ${SVCNAME}" + fi + + # Save the IN_BACKGROUND var as we need to clear it for stopping depends + local ib_save="${IN_BACKGROUND}" + unset IN_BACKGROUND + + for mydep in ${mydeps} ; do + for x in $(needsme "${mydep}") ; do + if service_started "${x}" || service_inactive "${x}" ; then + stop_service "${x}" + fi + service_list=( "${service_list[@]}" "${x}" ) + done + done + + for x in "${service_list[@]}" ; do + service_stopped "${x}" && continue + wait_service "${x}" + if ! service_stopped "${x}" ; then + eerror "ERROR: cannot stop ${SVCNAME} as ${x} is still up." + retval=1 + break + fi + done + + IN_BACKGROUND="${ib_save}" + + if [[ ${retval} == "0" ]] ; then + # Now that deps are stopped, stop our service + ( + exit() { + RC_QUIET_STDOUT="no" + eerror "DO NOT USE EXIT IN INIT.D SCRIPTS" + eerror "This IS a bug, please fix your broken init.d" + unset -f exit + exit "$@" + } + # Stop einfo/ebegin/eend from working as parallel messes us up + if [[ ${RC_PARALLEL_STARTUP} == "yes" ]] ; then + [[ ${RC_VERBOSE} != "yes" \ + || -e ${svcdir}/exclusive/${SVCNAME} ]] \ + && RC_QUIET_STDOUT="yes" + fi + stop + ) + retval="$?" + + # If a service has been marked inactive, exit now as something + # may attempt to start it again later + if [[ ${retval} == "0" ]] && service_inactive "${SVCNAME}" ; then + svcinactive=0 + return 0 + fi + fi + + if [[ ${retval} != 0 ]] ; then + # Did we fail to stop? create symlink to stop multible attempts at + # runlevel change. Note this is only used at runlevel change ... + is_runlevel_stop && mark_service_failed "${SVCNAME}" + + # If we are halting the system, do it as cleanly as possible + if [[ ${SOFTLEVEL} == "reboot" || ${SOFTLEVEL} == "shutdown" ]] ; then + mark_service_stopped "${SVCNAME}" + else + if [[ ${svcinactive} == "0" ]] ; then + mark_service_inactive "${SVCNAME}" + else + mark_service_started "${SVCNAME}" + fi + fi + + service_message "eerror" "ERROR: ${SVCNAME} failed to stop" + else + svcstarted=1 + if service_inactive "${SVCNAME}" ; then + svcinactive=0 + else + mark_service_stopped "${SVCNAME}" + fi + service_message "Service ${SVCNAME} stopped" + fi + + # Reset the trap + svc_trap + + return "${retval}" +} + +svc_start() { + local x= y= retval=0 startinactive= + + # Do not try to start if i have done so already on runlevel change + if is_runlevel_start && service_failed "${SVCNAME}" ; then + return 1 + elif service_started "${SVCNAME}" ; then + if [[ ${IN_HOTPLUG} != "1" ]] ; then + ewarn "WARNING: ${SVCNAME} has already been started." + fi + return 0 + elif service_inactive "${SVCNAME}" ; then + if [[ ${IN_BACKGROUND} != "true" ]] ; then + if [[ ${IN_HOTPLUG} != "1" ]] ; then + ewarn "WARNING: ${SVCNAME} has already been started." + fi + return 0 + fi + fi + + if ! mark_service_starting "${SVCNAME}" ; then + if service_stopping "${SVCNAME}" ; then + eerror "ERROR: ${SVCNAME} is already stopping." + else + eerror "ERROR: ${SVCNAME} is already starting." + fi + return 1 + fi + + # Ensure that we clean up if we abort for any reason + trap "svc_quit" INT QUIT TSTP + + service_message "Service ${SVCNAME} starting" + + if broken "${SVCNAME}" ; then + eerror "ERROR: Some services needed are missing. Run" + eerror " './${SVCNAME} broken' for a list of those" + eerror " services. ${SVCNAME} was not started." + retval=1 + fi + + # Save the IN_BACKGROUND var as we need to clear it for starting depends + local ib_save="${IN_BACKGROUND}" + unset IN_BACKGROUND + + if [[ ${retval} == "0" && ${RC_NO_DEPS} != "yes" ]] ; then + local startupservices="$(ineed "${SVCNAME}") $(valid_iuse "${SVCNAME}")" + local netservices= + for x in $(dolisting "/etc/runlevels/${BOOTLEVEL}/net.*") \ + $(dolisting "/etc/runlevels/${mylevel}/net.*") \ + $(dolisting "/var/lib/init.d/coldplugged/net.*") ; do + netservices="${netservices} ${x##*/}" + done + + # Start dependencies, if any. + if ! is_runlevel_start ; then + for x in ${startupservices} ; do + if [[ ${x} == "net" ]] && ! net_service "${SVCNAME}" \ + && ! is_net_up ; then + for y in ${netservices} ; do + service_stopped "${y}" && start_service "${y}" + done + elif [[ ${x} != "net" ]] ; then + service_stopped "${x}" && start_service "${x}" + fi + done + fi + + # We also wait for any services we're after to finish incase they + # have a "before" dep but we don't dep on them. + if is_runlevel_start ; then + startupservices="${startupservices} $(valid_iafter "${SVCNAME}")" + if net_service "${SVCNAME}" ; then + startupservices="${startupservices} $(valid_iafter "net")" + fi + fi + + if [[ " ${startupservices} " == *" net "* ]] ; then + startupservices=" ${startupservices} " + startupservices="${startupservices/ net / ${netservices} }" + startupservices="${startupservices// net /}" + fi + + # Wait for dependencies to finish. + for x in ${startupservices} ; do + lock "${x}" + if service_started "${x}"; then + unlock "${x}" + continue + fi + wait_service "${x}" + if ! service_started "${x}" ; then + # A 'need' dependency is critical for startup + if ineed -t "${SVCNAME}" "${x}" >/dev/null \ + || ( net_service "${x}" && ineed -t "${SVCNAME}" net \ + && ! is_net_up ) ; then + if service_inactive "${x}" || service_wasinactive "${x}" || \ + [[ -n $(dolisting "${svcdir}"/scheduled/*/"${x}") ]] ; then + svc_schedule_start "${x}" "${SVCNAME}" + [[ -n ${startinactive} ]] && startinactive="${startinactive}, " + startinactive="${startinactive}${x}" + else + eerror "ERROR: cannot start ${SVCNAME} as ${x} could not start" + retval=1 + unlock "${x}" + break + fi + fi + fi + unlock "${x}" + done + + if [[ -n ${startinactive} && ${retval} == "0" ]] ; then + # Change the last , to or for correct grammar. + x="${startinactive##*, }" + startinactive="${startinactive/%, ${x}/ or ${x}}" + ewarn "WARNING: ${SVCNAME} is scheduled to start when ${startinactive} has started." + retval=1 + fi + fi + + if [[ ${retval} == "0" ]] ; then + IN_BACKGROUND="${ib_save}" + ( + exit() { + RC_QUIET_STDOUT="no" + eerror "DO NOT USE EXIT IN INIT.D SCRIPTS" + eerror "This IS a bug, please fix your broken init.d" + unset -f exit + exit "$@" + } + + # Apply any ulimits if defined + [[ -n ${RC_ULIMIT} ]] && ulimit ${RC_ULIMIT} + + # Stop einfo/ebegin/eend from working as parallel messes us up + if [[ ${RC_PARALLEL_STARTUP} == "yes" ]] ; then + [[ ${RC_VERBOSE} != "yes" \ + || -e ${svcdir}/exclusive/${SVCNAME} ]] \ + && RC_QUIET_STDOUT="yes" + fi + + start + ) + retval="$?" + + # If a service has been marked inactive, exit now as something + # may attempt to start it again later + if [[ ${retval} == "0" ]] && service_inactive "${SVCNAME}" ; then + svcinactive=0 + service_message "ewarn" "WARNING: ${SVCNAME} has started but is inactive" + return 1 + fi + fi + + if [[ ${retval} != "0" ]] ; then + if [[ ${svcinactive} == "0" ]] ; then + mark_service_inactive "${SVCNAME}" + else + mark_service_stopped "${SVCNAME}" + fi + + if [[ -z ${startinactive} ]] ; then + is_runlevel_start && mark_service_failed "${SVCNAME}" + service_message "eerror" "ERROR: ${SVCNAME} failed to start" + fi + else + svcstarted=0 + mark_service_started "${SVCNAME}" + service_message "Service ${SVCNAME} started" + fi + + # Reset the trap + svc_trap + + return "${retval}" +} + +svc_restart() { + # We don't kill child processes if we're restarting + # This is especically important for sshd .... + RC_KILL_CHILDREN="no" + + # Create a snapshot of started services + rm -rf "${svcdir}/snapshot/$$" + mkdir -p "${svcdir}/snapshot/$$" + cp -pPR "${svcdir}"/started/* "${svcdir}"/inactive/* \ + "${svcdir}/snapshot/$$/" 2>/dev/null + rm -f "${svcdir}/snapshot/$$/${SVCNAME}" + + # Simple way to try and detect if the service use svc_{start,stop} + # to restart if it have a custom restart() funtion. + if [[ -n $(egrep '^[[:space:]]*restart[[:space:]]*()' "/etc/init.d/${SVCNAME}") ]] ; then + if [[ -z $(egrep 'svc_stop' "/etc/init.d/${SVCNAME}") || \ + -z $(egrep 'svc_start' "/etc/init.d/${SVCNAME}") ]] ; then + echo + ewarn "Please use 'svc_stop; svc_start' and not 'stop; start' to" + ewarn "restart the service in its custom 'restart()' function." + ewarn "Run ${SVCNAME} without arguments for more info." + echo + if ! service_stopped "${SVCNAME}" ; then + svc_stop || return "$?" + fi + svc_start + else + restart + fi + else + restart + fi + retval="$?" + + [[ -e "${svcdir}/scheduled/${SVCNAME}" ]] \ + && rm -Rf "${svcdir}/scheduled/${SVCNAME}" + + # Restart dependencies as well + for x in $(dolisting "${svcdir}/snapshot/$$/") ; do + x="${x##*/}" + lock "${x}" + if [[ -x /etc/init.d/"${x}" ]] && service_stopped "${x}" ; then + if service_inactive "${SVCNAME}" \ + || service_wasinactive "${SVCNAME}" ; then + svc_schedule_start "${SVCNAME}" "${x}" + ewarn "WARNING: ${x} is scheduled to start when ${SVCNAME} has started." + elif service_started "${SVCNAME}" ; then + start_service "${x}" + fi + fi + unlock "${x}" + done + rm -rf "${svcdir}/snapshot/$$" + + service_started "${SVCNAME}" + started="$?" + if [[ ${started} == 0 ]]; then + unlock "${SVCNAME}" + svc_start_scheduled + lock "${SVCNAME}" + fi + + # Wait for services to come up + if [[ ${IN_BACKGROUND} != "true" \ + && ${IN_BACKGROUND} != "1" ]] ; then + [[ ${RC_PARALLEL_STARTUP} == "yes" ]] && wait + fi + + return ${retval} +} + +svc_status() { + # The basic idea here is to have some sort of consistent + # output in the status() function which scripts can use + # as an generic means to detect status. Any other output + # should thus be formatted in the custom status() function + # to work with the printed " * status: foo". + local efunc="" state="" + + # If we are effectively root, check to see if required daemons are running + # and update our status accordingly + [[ ${EUID} == 0 ]] && update_service_status "${SVCNAME}" + + if service_stopping "${SVCNAME}" ; then + efunc="eerror" + state="stopping" + elif service_starting "${SVCNAME}" ; then + efunc="einfo" + state="starting" + elif service_inactive "${SVCNAME}" ; then + efunc="ewarn" + state="inactive" + elif service_started "${SVCNAME}" ; then + efunc="einfo" + state="started" + else + efunc="eerror" + state="stopped" + fi + + [[ ${RC_QUIET_STDOUT} != "yes" ]] \ + && ${efunc} "status: ${state}" + + status + # Return 0 if started, otherwise 1 + [[ ${state} == "started" ]] +} +