#!/usr/bin/env bats

load helpers

function setup() {
	setup_busybox
}

function teardown() {
	teardown_bundle
}

# This needs to be placed at the top of the bats file to work around
# a shellcheck bug. See <https://github.com/koalaman/shellcheck/issues/2873>.
test_host_pidns_kill() {
	requires cgroups_freezer

	update_config '	  .linux.namespaces -= [{"type": "pid"}]'
	set_cgroups_path
	if [ $EUID -ne 0 ]; then
		requires rootless_cgroup
		# Can't mount real /proc when rootless + no pidns,
		# so change it to a bind-mounted one from the host.
		update_config '	  .mounts |= map((select(.type == "proc")
					| .type = "none"
					| .source = "/proc"
					| .options = ["rbind", "nosuid", "nodev", "noexec"]
				  ) // .)'
	fi

	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
	[ "$status" -eq 0 ]
	cgpath=$(get_cgroup_path "pids")
	init_pid=$(cat "$cgpath"/cgroup.procs)

	# Start a few more processes.
	for _ in 1 2 3 4 5; do
		__runc exec -d test_busybox sleep 1h
	done

	if [ -v KILL_INIT ]; then
		# Now kill the container's init process. Since the container do
		# not have own PID ns, its init is no special and the container
		# will still be up and running (except for rootless container
		# AND systemd cgroup driver AND systemd > v245, when systemd
		# kills the container; see "kill KILL [host pidns + init gone]"
		# below).
		kill -9 "$init_pid"
		wait_pids_gone 10 0.2 "$init_pid"
	fi

	# Get the list of all container processes.
	mapfile -t pids < <(cat "$cgpath"/cgroup.procs)
	echo "pids:" "${pids[@]}"
	# Sanity check -- make sure all processes exist.
	for p in "${pids[@]}"; do
		kill -0 "$p"
	done

	runc kill test_busybox KILL
	[ "$status" -eq 0 ]
	# Wait and check that all processes are gone.
	wait_pids_gone 10 0.2 "${pids[@]}"

	# Make sure the container is in stopped state. Note if KILL_INIT
	# is set, container was already stopped by killing its $init_pid
	# and so this check is NOP/redundant.
	testcontainer test_busybox stopped

	# Make sure cgroup.procs is empty.
	mapfile -t pids < <(cat "$cgpath"/cgroup.procs || true)
	if [ ${#pids[@]} -gt 0 ]; then
		echo "expected empty cgroup.procs, got:" "${pids[@]}" 1>&2
		return 1
	fi
}

@test "kill detached busybox" {
	# run busybox detached
	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
	[ "$status" -eq 0 ]

	# check state
	testcontainer test_busybox running

	runc kill test_busybox KILL
	[ "$status" -eq 0 ]
	wait_for_container 10 1 test_busybox stopped

	# Check that kill errors on a stopped container.
	runc kill test_busybox 0
	[ "$status" -ne 0 ]
	[[ "$output" == *"container not running"* ]]

	# Check that -a (now obsoleted) makes kill return no error for a stopped container.
	runc kill -a test_busybox 0
	[ "$status" -eq 0 ]

	runc delete test_busybox
	[ "$status" -eq 0 ]
}

# This is roughly the same as TestPIDHostInitProcessWait in libcontainer/integration.
# The differences are:
#
# 1. Here we use separate processes to create and to kill a container, so the
#    processes inside a container are not children of "runc kill".
#
# 2. We hit different codepaths (nonChildProcess.signal rather than initProcess.signal).
@test "kill KILL [host pidns]" {
	unset KILL_INIT
	test_host_pidns_kill
}

# Same as above plus:
#
# 3. Test runc kill on a container whose init process is gone.
#
# Issue 4047, case "runc kill".
# See also: "runc delete --force [host pidns + init gone]" test in delete.bats.
@test "kill KILL [host pidns + init gone]" {
	# Apparently, for rootless test, when using systemd cgroup manager,
	# newer versions of systemd clean up the container as soon as its init
	# process is gone. This is all fine and dandy, except it prevents us to
	# test this case, thus we skip the test.
	#
	# It is not entirely clear which systemd version got this feature:
	# v245 works fine, and v249 does not.
	if [ $EUID -ne 0 ] && [ -v RUNC_USE_SYSTEMD ] && [ "$(systemd_version)" -gt 245 ]; then
		skip "rootless+systemd conflicts with systemd > 245"
	fi
	KILL_INIT=1
	test_host_pidns_kill
	unset KILL_INIT
}

# https://github.com/opencontainers/runc/issues/4394 (cgroup v1, rootless)
@test "kill KILL [shared pidns]" {
	update_config '.process.args = ["sleep", "infinity"]'

	runc run -d --console-socket "$CONSOLE_SOCKET" target_ctr
	[ "$status" -eq 0 ]
	testcontainer target_ctr running
	target_pid="$(__runc state target_ctr | jq .pid)"
	update_config '.linux.namespaces |= map(if .type == "user" or .type == "pid" then (.path = "/proc/'"$target_pid"'/ns/" + .type) else . end) | del(.linux.uidMappings) | del(.linux.gidMappings)'

	runc run -d --console-socket "$CONSOLE_SOCKET" attached_ctr
	[ "$status" -eq 0 ]
	testcontainer attached_ctr running

	runc kill attached_ctr 9
	[ "$status" -eq 0 ]

	runc delete --force attached_ctr
	[ "$status" -eq 0 ]

	runc delete --force target_ctr
	[ "$status" -eq 0 ]
}
