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

     1  #!/usr/bin/env bats
     2  
     3  load helpers
     4  
     5  function setup() {
     6  	setup_busybox
     7  }
     8  
     9  function teardown() {
    10  	teardown_bundle
    11  }
    12  
    13  @test "runc exec" {
    14  	# run busybox detached
    15  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    16  	[ "$status" -eq 0 ]
    17  
    18  	runc exec test_busybox echo Hello from exec
    19  	[ "$status" -eq 0 ]
    20  	echo text echoed = "'""${output}""'"
    21  	[[ "${output}" == *"Hello from exec"* ]]
    22  }
    23  
    24  @test "runc exec [exit codes]" {
    25  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    26  	[ "$status" -eq 0 ]
    27  
    28  	runc exec test_busybox false
    29  	[ "$status" -eq 1 ]
    30  
    31  	runc exec test_busybox sh -c "exit 42"
    32  	[ "$status" -eq 42 ]
    33  
    34  	runc exec --pid-file /non-existent/directory test_busybox true
    35  	[ "$status" -eq 255 ]
    36  
    37  	runc exec test_busybox no-such-binary
    38  	[ "$status" -eq 255 ]
    39  
    40  	runc exec no_such_container true
    41  	[ "$status" -eq 255 ]
    42  }
    43  
    44  @test "runc exec --pid-file" {
    45  	# run busybox detached
    46  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    47  	[ "$status" -eq 0 ]
    48  
    49  	runc exec --pid-file pid.txt test_busybox echo Hello from exec
    50  	[ "$status" -eq 0 ]
    51  	echo text echoed = "'""${output}""'"
    52  	[[ "${output}" == *"Hello from exec"* ]]
    53  
    54  	# check pid.txt was generated
    55  	[ -e pid.txt ]
    56  
    57  	output=$(cat pid.txt)
    58  	[[ "$output" =~ [0-9]+ ]]
    59  	[[ "$output" != $(__runc state test_busybox | jq '.pid') ]]
    60  }
    61  
    62  @test "runc exec --pid-file with new CWD" {
    63  	bundle="$(pwd)"
    64  	# create pid_file directory as the CWD
    65  	mkdir pid_file
    66  	cd pid_file
    67  
    68  	# run busybox detached
    69  	runc run -d -b "$bundle" --console-socket "$CONSOLE_SOCKET" test_busybox
    70  	[ "$status" -eq 0 ]
    71  
    72  	runc exec --pid-file pid.txt test_busybox echo Hello from exec
    73  	[ "$status" -eq 0 ]
    74  	echo text echoed = "'""${output}""'"
    75  	[[ "${output}" == *"Hello from exec"* ]]
    76  
    77  	# check pid.txt was generated
    78  	[ -e pid.txt ]
    79  
    80  	output=$(cat pid.txt)
    81  	[[ "$output" =~ [0-9]+ ]]
    82  	[[ "$output" != $(__runc state test_busybox | jq '.pid') ]]
    83  }
    84  
    85  @test "runc exec ls -la" {
    86  	# run busybox detached
    87  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
    88  	[ "$status" -eq 0 ]
    89  
    90  	runc exec test_busybox ls -la
    91  	[ "$status" -eq 0 ]
    92  	[[ ${lines[0]} == *"total"* ]]
    93  	[[ ${lines[1]} == *"."* ]]
    94  	[[ ${lines[2]} == *".."* ]]
    95  }
    96  
    97  @test "runc exec ls -la with --cwd" {
    98  	# run busybox detached
    99  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   100  	[ "$status" -eq 0 ]
   101  
   102  	runc exec --cwd /bin test_busybox pwd
   103  	[ "$status" -eq 0 ]
   104  	[[ ${output} == "/bin"* ]]
   105  }
   106  
   107  @test "runc exec --env" {
   108  	# run busybox detached
   109  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   110  	[ "$status" -eq 0 ]
   111  
   112  	runc exec --env RUNC_EXEC_TEST=true test_busybox env
   113  	[ "$status" -eq 0 ]
   114  
   115  	[[ ${output} == *"RUNC_EXEC_TEST=true"* ]]
   116  }
   117  
   118  @test "runc exec --user" {
   119  	# --user can't work in rootless containers that don't have idmap.
   120  	[ $EUID -ne 0 ] && requires rootless_idmap
   121  
   122  	# run busybox detached
   123  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   124  	[ "$status" -eq 0 ]
   125  
   126  	runc exec --user 1000:1000 test_busybox id
   127  	[ "$status" -eq 0 ]
   128  	[[ "${output}" == "uid=1000 gid=1000"* ]]
   129  }
   130  
   131  # https://github.com/opencontainers/runc/issues/3674.
   132  @test "runc exec --user vs /dev/null ownership" {
   133  	requires root
   134  
   135  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   136  	[ "$status" -eq 0 ]
   137  
   138  	ls -l /dev/null
   139  	__runc exec -d --user 1000:1000 test_busybox id </dev/null
   140  	ls -l /dev/null
   141  	UG=$(stat -c %u:%g /dev/null)
   142  
   143  	# Host's /dev/null must be owned by root.
   144  	[ "$UG" = "0:0" ]
   145  }
   146  
   147  @test "runc exec --additional-gids" {
   148  	requires root
   149  
   150  	# run busybox detached
   151  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   152  	[ "$status" -eq 0 ]
   153  
   154  	wait_for_container 15 1 test_busybox
   155  
   156  	runc exec --user 1000:1000 --additional-gids 100 --additional-gids 65534 test_busybox id -G
   157  	[ "$status" -eq 0 ]
   158  	[ "$output" = "1000 100 65534" ]
   159  }
   160  
   161  @test "runc exec --preserve-fds" {
   162  	# run busybox detached
   163  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   164  	[ "$status" -eq 0 ]
   165  
   166  	echo hello >preserve-fds.test
   167  	# fd 3 is used by bats, so we use 4
   168  	exec 4<preserve-fds.test
   169  	runc exec --preserve-fds=2 test_busybox cat /proc/self/fd/4
   170  	[ "$status" -eq 0 ]
   171  	[ "${output}" = "hello" ]
   172  }
   173  
   174  function check_exec_debug() {
   175  	[[ "$*" == *"nsexec container setup"* ]]
   176  	[[ "$*" == *"child process in init()"* ]]
   177  	[[ "$*" == *"setns_init: about to exec"* ]]
   178  }
   179  
   180  @test "runc --debug exec" {
   181  	runc run -d --console-socket "$CONSOLE_SOCKET" test
   182  	[ "$status" -eq 0 ]
   183  
   184  	runc --debug exec test true
   185  	[ "$status" -eq 0 ]
   186  	[[ "${output}" == *"level=debug"* ]]
   187  	check_exec_debug "$output"
   188  }
   189  
   190  @test "runc --debug --log exec" {
   191  	runc run -d --console-socket "$CONSOLE_SOCKET" test
   192  	[ "$status" -eq 0 ]
   193  
   194  	runc --debug --log log.out exec test true
   195  	# check output does not include debug info
   196  	[[ "${output}" != *"level=debug"* ]]
   197  
   198  	cat log.out >&2
   199  	# check expected debug output was sent to log.out
   200  	output=$(cat log.out)
   201  	[[ "${output}" == *"level=debug"* ]]
   202  	check_exec_debug "$output"
   203  }
   204  
   205  @test "runc exec --cgroup sub-cgroups [v1]" {
   206  	requires root cgroups_v1
   207  
   208  	set_cgroups_path
   209  	set_cgroup_mount_writable
   210  
   211  	__runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   212  	testcontainer test_busybox running
   213  
   214  	# Check we can't join parent cgroup.
   215  	runc exec --cgroup ".." test_busybox cat /proc/self/cgroup
   216  	[ "$status" -ne 0 ]
   217  	[[ "$output" == *" .. is not a sub cgroup path"* ]]
   218  
   219  	# Check we can't join non-existing subcgroup.
   220  	runc exec --cgroup nonexistent test_busybox cat /proc/self/cgroup
   221  	[ "$status" -ne 0 ]
   222  	[[ "$output" == *" adding pid "*"/nonexistent/cgroup.procs: no such file "* ]]
   223  
   224  	# Check we can't join non-existing subcgroup (for a particular controller).
   225  	runc exec --cgroup cpu:nonexistent test_busybox cat /proc/self/cgroup
   226  	[ "$status" -ne 0 ]
   227  	[[ "$output" == *" adding pid "*"/nonexistent/cgroup.procs: no such file "* ]]
   228  
   229  	# Check we can't specify non-existent controller.
   230  	runc exec --cgroup whaaat:/ test_busybox true
   231  	[ "$status" -ne 0 ]
   232  	[[ "$output" == *"unknown controller "* ]]
   233  
   234  	# Check we can join top-level cgroup (implicit).
   235  	runc exec test_busybox cat /proc/self/cgroup
   236  	[ "$status" -eq 0 ]
   237  	run ! grep -v ":$REL_CGROUPS_PATH\$" <<<"$output"
   238  
   239  	# Check we can join top-level cgroup (explicit).
   240  	runc exec --cgroup / test_busybox cat /proc/self/cgroup
   241  	[ "$status" -eq 0 ]
   242  	run ! grep -v ":$REL_CGROUPS_PATH\$" <<<"$output"
   243  
   244  	# Create a few subcgroups.
   245  	# Note that cpu,cpuacct may be mounted together or separate.
   246  	runc exec test_busybox sh -euc "mkdir -p /sys/fs/cgroup/memory/submem /sys/fs/cgroup/cpu/subcpu /sys/fs/cgroup/cpuacct/subcpu"
   247  	[ "$status" -eq 0 ]
   248  
   249  	# Check that explicit --cgroup works.
   250  	runc exec --cgroup memory:submem --cgroup cpu,cpuacct:subcpu test_busybox cat /proc/self/cgroup
   251  	[ "$status" -eq 0 ]
   252  	[[ "$output" == *":memory:$REL_CGROUPS_PATH/submem"* ]]
   253  	[[ "$output" == *":cpu"*":$REL_CGROUPS_PATH/subcpu"* ]]
   254  }
   255  
   256  @test "runc exec --cgroup subcgroup [v2]" {
   257  	requires root cgroups_v2
   258  
   259  	set_cgroups_path
   260  	set_cgroup_mount_writable
   261  
   262  	__runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   263  	testcontainer test_busybox running
   264  
   265  	# Check we can't join parent cgroup.
   266  	runc exec --cgroup ".." test_busybox cat /proc/self/cgroup
   267  	[ "$status" -ne 0 ]
   268  	[[ "$output" == *" .. is not a sub cgroup path"* ]]
   269  
   270  	# Check we can't join non-existing subcgroup.
   271  	runc exec --cgroup nonexistent test_busybox cat /proc/self/cgroup
   272  	[ "$status" -ne 0 ]
   273  	[[ "$output" == *" adding pid "*"/nonexistent/cgroup.procs: no such file "* ]]
   274  
   275  	# Check we can join top-level cgroup (implicit).
   276  	runc exec test_busybox grep '^0::/$' /proc/self/cgroup
   277  	[ "$status" -eq 0 ]
   278  
   279  	# Check we can join top-level cgroup (explicit).
   280  	runc exec --cgroup / test_busybox grep '^0::/$' /proc/self/cgroup
   281  	[ "$status" -eq 0 ]
   282  
   283  	# Now move "init" to a subcgroup, and check it was moved.
   284  	runc exec test_busybox sh -euc "mkdir /sys/fs/cgroup/foobar \
   285  		&& echo 1 > /sys/fs/cgroup/foobar/cgroup.procs \
   286  		&& grep -w foobar /proc/1/cgroup"
   287  	[ "$status" -eq 0 ]
   288  
   289  	# The following part is taken from
   290  	# @test "runc exec (cgroup v2 + init process in non-root cgroup) succeeds"
   291  
   292  	# The init process is now in "/foo", but an exec process can still
   293  	# join "/" because we haven't enabled any domain controller yet.
   294  	runc exec test_busybox grep '^0::/$' /proc/self/cgroup
   295  	[ "$status" -eq 0 ]
   296  
   297  	# Turn on a domain controller (memory).
   298  	runc exec test_busybox sh -euc 'echo $$ > /sys/fs/cgroup/foobar/cgroup.procs; echo +memory > /sys/fs/cgroup/cgroup.subtree_control'
   299  	[ "$status" -eq 0 ]
   300  
   301  	# An exec process can no longer join "/" after turning on a domain
   302  	# controller.  Check that cgroup v2 fallback to init cgroup works.
   303  	runc exec test_busybox sh -euc "cat /proc/self/cgroup && grep '^0::/foobar$' /proc/self/cgroup"
   304  	[ "$status" -eq 0 ]
   305  
   306  	# Check that --cgroup / disables the init cgroup fallback.
   307  	runc exec --cgroup / test_busybox true
   308  	[ "$status" -ne 0 ]
   309  	[[ "$output" == *" adding pid "*" to cgroups"*"/cgroup.procs: device or resource busy"* ]]
   310  
   311  	# Check that explicit --cgroup foobar works.
   312  	runc exec --cgroup foobar test_busybox grep '^0::/foobar$' /proc/self/cgroup
   313  	[ "$status" -eq 0 ]
   314  
   315  	# Check all processes is in foobar (this check is redundant).
   316  	runc exec --cgroup foobar test_busybox sh -euc '! grep -vwH foobar /proc/*/cgroup'
   317  	[ "$status" -eq 0 ]
   318  
   319  	# Add a second subcgroup, check we're in it.
   320  	runc exec --cgroup foobar test_busybox mkdir /sys/fs/cgroup/second
   321  	[ "$status" -eq 0 ]
   322  	runc exec --cgroup second test_busybox grep -w second /proc/self/cgroup
   323  	[ "$status" -eq 0 ]
   324  }
   325  
   326  @test "runc exec [execve error]" {
   327  	cat <<EOF >rootfs/run.sh
   328  #!/mmnnttbb foo bar
   329  sh
   330  EOF
   331  	chmod +x rootfs/run.sh
   332  	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
   333  	runc exec -t test_busybox /run.sh
   334  	[ "$status" -ne 0 ]
   335  
   336  	# After the sync socket closed, we should not send error to parent
   337  	# process, or else we will get a unnecessary error log(#4171).
   338  	# Although we never close the sync socket when doing exec,
   339  	# but we need to keep this test to ensure this behavior is always right.
   340  	[ ${#lines[@]} -eq 1 ]
   341  	[[ ${lines[0]} = *"exec /run.sh: no such file or directory"* ]]
   342  }
   343  
   344  @test "runc exec with isolated cpus affinity temporary transition [cgroup cpuset]" {
   345  	requires root cgroups_cpuset
   346  
   347  	tmp=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX")
   348  
   349  	set_cgroup_cpuset_all_cpus
   350  	local all_cpus
   351  	all_cpus="$(get_all_online_cpus)"
   352  
   353  	# set temporary isolated CPU affinity transition
   354  	update_config '.annotations += {"org.opencontainers.runc.exec.isolated-cpu-affinity-transition": "temporary"}'
   355  
   356  	runc run -d --console-socket "$CONSOLE_SOCKET" test_isolated_temporary_transition
   357  	[ "$status" -eq 0 ]
   358  
   359  	# set all online cpus as isolated
   360  	echo "nohz_full=$all_cpus" >"$tmp/cmdline"
   361  
   362  	mount --bind "$tmp/cmdline" /proc/cmdline
   363  
   364  	runc exec test_isolated_temporary_transition grep "Cpus_allowed_list:" /proc/self/status
   365  
   366  	umount /proc/cmdline
   367  
   368  	[ "$status" -eq 0 ]
   369  	[[ "${lines[0]}" == "Cpus_allowed_list:	$all_cpus" ]]
   370  }
   371  
   372  @test "runc exec with isolated cpus affinity definitive transition [cgroup cpuset]" {
   373  	requires root cgroups_cpuset
   374  
   375  	tmp=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX")
   376  
   377  	set_cgroup_cpuset_all_cpus
   378  	local all_cpus
   379  	all_cpus="$(get_all_online_cpus)"
   380  
   381  	# set definitive isolated CPU affinity transition
   382  	update_config '.annotations += {"org.opencontainers.runc.exec.isolated-cpu-affinity-transition": "definitive"}'
   383  
   384  	runc run -d --console-socket "$CONSOLE_SOCKET" test_isolated_definitive_transition
   385  	[ "$status" -eq 0 ]
   386  
   387  	# set all online cpus as isolated
   388  	echo "nohz_full=$all_cpus" >"$tmp/cmdline"
   389  
   390  	mount --bind "$tmp/cmdline" /proc/cmdline
   391  
   392  	runc exec test_isolated_definitive_transition grep "Cpus_allowed_list:" /proc/self/status
   393  
   394  	umount /proc/cmdline
   395  
   396  	[ "$status" -eq 0 ]
   397  
   398  	load /etc/os-release
   399  
   400  	# fix unbound variable in condition below
   401  	VERSION_ID=${VERSION_ID:-}
   402  
   403  	allowed_cpus=$all_cpus
   404  	# use first cpu on systems with RHEL >= 9 or systems with kernel >= 6.2
   405  	if [[ "${ID_LIKE:-}" =~ "rhel" && "${VERSION_ID%%.*}" -ge "9" ]] || is_kernel_gte 6.2; then
   406  		allowed_cpus="$(get_first_online_cpu)"
   407  	fi
   408  
   409  	[[ "${lines[0]}" == "Cpus_allowed_list:	$allowed_cpus" ]]
   410  }
   411  
   412  @test "runc exec with isolated cpus affinity bad transition [cgroup cpuset]" {
   413  	requires root cgroups_cpuset
   414  
   415  	tmp=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX")
   416  
   417  	set_cgroup_cpuset_all_cpus
   418  	local all_cpus
   419  	all_cpus="$(get_all_online_cpus)"
   420  
   421  	# set a bad isolated CPU affinity transition
   422  	update_config '.annotations += {"org.opencontainers.runc.exec.isolated-cpu-affinity-transition": "bad"}'
   423  
   424  	runc run -d --console-socket "$CONSOLE_SOCKET" test_isolated_bad_transition
   425  	[ "$status" -eq 0 ]
   426  
   427  	# set all online cpus as isolated
   428  	echo "nohz_full=$all_cpus" >"$tmp/cmdline"
   429  
   430  	mount --bind "$tmp/cmdline" /proc/cmdline
   431  
   432  	runc exec test_isolated_bad_transition true
   433  
   434  	umount /proc/cmdline
   435  
   436  	[ "$status" -eq 255 ]
   437  }
   438  
   439  @test "runc exec with taskset affinity [cgroup cpuset]" {
   440  	requires root cgroups_cpuset
   441  
   442  	set_cgroup_cpuset_all_cpus
   443  	local all_cpus
   444  	all_cpus="$(get_all_online_cpus)"
   445  
   446  	taskset -p -c "$(get_first_online_cpu)" $$
   447  
   448  	runc run -d --console-socket "$CONSOLE_SOCKET" test_with_taskset
   449  	[ "$status" -eq 0 ]
   450  
   451  	runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/1/status
   452  	[ "$status" -eq 0 ]
   453  	[[ "${lines[0]}" == "Cpus_allowed_list:	$all_cpus" ]]
   454  
   455  	runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/self/status
   456  	[ "$status" -eq 0 ]
   457  	[[ "${lines[0]}" == "Cpus_allowed_list:	$all_cpus" ]]
   458  }
   459  
   460  @test "runc exec with taskset affinity [rootless cgroups_v2]" {
   461  	requires rootless cgroups_v2
   462  
   463  	local all_cpus
   464  	all_cpus="$(get_all_online_cpus)"
   465  
   466  	taskset -p -c "$(get_first_online_cpu)" $$
   467  
   468  	runc run -d --console-socket "$CONSOLE_SOCKET" test_with_taskset
   469  	[ "$status" -eq 0 ]
   470  
   471  	runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/1/status
   472  	[ "$status" -eq 0 ]
   473  	[[ "${lines[0]}" == "Cpus_allowed_list:	$all_cpus" ]]
   474  
   475  	runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/self/status
   476  	[ "$status" -eq 0 ]
   477  	[[ "${lines[0]}" == "Cpus_allowed_list:	$all_cpus" ]]
   478  }