#! /usr/bin/atf-sh
#	$NetBSD: t_accept_max.sh,v 1.4 2026/06/23 18:53:29 riastradh Exp $
#
# Copyright (c) 2026 The NetBSD Foundation, Inc.
# All rights reserved.
#
# 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
#

atf_test_case max2_pos
max2_pos_head()
{
	atf_set "descr" "Test inetd with accept_max of 2 (positional syntax)"
	atf_set "timeout" 10
}
max2_pos_body()
{
	cat <<EOF >inetd.conf
$(pwd)/sock stream,2 unix nowait $(whoami) $(pwd)/run run
EOF
	max2_test
}

atf_test_case max2_kv
max2_kv_head()
{
	atf_set "descr" "Test inetd with accept_max of 2 (k/v syntax)"
	atf_set "timeout" 10
}
max2_kv_body()
{
	cat <<EOF >inetd.conf
$(pwd)/sock on
	socktype = stream,
	protocol = unix,
	accept_max = 2,
	wait = no,
	user = $(whoami),
	exec = $(pwd)/run,
	args = run;
EOF
	max2_test
}

max2_test()
{
	atf_require_prog ${INETD:-inetd}
	max2_subshell || atf_fail "max2_subshell failed with status $?"
}
max2_subshell()
(

	# Arrange to kill jobs on exit.  We have at most four
	# jobs running or pending at any given time, so job ids %1
	# through %4 should cover them all.
	#
	trap 'for j in 1 2 3 4; do kill -9 %$j 2>/dev/null || :; done; wait' \
	    ALRM EXIT HUP INT PIPE TERM
	set -eu -m -x

	# Create some state.
	#
	echo 0 >ntasks
	: >output
	mkfifo 1A 1B 2A 2B 3A 3B

	# Configure inetd with a daemon.  The daemon will:
	#
	# 1. read a connection number $client,
	# 2. append ${client} to a log of output,
	# 3. open fifo ${client}A for writing to notify the test that
	#    it has done so, and then
	# 4. open fifo ${client}B for reading before exiting in order
	#    to wait until the parent is ready for us to exit.
	#
	cat <<'EOF' >run
#!/bin/sh
set -eu
flock ntasks sh -euc 'n=$(cat ntasks); echo $((n + 1)) >ntasks'
read -r client
printf '%s\n' "$client" >>output
: >"${client}A"
: <"${client}B"
flock ntasks sh -euc 'n=$(cat ntasks); echo $((n - 1)) >ntasks'
EOF
	chmod +x run

	# Start inetd and wait for it to bind its sockets.
	#
	${INETD:-inetd} -d -f ./inetd.conf & inetd=$!
	echo inetd running as $inetd
	sleep 1

	# Nothing should have happened so far, and no tasks should be
	# running:
	#
	case $(cat ntasks):$(cat output) in
	0:)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output

	# Open connection 1, and wait for the server to acknowledge it:
	#
	timeout 10s sh -c 'echo 1 | nc -U sock' & client1=$!
	echo client 1 running as $client1
	timeout 10s sh -c ': <1A' || return $?
	case $(cat ntasks):$(cat output) in
	1:1)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output

	# After this, the server should hang until we open 1B for
	# write.
	#
	# Open client 2 and wait for the server to acknowledge it too.
	#
	timeout 10s sh -c 'echo 2 | nc -U sock' & client2=$!
	echo client 2 running as $client2
	timeout 10s sh -c ': <2A' || return $?
	case $(cat ntasks):$(cat output) in
	2:2)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output

	# Open client 3, and wait a second for it to answer -- it
	# shouldn't answer or increment ntasks, because, having hit the
	# accept_max, inetd should have suspended it:
	#
	timeout 10s sh -c 'echo 3 | nc -U sock' & client3=$!
	echo client 3 running as $client3
	sleep 1

	# Two tasks should still be running at this point, but no new
	# output should have happened:
	#
	case $(cat ntasks):$(cat output) in
	2:)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output

	# Now let the connection 1 finish by writing to 1B, and wait
	# for the server to acknowledge client 3, by reading from 3A --
	# it should now print 3, promptly, and the first connection
	# should finish promptly too:
	#
	timeout 10s sh -c ': >1B; : <3A' || return $?
	case $(cat ntasks):$(cat output) in
	2:3)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output
	wait $client1 || atf_fail "failed to wait for client 1 ($?)"
	case $(cat ntasks):$(cat output) in
	2:)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output

	# Finally, let connections 2 and 3 finish, and wait for them to
	# do so:
	#
	timeout 10s sh -c ': >2B; : >3B' || return $?
	wait $client2 || atf_fail "failed to wait for client 2 ($?)"
	wait $client3 || atf_fail "failed to wait for client 3 ($?)"

	# No more tasks should be running, and no more output should have
	# happened.
	#
	case $(cat ntasks):$(cat output) in
	0:)	;;
	*)	echo '# ntasks' >&2
		cat ntasks >&2
		echo '# output' >&2
		cat output >&2
		return 1
		;;
	esac
	: >output

	# All done; kill inetd and wait for it to exit.
	#
	kill $inetd
	wait $inetd
)

atf_init_test_cases()
{

	atf_add_test_case max2_kv
	atf_add_test_case max2_pos
}
