xref: /freebsd/usr.bin/lockf/tests/lockf_test.sh (revision c82203e65d08036d25ed117f2617ef4ad07d6f97)
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