1296a5a4dSKyle Evans# 2296a5a4dSKyle Evans# SPDX-License-Identifier: BSD-2-Clause 3296a5a4dSKyle Evans# 4296a5a4dSKyle Evans# Copyright (c) 2023 Klara, Inc. 5296a5a4dSKyle Evans# 6296a5a4dSKyle Evans# Redistribution and use in source and binary forms, with or without 7296a5a4dSKyle Evans# modification, are permitted provided that the following conditions 8296a5a4dSKyle Evans# are met: 9296a5a4dSKyle Evans# 1. Redistributions of source code must retain the above copyright 10296a5a4dSKyle Evans# notice, this list of conditions and the following disclaimer. 11296a5a4dSKyle Evans# 2. Redistributions in binary form must reproduce the above copyright 12296a5a4dSKyle Evans# notice, this list of conditions and the following disclaimer in the 13296a5a4dSKyle Evans# documentation and/or other materials provided with the distribution. 14296a5a4dSKyle Evans# 15296a5a4dSKyle Evans# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16296a5a4dSKyle Evans# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17296a5a4dSKyle Evans# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18296a5a4dSKyle Evans# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19296a5a4dSKyle Evans# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20296a5a4dSKyle Evans# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21296a5a4dSKyle Evans# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22296a5a4dSKyle Evans# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23296a5a4dSKyle Evans# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24296a5a4dSKyle Evans# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25296a5a4dSKyle Evans# SUCH DAMAGE. 26296a5a4dSKyle Evans# 27296a5a4dSKyle Evans 28296a5a4dSKyle Evans# sysexits(3) 29296a5a4dSKyle Evans: ${EX_USAGE:=64} 30296a5a4dSKyle Evans: ${EX_UNAVAILABLE:=69} 31296a5a4dSKyle Evans: ${EX_CANTCREAT:=73} 32296a5a4dSKyle Evans: ${EX_TEMPFAIL:=75} 33296a5a4dSKyle Evans 34*c82203e6SKyle Evanswaitlock() 35*c82203e6SKyle Evans{ 36*c82203e6SKyle Evans local cur lockfile tmo 37*c82203e6SKyle Evans 38*c82203e6SKyle Evans lockfile="$1" 39*c82203e6SKyle Evans 40*c82203e6SKyle Evans cur=0 41*c82203e6SKyle Evans tmo=20 42*c82203e6SKyle Evans 43*c82203e6SKyle Evans while [ "$cur" -lt "$tmo" -a ! -f "$lockfile" ]; do 44*c82203e6SKyle Evans sleep 0.1 45*c82203e6SKyle Evans cur=$((cur + 1)) 46*c82203e6SKyle Evans done 47*c82203e6SKyle Evans 48*c82203e6SKyle Evans atf_check_not_equal "$cur" "$tmo" 49*c82203e6SKyle Evans} 50*c82203e6SKyle Evans 51*c82203e6SKyle Evans 52296a5a4dSKyle Evansatf_test_case badargs 53296a5a4dSKyle Evansbadargs_body() 54296a5a4dSKyle Evans{ 55296a5a4dSKyle Evans atf_check -s exit:${EX_USAGE} -e not-empty lockf 56296a5a4dSKyle Evans atf_check -s exit:${EX_USAGE} -e not-empty lockf "testlock" 57296a5a4dSKyle Evans} 58296a5a4dSKyle Evans 59296a5a4dSKyle Evansatf_test_case basic 60296a5a4dSKyle Evansbasic_body() 61296a5a4dSKyle Evans{ 62296a5a4dSKyle Evans # Something innocent so that it does eventually go away without our 63296a5a4dSKyle Evans # intervention. 64296a5a4dSKyle Evans lockf "testlock" sleep 10 & 65296a5a4dSKyle Evans lpid=$! 66296a5a4dSKyle Evans 67296a5a4dSKyle Evans # Make sure that the lock exists... 68498b3b49SMark Johnston while ! test -e "testlock"; do 69498b3b49SMark Johnston sleep 0.1 70498b3b49SMark Johnston done 71296a5a4dSKyle Evans 72296a5a4dSKyle Evans # Attempt both verbose and silent re-lock 73296a5a4dSKyle Evans atf_check -s exit:${EX_TEMPFAIL} -e not-empty \ 74296a5a4dSKyle Evans lockf -t 0 "testlock" sleep 0 75296a5a4dSKyle Evans atf_check -s exit:${EX_TEMPFAIL} -e empty \ 76296a5a4dSKyle Evans lockf -t 0 -s "testlock" sleep 0 77296a5a4dSKyle Evans 78296a5a4dSKyle Evans # Make sure it cleans up after the initial sleep 10 is over. 79296a5a4dSKyle Evans wait "$lpid" 80296a5a4dSKyle Evans atf_check test ! -e "testlock" 81296a5a4dSKyle Evans} 82296a5a4dSKyle Evans 837e8afac0SKyle Evansatf_test_case bubble_error 847e8afac0SKyle Evansbubble_error_body() 857e8afac0SKyle Evans{ 867e8afac0SKyle Evans # Ensure that lockf bubbles up the error as expected. 877e8afac0SKyle Evans atf_check -s exit:9 lockf testlock sh -c 'exit 9' 887e8afac0SKyle Evans} 897e8afac0SKyle Evans 9009a7fe0aSKyle Evansatf_test_case fdlock 9109a7fe0aSKyle Evansfdlock_body() 9209a7fe0aSKyle Evans{ 9309a7fe0aSKyle Evans # First, make sure we don't get a false positive -- existing uses with 9409a7fe0aSKyle Evans # numeric filenames shouldn't switch to being fdlocks automatically. 9509a7fe0aSKyle Evans atf_check lockf -k "9" sleep 0 9609a7fe0aSKyle Evans atf_check test -e "9" 9709a7fe0aSKyle Evans rm "9" 9809a7fe0aSKyle Evans 9909a7fe0aSKyle Evans subexit_lockfail=1 10009a7fe0aSKyle Evans subexit_created=2 10109a7fe0aSKyle Evans subexit_lockok=3 10209a7fe0aSKyle Evans subexit_concurrent=4 10309a7fe0aSKyle Evans ( 10409a7fe0aSKyle Evans lockf -s -t 0 9 10509a7fe0aSKyle Evans if [ $? -ne 0 ]; then 10609a7fe0aSKyle Evans exit "$subexit_lockfail" 10709a7fe0aSKyle Evans fi 10809a7fe0aSKyle Evans 10909a7fe0aSKyle Evans if [ -e "9" ]; then 11009a7fe0aSKyle Evans exit "$subexit_created" 11109a7fe0aSKyle Evans fi 11209a7fe0aSKyle Evans ) 9> "testlock1" 11309a7fe0aSKyle Evans rc=$? 11409a7fe0aSKyle Evans 11509a7fe0aSKyle Evans atf_check test "$rc" -eq 0 11609a7fe0aSKyle Evans 11709a7fe0aSKyle Evans sub_delay=5 11809a7fe0aSKyle Evans 11909a7fe0aSKyle Evans # But is it actually locking? Child 1 will acquire the lock and then 12009a7fe0aSKyle Evans # signal that it's ok for the second child to try. The second child 12109a7fe0aSKyle Evans # will try to acquire the lock and fail immediately, signal that it 12209a7fe0aSKyle Evans # tried, then try again with an indefinite timeout. On that one, we'll 12309a7fe0aSKyle Evans # just check how long we ended up waiting -- it should be at least 12409a7fe0aSKyle Evans # $sub_delay. 12509a7fe0aSKyle Evans ( 12609a7fe0aSKyle Evans lockf -s -t 0 /dev/fd/9 12709a7fe0aSKyle Evans if [ $? -ne 0 ]; then 12809a7fe0aSKyle Evans exit "$subexit_lockfail" 12909a7fe0aSKyle Evans fi 13009a7fe0aSKyle Evans 13109a7fe0aSKyle Evans # Signal 13209a7fe0aSKyle Evans touch ".lock_acquired" 13309a7fe0aSKyle Evans 13409a7fe0aSKyle Evans while [ ! -e ".lock_attempted" ]; do 13509a7fe0aSKyle Evans sleep 0.5 13609a7fe0aSKyle Evans done 13709a7fe0aSKyle Evans 13809a7fe0aSKyle Evans sleep "$sub_delay" 13909a7fe0aSKyle Evans 14009a7fe0aSKyle Evans if [ -e ".lock_acquired_again" ]; then 14109a7fe0aSKyle Evans exit "$subexit_concurrent" 14209a7fe0aSKyle Evans fi 14309a7fe0aSKyle Evans ) 9> "testlock2" & 14409a7fe0aSKyle Evans lpid1=$! 14509a7fe0aSKyle Evans 14609a7fe0aSKyle Evans ( 14709a7fe0aSKyle Evans while [ ! -e ".lock_acquired" ]; do 14809a7fe0aSKyle Evans sleep 0.5 14909a7fe0aSKyle Evans done 15009a7fe0aSKyle Evans 15109a7fe0aSKyle Evans # Got the signal, try 15209a7fe0aSKyle Evans lockf -s -t 0 9 15309a7fe0aSKyle Evans if [ $? -ne "${EX_TEMPFAIL}" ]; then 15409a7fe0aSKyle Evans exit "$subexit_lockok" 15509a7fe0aSKyle Evans fi 15609a7fe0aSKyle Evans 15709a7fe0aSKyle Evans touch ".lock_attempted" 15809a7fe0aSKyle Evans start=$(date +"%s") 15909a7fe0aSKyle Evans lockf -s 9 16009a7fe0aSKyle Evans touch ".lock_acquired_again" 16109a7fe0aSKyle Evans now=$(date +"%s") 16209a7fe0aSKyle Evans elapsed=$((now - start)) 16309a7fe0aSKyle Evans 16409a7fe0aSKyle Evans if [ "$elapsed" -lt "$sub_delay" ]; then 16509a7fe0aSKyle Evans exit "$subexit_concurrent" 16609a7fe0aSKyle Evans fi 16709a7fe0aSKyle Evans ) 9> "testlock2" & 16809a7fe0aSKyle Evans lpid2=$! 16909a7fe0aSKyle Evans 17009a7fe0aSKyle Evans wait "$lpid1" 17109a7fe0aSKyle Evans status1=$? 17209a7fe0aSKyle Evans 17309a7fe0aSKyle Evans wait "$lpid2" 17409a7fe0aSKyle Evans status2=$? 17509a7fe0aSKyle Evans 17609a7fe0aSKyle Evans atf_check test "$status1" -eq 0 17709a7fe0aSKyle Evans atf_check test "$status2" -eq 0 17809a7fe0aSKyle Evans} 17909a7fe0aSKyle Evans 180296a5a4dSKyle Evansatf_test_case keep 181296a5a4dSKyle Evanskeep_body() 182296a5a4dSKyle Evans{ 183296a5a4dSKyle Evans lockf -k "testlock" sleep 10 & 184296a5a4dSKyle Evans lpid=$! 185296a5a4dSKyle Evans 186296a5a4dSKyle Evans # Make sure that the lock exists now... 187296a5a4dSKyle Evans while ! test -e "testlock"; do 188296a5a4dSKyle Evans sleep 0.5 189296a5a4dSKyle Evans done 190296a5a4dSKyle Evans 191296a5a4dSKyle Evans kill "$lpid" 192296a5a4dSKyle Evans wait "$lpid" 193296a5a4dSKyle Evans 194296a5a4dSKyle Evans # And it still exits after the lock has been relinquished. 195296a5a4dSKyle Evans atf_check test -e "testlock" 196296a5a4dSKyle Evans} 197296a5a4dSKyle Evans 198296a5a4dSKyle Evansatf_test_case needfile 199296a5a4dSKyle Evansneedfile_body() 200296a5a4dSKyle Evans{ 201296a5a4dSKyle Evans # Hopefully the clock doesn't jump. 202296a5a4dSKyle Evans start=$(date +"%s") 203296a5a4dSKyle Evans 204296a5a4dSKyle Evans # Should fail if the lockfile does not yet exist. 205296a5a4dSKyle Evans atf_check -s exit:"${EX_UNAVAILABLE}" lockf -sn "testlock" sleep 30 206296a5a4dSKyle Evans 207296a5a4dSKyle Evans # It's hard to guess how quickly we should have finished that; one would 208296a5a4dSKyle Evans # hope that it exits fast, but to be safe we specified a sleep 30 under 209296a5a4dSKyle Evans # lock so that we have a good margin below that duration that we can 210296a5a4dSKyle Evans # safely test to make sure we didn't actually execute the program, more 211296a5a4dSKyle Evans # or less. 212296a5a4dSKyle Evans now=$(date +"%s") 213296a5a4dSKyle Evans tpass=$((now - start)) 214296a5a4dSKyle Evans atf_check test "$tpass" -lt 10 215296a5a4dSKyle Evans} 216296a5a4dSKyle Evans 217*c82203e6SKyle Evansatf_test_case termchild 218*c82203e6SKyle Evanstermchild_body() 219*c82203e6SKyle Evans{ 220*c82203e6SKyle Evans lockf -kp testlock sleep 30 & 221*c82203e6SKyle Evans lpid=$! 222*c82203e6SKyle Evans 223*c82203e6SKyle Evans waitlock testlock 224*c82203e6SKyle Evans 225*c82203e6SKyle Evans atf_check -o file:testlock pgrep -F testlock 226*c82203e6SKyle Evans 227*c82203e6SKyle Evans start=$(date +"%s") 228*c82203e6SKyle Evans atf_check kill -TERM "$lpid" 229*c82203e6SKyle Evans wait "$lpid" 230*c82203e6SKyle Evans end=$(date +"%s") 231*c82203e6SKyle Evans elapsed=$((end - start)) 232*c82203e6SKyle Evans 233*c82203e6SKyle Evans if [ "$elapsed" -gt 5 ]; then 234*c82203e6SKyle Evans atf_fail "lockf seems to have dodged the SIGTERM ($elapsed passed)" 235*c82203e6SKyle Evans fi 236*c82203e6SKyle Evans 237*c82203e6SKyle Evans # We didn't start lockf with -T this time, so the process should not 238*c82203e6SKyle Evans # have been terminated. 239*c82203e6SKyle Evans atf_check -o file:testlock pgrep -F testlock 240*c82203e6SKyle Evans 241*c82203e6SKyle Evans lockf -kpT testlock sleep 30 & 242*c82203e6SKyle Evans lpid=$! 243*c82203e6SKyle Evans 244*c82203e6SKyle Evans waitlock testlock 245*c82203e6SKyle Evans 246*c82203e6SKyle Evans atf_check -o file:testlock pgrep -F testlock 247*c82203e6SKyle Evans 248*c82203e6SKyle Evans start=$(date +"%s") 249*c82203e6SKyle Evans atf_check kill -TERM "$lpid" 250*c82203e6SKyle Evans wait "$lpid" 251*c82203e6SKyle Evans end=$(date +"%s") 252*c82203e6SKyle Evans elapsed=$((end - start)) 253*c82203e6SKyle Evans 254*c82203e6SKyle Evans if [ "$elapsed" -gt 5 ]; then 255*c82203e6SKyle Evans atf_fail "lockf -T seems to have dodged the SIGTERM ($elapsed passed)" 256*c82203e6SKyle Evans fi 257*c82203e6SKyle Evans 258*c82203e6SKyle Evans # This time, it should have terminated (notably much earlier than our 259*c82203e6SKyle Evans # 30 second timeout). 260*c82203e6SKyle Evans atf_check -o empty -e not-empty -s not-exit:0 pgrep -F testlock 261*c82203e6SKyle Evans} 262*c82203e6SKyle Evans 263296a5a4dSKyle Evansatf_test_case timeout 264296a5a4dSKyle Evanstimeout_body() 265296a5a4dSKyle Evans{ 266296a5a4dSKyle Evans lockf "testlock" sleep 30 & 267296a5a4dSKyle Evans lpid=$! 268296a5a4dSKyle Evans 269296a5a4dSKyle Evans while ! test -e "testlock"; do 270296a5a4dSKyle Evans sleep 0.5 271296a5a4dSKyle Evans done 272296a5a4dSKyle Evans 273296a5a4dSKyle Evans start=$(date +"%s") 274296a5a4dSKyle Evans timeout=2 275296a5a4dSKyle Evans atf_check -s exit:${EX_TEMPFAIL} lockf -st "$timeout" "testlock" sleep 0 276296a5a4dSKyle Evans 277296a5a4dSKyle Evans # We should have taken no less than our timeout, at least. 278296a5a4dSKyle Evans now=$(date +"%s") 279296a5a4dSKyle Evans tpass=$((now - start)) 280296a5a4dSKyle Evans atf_check test "$tpass" -ge "$timeout" 281296a5a4dSKyle Evans 282296a5a4dSKyle Evans kill "$lpid" 283296a5a4dSKyle Evans wait "$lpid" || true 284296a5a4dSKyle Evans} 285296a5a4dSKyle Evans 286*c82203e6SKyle Evansatf_test_case writepid 287*c82203e6SKyle Evanswritepid_body() 288*c82203e6SKyle Evans{ 289*c82203e6SKyle Evans lockf -p "testlock" sleep 10 & 290*c82203e6SKyle Evans lpid=$! 291*c82203e6SKyle Evans 292*c82203e6SKyle Evans waitlock "testlock" 293*c82203e6SKyle Evans 294*c82203e6SKyle Evans atf_check test -s testlock 295*c82203e6SKyle Evans atf_check -o file:testlock pgrep -F testlock 296*c82203e6SKyle Evans atf_check -o file:testlock pgrep -F testlock -fx "sleep 10" 297*c82203e6SKyle Evans atf_check pkill -TERM -F testlock 298*c82203e6SKyle Evans 299*c82203e6SKyle Evans wait 300*c82203e6SKyle Evans 301*c82203e6SKyle Evans atf_check test ! -f testlock 302*c82203e6SKyle Evans} 303*c82203e6SKyle Evans 304*c82203e6SKyle Evansatf_test_case writepid_keep 305*c82203e6SKyle Evanswritepid_keep_body() 306*c82203e6SKyle Evans{ 307*c82203e6SKyle Evans # Check that we'll clobber any existing contents (a pid, usually) 308*c82203e6SKyle Evans # once we acquire the lock. 309*c82203e6SKyle Evans jot -b A -s "" 64 > testlock 310*c82203e6SKyle Evans atf_check lockf -kp testlock sleep 0 311*c82203e6SKyle Evans atf_check -o not-match:"A" cat testlock 312*c82203e6SKyle Evans} 313*c82203e6SKyle Evans 314296a5a4dSKyle Evansatf_test_case wrlock 315296a5a4dSKyle Evanswrlock_head() 316296a5a4dSKyle Evans{ 317296a5a4dSKyle Evans atf_set "require.user" "unprivileged" 318296a5a4dSKyle Evans} 319296a5a4dSKyle Evanswrlock_body() 320296a5a4dSKyle Evans{ 321296a5a4dSKyle Evans touch "testlock" 322296a5a4dSKyle Evans chmod -w "testlock" 323296a5a4dSKyle Evans 324296a5a4dSKyle Evans # Demonstrate that we can lock the file normally, but -w fails if we 325296a5a4dSKyle Evans # can't write. 326296a5a4dSKyle Evans atf_check lockf -kt 0 "testlock" sleep 0 327296a5a4dSKyle Evans atf_check -s exit:${EX_CANTCREAT} -e not-empty \ 328296a5a4dSKyle Evans lockf -wt 0 "testlock" sleep 0 329296a5a4dSKyle Evans} 330296a5a4dSKyle Evans 331296a5a4dSKyle Evansatf_init_test_cases() 332296a5a4dSKyle Evans{ 333296a5a4dSKyle Evans atf_add_test_case badargs 334296a5a4dSKyle Evans atf_add_test_case basic 3357e8afac0SKyle Evans atf_add_test_case bubble_error 33609a7fe0aSKyle Evans atf_add_test_case fdlock 337296a5a4dSKyle Evans atf_add_test_case keep 338296a5a4dSKyle Evans atf_add_test_case needfile 339*c82203e6SKyle Evans atf_add_test_case termchild 340296a5a4dSKyle Evans atf_add_test_case timeout 341*c82203e6SKyle Evans atf_add_test_case writepid 342*c82203e6SKyle Evans atf_add_test_case writepid_keep 343296a5a4dSKyle Evans atf_add_test_case wrlock 344296a5a4dSKyle Evans} 345