1# nbdkit
2# Common functions used by the tests.
3# @configure_input@
4# Copyright (C) 2017-2020 Red Hat Inc.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
9#
10# * Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in the
15# documentation and/or other materials provided with the distribution.
16#
17# * Neither the name of Red Hat nor the names of its contributors may be
18# used to endorse or promote products derived from this software without
19# specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
22# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
25# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32# SUCH DAMAGE.
33
34# cleanup_fn f [args]
35#
36# A generic trap handling function.  This runs the function or command
37# f plus optional args when the script exits for any reason.
38declare -a _cleanup_hook
39cleanup_fn ()
40{
41    _cleanup_hook[${#_cleanup_hook[@]}]="$@"
42}
43
44_run_cleanup_hooks ()
45{
46    local _status=$? _i
47
48    set +e
49    trap '' INT QUIT TERM EXIT ERR
50    echo $0: run cleanup hooks: exit code $_status
51
52    for (( _i = 0; _i < ${#_cleanup_hook[@]}; ++_i )); do
53        ${_cleanup_hook[_i]}
54    done
55
56    exit $_status
57}
58trap _run_cleanup_hooks INT QUIT TERM EXIT ERR
59
60# requires program [args]
61#
62# Check that ‘program [args]’ works.  If not, skip the test.
63# For example to check that qemu-img is available, do:
64#
65# requires qemu-img --version
66requires ()
67{
68    ( "$@" ) </dev/null >/dev/null 2>&1 || {
69        echo "$0: ‘$*’ failed with error code $?"
70        echo "$0: test prerequisite is missing or not working"
71        exit 77
72    }
73}
74
75# qemu cannot connect to ::1 if IPv6 is disabled because of
76# the way it uses getaddrinfo.  See:
77# https://bugzilla.redhat.com/show_bug.cgi?id=808147
78# https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/SXDLSZ3GKXL6NDAKP4MPJ25IMHKN67X3/
79requires_ipv6_loopback ()
80{
81    requires qemu-img --version
82
83    # This should fail with "Connection refused".  If IPv6 is broken
84    # then it fails with "Address family for hostname not supported"
85    # instead.  It's very unlikely that port 1 is open.
86    if LANG=C qemu-img info "nbd:[::1]:1" |& \
87       grep -sq "Address family for hostname not supported"; then
88        echo "$0: IPv6 loopback is not available, skipping this test"
89        exit 77
90    fi
91}
92
93# Test host kernel is Linux and minimum version.  It's usually better
94# to test features rather than using this, but there are cases where
95# testing features of the current kernel is too hard.
96requires_linux_kernel_version ()
97{
98    local kver
99
100    # Test the kernel is Linux.
101    requires test "$(uname -s)" = "Linux"
102
103    # Test that it's the minimum version.
104    requires cut --version
105    requires bc --version
106    kver=$(uname -r | cut -d. -f1-2)
107    requires test "$(echo "$kver >= $1" | bc -l)" = "1"
108}
109
110# start_nbdkit -P pidfile args...
111#
112# Run nbdkit with args and wait for it to start up.  If it fails to
113# start up, exit with an error message.  Also a cleanup handler is
114# installed automatically which kills nbdkit on exit.
115start_nbdkit ()
116{
117    local _pidfile _i
118
119    # -P <pidfile> must be the first two parameters.
120    if [ "$1" != "-P" ]; then
121       echo "$0: start_nbdkit: -P <pidfile> option must be first"
122       exit 1
123    fi
124    _pidfile="$2"
125
126    # Run nbdkit.
127    nbdkit -v "$@"
128
129    # Wait for the pidfile to appear.
130    for _i in {1..60}; do
131        if test -s "$_pidfile"; then
132            break
133        fi
134        sleep 1
135    done
136    if ! test -s "$_pidfile"; then
137        echo "$0: start_nbdkit: PID file $_pidfile was not created"
138        exit 1
139    fi
140
141    # Kill nbdkit on exit.
142    cleanup_fn kill_nbdkit "$(cat "$_pidfile")"
143}
144
145# kill_nbdkit pid
146#
147# End the nbkdit process with the given pid.  Exit this script with an
148# error if nbdkit does not gracefully shutdown in a timely manner.
149kill_nbdkit ()
150{
151    local pid=$1 i
152
153    # Start with SIGTERM, and wait for graceful exit
154    kill $pid
155    for i in {1..60}; do
156        if ! kill -0 $pid 2>/dev/null; then
157            break
158        fi
159        sleep 1
160    done
161    # If nbdkit has not exited, try SIGKILL and fail the test
162    if test $i = 60; then
163        echo "error: nbdkit pid $pid failed to respond to SIGTERM"
164        kill -9 $pid
165        # Append our failure after other cleanups
166        cleanup_fn exit 1
167    fi
168}
169
170# foreach_plugin f [args]
171#
172# For each plugin that was built, run the function or command f with
173# the plugin name as the first argument, optionally followed by the
174# remaining args.
175foreach_plugin ()
176{
177    local f d p
178
179    f="$1"
180    shift
181
182    for p in @plugins@; do
183        # Was the plugin built?
184        d="@top_builddir@/plugins/$p"
185        if [ -f "$d/.libs/nbdkit-$p-plugin.so" ] ||
186           [ -f "$d/nbdkit-$p-plugin" ]; then
187            # Yes so run the test.
188            "$f" "$p" "$@"
189        fi
190    done
191}
192
193# pick_unused_port
194#
195# Picks and returns an "unused" port, setting the global variable
196# $port.
197#
198# This is inherently racy so we only use it where it's absolutely
199# necessary (eg. testing TLS because qemu cannot do TLS over a Unix
200# domain socket).
201pick_unused_port ()
202{
203    requires ss --version
204
205    # Start at a random port to make it less likely that two parallel
206    # tests will conflict.
207    port=$(( 50000 + (RANDOM%15000) ))
208    while ss -ltn | grep -sqE ":$port\b"; do
209        ((port++))
210        if [ $port -eq 65000 ]; then port=50000; fi
211    done
212    echo picked unused port $port
213}
214