NAME
oc_lock, acquire_lock_or_exit, _ensure_shared_dir - acquire a single-instance OmniCube lock and maintain the shared lock directory
SYNOPSIS
amp;. /opt/omnicube/lib/common/utils.sh
acquire_lock_or_exit lockdir
_ensure_shared_dir path
DESCRIPTION
OmniCube jobs are started from cron, from SMF methods and by hand, sometimes all three within the same minute. Every job that mutates state (snapshots, zfs sends, pool imports, streak counters) therefore takes a single-instance lock first. The lock is a directory under ${LOCK_BASE}, which is /var/run/omnicube and hard-coded.
acquire_lock_or_exit
Attempts mkdir lockdir once, with stderr discarded. Directory creation is atomic in the kernel, so the directory is the mutex and concurrent invocations contend correctly. Three outcomes:
This single call replaces the older
[ -d ${LOCK_DIR} ] && exit 0; mkdir ${LOCK_DIR} || exit 1
two-step, which had a TOCTOU window between the test and the mkdir, and which printed a bogus File exists on standard error on every concurrent invocation, generating cron mail for a non-event.
The function never removes anything. Releasing the lock is entirely the caller's responsibility and must be done from a trap, so that the lock is also released when the job is killed, when the run-level guard aborts it (see oc_runlevel(3)) and when it exits on error:
trap '[[ -d ${LOCK_DIR} ]] && rmdir ${LOCK_DIR} 2>/dev/null' \\
EXIT HUP INT TERM
rmdir, not rm -rf: the lock directory is expected to be empty, and a recursive removal of a path built from a variable is not a risk worth taking in a trap.
The conventional lock name embeds ${logtag} so that two SMF instances of the same job (see OC_SMF_INSTANCE in omnicube_utils(3)) do not lock each other out:
LOCK_DIR=${LOCK_BASE}/${logtag}.autocleansnap.lock
_ensure_shared_dir
Creates path if needed and normalizes it to mode 1775 with group ${LOCK_GROUP}. The library calls it on ${LOCK_BASE} every single time it is sourced, because /var/run is tmpfs on illumos and is empty after every boot; consumers call it on their own runtime directories that have the same sharing requirement, for example /var/run/omnicube/sys_monitor in sys_monitor(8).
Steps: refuse immediately if path is a symbolic link; mkdir -p if it is not already a directory; re-test that it is now a directory and still not a symlink, since a concurrent racer may have won the creation; then chgrp ${LOCK_GROUP} and chmod 1775. Both ownership operations are attempted unconditionally and both have their errors discarded.
The symlink refusal is the security-relevant part. ${LOCK_BASE} is created by whichever OmniCube job runs first after boot, which may be an unprivileged pfexec(1) operator. If that path were a symlink, a later root invocation would chgrp and chmod the symlink target, handing an attacker a group-writable directory of their choosing. The shell has no O_NOFOLLOW equivalent for chmod or chgrp, so the only safe action is to touch nothing and let the caller fail loudly on its next mkdir.
Ownership model
Locks are shared between root and operators through the group, not by patching modes on individual lock directories:
The consequence is a deployment requirement: every account that runs these tools under pfexec(1) must be a member of ${LOCK_GROUP}. A non-member gets EPERM from mkdir and acquire_lock_or_exit() exits 1 with an error() line, which is the intended, visible failure. Nothing in the library grants access by widening a mode.
chgrp is attempted by every caller and not just by root because POSIX allows an owner who is also a member of the target group to change a directory's group. A ${LOCK_BASE} created by an operator therefore converges on the correct group and mode at the first invocation after boot rather than waiting for the next root cron tick.
RETURN VALUES
FILES
ENVIRONMENT
EXAMPLES
Example 1: the standard prologue
amp;. /opt/omnicube/lib/common/utils.sh
LOCK_DIR=${LOCK_BASE}/${logtag}.myjob.lock
acquire_lock_or_exit "${LOCK_DIR}"
trap '[[ -d ${LOCK_DIR} ]] && rmdir ${LOCK_DIR} 2>/dev/null' \\
EXIT HUP INT TERM
abort_if_shutting_down
Example 2: a shared runtime directory of one's own
STREAK_DIR=/var/run/omnicube/myjob
if ! _ensure_shared_dir ${STREAK_DIR}; then
error "cannot prepare ${STREAK_DIR}; counters disabled this run"
fi
Example 3: checking whether a job is running
[[ -d /var/run/omnicube/sync_pool.lock ]] && echo "sync_pool is running"
SECURITY
Lock creation is unprivileged; the sharing between root and RBAC operators is provided by group membership in ${LOCK_GROUP} plus mode 1775, and the sticky bit keeps users from releasing each other's locks. _ensure_shared_dir () refuses to chgrp or chmod a path that is a symbolic link, which prevents a symlink planted on tmpfs by an unprivileged first caller from being followed by a later root invocation.
SEE ALSO
omnicube_utils(3), oc_log(3), oc_runlevel(3), oc_validate(3), oc_ssh(3), oc_policy(3), isolate_node.sh(8), sys_monitor(8), sync_pool.sh(8), omnicube(7).
NOTES
A job that forgets the trap leaves its lock behind and locks itself out on the next tick, silently, because a pre-existing lock is an exit 0 condition. Nothing in the library detects an orphaned lock; sys_monitor(8) implements its own streak counters for that purpose.
acquire_lock_or_exit() protects against concurrent runs on one host only. It is not a cluster-wide lock. Serialization across nodes is done with ZFS user properties such as ${PROPPREFIX}:is_locked, not with this function.