#!/bin/bash
set -x -e

CI_PKGS=()

X86_64_PKGS=(gcc-multilib)

# Convert from string to array.
IFS=" " read -r -a ZDTM_OPTS <<< "$ZDTM_OPTS"

UNAME_M=$(uname -m)

if [ "$UNAME_M" != "x86_64" ]; then
	# Some tests rely on kernel features that may not be available
	# when running in a container. Here we assume that x86_64 systems
	# are baremetal, and skip the tests for all other  CPU architectures.
	# The RUN_TESTS environment variable can override this, e.g., for aarch64.
	[ -n "$RUN_TESTS" ] || SKIP_CI_TEST=1
fi

ci_prep () {
	[ -n "$SKIP_CI_PREP" ] && return

	cd ../../

	# At least one of the test cases run by this script (others/rpc)
	# expects a user with the ID 1000. sudo from 20.04 (focal) does
	# not run anymore with 'sudo -u \#1000' if the UID does not exist.
	adduser -u 1000 --disabled-password --gecos "criutest" criutest || :

	# This can fail on aarch64
	service apport stop || :

	# Ubuntu has set up AppArmor in 24.04 so that it blocks use of user
	# namespaces by unprivileged users. We need this for some of our tests.
	sysctl kernel.apparmor_restrict_unprivileged_userns=0 || :

	if [ "$CLANG" = "1" ]; then
		# clang support
		CC=clang
		# If this is running in an environment without gcc installed
		# compel-host-bin will fail as it is using HOSTCC. Also
		# set HOSTCC to clang to build compel-host-bin with it.
		export HOSTCC=clang
	else
		CC=gcc
	fi
	CI_PKGS+=("$CC")

	# Do not install x86_64 specific packages on other architectures
	if [ "$UNAME_M" = "x86_64" ]; then
		CI_PKGS+=("${X86_64_PKGS[@]}")
	fi

	contrib/dependencies/apt-packages.sh
	contrib/apt-install "${CI_PKGS[@]}"
	chmod a+x "$HOME"
}

test_stream() {
	# Testing CRIU streaming to criu-image-streamer

	# FIXME: Currently, hugetlb mappings is not premapped, so in the restore content
	# phase, we skip page read these pages, enqueue the iovec for later reading in
	# restorer and eventually close the page read. However, image-streamer expects the
	# whole image to be read and the image is not reopened, sent twice. These MAP_HUGETLB
	# test cases will result in EPIPE error at the moment.
	STREAM_TEST_EXCLUDE=(-x maps09 -x maps10)
	./test/zdtm.py run --stream -p 2 --keep-going -a "${STREAM_TEST_EXCLUDE[@]}" "${ZDTM_OPTS[@]}"
}

print_header() {
	echo "############### $1 ###############"
}

print_env() {
	set +x
	# As this script can run on multiple different CI systems
	# the following lines should give some context to the
	# evnvironment of this CI run.
	print_header "Environment variables"
	printenv
	print_header "uname -a"
	uname -a || :
	print_header "Mounted file systems"
	cat /proc/self/mountinfo || :
	print_header "Kernel command line"
	cat /proc/cmdline || :
	print_header "Kernel modules"
	lsmod || :
	print_header "Distribution information"
	[ -e /etc/lsb-release ] && cat /etc/lsb-release
	[ -e /etc/redhat-release ] && cat /etc/redhat-release
	[ -e /etc/alpine-release ] && cat /etc/alpine-release
	print_header "ulimit -a"
	ulimit -a
	print_header "Available memory"
	if [ -e /etc/alpine-release ]; then
		# Alpine's busybox based free does not understand -h
		free
	else
		free -h
	fi
	print_header "Available CPUs"
	lscpu || :
	set -x
}

# FIXME: workaround for the issue https://github.com/checkpoint-restore/criu/issues/1866
modprobe -v sit || :

print_env

ci_prep

if [ "${CD_TO_TOP}" = "1" ]; then
	cd ../../
fi

export GCOV CC
if [ -z "$COMPILE_FLAGS" ]; then
	LOCAL_COMPILE_FLAGS=("V=1")
else
	IFS=" " read -r -a LOCAL_COMPILE_FLAGS <<< "$COMPILE_FLAGS"
	LOCAL_COMPILE_FLAGS=("V=1" "${LOCAL_COMPILE_FLAGS[@]}")
fi
$CC --version
time make CC="$CC" -j4 "${LOCAL_COMPILE_FLAGS[@]}"

./criu/criu -v4 cpuinfo dump || :
./criu/criu -v4 cpuinfo check || :

# Check that help output fits into 80 columns
WIDTH=$(./criu/criu --help | wc --max-line-length)
if [ "$WIDTH" -gt 80 ]; then
	echo "criu --help output does not obey 80 characters line width!"
	exit 1
fi

# Unit tests at this point do not require any kernel or hardware capabilities.
# Just try to run it everywhere for now.
time make unittest

[ -n "$SKIP_CI_TEST" ] && exit 0

# Umount cpuset in cgroupv1 to make it move to cgroupv2
if [ -d /sys/fs/cgroup/cpuset ]; then
	umount /sys/fs/cgroup/cpuset
fi

ulimit -c unlimited

cgid=$$
cleanup_cgroup() {
	./test/zdtm_umount_cgroups $cgid
	dmesg
}
trap cleanup_cgroup EXIT
./test/zdtm_mount_cgroups $cgid

echo "|$(pwd)/test/abrt.sh %P %p %s %e" > /proc/sys/kernel/core_pattern

if [ "${COMPAT_TEST}x" = "yx" ] ; then
	# Dirty hack to keep both ia32 & x86_64 shared libs on a machine:
	# headers are probably not compatible, so apt-get doesn't allow
	# installing both versions, while we need one for CRIU and one
	# for 32-bit tests. A better way would involve launching docker..
	# But it would require making zdtm.py aware of docker and launching
	# tests inside the CT.
	INCOMPATIBLE_LIBS=(libaio-dev libcap-dev libnl-3-dev libnl-route-3-dev)
	IA32_PKGS=()
	REFUGE=64-refuge

	mkdir "$REFUGE"
	for i in "${INCOMPATIBLE_LIBS[@]}" ; do
		for j in $(dpkg --listfiles "$i" | grep '\.so$') ; do
			cp "$j" "$REFUGE/"
		done
		IA32_PKGS+=("$i:i386")
	done
	apt-get remove "${INCOMPATIBLE_LIBS[@]}"
	dpkg --add-architecture i386
	contrib/apt-install "${IA32_PKGS[@]}"
	mkdir -p /usr/lib/x86_64-linux-gnu/
	mv "$REFUGE"/* /usr/lib/x86_64-linux-gnu/
fi

time make CC="$CC" -j4 -C test/zdtm V=1

if [ "${COMPAT_TEST}x" = "yx" ] ; then
	# Cross-verify that zdtm tests are 32-bit
	file test/zdtm/static/env00 | grep 'ELF 32-bit' -q
fi

# umask has to be called before a first criu run, so that .gcda (coverage data)
# files are created with read-write permissions for all.
umask 0000
./criu/criu check
./criu/criu check --all || echo $?
if [ "$UNAME_M" == "x86_64" ]; then
	# This fails on aarch64 (aws-graviton2)
	./criu/criu cpuinfo dump
	./criu/criu cpuinfo check
fi

export SKIP_PREP=1

chmod 0777 test/
chmod 0777 test/zdtm/static
chmod 0777 test/zdtm/transition

# We run streaming tests separately to improve test completion times,
# hence the exit 0.
if [ "${STREAM_TEST}" = "1" ]; then
	./scripts/install-criu-image-streamer.sh
	test_stream
	exit 0
fi

./test/zdtm.py run -a -p 2 --keep-going "${ZDTM_OPTS[@]}"
if criu/criu check --feature move_mount_set_group; then
	./test/zdtm.py run -a -p 2 --mntns-compat-mode --keep-going "${ZDTM_OPTS[@]}"
fi

./test/zdtm.py run -a -p 2 --keep-going --criu-config "${ZDTM_OPTS[@]}"

# Newer kernels are blocking access to userfaultfd:
# uffd: Set unprivileged_userfaultfd sysctl knob to 1 if kernel faults must be handled without obtaining CAP_SYS_PTRACE capability
if [ -e /proc/sys/vm/unprivileged_userfaultfd ]; then
	echo 1 > /proc/sys/vm/unprivileged_userfaultfd
fi

LAZY_EXCLUDE=(-x maps04 -x cmdlinenv00 -x maps007)

LAZY_TESTS='.*(maps0|uffd-events|lazy-thp|futex|fork).*'
LAZY_OPTS=(-p 2 -T "$LAZY_TESTS" "${LAZY_EXCLUDE[@]}" "${ZDTM_OPTS[@]}")

./test/zdtm.py run "${LAZY_OPTS[@]}" --lazy-pages
./test/zdtm.py run "${LAZY_OPTS[@]}" --remote-lazy-pages
./test/zdtm.py run "${LAZY_OPTS[@]}" --remote-lazy-pages --tls

bash -x ./test/jenkins/criu-fault.sh
if [ "$UNAME_M" == "x86_64" ]; then
	# This fails on aarch64 (aws-graviton2) with:
	# 33: ERR: thread-bomb.c:49: pthread_attr_setstacksize(): 22
	bash -x ./test/jenkins/criu-fcg.sh
fi
bash -x ./test/jenkins/criu-inhfd.sh

if [ -z "$SKIP_EXT_DEV_TEST" ]; then
	make -C test/others/mnt-ext-dev/ run
	if criu/criu check --feature move_mount_set_group; then
		EXTRA_OPTS=--mntns-compat-mode make -C test/others/mnt-ext-dev/ run
	fi
fi

make -C test/others/make/ run CC="$CC"
if [ -n "$CIRCLECI" ]; then
       # GitHub Actions (and Cirrus CI) does not provide a real TTY and CRIU will fail with:
       # Error (criu/tty.c:1014): tty: Don't have tty to inherit session from, aborting
       make -C test/others/shell-job/ run
fi
make -C test/others/criu-ns/ run
make -C test/others/skip-file-rwx-check/ run
make -C test/others/rpc/ run

./test/zdtm.py run -t zdtm/static/env00 --sibling

./test/zdtm.py run -t zdtm/static/maps00 --preload-libfault
./test/zdtm.py run -t zdtm/static/maps02 --preload-libfault

./test/zdtm.py run -t zdtm/transition/maps007 --pre 2 --dedup
./test/zdtm.py run -t zdtm/transition/maps007 --pre 2 --noauto-dedup
./test/zdtm.py run -t zdtm/transition/maps007 --pre 2 --page-server
./test/zdtm.py run -t zdtm/transition/maps007 --pre 2 --page-server --dedup
./test/zdtm.py run -t zdtm/transition/maps007 --pre 2 --pre-dump-mode read

./test/zdtm.py run -t zdtm/transition/pid_reuse --pre 2 # start time based pid reuse detection
./test/zdtm.py run -t zdtm/transition/pidfd_store_sk --rpc --pre 2 # pidfd based pid reuse detection

./test/zdtm.py run -t zdtm/static/socket-tcp-local --norst

ip net add test
./test/zdtm.py run -t zdtm/static/env00 -f h --join-ns

# RPC testing
./test/zdtm.py run -t zdtm/static/env00 --rpc		# Basic
./test/zdtm.py run -t zdtm/static/env00 --rpc --pre 2 --page-server
./test/zdtm.py run -t zdtm/static/ptrace_sig -f h --rpc # Error handling (crfail test)

./test/zdtm.py run --empty-ns -T zdtm/static/socket-tcp*-local --iter 2

./test/zdtm.py run -t zdtm/static/env00 -t zdtm/transition/fork -t zdtm/static/ghost_holes00 -t zdtm/static/socket-tcp -t zdtm/static/msgque -k always
./test/crit-recode.py

# Rootless tests
# Check if cap_checkpoint_restore is supported and also if unshare -c is supported.
#
# Do not run this test in a container (see https://github.com/checkpoint-restore/criu/issues/2312).
# Before v6.8-rc1~215^2~6, the kernel currently did not show correct device and
# inode numbers in /proc/pid/maps for stackable file systems.
skip=0
findmnt -no FSTYPE / | grep overlay && {
	./criu/criu check --feature overlayfs_maps || skip=1
}
unshare -c /bin/true || skip=1
capsh --supports=cap_checkpoint_restore || skip=1

if [ "$skip" == 0 ]; then
	make -C test/zdtm/ cleanout
	rm -rf test/dump
	setcap cap_checkpoint_restore,cap_sys_ptrace+eip criu/criu
	if [ -d /sys/fs/selinux ] && command -v getenforce &>/dev/null; then
		# Note: selinux in Enforcing mode prevents us from calling clone3() or writing to ns_last_pid on restore; hence set to Permissive for the test and then set back.
		selinuxmode=$(getenforce)
		if [ "$selinuxmode" != "Disabled" ]; then
			setenforce Permissive
		fi

	fi
	# Run it as non-root in a user namespace. Since CAP_CHECKPOINT_RESTORE behaves differently in non-user namespaces (e.g. no access to map_files) this tests that we can dump and restore
	# under those conditions. Note that the "... && true" part is necessary; we need at least one statement after the tests so that bash can reap zombies in the user namespace,
	# otherwise it will exec the last statement and get replaced and nobody will be left to reap our zombies.
	sudo --user=#65534 --group=#65534 unshare -Ucfpm --mount-proc -- bash -c "./test/zdtm.py run -t zdtm/static/maps00 -f h --rootless && true"
	if [ -d /sys/fs/selinux ] && command -v getenforce &>/dev/null; then
		if [ "$selinuxmode" != "Disabled" ]; then
			setenforce "$selinuxmode"
		fi
	fi
	setcap -r criu/criu
else
	echo "Skipping unprivileged mode tests"
fi

# more crit testing
make -C test/others/crit run

# coredump testing
make -C test/others/criu-coredump run

# libcriu testing
make -C test/others/libcriu run

# external namespace testing
make -C test/others/ns_ext run

# config file parser and parameter testing
make -C test/others/config-file run

# action script testing
make -C test/others/action-script run

# Skip all further tests when running with GCOV=1
# The one test which currently cannot handle GCOV testing is compel/test
# Probably because the GCOV Makefile infrastructure does not exist in compel
[ -n "$GCOV" ] && exit 0

# compel testing
make -C compel/test

# amdgpu and cuda plugin testing
make amdgpu_plugin
make -C plugins/amdgpu/ test_topology_remap
./plugins/amdgpu/test_topology_remap

./test/zdtm.py run -t zdtm/static/maps00 -t zdtm/static/maps02 --criu-plugin cuda
./test/zdtm.py run -t zdtm/static/maps00 -t zdtm/static/maps02 --criu-plugin amdgpu
./test/zdtm.py run -t zdtm/static/maps00 -t zdtm/static/maps02 --criu-plugin amdgpu cuda
./test/zdtm.py run -t zdtm/static/busyloop00 --criu-plugin inventory_test_enabled inventory_test_disabled

./test/zdtm.py run -t zdtm/static/sigpending -t zdtm/static/pthread00 --mocked-cuda-checkpoint --fault 138
