# Copyright 2006 Oldrich Jedlicka # Distributed under the terms of the GNU General Public License v2 # RC functions for using named locks and "thread" variables - variables # belonging to a subshell. The basic checks for consistency are made as well # as the cleanup on EXIT from the subshell - this is important if somebody # terminated the script with SIGINT. Any number of locking within the same # subshell is allowed, all locks are cleared automatically when the subshell # terminates. # # The internal functionality depends on calls to svc_replace_signals - this is # called every time there is a lock/unlock or a thread variable usage. # # Errors in locking and unlocking are reported and exit status 1 is returned. # Exit status 0 is for all successful calls. # # NOTE ######################################################################## # # Keep in mind that changes in a subshell are not visible to a parent shell! # # END NOTE #################################################################### # # Good example 1: Simple lock # # lock "name" || die "Cannot lock" # ... do something exclusively # unlock "name" || die "Cannot unlock" # ... here is no lock active # # Good example 2: Double lock in the same shell # # lock "name" # lock "name" # ... do something exclusively # unlock "name" # ... still do something exclusively # unlock "name" # ... here is no lock active # # Good example 3: Double locking from the same process, but different subshell # lock "name" # ... do something exclusively # ( # lock "name" # ... do something and forget to unlock # ) # ... here is the lock from subshell already released # ... main lock is still active # unlock "name" # ... here is no lock active # # Good example 4: # use_thread_variable VARIABLE # VARIABLE=10 # echo $VARIABLE # 10 # ( # use_thread_variable VARIABLE # echo $VARIABLE # Empty string # VARIABLE=5 # echo $VARIABLE # 5 # ) # echo $VARIABLE # 10 # # Wrong example 5: Normal subshell variable behaviour # use_thread_variable VARIABLE # VARIABLE=10 # echo $VARIABLE # 10 # ( # echo $VARIABLE # 10 # VARIABLE=5 # echo $VARIABLE # 5 # ) # echo $VARIABLE # 10 # # Wrong but somehow working example 6: Locking and thread variables # use_thread_variable VARIABLE # VARIABLE=10 # echo $VARIABLE # 10 # ( # echo $VARIABLE # 10 # lock "name" # Does the job for cleaning the variables # echo $VARIABLE # Empty string # VARIABLE=5 # echo $VARIABLE # 5 # ) # echo $VARIABLE # 10 # RC_GOT_LOCKING="yes" [[ ${RC_GOT_FUNCTIONS} != "yes" ]] && source /sbin/functions.sh svclock=${svclock:-/var/lock/rcscripts} # bool is_in_list(char *item, char *list) # # Returns 0 if the item is in the list, otherwise 1. # # To work properly with arrays it has to be called as # # is_in_list "value" "$list1[@]" "$list2[@]" ... # # For normal operation it can be called as # # is_in_list "value" "item1" "item2" "item3" ... # is_in_list() { local x retval=1 item="$1" shift for x in "$@" do if [[ "$x" == "$item" ]] then retval=0 break fi done return ${retval} } # void use_thread_variable(char *name) # # Adds a variable to the list of thread variables. Those variables are # unset in a subshell. This should be called before anything is assigned # to the variable. # use_thread_variable() { svc_replace_signals if ! is_in_list "$1" $SVC_THREADVAR then SVC_THREADVAR="$1 $SVC_THREADVAR" fi } # void add_cleanup_function(char *function) # # Adds the function to the list of cleanup actions executed, when the # current subshell exits. # add_cleanup_function() { svc_replace_signals if [[ $(type -t "$1") == "function" ]] then if ! is_in_list "$1" "${SVC_CLEANUP[@]}" then SVC_CLEANUP[${#SVC_CLEANUP}]="$1" fi else echo "$1 is not a function!" > /dev/stderr fi } # bool trylock(char *name) # # Tries to acquire a named lock. Returns 0, if the locking was successful, # otherwise 1. # trylock() { local path="${svclock}/$1" local pid retval newcount if [[ ! -w ${svclock} ]] then # TODO: No locking possible. Error or silently ignore? return 0 fi add_cleanup_function svc_restore_locks pid=$(cat "$path" 2>/dev/null) pid=( $pid ) # Save lock count if [[ "$$" == "${pid[0]}" ]] then # We are already the owner svc_save_lock_count "$1" "${pid[1]}" newcount=$((${pid[1]}+1)) echo "$$ ${newcount}" > "$path" retval=$? else # We want to get an ownership svc_save_lock_count "$1" 0 (set -C; echo "$$ 1" > "$path") 2> /dev/null retval=$? fi return ${retval} } # void lock(char *name) # # Acquire the named lock. # lock() { if [[ ! -w ${svclock} ]] then return 0 fi while [ 1 ]; do if trylock "$1"; then return 0 else sleep 0.1 fi done } # void unlock(char *name, bool force) # # Release the named lock. Returns 1 if the process was not the owner of the # lock or there was an error during unlocking. # unlock() { local path="${svclock}/$1" local pid newcount mincount locked error if [[ ! -w ${svclock} ]] then # TODO: No locking possible. Error or silently ignore? return 0 fi # If we cannot get the contents of the file, it doesn't exist - we are # not the owner of the file pid=$(cat "$path" 2>/dev/null) pid=( $pid ) svc_was_locked "$1" && locked=1 || locked=0 if [[ "$$" == "${pid[0]}" && $locked -eq 1 ]] then newcount=$((${pid[1]}-1)) mincount=$( svc_get_lock_count "$1" ) if [[ $newcount -lt $mincount ]] then error="Unlocking more times than locking!" else if [[ $newcount == 0 ]]; then rm -f "$path" else echo "$$ ${newcount}" > "$path" fi retval=$? fi elif [[ $locked -eq 1 ]] then if [[ -z ${pid[0]} ]] then error="Unlocking already unlocked lock!" else error="Unlocking not owned lock!" fi else # The lock was not locked by us if [[ -z "${pid[0]}" ]] then error="Unlocking not locked lock!" else error="Unlocking not owned lock!" fi fi if [[ -n $error ]] then echo " ${BAD}***${NORMAL} $error" echo " ${BAD}**${GOOD}*${NORMAL} ${HILITE}HINT:${NORMAL} Maybe you have forgotten to make a lock." echo " ${BAD}*${GOOD}**${NORMAL} ${HILITE}HINT:${NORMAL} Maybe you did not lock in the same subshell." echo " ${GOOD}***${NORMAL} ${HILITE}HINT:${NORMAL} If you lock in a subshell, locks recover when you leave it." retval=1 fi return ${retval} } ############################################################################## # # # This should be the last public code in here, please add all public above!! # # # # *** START PRIVATE CODE *** # # # ############################################################################## # void svc_do_cleanup(void) # # This is normal EXIT signal handler. It does all actions defined with # add_cleanup_function. # svc_do_cleanup() { for name in $SVC_CLEANUP do eval $name done } # void svc_replace_signals(void) # # Replace the EXIT signal handler with our implementation. Delete all # thread variables if the new signal handler is installed. # svc_replace_signals() { local traps tmpfile var tmpfile=`mktemp /tmp/traps.XXXXXXXX` if [[ -z $tmpfile ]] then # Fatal error! Report and exit ( RC_QUIET_STDOUT="no" eerror "Cannot create temporary file in /tmp!" ) exit 1 fi # The only way how to get traps from the current shell is to redirect # output to a temportary file and read it back. trap -p EXIT > $tmpfile traps=$( < $tmpfile ) rm -f $tmpfile case $traps in *'svc_do_cleanup'* ) # Alredy installed, do nothing : ;; *) # New installation, clean all known thread variables SVC_CLEANUP="" for var in $SVC_THREADVAR do unset $var done trap svc_do_cleanup EXIT ;; esac } # void svc_addlock(char *name) # # Adds named lock to lock list. # svc_addlock() { if ! is_in_list "$1" "${SVC_LOCK[@]}" then SVC_LOCK[${#SVC_LOCK}]="$1" fi } # void svc_save_lock_count(char *name, int count) # # Save the lock count in the corresponding SVC_LOCKCOUNT_ thread # variable. # svc_save_lock_count() { local lockcountvar lockcount lockcountvar="SVC_LOCKCOUNT_"$( bash_variable "$1" ) use_thread_variable $lockcountvar lockcount=${!lockcountvar} if [[ -z $lockcount ]] then if [[ -z $2 ]] then lockcount=0 else lockcount=$2 fi eval "$lockcountvar=$lockcount" svc_addlock "$1" fi } # int svc_get_lock_count(char *name) # # Get the saved lock count from the thread variable SVC_LOCKCOUNT_. # Returns the number 0, if the lock was not used before in the current # subshell. # svc_get_lock_count() { local lockcountvar lockcountvar="SVC_LOCKCOUNT_"$( bash_variable "$1" ) use_thread_variable $lockcountvar echo ${!lockcountvar:-0} } # bool svc_was_locked(char *name) # # Returns 0 if the lock was used in the current subshell before, otherwise 1. # svc_was_locked() { local lockcountvar lockcountvar="SVC_LOCKCOUNT_"$( bash_variable "$1" ) use_thread_variable $lockcountvar [[ -n ${!lockcountvar} ]] } # void svc_restorelocks() # # Restore all locks from the saved values. It has to be called only from the # installed signal handler svc_do_cleanup, otherwise it will not work! # svc_restore_locks() { local pid name path lockcountvar lockcount for name in $SVC_LOCK do path="${svclock}/$name" pid=$(cat "$path" 2>/dev/null) pid=( $pid ) if [[ "$$" == "${pid[0]}" ]] then # Get the lock cound. lockcount=$( svc_get_lock_count "$name" ) if [[ $lockcount -eq 0 ]] then rm -f "$path" 2> /dev/null else echo "$$ ${lockcount}" > "$path" fi fi done exit 1 } ############################################################################## # # # This should be the last code in here, please add all functions above!! # # # # *** START LAST CODE *** # # # ############################################################################## if [[ ! -d "${svclock}" ]] ; then mkdir -p -m 0755 "${svclock}" fi ############################################################################## # # # *** END LAST CODE *** # # # # This should be the last code in here, please add all functions above!! # # # ############################################################################## # vim:ts=4