First process
2nd process
rc (or whatever) invokes net.ath0 start
we soon get to svc_start, and all of this code gets executed:
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
ewarn $"WARNING:" " ${SVCNAME}" $"has already been started."
return 0
elif service_inactive "${SVCNAME}" ; then
if [[ ${IN_BACKGROUND} != "true" \
&& ${IN_BACKGROUND} != "1" ]] ; then
ewarn $"WARNING:" " ${SVCNAME}" $"has already been started."
return 0
fi
elif [[ ${SOFTLEVEL} == "shutdown" || ${SOFTLEVEL} == "reboot" ]] ; then
ewarn $"WARNING: system shutting down, will not start" "${SVCNAME}"
return 1
elif [[ ${SOFTLEVEL} == "single" ]] ; then
eerror $"ERROR: system is in single user mode, will not start" "${SVCNAME}"
return 1
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
if begin_service "${SVCNAME}" ; then
svcbegin=true
else
svcbegin=false
fi
# Ensure that we clean up if we abort for any reason
trap "svc_quit" INT QUIT TSTP
# Store our e* messages in a buffer so we pretty print when parallel
[[ ${RC_PARALLEL_STARTUP} == "yes" && ${RC_QUIET} != "yes" ]] \
&& ebuffer "${svcdir}/ebuffer/${SVCNAME}"
veinfo $"Service" "${SVCNAME}" $"starting"
if [[ -n $(rc-depend --notrace -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
# Start dependencies, if any.
local startsvc=$(rc-depend -ineed -iuse "${SVCNAME}")
if ! is_runlevel_start ; then
for x in ${startsvc} ; do
service_stopped "${x}" && start_service "${x}"
done
fi
# Wait for dependencies to finish.
local ineed=$(rc-depend --notrace -ineed "${SVCNAME}")
for x in ${startsvc} $(rc-depend -iafter "${SVCNAME}") ; do
local timeout=3
while [[ ${timeout} -gt 0 ]] ; do
service_started "${x}" && continue 2
wait_service "${x}"
service_started "${x}" && continue 2
if service_inactive "${x}" || service_scheduled "${x}" ; then
if [[ " ${startsvc} " == *" ${x} "* ||
" ${ineed} " == *" $(rc-depend --notrace -iprovide "${x}") " ]] ; then
svc_schedule_start "${x}" "${SVCNAME}"
[[ -n ${startinactive} ]] && startinactive="${startinactive}, "
startinactive="${startinactive}${x}"
fi
continue 2
fi
service_stopped "${x}" && break
((timeout--))
done
[[ " ${ineed} " != *" ${x} "* ]] && continue
eerror "ERROR:" $"cannot start" "${SVCNAME}" $"as" "${x}" $"could not start"
retval=1
break
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}"
veindent
(
[[ ${RC_QUIET} == "yes" ]] && RC_QUIET_STDOUT="yes"
exit() {
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}
start
)
retval="$?"
net.ath0 is now in state 'starting'.
The above "start" call eventually causes execution to get to wpa_supplicant_pre_start():
wpa_supplicant_pre_start() {
local iface="$1" opts= timeout= actfile= cfgfile=
# We don't configure wireless if we're being called from
# the background unless we're not currently running
if ${IN_BACKGROUND} ; then
if service_started_daemon "net.${iface}" /sbin/wpa_supplicant ; then
if wpa_supplicant_exists "${iface}" ; then
ESSID=$(wpa_supplicant_get_essid "${iface}")
ESSIDVAR=$(bash_variable "${ESSID}")
save_options "ESSID" "${ESSID}"
metric=2000
fi
return 0
fi
fi
save_options "ESSID" ""
local ifvar=$(bash_variable "${iface}")
opts="wpa_supplicant_${ifvar}"
opts=" ${!opts} "
# We only work on wirelesss interfaces unless a driver for wired
# has been defined
if [[ ${opts} != *" -Dwired "* && ${opts} != *" -D wired "* ]] ; then
if ! wpa_supplicant_exists "${iface}" ; then
veinfo $"wpa_supplicant only works on wireless" \
"interfaces unless the -D wired option" \
"is specified"
return 0
fi
fi
# If wireless-tools is installed, try and apply our user config
# This is needed for some drivers - such as hostap because they start
# the card in Master mode which causes problems with wpa_supplicant.
if is_function iwconfig_defaults ; then
if wpa_supplicant_exists "${iface}" ; then
iwconfig_defaults "${iface}"
iwconfig_user_config "${iface}"
fi
fi
# Check for rf_kill - only ipw supports this at present, but other
# cards may in the future
if [[ -e "/sys/class/net/${iface}/device/rf_kill" ]] ; then
if [[ $( < "/sys/class/net/${iface}/device/rf_kill" ) != 0 ]] ; then
ewarn $"Wireless radio has been killed for interface" "${iface}"
local asc="associate_timeout_${ifvar}"
if [[ -n ${!asc} && ${!asc} -ge 0 ]] ; then
eerror $"As you have" "${asc}" $"set to 0 or" \
$"greater we will abort as we cannot" \
$"associate"
return 1
fi
ewarn $"wpa_supplicant will launch, but not associate" \
$"until wireles radio is re-enabled for" \
$"interface" "${iface}"
fi
fi
ebegin "Starting wpa_supplicant on" "${iface}"
cfgfile="${opts##* -c}"
if [[ -n ${cfgfile} && ${cfgfile} != "${opts}" ]] ; then
[[ ${cfgfile:0:1} == " " ]] && cfgfile="${cfgfile# *}"
cfgfile="${cfgfile%% *}"
else
# Support new and old style locations
cfgfile="/etc/wpa_supplicant/wpa_supplicant-${iface}.conf"
[[ ! -e ${cfgfile} ]] \
&& cfgfile="/etc/wpa_supplicant/wpa_supplicant.conf"
[[ ! -e ${cfgfile} ]] \
&& cfgfile="/etc/wpa_supplicant.conf"
opts="${opts} -c${cfgfile}"
fi
if [[ ! -f ${cfgfile} ]] ; then
eend 1 "/etc/wpa_supplicant/wpa_supplicant.conf" $"not found!"
return 1
fi
# Work out where the ctrl_interface dir is if it's not specified
local ctrl_dir=$(sed -n -e 's/[ \t]*#.*//g;s/[ \t]*$//g;s/^ctrl_interface=//p' "${cfgfile}")
if [[ -z ${ctrl_dir} ]] ; then
ctrl_dir="${opts##* -C}"
if [[ -n ${ctrl_dir} && ${ctrl_dir} != "${opts}" ]] ; then
[[ ${ctrl_dir:0:1} == " " ]] && ctrl_dir="${ctrl_dir# *}"
ctrl_dir="${ctrl_dir%% *}"
else
ctrl_dir="/var/run/wpa_supplicant"
opts="${opts} -C${ctrl_dir}"
fi
fi
save_options ctrl_dir "${ctrl_dir}"
# Some drivers require the interface to be up
interface_up "${iface}"
# wpa_supplicant 0.4.0 and greater supports wpa_cli actions
# This is very handy as if and when different association mechanisms are
# introduced to wpa_supplicant we don't have to recode for them as
# wpa_cli is now responsible for informing us of success/failure.
# The downside of this is that we don't see the interface being configured
# for DHCP/static.
actfile="/etc/wpa_supplicant/wpa_cli.sh"
opts="${opts} -W"
eval start-stop-daemon --start --exec /sbin/wpa_supplicant \
--pidfile "/var/run/wpa_supplicant-${iface}.pid" \
-- "${opts}" -B -i"${iface}" \
-P"/var/run/wpa_supplicant-${iface}.pid"
eend "$?" || return 1
# Starting wpa_supplication-0.4.0, we can get wpa_cli to
# start/stop our scripts from wpa_supplicant messages
mark_service_inactive "net.${iface}"
ebegin $"Starting wpa_cli on" "${iface}"
start-stop-daemon --start --exec /bin/wpa_cli \
--pidfile "/var/run/wpa_cli-${iface}.pid" \
-- -a"${actfile}" -p"${ctrl_dir}" -i"${iface}" \
-P"/var/run/wpa_cli-${iface}.pid" -B
eend "$?" || return 1
At this point, wpa_supplicant and wpa_cli are running and net.ath0 is now in state INACTIVE.
When it's happy, wpa_supplicant starts scanning and associating. When it has associated, wpa_cli is notified, and wpa_cli launches /etc/wpa_supplicant/wpa_cli.sh
I'll call wpa_cli.sh the 2nd process. wpa_cli launches /etc/init.d/net.ath0 start and we eventually get back into svc_start():
[Note that the process 1 svc_start() hasnt finished yet, it's been preempted at the last mentioned point]
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
ewarn $"WARNING:" " ${SVCNAME}" $"has already been started."
return 0
elif service_inactive "${SVCNAME}" ; then
if [[ ${IN_BACKGROUND} != "true" \
&& ${IN_BACKGROUND} != "1" ]] ; then
ewarn $"WARNING:" " ${SVCNAME}" $"has already been started."
return 0
fi
elif [[ ${SOFTLEVEL} == "shutdown" || ${SOFTLEVEL} == "reboot" ]] ; then
ewarn $"WARNING: system shutting down, will not start" "${SVCNAME}"
return 1
elif [[ ${SOFTLEVEL} == "single" ]] ; then
eerror $"ERROR: system is in single user mode, will not start" "${SVCNAME}"
return 1
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
net.ath0 is now in state 'starting'
back to process 1. continuing where we left off, wpa_supplicant_assocaite gets called, which does nothing but "exit 0" and control is returned to svc_start():
# Don't trust init scripts to reset indentation properly
# Needed for ebuffer
eoutdent 99999
# 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
ewarn $"WARNING:" " ${SVCNAME}" $"has started but is inactive"
if [[ ${RC_PARALLEL_STARTUP} == "yes" && ${RC_QUIET} != "yes" ]] ; then
eflush
ebuffer ""
fi
return 1
fi
fi
the service_inactive check fails, so svc_start continues.
if [[ ${retval} != "0" ]] ; then
if [[ ${svcinactive} == "0" ]] ; then
mark_service_inactive "${SVCNAME}"
elif [[ -z ${startinactive} ]] ; then
mark_service_stopped "${SVCNAME}"
is_runlevel_start && mark_service_failed "${SVCNAME}"
eerror $"ERROR:" " ${SVCNAME}" $"failed to start"
fi
else
svcstarted=0
mark_service_started "${SVCNAME}"
veinfo $"Service" "${SVCNAME}" $"started"
fi
net.ath0 is now in state STARTED even though we have no indication at all of the status of process 2 (this has not yet even thought about invoking dhcpcd).
to conclude I will highlight in red the inner part of critical section in which the race is significant. in this area, svc_start can run again in another process, and alter the state of the process, and therefore alter the behavior of the first process outside of the highlighted section.
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
ewarn $"WARNING:" " ${SVCNAME}" $"has already been started."
return 0
elif service_inactive "${SVCNAME}" ; then
if [[ ${IN_BACKGROUND} != "true" \
&& ${IN_BACKGROUND} != "1" ]] ; then
ewarn $"WARNING:" " ${SVCNAME}" $"has already been started."
return 0
fi
elif [[ ${SOFTLEVEL} == "shutdown" || ${SOFTLEVEL} == "reboot" ]] ; then
ewarn $"WARNING: system shutting down, will not start" "${SVCNAME}"
return 1
elif [[ ${SOFTLEVEL} == "single" ]] ; then
eerror $"ERROR: system is in single user mode, will not start" "${SVCNAME}"
return 1
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
if begin_service "${SVCNAME}" ; then
svcbegin=true
else
svcbegin=false
fi
# Ensure that we clean up if we abort for any reason
trap "svc_quit" INT QUIT TSTP
# Store our e* messages in a buffer so we pretty print when parallel
[[ ${RC_PARALLEL_STARTUP} == "yes" && ${RC_QUIET} != "yes" ]] \
&& ebuffer "${svcdir}/ebuffer/${SVCNAME}"
veinfo $"Service" "${SVCNAME}" $"starting"
if [[ -n $(rc-depend --notrace -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
# Start dependencies, if any.
local startsvc=$(rc-depend -ineed -iuse "${SVCNAME}")
if ! is_runlevel_start ; then
for x in ${startsvc} ; do
service_stopped "${x}" && start_service "${x}"
done
fi
# Wait for dependencies to finish.
local ineed=$(rc-depend --notrace -ineed "${SVCNAME}")
for x in ${startsvc} $(rc-depend -iafter "${SVCNAME}") ; do
local timeout=3
while [[ ${timeout} -gt 0 ]] ; do
service_started "${x}" && continue 2
wait_service "${x}"
service_started "${x}" && continue 2
if service_inactive "${x}" || service_scheduled "${x}" ; then
if [[ " ${startsvc} " == *" ${x} "* ||
" ${ineed} " == *" $(rc-depend --notrace -iprovide "${x}") " ]] ; then
svc_schedule_start "${x}" "${SVCNAME}"
[[ -n ${startinactive} ]] && startinactive="${startinactive}, "
startinactive="${startinactive}${x}"
fi
continue 2
fi
service_stopped "${x}" && break
((timeout--))
done
[[ " ${ineed} " != *" ${x} "* ]] && continue
eerror "ERROR:" $"cannot start" "${SVCNAME}" $"as" "${x}" $"could not start"
retval=1
break
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}"
veindent
(
[[ ${RC_QUIET} == "yes" ]] && RC_QUIET_STDOUT="yes"
exit() {
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}
start
)
retval="$?"
# Don't trust init scripts to reset indentation properly
# Needed for ebuffer
eoutdent 99999
# 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
ewarn $"WARNING:" " ${SVCNAME}" $"has started but is inactive"
if [[ ${RC_PARALLEL_STARTUP} == "yes" && ${RC_QUIET} != "yes" ]] ; then
eflush
ebuffer ""
fi
return 1
fi
fi
if [[ ${retval} != "0" ]] ; then
if [[ ${svcinactive} == "0" ]] ; then
mark_service_inactive "${SVCNAME}"
elif [[ -z ${startinactive} ]] ; then
mark_service_stopped "${SVCNAME}"
is_runlevel_start && mark_service_failed "${SVCNAME}"
eerror $"ERROR:" " ${SVCNAME}" $"failed to start"
fi
else
svcstarted=0
mark_service_started "${SVCNAME}"
veinfo $"Service" "${SVCNAME}" $"started"
fi
# Flush the ebuffer
if [[ ${RC_PARALLEL_STARTUP} == "yes" && ${RC_QUIET} != "yes" ]] ; then
eflush
ebuffer ""
fi
if ${svcbegin} ; then
svcbegin=false
end_service "${SVCNAME}"
fi
# Reset the trap
svc_trap
return "${retval}"
}