# Copyright (c) 2017 The NetBSD Foundation, Inc. # All rights reserved. # # This code is derived from software contributed to The NetBSD Foundation # by Johnny C. Lam. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # NAME # lock.subr -- create or release a lock file # # SYNOPSIS # task_lock [-nr] lockfile # # DESCRIPTION # The task_lock function can create or release a lock file on behalf # of a shell script. The requested lock file is a symlink to a # uniquely-named file. # # The options are as follows: # # -n Don't block and fail immediately if the lock could not be # obtained. # # -r Release the existing lock file. # # The task_lock function uses the symlink(2) system call via ln(1) # to create the target lock file, which is an atomic operation. It # writes the name of the symlink target into the requested lock file. # # The task_lock function uses the rename(2) system call via mv(1) # to release the target lock file, which is an atomic operation. # Upon success, it deletes the renamed symlink as well as the file # named within the symlink target. # # RETURN VALUES # Returns 0 on success, and >0 if an error occurs. # # ENVIRONMENT # The following variables are used if they are set: # # LN The name or path to the ln(1) utility. # # MV The name or path to the mv(1) utility. # # RM The name or path to the rm(1) utility. # # SLEEP The name or path to the sleep(1) utility. # # EXAMPLES # o Acquire a lock, waiting until it is created before continuing. # # lockfile="/tmp/foo.lock" # if task_lock "$lockfile"; then # # do what required the lock # # ... # # release the lock # task_lock -r "$lockfile" # fi # # o Attempt to create a lock, but fail immediately if not created. # # lockfile="/tmp/foo.lock" # if task_lock -n "$lockfile"; then # # do what required the lock # # ... # # release the lock # task_lock -r "$lockfile" # else # echo "Lock $lockfile already held by another process." # fi # # BUGS # The task_lock function should accept an optional timeout parameter. # __task_lock__="yes" __task_lock_init__="_task_lock_init" task_load cleanup task_load maketemp task_load quote task_lock() { local action="create" local nonblocking= local timeout= local arg local OPTIND=1 while getopts ":nrw:" arg "$@"; do case $arg in n) nonblocking="-n" ;; r) action="release" ;; w) timeout=${OPTARG} ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) [ $# -eq 1 ] || return 127 local lockfile="$1"; shift [ -n "$lockfile" ] || return 1 case $action in create) _task_lock_create $nonblocking "$lockfile" || return 1 ;; release) _task_lock_release "$lockfile" || return 1 ;; esac return 0 } _task_lock_create() { : ${LN:=ln} : ${RM:=rm} : ${SLEEP:=sleep} local nonblocking= local arg local OPTIND=1 while getopts ":n" arg "$@"; do case $arg in n) nonblocking="yes" ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) [ $# -eq 1 ] || return 1 local lockfile="$1"; shift local target quoted target=$( task_maketemp "$lockfile.pkgtasks.XXXXXXXXXX" ) || return 1 task_quote "$target" __task_lock_temps__="$__task_lock_temps__ $quoted" echo "$target" > $target while : ; do # symlink(2) is atomic. # # Redirect standard error so an error message isn't # written every time this symlink(2) is attempted while # we're spinning. # if ${LN} -s "$target" "$lockfile" >/dev/null 2>&1; then # lock created return 0 fi # don't spinlock if nonblocking was requested [ -z "$nonblocking" ] || break # sleep for 1s and try to create the lock again ${SLEEP} 1 done # lock not created _task_lock_cleanup return 1 } _task_lock_release() { : ${MV:=mv} : ${RM:=rm} [ $# -eq 1 ] || return 1 local lockfile="$1"; shift [ -n "$lockfile" ] || return 1 # release a lock local release="$lockfile.release" # rename(2) is atomic. # # Redirect standard error so an error message isn't written every # time this rename(2) is attempted. # if ${MV} -f "$lockfile" "$release" >/dev/null 2>&1; then # lock released if [ -f "$release" ]; then # clean up by deleting the target of the released lock local target while IFS= read target; do case $target in "$lockfile".*) ${RM} -f "$target" ;; *) : "skip invalid lockfile name" ;; esac done < $release fi ${RM} -f "$release" return 0 fi # lock not released return 1 } _task_lock_cleanup() { : ${RM:=rm} set -o noglob; eval set -- $__task_lock_temps__; set +o noglob local file for file; do ${RM} -f "$file" done __task_lock_temps__= } _task_lock_init() { task_cleanup_add_hook _task_lock_cleanup } # Static variable for temporary files that should be removed if an error occurs. __task_lock_temps__=