pkgsrc/pkgtools/pkgtasks/files/lock.subr
jlam db8c40249f Import pkgtasks-1-1.9 as pkgsrc/pkgtools/pkgtasks.
pkgtasks is a shell script library to ease writing POSIX-compliant
shell scripts to handle common tasks during installation or removal
of a package, e.g.,

  * creating groups and users needed by the package

  * creating and removing directories with special permissions and
    ownership,

  * copying example config files to their final locations during
    package installation, and removing them during package removal
    if they don't differ from the example ones,

  * reminding the user of files that may be customized after
    package installation.
2017-06-01 01:58:34 +00:00

233 lines
5.8 KiB
Text

# 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__=