#!/usr/bin/env bats

load helpers

function setup() {
	setup_busybox
	update_config '.process.args = ["/bin/echo", "Hello World"]'
}

function teardown() {
	teardown_bundle
}

@test "runc run" {
	runc run test_hello
	[ "$status" -eq 0 ]

	runc state test_hello
	[ "$status" -ne 0 ]
}

@test "runc run --keep" {
	runc run --keep test_run_keep
	[ "$status" -eq 0 ]

	testcontainer test_run_keep stopped

	runc state test_run_keep
	[ "$status" -eq 0 ]

	runc delete test_run_keep

	runc state test_run_keep
	[ "$status" -ne 0 ]
}

@test "runc run --keep (check cgroup exists)" {
	# for systemd driver, the unit's cgroup path will be auto removed if container's all processes exited
	requires no_systemd
	[ $EUID -ne 0 ] && requires rootless_cgroup

	set_cgroups_path

	runc run --keep test_run_keep
	[ "$status" -eq 0 ]

	testcontainer test_run_keep stopped

	runc state test_run_keep
	[ "$status" -eq 0 ]

	# check that cgroup exists
	check_cgroup_value "pids.max" "max"

	runc delete test_run_keep

	runc state test_run_keep
	[ "$status" -ne 0 ]
}

@test "runc run [hostname domainname]" {
	update_config ' .process.args |= ["sh"]
			| .hostname = "myhostname"
			| .domainname= "mydomainname"'

	runc run -d --console-socket "$CONSOLE_SOCKET" test_utc
	[ "$status" -eq 0 ]

	# test hostname
	runc exec test_utc hostname
	[ "$status" -eq 0 ]
	[[ "${lines[0]}" == *'myhostname'* ]]

	# test domainname
	runc exec test_utc cat /proc/sys/kernel/domainname
	[ "$status" -eq 0 ]
	[[ "${lines[0]}" == *'mydomainname'* ]]
}

# https://github.com/opencontainers/runc/issues/3952
@test "runc run with tmpfs" {
	requires root

	chmod 'a=rwx,ug+s,+t' rootfs/tmp # set all bits
	mode=$(stat -c %A rootfs/tmp)

	update_config '.process.args = ["sh", "-c", "stat -c %A /tmp"]'
	update_config '.mounts += [{"destination": "/tmp", "type": "tmpfs", "source": "tmpfs", "options":["noexec","nosuid","nodev","rprivate"]}]'

	runc run test_tmpfs
	[ "$status" -eq 0 ]
	[ "${lines[0]}" = "$mode" ]
}

@test "runc run with tmpfs perms" {
	update_config '.process.args = ["sh", "-c", "stat -c %a /tmp/test"]'
	update_config '.mounts += [{"destination": "/tmp/test", "type": "tmpfs", "source": "tmpfs", "options": ["mode=0444"]}]'

	# Directory is to be created by runc.
	runc run test_tmpfs
	[ "$status" -eq 0 ]
	[ "${lines[0]}" = "444" ]

	# Run a 2nd time with the pre-existing directory.
	# Ref: https://github.com/opencontainers/runc/issues/3911
	runc run test_tmpfs
	[ "$status" -eq 0 ]
	[ "${lines[0]}" = "444" ]

	# Existing directory, custom perms, no mode on the mount,
	# so it should use the directory's perms.
	update_config '.mounts[-1].options = []'
	chmod 0710 rootfs/tmp/test
	runc run test_tmpfs
	[ "$status" -eq 0 ]
	[ "${lines[0]}" = "710" ]

	# Add back the mode on the mount, and it should use that instead.
	# Just for fun, use different perms than was used earlier.
	update_config '.mounts[-1].options = ["mode=0410"]'
	runc run test_tmpfs
	[ "$status" -eq 0 ]
	[ "${lines[0]}" = "410" ]
}

@test "runc run [/proc/self/exe clone]" {
	runc --debug run test_hello
	[ "$status" -eq 0 ]
	[[ "$output" = *"Hello World"* ]]
	[[ "$output" = *"runc-dmz: using /proc/self/exe clone"* ]]
	# runc will use fsopen("overlay") if it can.
	if can_fsopen overlay; then
		[[ "$output" = *"runc-dmz: using overlayfs for sealed /proc/self/exe"* ]]
	fi
}

@test "runc run [joining existing container namespaces]" {
	requires timens

	# Create a detached container with the namespaces we want. We notably want
	# to include both userns and timens, which require config-related
	# configuration.
	if [ $EUID -eq 0 ]; then
		update_config '.linux.namespaces += [{"type": "user"}]
			| .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 100}]
			| .linux.gidMappings += [{"containerID": 0, "hostID": 200000, "size": 200}]'
		remap_rootfs
	fi
	update_config '.linux.namespaces += [{"type": "time"}]
		| .linux.timeOffsets = {
			"monotonic": { "secs": 7881, "nanosecs": 2718281 },
			"boottime": { "secs": 1337, "nanosecs": 3141519 }
		}'
	update_config '.process.args = ["sleep", "infinity"]'

	runc run -d --console-socket "$CONSOLE_SOCKET" target_ctr
	[ "$status" -eq 0 ]

	# Modify our container's configuration such that it is just going to
	# inherit all of the namespaces of the target container.
	#
	# NOTE: We cannot join the mount namespace of another container because of
	# some quirks of the runtime-spec. In particular, we MUST pivot_root into
	# root.path and root.path MUST be set in the config, so runc cannot just
	# ignore root.path when joining namespaces (and root.path doesn't exist
	# inside root.path, for obvious reasons).
	#
	# We could hack around this (create a copy of the rootfs inside the rootfs,
	# or use a simpler mount namespace target), but those wouldn't be similar
	# tests to the other namespace joining tests.
	target_pid="$(__runc state target_ctr | jq .pid)"
	update_config '.linux.namespaces |= map_values(.path = if .type == "mount" then "" else "/proc/'"$target_pid"'/ns/" + ({"network": "net", "mount": "mnt"}[.type] // .type) end)'
	# Remove the userns and timens configuration (they cannot be changed).
	update_config '.linux |= (del(.uidMappings) | del(.gidMappings) | del(.timeOffsets))'

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

	# Make sure there are two sleep processes in our container.
	runc exec attached_ctr ps aux
	[ "$status" -eq 0 ]
	run -0 grep "sleep infinity" <<<"$output"
	[ "${#lines[@]}" -eq 2 ]

	# ... that the userns mappings are the same...
	runc exec attached_ctr cat /proc/self/uid_map
	[ "$status" -eq 0 ]
	if [ $EUID -eq 0 ]; then
		grep -E '^\s+0\s+100000\s+100$' <<<"$output"
	else
		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
	fi
	runc exec attached_ctr cat /proc/self/gid_map
	[ "$status" -eq 0 ]
	if [ $EUID -eq 0 ]; then
		grep -E '^\s+0\s+200000\s+200$' <<<"$output"
	else
		grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output"
	fi

	# ... as well as the timens offsets.
	runc exec attached_ctr cat /proc/self/timens_offsets
	grep -E '^monotonic\s+7881\s+2718281$' <<<"$output"
	grep -E '^boottime\s+1337\s+3141519$' <<<"$output"
}

@test "runc run [execve error]" {
	cat <<EOF >rootfs/run.sh
#!/mmnnttbb foo bar
sh
EOF
	chmod +x rootfs/run.sh
	update_config '.process.args = [ "/run.sh" ]'
	runc run test_hello
	[ "$status" -ne 0 ]

	# After the sync socket closed, we should not send error to parent
	# process, or else we will get a unnecessary error log(#4171).
	[ ${#lines[@]} -eq 1 ]
	[[ ${lines[0]} = "exec /run.sh: no such file or directory" ]]
}
