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