putlock - create lockfiles
Creating lockfiles is another task that is sometimes required from within awk but not easy to solve. Of course the is lockfile(1) that comes with the procmail packages. But (as for temporary files) the question is how to make sure that the lockfile is removed when the awk script terminates. A second question (but this arises because I want to solve this task with a shell script) is how to make the lock operation "atomic", avoiding race conditions (two programs locking at the same time both thinking they successfully placed the lock etc).
The first question (reliable lockfile removal) is addressed by
- trapping the usual process termination signals (lines #46, #98) and cleaning the lockfiles (line #9), and
- detection that the caller script terminated by waiting for input from it (line #123).
Especially #2 is the reason why putlock is used as a co-process:
cmd = "putlock " filetolock;
printf ("\n") |& cmd;
cmd |& getline status;
if (status != "OK") {
-- do some error handling --
exit (1);
}
-- proceed with the locked file --
Of course it is possible to send real text to putlock but it is not neccessary. Any input is ignored but waiting gives the most reliable way to detect that the calling awk script terminated (for whatever reason).
1 #!/bin/sh
2 #
3
4 #
5 # putlock -- create one or more lockfiles, based on rtc
6 #
7
8
9 cleanup() {
10 if [ "$LOCKFILES" != "" ]; then
11 rm $LOCKFILES
12 fi
13
14 trap - $trapsignals
15 if [ "$cmdpid" != "" ]; then
16 kill $cmdpid >/dev/null 2>&1
17 fi
18
19 if [ "$alarmpid" != "" ]; then
20 kill $alarmpid >/dev/null 2>&1
21 fi
22 }
23
24 setalarm() {
25 trap sigalrmhandler 14
26 sleep $1 && kill -s 14 $$ &
27 alarmpid=$!
28 }
29
30 sigalrmhandler() {
31 if [ "$silent" = "no" ]; then
32 echo $program: alarm clock >&2
33 fi
34
35 alarmpid=''
36 if [ "$cmdpid" != '' ]; then
37 kill -ALRM $cmdpid
38 fi
39
40 exit 1
41 }
42
43
44
45 program=`basename $0`
46 trapsignals="0 1 2 3 15"
47
48 TIMEOUT=10
49
50 options=`getopt dt: $*` || exit 1
51 set -- $options
52 while true; do
53 opt=$1
54 case $opt in
55 -d)
56 set -x
57 shift
58 ;;
59
60 -t)
61 TIMEOUT=$2
62 shift 2
63 ;;
64
65 --)
66 shift
67 break
68 ;;
69
70 -*)
71 echo $program: unknown option: $opt 1>&2
72 exit 1
73 ;;
74
75 *)
76 break
77 ;;
78
79 esac
80 done
81
82
83
84 #
85 # Check parameters ...
86 #
87
88 if [ "$#" '<' 1 ]; then
89 echo usage: $program '[<options>] <filename> [...]' 1>&2
90 exit 1
91 fi
92
93
94 #
95 # ... capture some signals and set clock.
96 #
97
98 trap 'cleanup; exit' $trapsignals
99 setalarm $TIMEOUT
100
101
102 #
103 # Put the lockfiles
104 #
105
106 for file in $*; do
107 lock=$file.lock
108 while (! ln -s $file $lock >/dev/null 2>&1); do
109 sleep 1
110 done
111 LOCKFILES="$LOCKFILES $lock"
112 done
113
114
115 #
116 # Turn off the alarm and wait for input, send ok to caller
117 # and wait for termination.
118 #
119
120 kill $alarmpid
121 echo OK
122
123 while read line; do
124 x=x # do nothing
125 done
126
127 exit 0
128
129
The second question the script has to solve was "How can the lock action be most atomic?" The best thing I found is the ln(1) command (see line #108). I'm not really sure it is or not because neither ln(1) or link(2) says it.
The whole process of locking all files from the comamand line (line #106) is gouverned by a timeout (lines #99, #24, #30): if after a certain amount of time (default is 10 seconds, see line #48) not all requested files are locked the shell script receives an ALRM signal (line #37) and terminates without having send the "OK" to the caller. The alarm can only be terminated after all lockfiles are created.