1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2023 Klara, Inc. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27 28# sysexits(3) 29: ${EX_USAGE:=64} 30: ${EX_UNAVAILABLE:=69} 31: ${EX_CANTCREAT:=73} 32: ${EX_TEMPFAIL:=75} 33 34waitlock() 35{ 36 local cur lockfile tmo 37 38 lockfile="$1" 39 40 cur=0 41 tmo=20 42 43 while [ "$cur" -lt "$tmo" -a ! -f "$lockfile" ]; do 44 sleep 0.1 45 cur=$((cur + 1)) 46 done 47 48 atf_check_not_equal "$cur" "$tmo" 49} 50 51 52atf_test_case badargs 53badargs_body() 54{ 55 atf_check -s exit:${EX_USAGE} -e not-empty lockf 56 atf_check -s exit:${EX_USAGE} -e not-empty lockf "testlock" 57} 58 59atf_test_case basic 60basic_body() 61{ 62 # Something innocent so that it does eventually go away without our 63 # intervention. 64 lockf "testlock" sleep 10 & 65 lpid=$! 66 67 # Make sure that the lock exists... 68 while ! test -e "testlock"; do 69 sleep 0.1 70 done 71 72 # Attempt both verbose and silent re-lock 73 atf_check -s exit:${EX_TEMPFAIL} -e not-empty \ 74 lockf -t 0 "testlock" sleep 0 75 atf_check -s exit:${EX_TEMPFAIL} -e empty \ 76 lockf -t 0 -s "testlock" sleep 0 77 78 # Make sure it cleans up after the initial sleep 10 is over. 79 wait "$lpid" 80 atf_check test ! -e "testlock" 81} 82 83atf_test_case bubble_error 84bubble_error_body() 85{ 86 # Ensure that lockf bubbles up the error as expected. 87 atf_check -s exit:9 lockf testlock sh -c 'exit 9' 88} 89 90atf_test_case fdlock 91fdlock_body() 92{ 93 # First, make sure we don't get a false positive -- existing uses with 94 # numeric filenames shouldn't switch to being fdlocks automatically. 95 atf_check lockf -k "9" sleep 0 96 atf_check test -e "9" 97 rm "9" 98 99 subexit_lockfail=1 100 subexit_created=2 101 subexit_lockok=3 102 subexit_concurrent=4 103 ( 104 lockf -s -t 0 9 105 if [ $? -ne 0 ]; then 106 exit "$subexit_lockfail" 107 fi 108 109 if [ -e "9" ]; then 110 exit "$subexit_created" 111 fi 112 ) 9> "testlock1" 113 rc=$? 114 115 atf_check test "$rc" -eq 0 116 117 sub_delay=5 118 119 # But is it actually locking? Child 1 will acquire the lock and then 120 # signal that it's ok for the second child to try. The second child 121 # will try to acquire the lock and fail immediately, signal that it 122 # tried, then try again with an indefinite timeout. On that one, we'll 123 # just check how long we ended up waiting -- it should be at least 124 # $sub_delay. 125 ( 126 lockf -s -t 0 /dev/fd/9 127 if [ $? -ne 0 ]; then 128 exit "$subexit_lockfail" 129 fi 130 131 # Signal 132 touch ".lock_acquired" 133 134 while [ ! -e ".lock_attempted" ]; do 135 sleep 0.5 136 done 137 138 sleep "$sub_delay" 139 140 if [ -e ".lock_acquired_again" ]; then 141 exit "$subexit_concurrent" 142 fi 143 ) 9> "testlock2" & 144 lpid1=$! 145 146 ( 147 while [ ! -e ".lock_acquired" ]; do 148 sleep 0.5 149 done 150 151 # Got the signal, try 152 lockf -s -t 0 9 153 if [ $? -ne "${EX_TEMPFAIL}" ]; then 154 exit "$subexit_lockok" 155 fi 156 157 touch ".lock_attempted" 158 start=$(date +"%s") 159 lockf -s 9 160 touch ".lock_acquired_again" 161 now=$(date +"%s") 162 elapsed=$((now - start)) 163 164 if [ "$elapsed" -lt "$sub_delay" ]; then 165 exit "$subexit_concurrent" 166 fi 167 ) 9> "testlock2" & 168 lpid2=$! 169 170 wait "$lpid1" 171 status1=$? 172 173 wait "$lpid2" 174 status2=$? 175 176 atf_check test "$status1" -eq 0 177 atf_check test "$status2" -eq 0 178} 179 180atf_test_case keep 181keep_body() 182{ 183 lockf -k "testlock" sleep 10 & 184 lpid=$! 185 186 # Make sure that the lock exists now... 187 while ! test -e "testlock"; do 188 sleep 0.5 189 done 190 191 kill "$lpid" 192 wait "$lpid" 193 194 # And it still exits after the lock has been relinquished. 195 atf_check test -e "testlock" 196} 197 198atf_test_case needfile 199needfile_body() 200{ 201 # Hopefully the clock doesn't jump. 202 start=$(date +"%s") 203 204 # Should fail if the lockfile does not yet exist. 205 atf_check -s exit:"${EX_UNAVAILABLE}" lockf -sn "testlock" sleep 30 206 207 # It's hard to guess how quickly we should have finished that; one would 208 # hope that it exits fast, but to be safe we specified a sleep 30 under 209 # lock so that we have a good margin below that duration that we can 210 # safely test to make sure we didn't actually execute the program, more 211 # or less. 212 now=$(date +"%s") 213 tpass=$((now - start)) 214 atf_check test "$tpass" -lt 10 215} 216 217atf_test_case termchild 218termchild_body() 219{ 220 lockf -kp testlock sleep 30 & 221 lpid=$! 222 223 waitlock testlock 224 225 atf_check -o file:testlock pgrep -F testlock 226 227 start=$(date +"%s") 228 atf_check kill -TERM "$lpid" 229 wait "$lpid" 230 end=$(date +"%s") 231 elapsed=$((end - start)) 232 233 if [ "$elapsed" -gt 5 ]; then 234 atf_fail "lockf seems to have dodged the SIGTERM ($elapsed passed)" 235 fi 236 237 # We didn't start lockf with -T this time, so the process should not 238 # have been terminated. 239 atf_check -o file:testlock pgrep -F testlock 240 241 lockf -kpT testlock sleep 30 & 242 lpid=$! 243 244 waitlock testlock 245 246 atf_check -o file:testlock pgrep -F testlock 247 248 start=$(date +"%s") 249 atf_check kill -TERM "$lpid" 250 wait "$lpid" 251 end=$(date +"%s") 252 elapsed=$((end - start)) 253 254 if [ "$elapsed" -gt 5 ]; then 255 atf_fail "lockf -T seems to have dodged the SIGTERM ($elapsed passed)" 256 fi 257 258 # This time, it should have terminated (notably much earlier than our 259 # 30 second timeout). 260 atf_check -o empty -e not-empty -s not-exit:0 pgrep -F testlock 261} 262 263atf_test_case timeout 264timeout_body() 265{ 266 lockf "testlock" sleep 30 & 267 lpid=$! 268 269 while ! test -e "testlock"; do 270 sleep 0.5 271 done 272 273 start=$(date +"%s") 274 timeout=2 275 atf_check -s exit:${EX_TEMPFAIL} lockf -st "$timeout" "testlock" sleep 0 276 277 # We should have taken no less than our timeout, at least. 278 now=$(date +"%s") 279 tpass=$((now - start)) 280 atf_check test "$tpass" -ge "$timeout" 281 282 kill "$lpid" 283 wait "$lpid" || true 284} 285 286atf_test_case writepid 287writepid_body() 288{ 289 lockf -p "testlock" sleep 10 & 290 lpid=$! 291 292 waitlock "testlock" 293 294 atf_check test -s testlock 295 atf_check -o file:testlock pgrep -F testlock 296 atf_check -o file:testlock pgrep -F testlock -fx "sleep 10" 297 atf_check pkill -TERM -F testlock 298 299 wait 300 301 atf_check test ! -f testlock 302} 303 304atf_test_case writepid_keep 305writepid_keep_body() 306{ 307 # Check that we'll clobber any existing contents (a pid, usually) 308 # once we acquire the lock. 309 jot -b A -s "" 64 > testlock 310 atf_check lockf -kp testlock sleep 0 311 atf_check -o not-match:"A" cat testlock 312} 313 314atf_test_case wrlock 315wrlock_head() 316{ 317 atf_set "require.user" "unprivileged" 318} 319wrlock_body() 320{ 321 touch "testlock" 322 chmod -w "testlock" 323 324 # Demonstrate that we can lock the file normally, but -w fails if we 325 # can't write. 326 atf_check lockf -kt 0 "testlock" sleep 0 327 atf_check -s exit:${EX_CANTCREAT} -e not-empty \ 328 lockf -wt 0 "testlock" sleep 0 329} 330 331atf_init_test_cases() 332{ 333 atf_add_test_case badargs 334 atf_add_test_case basic 335 atf_add_test_case bubble_error 336 atf_add_test_case fdlock 337 atf_add_test_case keep 338 atf_add_test_case needfile 339 atf_add_test_case termchild 340 atf_add_test_case timeout 341 atf_add_test_case writepid 342 atf_add_test_case writepid_keep 343 atf_add_test_case wrlock 344} 345