xref: /freebsd/usr.bin/lockf/tests/lockf_test.sh (revision 7ab1a32cd43cbae61ad4dd435d6a482bbf61cb52)
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
34atf_test_case badargs
35badargs_body()
36{
37	atf_check -s exit:${EX_USAGE} -e not-empty lockf
38	atf_check -s exit:${EX_USAGE} -e not-empty lockf "testlock"
39}
40
41atf_test_case basic
42basic_body()
43{
44	# Something innocent so that it does eventually go away without our
45	# intervention.
46	lockf "testlock" sleep 10 &
47	lpid=$!
48
49	# Make sure that the lock exists...
50	while ! test -e "testlock"; do
51		sleep 0.1
52	done
53
54	# Attempt both verbose and silent re-lock
55	atf_check -s exit:${EX_TEMPFAIL} -e not-empty \
56	    lockf -t 0 "testlock" sleep 0
57	atf_check -s exit:${EX_TEMPFAIL} -e empty \
58	    lockf -t 0 -s "testlock" sleep 0
59
60	# Make sure it cleans up after the initial sleep 10 is over.
61	wait "$lpid"
62	atf_check test ! -e "testlock"
63}
64
65atf_test_case fdlock
66fdlock_body()
67{
68	# First, make sure we don't get a false positive -- existing uses with
69	# numeric filenames shouldn't switch to being fdlocks automatically.
70	atf_check lockf -k "9" sleep 0
71	atf_check test -e "9"
72	rm "9"
73
74	subexit_lockfail=1
75	subexit_created=2
76	subexit_lockok=3
77	subexit_concurrent=4
78	(
79		lockf -s -t 0 9
80		if [ $? -ne 0 ]; then
81			exit "$subexit_lockfail"
82		fi
83
84		if [ -e "9" ]; then
85			exit "$subexit_created"
86		fi
87	) 9> "testlock1"
88	rc=$?
89
90	atf_check test "$rc" -eq 0
91
92	sub_delay=5
93
94	# But is it actually locking?  Child 1 will acquire the lock and then
95	# signal that it's ok for the second child to try.  The second child
96	# will try to acquire the lock and fail immediately, signal that it
97	# tried, then try again with an indefinite timeout.  On that one, we'll
98	# just check how long we ended up waiting -- it should be at least
99	# $sub_delay.
100	(
101		lockf -s -t 0 /dev/fd/9
102		if [ $? -ne 0 ]; then
103			exit "$subexit_lockfail"
104		fi
105
106		# Signal
107		touch ".lock_acquired"
108
109		while [ ! -e ".lock_attempted" ]; do
110			sleep 0.5
111		done
112
113		sleep "$sub_delay"
114
115		if [ -e ".lock_acquired_again" ]; then
116			exit "$subexit_concurrent"
117		fi
118	) 9> "testlock2" &
119	lpid1=$!
120
121	(
122		while [ ! -e ".lock_acquired" ]; do
123			sleep 0.5
124		done
125
126		# Got the signal, try
127		lockf -s -t 0 9
128		if [ $? -ne "${EX_TEMPFAIL}" ]; then
129			exit "$subexit_lockok"
130		fi
131
132		touch ".lock_attempted"
133		start=$(date +"%s")
134		lockf -s 9
135		touch ".lock_acquired_again"
136		now=$(date +"%s")
137		elapsed=$((now - start))
138
139		if [ "$elapsed" -lt "$sub_delay" ]; then
140			exit "$subexit_concurrent"
141		fi
142	) 9> "testlock2" &
143	lpid2=$!
144
145	wait "$lpid1"
146	status1=$?
147
148	wait "$lpid2"
149	status2=$?
150
151	atf_check test "$status1" -eq 0
152	atf_check test "$status2" -eq 0
153}
154
155atf_test_case keep
156keep_body()
157{
158	lockf -k "testlock" sleep 10 &
159	lpid=$!
160
161	# Make sure that the lock exists now...
162	while ! test -e "testlock"; do
163		sleep 0.5
164	done
165
166	kill "$lpid"
167	wait "$lpid"
168
169	# And it still exits after the lock has been relinquished.
170	atf_check test -e "testlock"
171}
172
173atf_test_case needfile
174needfile_body()
175{
176	# Hopefully the clock doesn't jump.
177	start=$(date +"%s")
178
179	# Should fail if the lockfile does not yet exist.
180	atf_check -s exit:"${EX_UNAVAILABLE}" lockf -sn "testlock" sleep 30
181
182	# It's hard to guess how quickly we should have finished that; one would
183	# hope that it exits fast, but to be safe we specified a sleep 30 under
184	# lock so that we have a good margin below that duration that we can
185	# safely test to make sure we didn't actually execute the program, more
186	# or less.
187	now=$(date +"%s")
188	tpass=$((now - start))
189	atf_check test "$tpass" -lt 10
190}
191
192atf_test_case timeout
193timeout_body()
194{
195	lockf "testlock" sleep 30 &
196	lpid=$!
197
198	while ! test -e "testlock"; do
199		sleep 0.5
200	done
201
202	start=$(date +"%s")
203	timeout=2
204	atf_check -s exit:${EX_TEMPFAIL} lockf -st "$timeout" "testlock" sleep 0
205
206	# We should have taken no less than our timeout, at least.
207	now=$(date +"%s")
208	tpass=$((now - start))
209	atf_check test "$tpass" -ge "$timeout"
210
211	kill "$lpid"
212	wait "$lpid" || true
213}
214
215atf_test_case wrlock
216wrlock_head()
217{
218	atf_set "require.user" "unprivileged"
219}
220wrlock_body()
221{
222	touch "testlock"
223	chmod -w "testlock"
224
225	# Demonstrate that we can lock the file normally, but -w fails if we
226	# can't write.
227	atf_check lockf -kt 0 "testlock" sleep 0
228	atf_check -s exit:${EX_CANTCREAT} -e not-empty \
229	    lockf -wt 0 "testlock" sleep 0
230}
231
232atf_init_test_cases()
233{
234	atf_add_test_case badargs
235	atf_add_test_case basic
236	atf_add_test_case fdlock
237	atf_add_test_case keep
238	atf_add_test_case needfile
239	atf_add_test_case timeout
240	atf_add_test_case wrlock
241}
242