NAME
oc_runlevel, _current_runlevel, is_shutting_down, abort_if_shutting_down - stop OmniCube jobs when the host is not in a normal multi-user state
SYNOPSIS
amp;. /opt/omnicube/lib/common/utils.sh
_current_runlevel
is_shutting_down
abort_if_shutting_down
DESCRIPTION
A snapshot, a zfs send or a zone relocation that races a shutdown is the worst class of failure this suite can produce: services are half torn down, pools are being exported, filesystems are unmounting, and the job fails somewhere in the middle of a sequence whose recovery is manual. The run-level guard exists so that such a job never starts, and stops at the next iteration if the state changes while it runs.
Consumers call abort_if_shutting_down() immediately after sourcing the library and again at the top of every long loop body; see autosnap(8), autosync(8), sys_monitor(8), zones_srv_monitor.sh(8) and monitor_nvme.sh(8).
_current_runlevel
Prints the current numeric or letter run level on standard output, or nothing if it cannot be determined. Implemented as who -r piped through awk(1) which scans the fields for the literal token run-level and prints the field after it.
The token scan, rather than a fixed column, is what makes this portable. On illumos who -r prints
amp;. run-level 3 Jul 21 17:01 3 0 S
so the level is the third field, while on Linux the token is the first field and the digit is the second. Hard-coding awk '{print $2}' would read the string run-level on illumos, match none of the shutdown levels, and silently disable the entire guard.
is_shutting_down
A pure predicate: no output, no exit, usable in a conditional, in && chains and inside command substitution. Returns true (0) when either
Those levels are, per illumos init(8): 0 going to firmware or halt, 1 administrative single-user, 5 shut down and power off, 6 reboot, and S s single-user with the root filesystem only. Levels 2 and 3 are the normal multi-user states and are the only ones treated as “running normally”; any level not in the list, and an empty result from _current_runlevel(), is treated as normal, so a host where who -r gives no answer keeps working.
/etc/nologin is checked first because the shutdown sequence creates it a few minutes before the run level actually changes. Treating its presence as “going down” trips the guard earlier and covers a shutdown -g or an init that is in progress but has not yet changed level.
abort_if_shutting_down
The action wrapper. If OC_IGNORE_RUNLEVEL is set to a non-empty value it returns 0 at once. Otherwise it calls is_shutting_down() and returns 0 if the host is normal. If the host is going down it composes a reason,
/etc/nologin present (shutdown in progress)
or
run-level <level>
logs one line through info() in the form
<script>: system not in multi-user state (<why>); exiting
and terminates the whole script with exit 0.
Two design points follow from that:
Because it calls exit, abort_if_shutting_down() must be called from the script body. Inside $(), a pipeline or any other subshell the exit only leaves the subshell and the script continues; use the bare is_shutting_down() predicate in those positions.
RETURN VALUES
FILES
ENVIRONMENT
EXAMPLES
Example 1: guard the job, then guard each iteration
amp;. /opt/omnicube/lib/common/utils.sh
abort_if_shutting_down
for DS in ${DSLIST}; do
abort_if_shutting_down
${PFEXEC} zfs snapshot "${DS}@${SNAPNAME}"
done
Example 2: the predicate where exit would not work
if is_shutting_down; then
info "skipping remote work, host is going down"
else
${_SSH_CMD} "${node}" "pfexec zpool import ${pool}"
fi
Example 3: single-user maintenance by hand
OC_IGNORE_RUNLEVEL=1 /opt/omnicube/sbin/manage_zone.sh -z web01 -b
SEE ALSO
omnicube_utils(3), oc_log(3), oc_lock(3), oc_validate(3), oc_ssh(3), oc_policy(3), autosnap(8), sys_monitor(8), manage_zone.sh(8), omnicube(7).
NOTES
The guard is advisory and coarse. It cannot detect a shutdown that has been requested but has neither created /etc/nologin nor changed the run level yet, so a job started in that window still races the shutdown; the per-iteration call is what limits the damage.
Setting OC_IGNORE_RUNLEVEL in a cron environment or in an SMF method disables the protection for every job that inherits it. It is an interactive tool only.