mirror of
https://github.com/freebsd/freebsd-src
synced 2024-07-25 04:07:37 +00:00
09a7fe0a55
This is most useful inside a shell script, allowing one to lock just portions of a script rather than having to wrap the entire script in a lock. PR: 262738 Reviewed by: 0mp, allanjude (both previous versions) Co-authored-by: Daniel O'Connor <darius@dons.net.au> Sponsored by: Klara, Inc. Differential Revision: https://reviews.freebsd.org/D42718
240 lines
5.6 KiB
Bash
240 lines
5.6 KiB
Bash
#
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
#
|
|
# Copyright (c) 2023 Klara, Inc.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
# SUCH DAMAGE.
|
|
#
|
|
|
|
# sysexits(3)
|
|
: ${EX_USAGE:=64}
|
|
: ${EX_UNAVAILABLE:=69}
|
|
: ${EX_CANTCREAT:=73}
|
|
: ${EX_TEMPFAIL:=75}
|
|
|
|
atf_test_case badargs
|
|
badargs_body()
|
|
{
|
|
atf_check -s exit:${EX_USAGE} -e not-empty lockf
|
|
atf_check -s exit:${EX_USAGE} -e not-empty lockf "testlock"
|
|
}
|
|
|
|
atf_test_case basic
|
|
basic_body()
|
|
{
|
|
# Something innocent so that it does eventually go away without our
|
|
# intervention.
|
|
lockf "testlock" sleep 10 &
|
|
lpid=$!
|
|
|
|
# Make sure that the lock exists...
|
|
atf_check test -e "testlock"
|
|
|
|
# Attempt both verbose and silent re-lock
|
|
atf_check -s exit:${EX_TEMPFAIL} -e not-empty \
|
|
lockf -t 0 "testlock" sleep 0
|
|
atf_check -s exit:${EX_TEMPFAIL} -e empty \
|
|
lockf -t 0 -s "testlock" sleep 0
|
|
|
|
# Make sure it cleans up after the initial sleep 10 is over.
|
|
wait "$lpid"
|
|
atf_check test ! -e "testlock"
|
|
}
|
|
|
|
atf_test_case fdlock
|
|
fdlock_body()
|
|
{
|
|
# First, make sure we don't get a false positive -- existing uses with
|
|
# numeric filenames shouldn't switch to being fdlocks automatically.
|
|
atf_check lockf -k "9" sleep 0
|
|
atf_check test -e "9"
|
|
rm "9"
|
|
|
|
subexit_lockfail=1
|
|
subexit_created=2
|
|
subexit_lockok=3
|
|
subexit_concurrent=4
|
|
(
|
|
lockf -s -t 0 9
|
|
if [ $? -ne 0 ]; then
|
|
exit "$subexit_lockfail"
|
|
fi
|
|
|
|
if [ -e "9" ]; then
|
|
exit "$subexit_created"
|
|
fi
|
|
) 9> "testlock1"
|
|
rc=$?
|
|
|
|
atf_check test "$rc" -eq 0
|
|
|
|
sub_delay=5
|
|
|
|
# But is it actually locking? Child 1 will acquire the lock and then
|
|
# signal that it's ok for the second child to try. The second child
|
|
# will try to acquire the lock and fail immediately, signal that it
|
|
# tried, then try again with an indefinite timeout. On that one, we'll
|
|
# just check how long we ended up waiting -- it should be at least
|
|
# $sub_delay.
|
|
(
|
|
lockf -s -t 0 /dev/fd/9
|
|
if [ $? -ne 0 ]; then
|
|
exit "$subexit_lockfail"
|
|
fi
|
|
|
|
# Signal
|
|
touch ".lock_acquired"
|
|
|
|
while [ ! -e ".lock_attempted" ]; do
|
|
sleep 0.5
|
|
done
|
|
|
|
sleep "$sub_delay"
|
|
|
|
if [ -e ".lock_acquired_again" ]; then
|
|
exit "$subexit_concurrent"
|
|
fi
|
|
) 9> "testlock2" &
|
|
lpid1=$!
|
|
|
|
(
|
|
while [ ! -e ".lock_acquired" ]; do
|
|
sleep 0.5
|
|
done
|
|
|
|
# Got the signal, try
|
|
lockf -s -t 0 9
|
|
if [ $? -ne "${EX_TEMPFAIL}" ]; then
|
|
exit "$subexit_lockok"
|
|
fi
|
|
|
|
touch ".lock_attempted"
|
|
start=$(date +"%s")
|
|
lockf -s 9
|
|
touch ".lock_acquired_again"
|
|
now=$(date +"%s")
|
|
elapsed=$((now - start))
|
|
|
|
if [ "$elapsed" -lt "$sub_delay" ]; then
|
|
exit "$subexit_concurrent"
|
|
fi
|
|
) 9> "testlock2" &
|
|
lpid2=$!
|
|
|
|
wait "$lpid1"
|
|
status1=$?
|
|
|
|
wait "$lpid2"
|
|
status2=$?
|
|
|
|
atf_check test "$status1" -eq 0
|
|
atf_check test "$status2" -eq 0
|
|
}
|
|
|
|
atf_test_case keep
|
|
keep_body()
|
|
{
|
|
lockf -k "testlock" sleep 10 &
|
|
lpid=$!
|
|
|
|
# Make sure that the lock exists now...
|
|
while ! test -e "testlock"; do
|
|
sleep 0.5
|
|
done
|
|
|
|
kill "$lpid"
|
|
wait "$lpid"
|
|
|
|
# And it still exits after the lock has been relinquished.
|
|
atf_check test -e "testlock"
|
|
}
|
|
|
|
atf_test_case needfile
|
|
needfile_body()
|
|
{
|
|
# Hopefully the clock doesn't jump.
|
|
start=$(date +"%s")
|
|
|
|
# Should fail if the lockfile does not yet exist.
|
|
atf_check -s exit:"${EX_UNAVAILABLE}" lockf -sn "testlock" sleep 30
|
|
|
|
# It's hard to guess how quickly we should have finished that; one would
|
|
# hope that it exits fast, but to be safe we specified a sleep 30 under
|
|
# lock so that we have a good margin below that duration that we can
|
|
# safely test to make sure we didn't actually execute the program, more
|
|
# or less.
|
|
now=$(date +"%s")
|
|
tpass=$((now - start))
|
|
atf_check test "$tpass" -lt 10
|
|
}
|
|
|
|
atf_test_case timeout
|
|
timeout_body()
|
|
{
|
|
lockf "testlock" sleep 30 &
|
|
lpid=$!
|
|
|
|
while ! test -e "testlock"; do
|
|
sleep 0.5
|
|
done
|
|
|
|
start=$(date +"%s")
|
|
timeout=2
|
|
atf_check -s exit:${EX_TEMPFAIL} lockf -st "$timeout" "testlock" sleep 0
|
|
|
|
# We should have taken no less than our timeout, at least.
|
|
now=$(date +"%s")
|
|
tpass=$((now - start))
|
|
atf_check test "$tpass" -ge "$timeout"
|
|
|
|
kill "$lpid"
|
|
wait "$lpid" || true
|
|
}
|
|
|
|
atf_test_case wrlock
|
|
wrlock_head()
|
|
{
|
|
atf_set "require.user" "unprivileged"
|
|
}
|
|
wrlock_body()
|
|
{
|
|
touch "testlock"
|
|
chmod -w "testlock"
|
|
|
|
# Demonstrate that we can lock the file normally, but -w fails if we
|
|
# can't write.
|
|
atf_check lockf -kt 0 "testlock" sleep 0
|
|
atf_check -s exit:${EX_CANTCREAT} -e not-empty \
|
|
lockf -wt 0 "testlock" sleep 0
|
|
}
|
|
|
|
atf_init_test_cases()
|
|
{
|
|
atf_add_test_case badargs
|
|
atf_add_test_case basic
|
|
atf_add_test_case fdlock
|
|
atf_add_test_case keep
|
|
atf_add_test_case needfile
|
|
atf_add_test_case timeout
|
|
atf_add_test_case wrlock
|
|
}
|