github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/tests/integration/helpers.bash (about)

     1  #!/bin/bash
     2  
     3  set -u
     4  
     5  bats_require_minimum_version 1.5.0
     6  
     7  # Root directory of integration tests.
     8  INTEGRATION_ROOT=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
     9  
    10  # Download images, get *_IMAGE variables.
    11  IMAGES=$("${INTEGRATION_ROOT}"/get-images.sh)
    12  eval "$IMAGES"
    13  unset IMAGES
    14  
    15  : "${RUNC:="${INTEGRATION_ROOT}/../../runc"}"
    16  RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty"
    17  SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
    18  SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent"
    19  FS_IDMAP="${INTEGRATION_ROOT}/../../contrib/cmd/fs-idmap/fs-idmap"
    20  PIDFD_KILL="${INTEGRATION_ROOT}/../../contrib/cmd/pidfd-kill/pidfd-kill"
    21  REMAP_ROOTFS="${INTEGRATION_ROOT}/../../contrib/cmd/remap-rootfs/remap-rootfs"
    22  
    23  # Some variables may not always be set. Set those to empty value,
    24  # if unset, to avoid "unbound variable" error.
    25  : "${ROOTLESS_FEATURES:=}"
    26  
    27  # Test data path.
    28  # shellcheck disable=SC2034
    29  TESTDATA="${INTEGRATION_ROOT}/testdata"
    30  
    31  # Kernel version
    32  KERNEL_VERSION="$(uname -r)"
    33  KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
    34  KERNEL_MINOR="${KERNEL_VERSION#"$KERNEL_MAJOR".}"
    35  KERNEL_MINOR="${KERNEL_MINOR%%.*}"
    36  
    37  ARCH=$(uname -m)
    38  
    39  # Seccomp agent socket.
    40  SECCCOMP_AGENT_SOCKET="$BATS_TMPDIR/seccomp-agent.sock"
    41  
    42  # Wrapper for runc.
    43  function runc() {
    44  	run __runc "$@"
    45  
    46  	# Some debug information to make life easier. bats will only print it if the
    47  	# test failed, in which case the output is useful.
    48  	# shellcheck disable=SC2154
    49  	echo "$(basename "$RUNC") $* (status=$status):" >&2
    50  	# shellcheck disable=SC2154
    51  	echo "$output" >&2
    52  }
    53  
    54  # Raw wrapper for runc.
    55  function __runc() {
    56  	"$RUNC" ${RUNC_USE_SYSTEMD+--systemd-cgroup} \
    57  		${ROOT:+--root "$ROOT/state"} "$@"
    58  }
    59  
    60  # Wrapper for runc spec.
    61  function runc_spec() {
    62  	local rootless=""
    63  	[ $EUID -ne 0 ] && rootless="--rootless"
    64  
    65  	runc spec $rootless
    66  
    67  	# Always add additional mappings if we have idmaps.
    68  	if [[ $EUID -ne 0 && "$ROOTLESS_FEATURES" == *"idmap"* ]]; then
    69  		runc_rootless_idmap
    70  	fi
    71  }
    72  
    73  # Helper function to reformat config.json file. Input uses jq syntax.
    74  function update_config() {
    75  	jq "$@" "./config.json" | awk 'BEGIN{RS="";getline<"-";print>ARGV[1]}' "./config.json"
    76  }
    77  
    78  # Shortcut to add additional uids and gids, based on the values set as part of
    79  # a rootless configuration.
    80  function runc_rootless_idmap() {
    81  	update_config ' .mounts |= map((select(.type == "devpts") | .options += ["gid=5"]) // .)
    82  			| .linux.uidMappings += [{"hostID": '"$ROOTLESS_UIDMAP_START"', "containerID": 1000, "size": '"$ROOTLESS_UIDMAP_LENGTH"'}]
    83  			| .linux.gidMappings += [{"hostID": '"$ROOTLESS_GIDMAP_START"', "containerID": 100, "size": 1}]
    84  			| .linux.gidMappings += [{"hostID": '"$((ROOTLESS_GIDMAP_START + 10))"', "containerID": 1, "size": 20}]
    85  			| .linux.gidMappings += [{"hostID": '"$((ROOTLESS_GIDMAP_START + 100))"', "containerID": 1000, "size": '"$((ROOTLESS_GIDMAP_LENGTH - 1000))"'}]'
    86  }
    87  
    88  # Returns systemd version as a number (-1 if systemd is not enabled/supported).
    89  function systemd_version() {
    90  	if [ -v RUNC_USE_SYSTEMD ]; then
    91  		systemctl --version | awk '/^systemd / {print $2; exit}'
    92  		return
    93  	fi
    94  
    95  	echo "-1"
    96  }
    97  
    98  function init_cgroup_paths() {
    99  	# init once
   100  	[[ -v CGROUP_V1 || -v CGROUP_V2 ]] && return
   101  
   102  	if stat -f -c %t /sys/fs/cgroup | grep -qFw 63677270; then
   103  		CGROUP_V2=yes
   104  		local controllers="/sys/fs/cgroup/cgroup.controllers"
   105  		# For rootless + systemd case, controllers delegation is required,
   106  		# so check the controllers that the current user has, not the top one.
   107  		# NOTE: delegation of cpuset requires systemd >= 244 (Fedora >= 32, Ubuntu >= 20.04).
   108  		if [[ $EUID -ne 0 && -v RUNC_USE_SYSTEMD ]]; then
   109  			controllers="/sys/fs/cgroup/user.slice/user-${UID}.slice/user@${UID}.service/cgroup.controllers"
   110  		fi
   111  
   112  		# "pseudo" controllers do not appear in /sys/fs/cgroup/cgroup.controllers.
   113  		# - devices (since kernel 4.15) we must assume to be supported because
   114  		#   it's quite hard to test.
   115  		# - freezer (since kernel 5.2) we can auto-detect by looking for the
   116  		#   "cgroup.freeze" file a *non-root* cgroup.
   117  		CGROUP_SUBSYSTEMS=$(
   118  			cat "$controllers"
   119  			echo devices
   120  		)
   121  		CGROUP_BASE_PATH=/sys/fs/cgroup
   122  
   123  		# Find any cgroup.freeze files...
   124  		if [ -n "$(find "$CGROUP_BASE_PATH" -type f -name "cgroup.freeze" -print -quit)" ]; then
   125  			CGROUP_SUBSYSTEMS+=" freezer"
   126  		fi
   127  	else
   128  		if stat -f -c %t /sys/fs/cgroup/unified 2>/dev/null | grep -qFw 63677270; then
   129  			CGROUP_HYBRID=yes
   130  		fi
   131  		CGROUP_V1=yes
   132  		CGROUP_SUBSYSTEMS=$(awk '!/^#/ {print $1}' /proc/cgroups)
   133  		local g base_path
   134  		for g in ${CGROUP_SUBSYSTEMS}; do
   135  			# This uses gawk-specific feature (\< ... \>).
   136  			base_path=$(gawk '$(NF-2) == "cgroup" && $NF ~ /\<'"${g}"'\>/ { print $5; exit }' /proc/self/mountinfo)
   137  			test -z "$base_path" && continue
   138  			eval CGROUP_"${g^^}"_BASE_PATH="${base_path}"
   139  		done
   140  	fi
   141  }
   142  
   143  function create_parent() {
   144  	if [ -v RUNC_USE_SYSTEMD ]; then
   145  		[ ! -v SD_PARENT_NAME ] && return
   146  		"$SD_HELPER" --parent machine.slice start "$SD_PARENT_NAME"
   147  	else
   148  		[ ! -v REL_PARENT_PATH ] && return
   149  		if [ -v CGROUP_V2 ]; then
   150  			mkdir "/sys/fs/cgroup$REL_PARENT_PATH"
   151  		else
   152  			local subsys
   153  			for subsys in ${CGROUP_SUBSYSTEMS}; do
   154  				# Have to ignore EEXIST (-p) as some subsystems
   155  				# are mounted together (e.g. cpu,cpuacct), so
   156  				# the path is created more than once.
   157  				mkdir -p "/sys/fs/cgroup/$subsys$REL_PARENT_PATH"
   158  			done
   159  		fi
   160  	fi
   161  }
   162  
   163  function remove_parent() {
   164  	if [ -v RUNC_USE_SYSTEMD ]; then
   165  		[ ! -v SD_PARENT_NAME ] && return
   166  		"$SD_HELPER" --parent machine.slice stop "$SD_PARENT_NAME"
   167  	else
   168  		[ ! -v REL_PARENT_PATH ] && return
   169  		if [ -v CGROUP_V2 ]; then
   170  			rmdir "/sys/fs/cgroup/$REL_PARENT_PATH"
   171  		else
   172  			local subsys
   173  			for subsys in ${CGROUP_SUBSYSTEMS} systemd; do
   174  				rmdir "/sys/fs/cgroup/$subsys/$REL_PARENT_PATH"
   175  			done
   176  		fi
   177  	fi
   178  	unset SD_PARENT_NAME
   179  	unset REL_PARENT_PATH
   180  }
   181  
   182  function set_parent_systemd_properties() {
   183  	[ ! -v SD_PARENT_NAME ] && return
   184  	local user=""
   185  	[ $EUID -ne 0 ] && user="--user"
   186  	systemctl set-property $user "$SD_PARENT_NAME" "$@"
   187  }
   188  
   189  # Randomize cgroup path(s), and update cgroupsPath in config.json.
   190  # This function also sets a few cgroup-related variables that are used
   191  # by other cgroup-related functions.
   192  #
   193  # If this function is not called (and cgroupsPath is not set in config),
   194  # runc uses default container's cgroup path derived from the container's name
   195  # (except for rootless containers, that have no default cgroup path).
   196  #
   197  # Optional parameter $1 is a pod/parent name. If set, a parent/pod cgroup is
   198  # created, and variables $REL_PARENT_PATH and $SD_PARENT_NAME can be used to
   199  # refer to it.
   200  function set_cgroups_path() {
   201  	init_cgroup_paths
   202  	local pod dash_pod="" slash_pod="" pod_slice=""
   203  	if [ "$#" -ne 0 ] && [ "$1" != "" ]; then
   204  		# Set up a parent/pod cgroup.
   205  		pod="$1"
   206  		dash_pod="-$pod"
   207  		slash_pod="/$pod"
   208  		SD_PARENT_NAME="machine-${pod}.slice"
   209  		pod_slice="/$SD_PARENT_NAME"
   210  	fi
   211  
   212  	local rnd="$RANDOM"
   213  	if [ -v RUNC_USE_SYSTEMD ]; then
   214  		SD_UNIT_NAME="runc-cgroups-integration-test-${rnd}.scope"
   215  		if [ $EUID -eq 0 ]; then
   216  			REL_PARENT_PATH="/machine.slice${pod_slice}"
   217  			OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}"
   218  		else
   219  			REL_PARENT_PATH="/user.slice/user-${UID}.slice/user@${UID}.service/machine.slice${pod_slice}"
   220  			# OCI path doesn't contain "/user.slice/user-${UID}.slice/user@${UID}.service/" prefix
   221  			OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}"
   222  		fi
   223  		REL_CGROUPS_PATH="$REL_PARENT_PATH/$SD_UNIT_NAME"
   224  	else
   225  		REL_PARENT_PATH="/runc-cgroups-integration-test${slash_pod}"
   226  		REL_CGROUPS_PATH="$REL_PARENT_PATH/test-cgroup-${rnd}"
   227  		OCI_CGROUPS_PATH=$REL_CGROUPS_PATH
   228  	fi
   229  
   230  	# Absolute path to container's cgroup v2.
   231  	if [ -v CGROUP_V2 ]; then
   232  		CGROUP_V2_PATH=${CGROUP_BASE_PATH}${REL_CGROUPS_PATH}
   233  	fi
   234  
   235  	[ -v pod ] && create_parent
   236  
   237  	update_config '.linux.cgroupsPath |= "'"${OCI_CGROUPS_PATH}"'"'
   238  }
   239  
   240  # Get a path to cgroup directory, based on controller name.
   241  # Parameters:
   242  #  $1: controller name (like "pids") or a file name (like "pids.max").
   243  function get_cgroup_path() {
   244  	if [ -v CGROUP_V2 ]; then
   245  		echo "$CGROUP_V2_PATH"
   246  		return
   247  	fi
   248  
   249  	local var cgroup
   250  	var=${1%%.*}                  # controller name (e.g. memory)
   251  	var=CGROUP_${var^^}_BASE_PATH # variable name (e.g. CGROUP_MEMORY_BASE_PATH)
   252  	eval cgroup=\$"${var}${REL_CGROUPS_PATH}"
   253  	echo "$cgroup"
   254  }
   255  
   256  # Get a value from a cgroup file.
   257  function get_cgroup_value() {
   258  	local cgroup
   259  	cgroup="$(get_cgroup_path "$1")"
   260  	cat "$cgroup/$1"
   261  }
   262  
   263  # Helper to check a if value in a cgroup file matches the expected one.
   264  function check_cgroup_value() {
   265  	local current
   266  	current="$(get_cgroup_value "$1")"
   267  	local expected=$2
   268  
   269  	echo "current $current !? $expected"
   270  	[ "$current" = "$expected" ]
   271  }
   272  
   273  # Helper to check a value in systemd.
   274  function check_systemd_value() {
   275  	[ ! -v RUNC_USE_SYSTEMD ] && return
   276  	local source="$1"
   277  	[ "$source" = "unsupported" ] && return
   278  	local expected="$2"
   279  	local expected2="${3:-}"
   280  	local user=""
   281  	[ $EUID -ne 0 ] && user="--user"
   282  
   283  	current=$(systemctl show $user --property "$source" "$SD_UNIT_NAME" | awk -F= '{print $2}')
   284  	echo "systemd $source: current $current !? $expected $expected2"
   285  	[ "$current" = "$expected" ] || [[ -n "$expected2" && "$current" = "$expected2" ]]
   286  }
   287  
   288  function check_cpu_quota() {
   289  	local quota=$1
   290  	local period=$2
   291  	local sd_quota=$3
   292  
   293  	if [ -v CGROUP_V2 ]; then
   294  		if [ "$quota" = "-1" ]; then
   295  			quota="max"
   296  		fi
   297  		check_cgroup_value "cpu.max" "$quota $period"
   298  	else
   299  		check_cgroup_value "cpu.cfs_quota_us" "$quota"
   300  		check_cgroup_value "cpu.cfs_period_us" "$period"
   301  	fi
   302  	# systemd values are the same for v1 and v2
   303  	check_systemd_value "CPUQuotaPerSecUSec" "$sd_quota"
   304  
   305  	# CPUQuotaPeriodUSec requires systemd >= v242
   306  	[ "$(systemd_version)" -lt 242 ] && return
   307  
   308  	local sd_period=$((period / 1000))ms
   309  	[ "$sd_period" = "1000ms" ] && sd_period="1s"
   310  	local sd_infinity=""
   311  	# 100ms is the default value, and if not set, shown as infinity
   312  	[ "$sd_period" = "100ms" ] && sd_infinity="infinity"
   313  	check_systemd_value "CPUQuotaPeriodUSec" $sd_period $sd_infinity
   314  }
   315  
   316  function check_cpu_burst() {
   317  	local burst=$1
   318  	if [ -v CGROUP_V2 ]; then
   319  		burst=$((burst / 1000))
   320  		check_cgroup_value "cpu.max.burst" "$burst"
   321  	else
   322  		check_cgroup_value "cpu.cfs_burst_us" "$burst"
   323  	fi
   324  }
   325  
   326  # Works for cgroup v1 and v2, accepts v1 shares as an argument.
   327  function check_cpu_shares() {
   328  	local shares=$1
   329  
   330  	if [ -v CGROUP_V2 ]; then
   331  		local weight=$((1 + ((shares - 2) * 9999) / 262142))
   332  		check_cpu_weight "$weight"
   333  	else
   334  		check_cgroup_value "cpu.shares" "$shares"
   335  		check_systemd_value "CPUShares" "$shares"
   336  	fi
   337  }
   338  
   339  # Works only for cgroup v2, accept v2 weight.
   340  function check_cpu_weight() {
   341  	local weight=$1
   342  
   343  	check_cgroup_value "cpu.weight" "$weight"
   344  	check_systemd_value "CPUWeight" "$weight"
   345  }
   346  
   347  # Helper function to set a resources limit
   348  function set_resources_limit() {
   349  	update_config '.linux.resources.pids.limit |= 100'
   350  }
   351  
   352  # Helper function to make /sys/fs/cgroup writable
   353  function set_cgroup_mount_writable() {
   354  	update_config '.mounts |= map((select(.type == "cgroup") | .options -= ["ro"]) // .)'
   355  }
   356  
   357  # Helper function to get all online cpus.
   358  function get_all_online_cpus() {
   359  	cat /sys/devices/system/cpu/online
   360  }
   361  
   362  # Helper function to get the first online cpu.
   363  function get_first_online_cpu() {
   364  	[[ $(get_all_online_cpus) =~ [^0-9]*([0-9]+)([-,][0-9]+)? ]] && echo "${BASH_REMATCH[1]}"
   365  }
   366  
   367  # Helper function to set all cpus/mems in container cgroup cpuset.
   368  function set_cgroup_cpuset_all_cpus() {
   369  	update_config ".linux.resources.cpu.cpus = \"$(get_all_online_cpus)\""
   370  
   371  	local mems
   372  	mems="$(cat /sys/devices/system/node/online 2>/dev/null || true)"
   373  	if [[ -n $mems ]]; then
   374  		update_config ".linux.resources.cpu.mems = \"$mems\""
   375  	fi
   376  }
   377  
   378  # Fails the current test, providing the error given.
   379  function fail() {
   380  	echo "$@" >&2
   381  	exit 1
   382  }
   383  
   384  # Check whether rootless runc can use cgroups.
   385  function rootless_cgroup() {
   386  	[[ "$ROOTLESS_FEATURES" == *"cgroup"* || -v RUNC_USE_SYSTEMD ]]
   387  }
   388  
   389  # Check if criu is available and working.
   390  function have_criu() {
   391  	command -v criu &>/dev/null || return 1
   392  
   393  	# Workaround for https://github.com/opencontainers/runc/issues/3532.
   394  	local ver
   395  	ver=$(rpm -q criu 2>/dev/null || true)
   396  	run ! grep -q '^criu-3\.17-[123]\.el9' <<<"$ver"
   397  }
   398  
   399  # Allows a test to specify what things it requires. If the environment can't
   400  # support it, the test is skipped with a message.
   401  function requires() {
   402  	for var in "$@"; do
   403  		local skip_me
   404  		case $var in
   405  		criu)
   406  			if ! have_criu; then
   407  				skip_me=1
   408  			fi
   409  			;;
   410  		criu_feature_*)
   411  			var=${var#criu_feature_}
   412  			if ! criu check --feature "$var"; then
   413  				skip "requires CRIU feature ${var}"
   414  			fi
   415  			;;
   416  		root)
   417  			if [ $EUID -ne 0 ]; then
   418  				skip_me=1
   419  			fi
   420  			;;
   421  		rootless)
   422  			if [ $EUID -eq 0 ]; then
   423  				skip_me=1
   424  			fi
   425  			;;
   426  		rootless_idmap)
   427  			if [[ "$ROOTLESS_FEATURES" != *"idmap"* ]]; then
   428  				skip_me=1
   429  			fi
   430  			;;
   431  		rootless_cgroup)
   432  			if ! rootless_cgroup; then
   433  				skip_me=1
   434  			fi
   435  			;;
   436  		rootless_no_cgroup)
   437  			if rootless_cgroup; then
   438  				skip_me=1
   439  			fi
   440  			;;
   441  		rootless_no_features)
   442  			if [ -n "$ROOTLESS_FEATURES" ]; then
   443  				skip_me=1
   444  			fi
   445  			;;
   446  		cgroups_rt)
   447  			init_cgroup_paths
   448  			if [ ! -e "${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us" ]; then
   449  				skip_me=1
   450  			fi
   451  			;;
   452  		cgroups_swap)
   453  			init_cgroup_paths
   454  			if [ -v CGROUP_V1 ] && [ ! -e "${CGROUP_MEMORY_BASE_PATH}/memory.memsw.limit_in_bytes" ]; then
   455  				skip_me=1
   456  			fi
   457  			;;
   458  		cgroups_cpu_idle)
   459  			local p
   460  			init_cgroup_paths
   461  			[ -v CGROUP_V1 ] && p="$CGROUP_CPU_BASE_PATH"
   462  			[ -v CGROUP_V2 ] && p="$CGROUP_BASE_PATH"
   463  			if [ -z "$(find "$p" -name cpu.idle -print -quit)" ]; then
   464  				skip_me=1
   465  			fi
   466  			;;
   467  		cgroups_cpu_burst)
   468  			local p f
   469  			init_cgroup_paths
   470  			if [ -v CGROUP_V1 ]; then
   471  				p="$CGROUP_CPU_BASE_PATH"
   472  				f="cpu.cfs_burst_us"
   473  			elif [ -v CGROUP_V2 ]; then
   474  				# https://github.com/torvalds/linux/commit/f4183717b370ad28dd0c0d74760142b20e6e7931
   475  				requires_kernel 5.14
   476  				p="$CGROUP_BASE_PATH"
   477  				f="cpu.max.burst"
   478  			fi
   479  			if [ -z "$(find "$p" -name "$f" -print -quit)" ]; then
   480  				skip_me=1
   481  			fi
   482  			;;
   483  		cgroups_io_weight)
   484  			local p f1 f2
   485  			init_cgroup_paths
   486  			if [ -v CGROUP_V1 ]; then
   487  				p="$CGROUP_CPU_BASE_PATH"
   488  				f1="blkio.weight"
   489  				f2="blkio.bfq.weight"
   490  			elif [ -v CGROUP_V2 ]; then
   491  				p="$CGROUP_BASE_PATH"
   492  				f1="io.weight"
   493  				f2="io.bfq.weight"
   494  			fi
   495  			if [ -z "$(find "$p" -type f \( -name "$f1" -o -name "$f2" \) -print -quit)" ]; then
   496  				skip_me=1
   497  			fi
   498  			;;
   499  		cgroupns)
   500  			if [ ! -e "/proc/self/ns/cgroup" ]; then
   501  				skip_me=1
   502  			fi
   503  			;;
   504  		timens)
   505  			if [ ! -e "/proc/self/ns/time" ]; then
   506  				skip_me=1
   507  			fi
   508  			;;
   509  		cgroups_v1)
   510  			init_cgroup_paths
   511  			if [ ! -v CGROUP_V1 ]; then
   512  				skip_me=1
   513  			fi
   514  			;;
   515  		cgroups_v2)
   516  			init_cgroup_paths
   517  			if [ ! -v CGROUP_V2 ]; then
   518  				skip_me=1
   519  			fi
   520  			;;
   521  		cgroups_hybrid)
   522  			init_cgroup_paths
   523  			if [ ! -v CGROUP_HYBRID ]; then
   524  				skip_me=1
   525  			fi
   526  			;;
   527  		cgroups_*)
   528  			init_cgroup_paths
   529  			var=${var#cgroups_}
   530  			if [[ "$CGROUP_SUBSYSTEMS" != *"$var"* ]]; then
   531  				skip_me=1
   532  			fi
   533  			;;
   534  		smp)
   535  			local cpus
   536  			cpus=$(grep -c '^processor' /proc/cpuinfo)
   537  			if [ "$cpus" -lt 2 ]; then
   538  				skip_me=1
   539  			fi
   540  			;;
   541  		systemd)
   542  			if [ ! -v RUNC_USE_SYSTEMD ]; then
   543  				skip_me=1
   544  			fi
   545  			;;
   546  		systemd_v*)
   547  			var=${var#systemd_v}
   548  			if [ "$(systemd_version)" -lt "$var" ]; then
   549  				skip "requires systemd >= v${var}"
   550  			fi
   551  			;;
   552  		no_systemd)
   553  			if [ -v RUNC_USE_SYSTEMD ]; then
   554  				skip_me=1
   555  			fi
   556  			;;
   557  		arch_x86_64)
   558  			if [ "$ARCH" != "x86_64" ]; then
   559  				skip_me=1
   560  			fi
   561  			;;
   562  		more_than_8_core)
   563  			local cpus
   564  			cpus=$(grep -c '^processor' /proc/cpuinfo)
   565  			if [ "$cpus" -le 8 ]; then
   566  				skip_me=1
   567  			fi
   568  			;;
   569  		psi)
   570  			# If PSI is not compiled in the kernel, the file will not exist.
   571  			# If PSI is compiled, but not enabled, read will fail with ENOTSUPP.
   572  			if ! cat /sys/fs/cgroup/cpu.pressure &>/dev/null; then
   573  				skip_me=1
   574  			fi
   575  			;;
   576  		*)
   577  			fail "BUG: Invalid requires $var."
   578  			;;
   579  		esac
   580  		if [ -v skip_me ]; then
   581  			skip "test requires $var"
   582  		fi
   583  	done
   584  }
   585  
   586  # Allow a test to specify that it will not work properly on a given OS. The
   587  # fingerprint for the OS used for this test is $ID-$VERSION_ID, using the
   588  # variables in /etc/os-release. The arguments are regular expressions, and any
   589  # match will cause the test to be skipped.
   590  function exclude_os() {
   591  	local host
   592  	host="$(sh -c '. /etc/os-release ; echo "$ID-$VERSION_ID"')"
   593  	for bad_os in "$@"; do
   594  		if [[ "$host" =~ ^$bad_os$ ]]; then
   595  			skip "test doesn't work on $bad_os"
   596  		fi
   597  	done
   598  }
   599  
   600  # Retry a command $1 times until it succeeds. Wait $2 seconds between retries.
   601  function retry() {
   602  	local attempts=$1
   603  	shift
   604  	local delay=$1
   605  	shift
   606  	local i
   607  
   608  	for ((i = 0; i < attempts; i++)); do
   609  		run "$@"
   610  		if [[ "$status" -eq 0 ]]; then
   611  			return 0
   612  		fi
   613  		sleep "$delay"
   614  	done
   615  
   616  	echo "Command \"$*\" failed $attempts times. Output: $output"
   617  	false
   618  }
   619  
   620  # retry until the given container has state
   621  function wait_for_container() {
   622  	if [ $# -eq 3 ]; then
   623  		retry "$1" "$2" __runc state "$3"
   624  	elif [ $# -eq 4 ]; then
   625  		retry "$1" "$2" eval "__runc state $3 | grep -qw $4"
   626  	else
   627  		echo "Usage: wait_for_container ATTEMPTS DELAY ID [STATUS]" 1>&2
   628  		return 1
   629  	fi
   630  }
   631  
   632  function testcontainer() {
   633  	# test state of container
   634  	runc state "$1"
   635  	if [ "$2" = "checkpointed" ]; then
   636  		[ "$status" -eq 1 ]
   637  		return
   638  	fi
   639  	[ "$status" -eq 0 ]
   640  	[[ "${output}" == *"$2"* ]]
   641  }
   642  
   643  # Check that all the listed processes are gone. Use after kill/stop etc.
   644  function wait_pids_gone() {
   645  	if [ $# -lt 3 ]; then
   646  		echo "Usage: wait_pids_gone ITERATIONS SLEEP PID [PID ...]"
   647  		return 1
   648  	fi
   649  	local iter=$1
   650  	shift
   651  	local sleep=$1
   652  	shift
   653  	local pids=("$@")
   654  
   655  	while true; do
   656  		for i in "${!pids[@]}"; do
   657  			# Check if the pid is there; if not, remove it from the list.
   658  			kill -0 "${pids[i]}" 2>/dev/null || unset "pids[i]"
   659  		done
   660  		[ ${#pids[@]} -eq 0 ] && return 0
   661  		# Rebuild pids array to avoid sparse array issues.
   662  		pids=("${pids[@]}")
   663  
   664  		((--iter > 0)) || break
   665  
   666  		sleep "$sleep"
   667  	done
   668  
   669  	echo "Expected all PIDs to be gone, but some are still there:" "${pids[@]}" 1>&2
   670  	return 1
   671  }
   672  
   673  function setup_recvtty() {
   674  	[ ! -v ROOT ] && return 1 # must not be called without ROOT set
   675  	local dir="$ROOT/tty"
   676  
   677  	mkdir "$dir"
   678  	export CONSOLE_SOCKET="$dir/sock"
   679  
   680  	# We need to start recvtty in the background, so we double fork in the shell.
   681  	("$RECVTTY" --pid-file "$dir/pid" --mode null "$CONSOLE_SOCKET" &) &
   682  }
   683  
   684  function teardown_recvtty() {
   685  	[ ! -v ROOT ] && return 0 # nothing to teardown
   686  	local dir="$ROOT/tty"
   687  
   688  	# When we kill recvtty, the container will also be killed.
   689  	if [ -f "$dir/pid" ]; then
   690  		kill -9 "$(cat "$dir/pid")"
   691  	fi
   692  
   693  	# Clean up the files that might be left over.
   694  	rm -rf "$dir"
   695  }
   696  
   697  function setup_seccompagent() {
   698  	("${SECCOMP_AGENT}" -socketfile="$SECCCOMP_AGENT_SOCKET" -pid-file "$BATS_TMPDIR/seccompagent.pid" &) &
   699  }
   700  
   701  function teardown_seccompagent() {
   702  	if [ -f "$BATS_TMPDIR/seccompagent.pid" ]; then
   703  		kill -9 "$(cat "$BATS_TMPDIR/seccompagent.pid")"
   704  	fi
   705  	rm -f "$BATS_TMPDIR/seccompagent.pid"
   706  	rm -f "$SECCCOMP_AGENT_SOCKET"
   707  }
   708  
   709  function setup_bundle() {
   710  	local image="$1"
   711  
   712  	# Root for various container directories (state, tty, bundle).
   713  	ROOT=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX")
   714  	mkdir -p "$ROOT/state" "$ROOT/bundle/rootfs"
   715  
   716  	# Directories created by mktemp -d have 0700 permission bits. Tests
   717  	# running inside userns (see userns.bats) need to access the directory
   718  	# as a different user to mount the rootfs. Since kernel v5.12, parent
   719  	# directories are also checked. Give a+x for these tests to work.
   720  	chmod a+x "$ROOT" "$BATS_RUN_TMPDIR"
   721  
   722  	setup_recvtty
   723  	cd "$ROOT/bundle" || return
   724  
   725  	tar --exclude './dev/*' -C rootfs -xf "$image"
   726  
   727  	runc_spec
   728  }
   729  
   730  function setup_busybox() {
   731  	setup_bundle "$BUSYBOX_IMAGE"
   732  }
   733  
   734  function setup_debian() {
   735  	setup_bundle "$DEBIAN_IMAGE"
   736  }
   737  
   738  function teardown_bundle() {
   739  	[ ! -v ROOT ] && return 0 # nothing to teardown
   740  
   741  	cd "$INTEGRATION_ROOT" || return
   742  	teardown_recvtty
   743  	local ct
   744  	for ct in $(__runc list -q); do
   745  		__runc delete -f "$ct"
   746  	done
   747  	rm -rf "$ROOT"
   748  	remove_parent
   749  }
   750  
   751  function remap_rootfs() {
   752  	[ ! -v ROOT ] && return 0 # nothing to remap
   753  
   754  	"$REMAP_ROOTFS" "$ROOT/bundle"
   755  }
   756  
   757  function is_kernel_gte() {
   758  	local major_required minor_required
   759  	major_required=$(echo "$1" | cut -d. -f1)
   760  	minor_required=$(echo "$1" | cut -d. -f2)
   761  	[[ "$KERNEL_MAJOR" -gt $major_required || ("$KERNEL_MAJOR" -eq $major_required && "$KERNEL_MINOR" -ge $minor_required) ]]
   762  }
   763  
   764  function requires_kernel() {
   765  	if ! is_kernel_gte "$@"; then
   766  		skip "requires kernel >= $1"
   767  	fi
   768  }
   769  
   770  function requires_idmap_fs() {
   771  	local fs
   772  	fs=$1
   773  
   774  	# We need to "|| true" it to avoid CI failure as this binary may return with
   775  	# something different than 0.
   776  	stderr=$($FS_IDMAP "$fs" 2>&1 >/dev/null || true)
   777  
   778  	case $stderr in
   779  	*invalid\ argument)
   780  		skip "$fs underlying file system does not support ID map mounts"
   781  		;;
   782  	*operation\ not\ permitted)
   783  		if uname -r | grep -q el9; then
   784  			# centos kernel 5.14.0-200 does not permit using ID map mounts due to a
   785  			# specific patch added to their sources:
   786  			# 	https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/131
   787  			#
   788  			# There doesn't seem to be any technical reason behind
   789  			# it, none was provided in numerous examples, like:
   790  			# 	https://lore.kernel.org/lkml/20210213130042.828076-1-christian.brauner@ubuntu.com/T/#m3a9df31aa183e8797c70bc193040adfd601399ad
   791  			#	https://lore.kernel.org/lkml/20210213130042.828076-1-christian.brauner@ubuntu.com/T/#m59cdad9630d5a279aeecd0c1f117115144bc15eb
   792  			#	https://lore.kernel.org/lkml/m1r1ifzf8x.fsf@fess.ebiederm.org
   793  			#	https://lore.kernel.org/lkml/20210510125147.tkgeurcindldiwxg@wittgenstein
   794  			#
   795  			# So, sadly we just need to skip this on centos.
   796  			#
   797  			# TODO Nonetheless, there are ongoing works to revert the patch
   798  			# deactivating ID map mounts:
   799  			# https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/2179/diffs?commit_id=06f4fe946394cb94d2cf274aa7f3091d8f8469dc
   800  			# Once this patch is merge, we should be able to remove the below skip
   801  			# if the revert is backported or if CI centos kernel is upgraded.
   802  			skip "sadly, centos kernel 5.14 does not permit using ID map mounts"
   803  		fi
   804  		;;
   805  	esac
   806  	# If we have another error, the integration test will fail and report it.
   807  }
   808  
   809  # setup_pidfd_kill runs pidfd-kill process in background and receives the
   810  # SIGTERM as signal to send the given signal to init process.
   811  function setup_pidfd_kill() {
   812  	local signal=$1
   813  
   814  	[ ! -v ROOT ] && return 1
   815  	local dir="${ROOT}/pidfd"
   816  
   817  	mkdir "${dir}"
   818  	export PIDFD_SOCKET="${dir}/sock"
   819  
   820  	("${PIDFD_KILL}" --pid-file "${dir}/pid" --signal "${signal}" "${PIDFD_SOCKET}" &) &
   821  
   822  	# ensure socket is ready
   823  	retry 10 1 stat "${PIDFD_SOCKET}"
   824  }
   825  
   826  # teardown_pidfd_kill cleanups all the resources related to pidfd-kill.
   827  function teardown_pidfd_kill() {
   828  	[ ! -v ROOT ] && return 0
   829  
   830  	local dir="${ROOT}/pidfd"
   831  
   832  	if [ -f "${dir}/pid" ]; then
   833  		kill -9 "$(cat "${dir}/pid")"
   834  	fi
   835  
   836  	rm -rf "${dir}"
   837  }
   838  
   839  # pidfd_kill sends the signal to init process.
   840  function pidfd_kill() {
   841  	[ ! -v ROOT ] && return 0
   842  
   843  	local dir="${ROOT}/pidfd"
   844  
   845  	if [ -f "${dir}/pid" ]; then
   846  		kill "$(cat "${dir}/pid")"
   847  	fi
   848  }