#!/bin/sh # # Copyright 2005 Martin R. Fick # http://www.theficks.name/ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # # A simple locking mechanism. # # Provide a PID and a file to lock on. If the process with # the pid exits, it will abandon any locks it holds. # # lock PID file # unlock file # V 2.2 # ------------------ Tests ------------------ test_cycle() { # pid file if [ $# -lt 2 ] ; then echo "Error, use: $0 test_cycle PID file">&2 ; return 1 fi lock "$1" "$2" || return 1 echo "locked $1" lock_unlock "$1" "$2" || return 2 echo "unlocked $1" } test1() { # file typeset file="$1" cnt=0 max=100 if [ $# -lt 1 ] ; then echo "Error, use: $0 test1 file">&2; return 1 fi while [ $cnt -lt $max ] ; do test_cycle "$$" "$file" || echo "Missed" >&2 cnt=$(($cnt+1)) ; done } test2() { # file if [ $# -lt 1 ] ; then echo "Error, use: $0 test2 file">&2; return 1 fi "$0" --call test1 "$1" & "$0" --call test1 "$1" & wait } # ------------------ Internal Generic ------------------ # ARG # v 1.1 is_number(){ [ "$(($1+0))" = "$1" ] ; } # PID # v1.0 is_pid_alive(){ [ "$(ps "$1"|awk 'NR==2' 2>/dev/null)" != "" ] ; } # ------------------ Internal Locking ------------------ # file # v 2.0 lock_getPid() { cat "$1.lock" 2>/dev/null ; } # PID file # v 2.0 lock_isPid() { [ "$(lock_getPid "$2")" = "$1" ] ; } # 0 blocked for sure! 1 not-blocked, >1 might be, 2 bad lock, 3 shifting lock_isBlocked() # v 2.0 { # file typeset file="$1" pid pid=$(lock_getPid "$file") || return 1 is_number "$pid" || return 2 is_pid_alive "$pid" && return 1 # Lock may have been a good lock when we first got the PID, # and lock could have been released and pid finished before # we checked to see if it was alive. Check to make sure # lock is still there to avoid signaling a false block. lock_isPid "$pid" "$file" || return 2 return 0 } lock_unblock() # v 2.1 { # PID file typeset pid="$1" file="$2" lck st lck="$file.lock" lock_isBlocked "$file" ; st=$? if [ $st -gt 1 ] ; then echo "Not sure if blocked, sleep, check again">&2 sleep 1 lock_isBlocked "$file" ; st=$? fi if [ $st -eq 1 ] ; then return 1 ; fi # Let's lock the abandoned lock so that we are the # only attempting to delete it! # Carefull, this is recursive lock "$pid" "$lck" || return 2 # Someone may have already delete the abandoned lock and # aquired a new lock before we got a lock on the lock # If we enusre that the lock is still blocked before # deleting it, we know that we are deleting a blocked lock, # not a newly aquired one! lock_isBlocked "$file" ; st=$? if [ $st -eq 1 ] ; then lock_unlock "$pid" "$lck" ; return 3 fi echo " pid:$pid removing abandonned lock by $(lock_getPid "$file") on $file">&2 rm -f "$lck" lock_unlock "$pid" "$lck" } # 50 (40) works well on carrera lock_spin() # v 1.0 { # pid file max typeset pid="$1" file="$2" max="$3" lck cnt=0 lck="$file.lock" set -o noclobber while [ $cnt -lt $max ] ; do (echo "$pid" > "$lck") >/dev/null 2>&1 && return 0 cnt=$(($cnt+1)) ; done return 1 } # ------------------ External API ------------------ ## Err 99 : Warning Hijacked Lock! Program accordingly! lock_unlock() # v 2.0 { # pid file typeset pid="$1" file="$2" if [ $# -lt 2 ] ; then echo "Error, use: $0 lock_unlock PID file">&2; return 1 fi if lock_isPid "$pid" "$file" ; then rm -f "$file.lock" >/dev/null 2>&1 && return 0 # Clean Unlock echo "Error: cannot unlock $1" >&2; return 3 fi # Bad PID, assume foul play, but do not make worse by # unlocking someone else's lock! echo "Error: our lock (pid-$pid) on $file was hijacked by\ $( lock_getPid "$file")" >&2 return 99 } ## Err 99 : You already own this lock, they are not nestable! lock() # v 2.0 { # pif file typeset pid="$1" file="$2" cnt=0 try=100 spin=50 if [ $# -lt 2 ] ; then echo "Error, use: $0 lock PID file">&2; return 1 fi if is_number "$pid" ; then : ; else echo "Error: pid:$pid is not a valid pid">&2; return 2 fi while [ $cnt -lt $try ] ; do if is_pid_alive "$pid" ; then : ; else echo "Error: pid:$pid is not alive">&2; return 3 fi lock_spin "$pid" "$file" $spin && return 0 if lock_isPid "$pid" "$file" ; then echo "Error: pid:$pid already holds lock $file">&2; return 99 fi lock_unblock "$pid" "$file" cnt=$(($cnt+1)) ; done echo "Error: $pid cannot get lock for $file">&2 return 4 } if [ "$1" = "--call" ] ; then shift ; "$@" ; exit ; fi if [ "$1" = "lock" ] ; then shift ; lock "$@" ; exit ; fi if [ "$1" = "unlock" ] ; then shift ; lock_unlock "$@" ; exit ; fi