Compare commits

...

104 Commits

Author SHA1 Message Date
Mrunal Patel a5847db387
Merge pull request #2506 from kolyshkin/cgroup-fixes
cgroupv1 removal nits
2020-08-17 21:13:31 -07:00
Mrunal Patel 7930f0c150
Merge pull request #2549 from kolyshkin/bats-shellcheck
Add shellcheck to bats files
2020-08-17 17:22:44 -07:00
Mrunal Patel 49a7346333
Merge pull request #2547 from kolyshkin/moar-v2-tests
libct/integration: enable some tests for cgroupv2
2020-08-17 11:46:22 -07:00
Mrunal Patel 9ada2e6d4f
Merge pull request #2539 from kolyshkin/ext-pidns-nits
external pidns c/r code nits
2020-08-17 11:41:46 -07:00
Mrunal Patel b70de388e4
Merge pull request #2540 from kolyshkin/unify-test-inval-cgroup
cgroups/fs tests: unify TestInvalid*Cgroup*
2020-08-17 11:40:44 -07:00
Mrunal Patel 0509b5ba3c
Merge pull request #2553 from AkihiroSuda/support-kernel59-caps
support CAP_PERFMON, CAP_BPF, and CAP_CHECKPOINT_RESTORE
2020-08-17 11:39:08 -07:00
Akihiro Suda 6dfbe9b807
support CAP_PERFMON, CAP_BPF, and CAP_CHECKPOINT_RESTORE
CAP_PERFMON and CAP_BPF were introduced in kernel 5.8: https://kernelnewbies.org/Linux_5.8#Introduce_CAP_BPF_and_CAP_PERFMON_security_capabilities

CAP_CHECKPOINT_RESTORE was merged on the master recently and will be available in the next version of the kernel. 124ea650d3

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2020-08-15 15:47:47 +09:00
Akihiro Suda 54c53b10d3
Merge pull request #2533 from XiaodongLoong/fix_cgMode_redundant
use criu cgroup mode const from go-criu
2020-08-13 12:13:24 +09:00
Mrunal Patel a2d1f85be0
Merge pull request #2542 from AkihiroSuda/go1.15
upgrade Go to 1.15
2020-08-12 09:08:15 -07:00
Akihiro Suda 4c71a68c6e
upgrade Go to 1.15
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2020-08-12 15:37:25 +09:00
Kir Kolyshkin d34f1c819d CI: add shellcheck of bats files
Currently all the shellcheck warnings are fixed, and we'd like it to
stay thay way. So, add shellcheck call to validate target in Makefile,
which is run on Travis CI.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:42:48 -07:00
Kir Kolyshkin f36fb46bdf tests/int/*bats: ignore SC2016
Ignore the shellcheck warnings like this one:

> In tty.bats line 32:
> 	update_config '(.. | select(.[]? == "sh")) += ["-c", "stat -c %u:%g $(tty) | tr : \\\\n"]'
>                     ^-- SC2016: Expressions don't expand in single quotes, use double quotes for that.

While at it, fix some minor whitespace issues in tty.bats.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 598d8b73a5 tests/int/checkpoint.bats: ignore SC2206
Ignore warnings like this:

> In checkpoint.bats line 169:
>   PIDS_TO_KILL=($cpt_pid)
>                 ^------^ SC2206: Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a.

Since in all the cases we deal with either pids or fds, and they don't
have spaces.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 08766b9848 tests/int/*bats: fix/ignore shellcheck SC2046
Fix or ignore warnings like this one:

> In cgroups.bats line 107:
>             if [ $(id -u) = "0" ]; then
>                  ^------^ SC2046: Quote this to prevent word splitting.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 4ba4baea0e tests/int/*bats: fix shellcheck SC2086, SC2006
Those are pretty simple to allow shellcheck to fix these, so
this commit is courtesy of

> shellcheck -i SC2086 -i SC2006 -f diff *.bats > fix.diff
> patch -p1 < fix.diff

repeated 3 times ;)

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin b02ca2dc9c tests/int: fix shellcheck warning SC2002
Fix all warnings like this one:

> In checkpoint.bats line 197:
>   cat ./work-dir/restore.log | grep -B 5 Error || true
>       ^--------------------^ SC2002: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 3b80850eaa tests/int/update.bats: fix a shellcheck warning
This fixes the following warning, and implements a suggestion:

> In update.bats line 426:
>     IFS='/' read -r -a dirs <<< $(echo ${CGROUP_CPU} | sed -e s@^${CGROUP_CPU_BASE_PATH}/@@)
>                                 ^-- SC2046: Quote this to prevent word splitting.
>                                   ^-- SC2001: See if you can use ${variable//search/replace} instead.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 612d079086 tests/int/update.bats: fix a shellcheck warning
Fixes the following warning:

> In update.bats line 422:
>     local root_period=$(cat "${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us")
>           ^---------^ SC2155: Declare and assign separately to avoid masking return values.
>
>
> In update.bats line 423:
>     local root_runtime=$(cat "${CGROUP_CPU_BASE_PATH}/cpu.rt_runtime_us")
>           ^----------^ SC2155: Declare and assign separately to avoid masking return values.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 82836d2429 tests/int/cgroups.bats: fix a shellcheck warning
Fixes the following warning:

> In cgroups.bats line 58:
>     if [ "$KERNEL_MAJOR" -lt 4 ] || [ "$KERNEL_MAJOR" -eq 4 -a "$KERNEL_MINOR" -le 5 ]; then
>                                                             ^-- SC2166: Prefer [ p ] && [ q ] as [ p -a q ] is not well defined.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 4b8ff6a17c tests/int/checkpoint.bats: ignore some shellcheck warnings
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin ce50e1da7e test/int/spec.bats: simplify setup/teardown
1. cd is useless as all the paths are absolute
2. run is redundant, does not make sense to use it
3. use mkdir -p to save a line of code

This also eliminates shellcheck warnings like this one:

> In spec.bats line 8:
>   cd "$INTEGRATION_ROOT"
>   ^--------------------^ SC2164: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 699fdf8952 tests/int/mount.bats: fix a check
It's not a regex but a substring, so use a substring match.

Fixes the following warning by shellcheck:

> In mounts.bats line 20:
> 	[[ "${lines[0]}" =~ '/tmp/bind/config.json' ]]
>                           ^---------------------^ SC2076: Don't quote right-hand side of =~, it'll match literally rather than as a regex.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Kir Kolyshkin 85a3069878 test/int/hooks.bats: fix here-doc
The ending EOF should be
 - all by itself (i.e. no extra characters on the same line);
 - with no whitespace before it.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-10 07:41:49 -07:00
Mrunal Patel dedadbf9ea
Merge pull request #2545 from kolyshkin/go-mod-vendor
Makefile: fix go vet/fmt
2020-08-10 07:41:04 -07:00
Mrunal Patel 809dc64041
Merge pull request #2548 from kolyshkin/int-cr-fix
tests/int: fix error handling and logging
2020-08-10 07:39:29 -07:00
Xiaodong Liu 7f64fb4786 use criu cgroup mode const from go-criu
Signed-off-by: Xiaodong Liu <liuxiaodong@loongson.cn>
2020-08-10 10:25:53 +08:00
Kir Kolyshkin 5026bfab9c tests/int: fix error handling and logging
TL;DR: this allows to show logs from failed runc restore.

Bats scripts are run with `set -e`. This is well known and obvious,
and yet there are a few errors with respect to that, including a few
"gems" by yours truly.

1. bats scripts are run with `set -e`, meaning that `[ $? -eq 0 ]` is
   useless since the execution won't ever reach this line in case of
   non-zero exit code from a preceding command. So, remove all such
   checks, they are useless and misleading.

2. bats scripts are run with `set -e`, meaning that `ret=$?` is useless
   since the execution won't ever reach this line in case of non-zero
   exit code from a preceding command.

In particular, the code that calls runc restore needs to save the exit
code, show the errors in the log, and only when check the exit code and
fail if it's non-zero. It can not use `run` (or `runc` which uses `run`)
because of shell redirection that we need to set up.

The solution, implemented in this patch, is to use code like this:

```bash
ret=0
__runc ... || ret=$?
show_logs
[ $ret -eq 0 ]
```

In case __runc exits with non-zero exit code, `ret=$?` is executed, and
it always succeeds, so we won't fail just yet and have a chance to show
logs before checking the value of $ret.

In case __runc succeeds, `ret=$?` is never executed, so $ret will still
be zero (this is the reason why it needs to be set explicitly).

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-08 20:33:04 -07:00
Kir Kolyshkin 2de0b5aaf3 libct/integration: enable some tests for cgroupv2
The only two tests that are still skipped on v2 are kmem
and invalid CpuShares test -- since v2 does not support either.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-08 19:19:31 -07:00
Kir Kolyshkin 985bd24f62 Makefile: fix go vet/fmt
I have noticed that `go vet` from golang 1.13 ignores the vendor/
subdir, downloading all the modules when invoked in Travis CI env.

As the other go commands, in 1.13 it needs explicit -mod=vendor
flag, so let's provide one.

PS once golang 1.13 is unsupported, we will drop it.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-08-06 19:18:09 -07:00
Kir Kolyshkin a340fa9b56
Merge pull request #2543 from mrunalp/release_1.0.0-rc92
Release 1.0.0 rc92
2020-08-05 21:49:10 -07:00
Mrunal Patel 1ff1bf3452 VERSION: back to development
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
2020-08-05 09:34:30 -07:00
Mrunal Patel ff819c7e91 VERSION: release 1.0.0-rc92
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
2020-08-05 09:26:49 -07:00
Akihiro Suda f668854938
Merge pull request #2499 from kolyshkin/find-cgroup-mountpoint-fastpath
cgroupv1/FindCgroupMountpoint: add a fast path
2020-08-04 14:06:41 +09:00
Akihiro Suda 234d15ecd0
Merge pull request #2520 from thaJeztah/bump_runtime_spec
vendor: update runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6
2020-08-04 14:05:33 +09:00
Akihiro Suda 78d02e8563
Merge pull request #2534 from adrianreber/go-criu-4-1-0
Pass location of CRIU binary to go-criu
2020-08-03 16:21:50 +09:00
Kir Kolyshkin 637d54b7ce cgroups/fs tests: unify TestInvalid*Cgroup*
All the test cases are doing the same checks, only input differs,
so we can unify those using a test data table.

While at it:
 - use t.Fatalf where it makes sense (no further checks are possible);
 - remove the "XXX" comments as we won't get rid of cgroup Name/Parent.

PS I tried using t.Parallel() as well but it did not result in any
noticeable speedup, so I dropped it for simplicity.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-31 18:02:05 -07:00
Kir Kolyshkin e54d1e4715 libct: initialize inheritFD in place
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-31 17:55:34 -07:00
Kir Kolyshkin 8b973997a4 libct: criuNsToKey doesn't have to be a method
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-31 17:52:09 -07:00
Kir Kolyshkin 3de3112c61
Merge pull request #2525 from adrianreber/external-pidns
Tell CRIU to use an external pid namespace if necessary
2020-07-31 17:50:27 -07:00
Adrian Reber 6f4616dd73
Pass location of CRIU binary to go-criu
If the CRIU binary is in a non $PATH location and passed to runc via
'--criu /path/to/criu', this information has not been passed to go-criu
and since the switch to use go-criu for CRIU version detection, non
$PATH CRIU usage was broken. This uses the newly added go-criu interface
to pass the location of the binary to go-criu.

Signed-off-by: Adrian Reber <areber@redhat.com>
2020-07-31 11:14:15 +02:00
Adrian Reber 267b7148cb
Upgrade go-criu to 4.1.0
Signed-off-by: Adrian Reber <areber@redhat.com>
2020-07-31 11:14:15 +02:00
Akihiro Suda d6f5641c20
Merge pull request #2507 from kolyshkin/alt-to-2497
libct/cgroups/GetCgroupRoot: make it faster
2020-07-31 11:43:38 +09:00
Mrunal Patel 46243fcea1
Merge pull request #2500 from kolyshkin/fs-apply
libct/cgroups/fs: rework Apply()
2020-07-30 16:39:53 -07:00
Kir Kolyshkin e0c0b0cf32 libct/cgroups/GetCgroupRoot: make it faster
...by checking the default path first.

Quick benchmark shows it's about 5x faster on an idle system, and the
gain should be much more on a system doing mounts etc.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-30 13:45:21 -07:00
Sebastiaan van Stijn 901dccf05d
vendor: update runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-07-30 22:08:54 +02:00
Akihiro Suda 97b02cf9c0
Merge pull request #2531 from JFHwang/gomod_update
Update go.mod
2020-07-31 03:32:32 +09:00
John Hwang 5935296367 Update go.mod
Signed-off-by: John Hwang <john.f.hwang@gmail.com>
2020-07-30 05:28:39 -07:00
Aleksa Sarai 67169a9d43
merge branch 'pr-2529'
Aleksa Sarai (1):
  devices: correctly check device types

LGTMs: @AkihiroSuda @mrunalp
Closes #2529
2020-07-29 12:13:11 +10:00
Aleksa Sarai 95a59bf206
devices: correctly check device types
(mode&S_IFCHR == S_IFCHR) is the wrong way of checking the type of an
inode because the S_IF* bits are actually not a bitmask and instead must
be checked using S_IF*. This bug was neatly hidden behind a (major == 0)
sanity-check but that was removed by [1].

In addition, add a test that makes sure that HostDevices() doesn't give
rubbish results -- because we broke this and fixed this before[2].

[1]: 24388be71e ("configs: use different types for .Devices and .Resources.Devices")
[2]: 3ed492ad33 ("Handle non-devices correctly in DeviceFromPath")

Fixes: b0d014d0e1 ("libcontainer: one more switch from syscall to x/sys/unix")
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2020-07-28 19:04:30 +10:00
Adrian Reber 09e103b01e
Tell CRIU to use an external pid namespace if necessary
Trying to checkpoint a container out of pod in cri-o fails with:

  Error (criu/namespaces.c:1081): Can't dump a pid namespace without the process init

Starting with the upcoming CRIU release 3.15, CRIU can be told to ignore
the PID namespace during checkpointing and to restore processes into an
existing network namespace.

With the changes from this commit and CRIU 3.15 it is possible to
checkpoint a container out of a pod in cri-o.

Signed-off-by: Adrian Reber <areber@redhat.com>
2020-07-27 10:14:08 +02:00
Adrian Reber 610c5ad75c
Factor out checkpointing with external namespace code
To checkpoint and restore a container with an external network namespace
(like with Podman and CNI), runc tells CRIU to ignore the network
namespace during checkpoint and restore.

This commit moves that code to their own functions to be able to reuse
the same code path for external PID namespaces which are necessary for
checkpointing and restoring containers out of a pod in cri-o.

Signed-off-by: Adrian Reber <areber@redhat.com>
2020-07-27 10:14:07 +02:00
Kir Kolyshkin d65df61dc5
Merge pull request #2521 from zvier/master
cleancode: clean code for utils_linux.go
2020-07-23 12:58:24 -07:00
zvier 92e2175de1 cleancode: clean code for utils_linux.go
Signed-off-by: Jeff Zvier <zvier20@gmail.com>
2020-07-23 06:12:27 +08:00
Kir Kolyshkin 86d9399c80
Merge pull request #2524 from adrianreber/fix-travis
Fix .travis.yml warnings
2020-07-22 11:16:24 -07:00
Adrian Reber b7683d6b0f
Fix .travis.yml warnings
Travis reports following warnings which are fixed with this commit.

   root: deprecated key sudo (The key `sudo` has no effect anymore.)
   root: missing os, using the default linux
   root: key matrix is an alias for jobs, using jobs

Signed-off-by: Adrian Reber <areber@redhat.com>
2020-07-21 10:27:48 +02:00
Aleksa Sarai f8749ba098
merge branch 'pr-2509'
Kir Kolyshkin (2):
  tests/int/checkpoint: fds and pids cleanup
  tests/int/checkpoint: don't remove readonly flag

LGTMs: @mrunalp @AkihiroSuda @cyphar
Closes #2509
2020-07-20 13:03:38 +10:00
Kir Kolyshkin f9850afa91
Merge pull request #2518 from XiaodongLoong/redundant_chroot_param
remove redundant parameter of chroot function
2020-07-15 17:26:24 -07:00
Xiaodong Liu af283b3f47 remove redundant the parameter of chroot function
Signed-off-by: Xiaodong Liu <liuxiaodong@loongson.cn>
2020-07-15 16:22:07 +08:00
Mrunal Patel b7d8f3bf0d
Merge pull request #2516 from ide-rea/fix-typo
fix small typo
2020-07-13 09:04:31 -07:00
Mrunal Patel 47fbafb7bc
Merge pull request #2510 from kolyshkin/criu-el7
tests/centos7: add criu
2020-07-13 07:51:08 -07:00
Xiaoyu Zhang 76b05e6d13 fix small typo
Signed-off-by: Xiaoyu Zhang <mateuszhang@tencent.com>
2020-07-11 16:36:32 +08:00
Mrunal Patel cf1273abf4
Merge pull request #2498 from kolyshkin/v1-code-cleanups
libct/cgroups/fs: code cleanups
2020-07-09 15:58:06 -07:00
Mrunal Patel 545ebdd14a
Merge pull request #2511 from kolyshkin/fedora-dnf-fix
tests/fedora32: retry dnf
2020-07-08 21:20:05 -07:00
Kir Kolyshkin fbf047bf2f
Merge pull request #2501 from XiaodongLoong/systemderror-fix
fix TestPidsSystemd and TestRunWithKernelMemorySystemd test error
2020-07-08 20:39:39 -07:00
Xiaodong Liu f57bb2fe3d fix TestPidsSystemd and TestRunWithKernelMemorySystemd test error
Signed-off-by: Xiaodong Liu <liuxiaodong@loongson.cn>
2020-07-09 09:36:03 +08:00
Mrunal Patel ce54a9d4d7
Merge pull request #2514 from rhatdan/windows
Allow libcontainer/configs to be imported on Windows
2020-07-08 14:00:54 -07:00
Kir Kolyshkin 6d5125f8b4 tests/int/checkpoint: don't remove readonly flag
This should not longer be necessary (in theory, at least),
let's see how it goes.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-08 12:56:25 -07:00
Kir Kolyshkin 9806eb5567
Merge pull request #2513 from lsm5/custom-PREFIX-in-Makefile
allow customizable PREFIX variable
2020-07-08 12:54:11 -07:00
Daniel J Walsh d78ee47154
Allow libcontainer/configs to be imported on Windows
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2020-07-08 15:20:37 -04:00
Kir Kolyshkin 5517d1d71d
Merge pull request #2505 from XiaodongLoong/redundant-copy-src
fix redundant source code copy issue
2020-07-08 07:37:55 -07:00
Kir Kolyshkin ffe9f0b0fb Vagrantfile.centos7: do not ignore script failures
Add `set -e -u -o pipefail` so the script will fail early
if there's an error.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-08 07:32:41 -07:00
Lokesh Mandvekar bc1a9c11a2 allow customizable PREFIX variable
This change would let me specify my own PREFIX so that I can reuse
Makefile targets for building rpm packages.

Signed-off-by: Lokesh Mandvekar <lsm5@fedoraproject.org>
2020-07-08 09:20:03 -04:00
Kir Kolyshkin a73ce38d16 cgroupv1/FindCgroupMountpoint: add a fast path
In case cgroupPath is under the default cgroup prefix, let's try to
guess the mount point by adding the subsystem name to the default
prefix, and resolving the resulting path in case it's a symlink.

In most cases, given the default cgroup setup, this trick
should result in returning the same result faster, and avoiding
/proc/self/mountinfo parsing which is relatively slow and problematic.

Be very careful with the default path, checking it is
 - a directory;
 - a mount point;
 - has cgroup fstype.

If something is not right, fall back to parsing mountinfo.

While at it, remove the obsoleted comment about mountinfo parsing.  The
comment belongs to findCgroupMountpointAndRootFromReader(), but rather
than moving it there, let's just remove it, since it does not add any
value in understanding the current code.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-07 13:57:33 -07:00
Kir Kolyshkin c27b8e7fe7 tests/fedora32: retry dnf
Fedora mirrors are not very stable recently, leading to CI failures
that usually look like this:

> sudo: make: command not found

In fact it's caused by dnf failure to read metadata from mirrors:

> Errors during downloading metadata for repository 'updates':
>    - Downloading successful, but checksum doesn't match. Calculated: <....>
> Error: Failed to download metadata for repo 'updates': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried

The error went undetected due to lack of exit code check.

This commit:
 - adds `set -e -u -o pipefail` so the script will fail early;
 - adds a retry loop with a sleep around dnf invocation.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-07 12:31:52 -07:00
Kir Kolyshkin 92f498210a tests/centos7: add criu
Enable criu tests on centos 7 by using criu from Adrian's repo
(https://copr.fedorainfracloud.org/coprs/adrian/criu-el7/)

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-07 11:45:41 -07:00
Kir Kolyshkin 98c7c01df9 tests/int/checkpoint: require cgroupns
Otherwise the test will fail on e.g. CentOS 7.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-07 11:24:36 -07:00
Kir Kolyshkin c1adc99a20 cgroup/fs: rework Apply()
In manager.Apply() method, a path to each subsystem is obtained by
calling d.path(sys.Name()), and the sys.Apply() is called that does
the same call to d.path() again.

d.path() is an expensive call, so rather than to call it twice, let's
reuse the result.

This results the number of times we parse mountinfo during container
start from 62 to 34 on my setup.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-07 10:58:37 -07:00
Kir Kolyshkin 417f5ff40d tests/int/checkpoint: fds and pids cleanup
1. Do not use hardcoded fd numbers, instead relying on bash feature of
   assigning an fd to a variable.

   This looks very weird, but the rule of thumb here is:
   - if this is in exec, use {var} (i.e. no $);
   - otherwise, use as normal ($var or ${var}).

2. Add killing the background processes and closing the fds to teardown.
   This is helpful in case of a test failure, in order to not affect the
   subsequent tests.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-07 10:54:23 -07:00
Kir Kolyshkin 335f0806c0 tests/int/delete: cgroupv1 with sub-cgroups removal case
This is similar to what we did before for v2.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 21:08:04 -07:00
Aleksa Sarai 819fcc687e
merge branch 'pr-2495'
Kir Kolyshkin (1):
  cgroups/fs/path: optimize

LGTMs: @mrunalp @cyphar
Closes #2495
2020-07-07 11:51:06 +10:00
Kir Kolyshkin 2a322e91ec cgroupv1: remove subsystemSet.Get()
Instead of iterating over m.paths, iterate over subsystems and look up
the path for each. This is faster since a map lookup is faster than
iterating over the names in Get. A quick benchmark shows that the new
way is 2.5x faster than the old one.

Note though that this is not done to make things faster, as savings are
negligible, but to make things simpler by removing some code.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 18:31:46 -07:00
Kir Kolyshkin daf30cb7ca cgroups/fs: rm getSubsystems
It does not add any value.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 18:29:14 -07:00
Kir Kolyshkin 2e22579946 libct/cgroups/fs.GetStats: drop PathExists check
Half of controllers' GetStats just return nil, and most of the others
ignore ENOENT on files, so it will be cheaper to not check that the
path exists in the main GetStats method, offloading that to the
controllers.

Drop PathExists check from GetStats, add it to those controllers'
GetStats where it was missing.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 18:02:17 -07:00
Kir Kolyshkin 11fb94965c cgroups/fs: rm Remove method from controllers
To my surprise, those are not used anywhere in the code.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 18:02:17 -07:00
Kir Kolyshkin 19be8e5ba5 libct/cgroups.RemovePaths: speedup
Using os.RemoveAll has the following two issues:

 1. it tries to remove all files, which does not make sense for cgroups;
 2. it tries rm(2) which fails to directories, and then rmdir(2).

Let's reuse our RemovePath instead, and add warnings and errors logging.

PS I am somewhat hesitant to remove the weird checking my means of stat,
as it might break something. Unfortunately, neither commit 6feb7bda04
nor the PR it contains [1] do not explain what kind of weird errors were
seen from os.RemoveAll. Most probably our code won't return any bogus
errors, but let's keep the old code to be on the safe side.

[1] https://github.com/docker-archive/libcontainer/pull/308

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 17:54:44 -07:00
Kir Kolyshkin 3f14242e0a libct/cgroups: move RemovePath from fs2
This is to be used by RemovePaths.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 17:54:44 -07:00
Kir Kolyshkin 254d23b964 libc/cgroups: empty map in RemovePaths
RemovePaths() deletes elements from the paths map for paths that has
been successfully removed.

Although, it does not empty the map itself (which is needed that AFAIK
Go garbage collector does not shrink the map), but all its callers do.

Move this operation from callers to RemovePaths.

No functional change, except the old map should be garbage collected now.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-06 17:54:44 -07:00
Mrunal Patel 30dc54a995
Merge pull request #2503 from giuseppe/cgroup-fixes
cgroup, systemd: cleanup cgroups
2020-07-06 15:14:29 -07:00
Mrunal Patel 3f81131845
Merge pull request #2490 from kolyshkin/dev-opt
libct/cgroups: add SkipDevices to Resources
2020-07-06 14:28:30 -07:00
Giuseppe Scrivano 32034481ea
cgroup, systemd: cleanup cgroups
some hierarchies were created directly by .Apply() on top of systemd
managed cgroups.  systemd doesn't manage these and as a result we leak
these cgroups.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2020-07-06 23:06:16 +02:00
Mrunal Patel 46a304b592
Merge pull request #2502 from tjucoder/master
make sure pty.Close() will be called and fix comment
2020-07-06 11:49:20 -07:00
Mrunal Patel e638eda0cb
Merge pull request #2496 from kolyshkin/freeze-nits
libct/cgroups/fs: simplify/speedup freezer code
2020-07-06 11:30:01 -07:00
Xiaodong Liu a4cb88f307 redundant souce code copy
There is a docker -v flag for test in Makefile

Signed-off-by: Xiaodong Liu <liuxiaodong@loongson.cn>
2020-07-06 19:03:26 +08:00
Giuseppe Scrivano 2deaeab08f
cgroup: store the result of IsRunningSystemd
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2020-07-05 12:42:27 +02:00
tjucoder ab35cfe23c make sure pty.Close() will be called and fix comment
Signed-off-by: tjucoder <chinesecoder@foxmail.com>
2020-07-05 16:37:21 +08:00
Kir Kolyshkin 62a30709d2 cgroups/fs/path: optimize
The result of cgroupv1.FindCgroupMountpoint() call (which is relatively
expensive) is only used in case raw.innerPath is absolute, so it only
makes sense to call it in that case.

This drastically reduces the number of calls to FindCgroupMountpoint
during container start (from 116 to 62 in my setup).

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-03 14:07:27 -07:00
Kir Kolyshkin 46b26bc05d cgroups/fs/Freeze: simplify
In here, defer looks like an overkill, since the code is very simple and
we already have an error path.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-03 14:02:57 -07:00
Kir Kolyshkin cd479f9d14 cgroupv1/freezer: don't use subsystemSet.Get()
Iterating over the list of subsystems and comparing their names to get an
instance of fs.cgroupFreezer is useless and a waste of time, since it is
a shallow type (i.e. does not have any data/state) and we can create an
instance in place.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-03 14:00:44 -07:00
Akihiro Suda 3cb1909c70
Merge pull request #2493 from thaJeztah/bump_ebpf
vendor: update cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775
2020-07-03 11:43:59 +09:00
Kir Kolyshkin 108ee85b82 libct/cgroups: add SkipDevices to Resources
The kubelet uses libct/cgroups code to set up cgroups. It creates a
parent cgroup (kubepods) to put the containers into.

The problem (for cgroupv2 that uses eBPF for device configuration) is
the hard requirement to have devices cgroup configured results in
leaking an eBPF program upon every kubelet restart.  program. If kubelet
is restarted 64+ times, the cgroup can't be configured anymore.

Work around this by adding a SkipDevices flag to Resources.

A check was added so that if SkipDevices is set, such a "container"
can't be started (to make sure it is only used for non-containers).

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-07-02 15:19:31 -07:00
Sebastiaan van Stijn f49adb5277
vendor: update cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775
full diff: a9f01edf17...1c8d4c9ef7

drops support for go1.12, and removes dependency on the golang.org/x/xerrors
transitional package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-07-02 14:48:23 +02:00
Aleksa Sarai 6f5edda901
merge branch 'pr-2491'
Mrunal Patel (2):
  VERSION: back to development
  VERSION: release 1.0.0-rc91

Vote: +7 -0 #0
Closes #2491
2020-07-02 10:52:29 +10:00
Mrunal Patel d0e928961e VERSION: back to development
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
2020-06-30 08:25:05 -07:00
Mrunal Patel 24a3cf88a7 VERSION: release 1.0.0-rc91
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
2020-06-30 08:24:30 -07:00
394 changed files with 36182 additions and 14689 deletions

View File

@ -1,19 +1,20 @@
dist: bionic
language: go
os: linux
go:
- 1.15.x
- 1.14.x
- 1.13.x
- tip
cache:
directories:
- /home/travis/.vagrant.d/boxes
matrix:
jobs:
include:
- go: 1.14.x
- go: 1.15.x
name: "verify-dependencies"
script:
- make verify-dependencies
- go: 1.13.x
- go: 1.15.x
name: "cgroup-systemd"
env:
- RUNC_USE_SYSTEMD=1
@ -43,10 +44,7 @@ matrix:
script:
# kernel 3.10 (frankenized), systemd 219
- sudo ssh default 'rpm -q centos-release kernel systemd'
# FIXME: the following unit tests are skipped (TESTFLAGS=-short):
# FAIL: TestPidsSystemd: utils_test.go:55: exec_test.go:630: unexpected error: container_linux.go:353: starting container process caused: process_linux.go:326: applying cgroup configuration for process caused: mountpoint for devices not found
# FAIL: TestRunWithKernelMemorySystemd: exec_test.go:713: runContainer failed with kernel memory limit: container_linux.go:353: starting container process caused: process_linux.go:326: applying cgroup configuration for process caused: mkdir : no such file or directory
- sudo ssh default -t 'sudo -i make -C /vagrant localunittest TESTFLAGS=-short'
- sudo ssh default -t 'sudo -i make -C /vagrant localunittest'
- sudo ssh default -t 'sudo -i make -C /vagrant localintegration'
- sudo ssh default -t 'sudo -i make -C /vagrant localintegration RUNC_USE_SYSTEMD=1'
# FIXME: rootless is skipped because of EPERM on writing cgroup.procs
@ -58,7 +56,6 @@ matrix:
go_import_path: github.com/opencontainers/runc
# `make ci` uses Docker.
sudo: required
services:
- docker

View File

@ -1,4 +1,4 @@
ARG GO_VERSION=1.13
ARG GO_VERSION=1.15
ARG BATS_VERSION=v1.2.0
ARG CRIU_VERSION=v3.14
@ -95,5 +95,3 @@ ENV DEBIAN_ROOTFS /debian
RUN mkdir -p "${DEBIAN_ROOTFS}"
RUN . tests/integration/multi-arch.bash \
&& get_and_extract_debian "$DEBIAN_ROOTFS"
COPY . .

View File

@ -1,7 +1,7 @@
CONTAINER_ENGINE := docker
GO := go
PREFIX := $(DESTDIR)/usr/local
PREFIX ?= $(DESTDIR)/usr/local
BINDIR := $(PREFIX)/sbin
MANDIR := $(PREFIX)/share/man
@ -52,8 +52,8 @@ dbuild: runcimage
$(RUNC_IMAGE) make clean all
lint:
$(GO) vet ./...
$(GO) fmt ./...
$(GO) vet $(MOD_VENDOR) ./...
$(GO) fmt $(MOD_VENDOR) ./...
man:
man/md2man-all.sh
@ -120,7 +120,8 @@ clean:
validate:
script/validate-gofmt
script/validate-c
$(GO) vet ./...
$(GO) vet $(MOD_VENDOR) ./...
shellcheck tests/integration/*.bats
ci: validate test release

View File

@ -1 +1 @@
1.0.0-rc10+dev
1.0.0-rc92+dev

View File

@ -12,13 +12,16 @@ Vagrant.configure("2") do |config|
v.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
set -e -u -o pipefail
# configuration
GO_VERSION="1.13.11"
GO_VERSION="1.15"
BATS_VERSION="v1.2.0"
# install yum packages
yum install -y -q epel-release
yum install -y -q gcc git iptables jq libseccomp-devel make skopeo
(cd /etc/yum.repos.d && curl -O https://copr.fedorainfracloud.org/coprs/adrian/criu-el7/repo/epel-7/adrian-criu-el7-epel-7.repo)
yum install -y -q gcc git iptables jq libseccomp-devel make skopeo criu
yum clean all
# install Go
@ -34,8 +37,6 @@ Vagrant.configure("2") do |config|
git checkout $BATS_VERSION
./install.sh /usr/local
# NOTE: criu is NOT installed. criu tests are skipped.
# set PATH (NOTE: sudo without -i ignores this PATH)
cat >> /etc/profile.d/sh.local <<EOF
PATH=/usr/local/go/bin:/usr/local/bin:$PATH

View File

@ -13,13 +13,18 @@ Vagrant.configure("2") do |config|
v.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
cat << EOF | dnf -y shell
set -e -u -o pipefail
# Work around dnf mirror failures by retrying a few times
for i in $(seq 0 2); do
sleep $i
cat << EOF | dnf -y shell && break
config exclude kernel,kernel-core
config install_weak_deps false
update
install iptables gcc make golang-go libseccomp-devel bats jq git-core criu skopeo
ts run
EOF
done
dnf clean all
# Add a user for rootless tests

View File

@ -9,6 +9,7 @@ import (
"strconv"
"strings"
criu "github.com/checkpoint-restore/go-criu/v4/rpc"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runtime-spec/specs-go"
@ -109,11 +110,11 @@ func setManageCgroupsMode(context *cli.Context, options *libcontainer.CriuOpts)
if cgOpt := context.String("manage-cgroups-mode"); cgOpt != "" {
switch cgOpt {
case "soft":
options.ManageCgroupsMode = libcontainer.CRIU_CG_MODE_SOFT
options.ManageCgroupsMode = criu.CriuCgMode_SOFT
case "full":
options.ManageCgroupsMode = libcontainer.CRIU_CG_MODE_FULL
options.ManageCgroupsMode = criu.CriuCgMode_FULL
case "strict":
options.ManageCgroupsMode = libcontainer.CRIU_CG_MODE_STRICT
options.ManageCgroupsMode = criu.CriuCgMode_STRICT
default:
fatal(errors.New("Invalid manage cgroups mode"))
}

18
go.mod
View File

@ -3,24 +3,24 @@ module github.com/opencontainers/runc
go 1.14
require (
github.com/checkpoint-restore/go-criu/v4 v4.0.2
github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3
github.com/checkpoint-restore/go-criu/v4 v4.1.0
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775
github.com/containerd/console v1.0.0
github.com/coreos/go-systemd/v22 v22.0.0
github.com/coreos/go-systemd/v22 v22.1.0
github.com/cyphar/filepath-securejoin v0.2.2
github.com/docker/go-units v0.4.0
github.com/godbus/dbus/v5 v5.0.3
github.com/golang/protobuf v1.3.5
github.com/golang/protobuf v1.4.2
github.com/moby/sys/mountinfo v0.1.3
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2
github.com/opencontainers/selinux v1.5.1
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6
github.com/opencontainers/selinux v1.6.0
github.com/pkg/errors v0.9.1
github.com/seccomp/libseccomp-golang v0.9.1
github.com/sirupsen/logrus v1.6.0
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
// NOTE: urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092
github.com/urfave/cli v1.22.1
github.com/vishvananda/netlink v1.1.0
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1
)

58
go.sum
View File

@ -1,14 +1,12 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/checkpoint-restore/go-criu/v4 v4.0.2 h1:jt+rnBIhFtPw0fhtpYGcUOilh4aO9Hj7r+YLEtf30uA=
github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f h1:W1RQPz3nR8RxUw/Uqk71GU3JlZ7pNa1pXrHs98h0o9U=
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s=
github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3 h1:qcqzLJa2xCo9sgdCzpT/SJSYxROTEstuhf7ZBHMirms=
github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s=
github.com/checkpoint-restore/go-criu/v4 v4.1.0 h1:WW2B2uxx9KWF6bGlHqhm8Okiafwwx7Y2kcpn8lCpjgo=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775 h1:cHzBGGVew0ezFsq2grfy2RsB8hO/eNyBgOLHBCqfR1U=
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
@ -21,20 +19,27 @@ github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/moby/sys/mountinfo v0.1.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI=
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 h1:7InQ7/zrOh6SlFjaXFubv0xX0HsuC9qJsdqm7bNQpYM=
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 h1:9mv9SC7GWmRWE0J/+oD8w3GsN2KYGKtg6uwLN7hfP5E=
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.4.0 h1:cpiX/2wWIju/6My60T6/z9CxNG7c8xTQyEmA9fChpUo=
github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/opencontainers/selinux v1.5.1 h1:jskKwSMFYqyTrHEuJgQoUlTcId0av64S6EWObrIfn5Y=
github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976 h1:aZQToFSLH8ejFeSkTc3r3L4dPImcj7Ib/KgmkQqbGGg=
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6 h1:NhsM2gc769rVWDqJvapK37r+7+CBXI8xHhnfnt8uQsg=
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY=
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -50,22 +55,31 @@ github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -22,12 +22,8 @@ func (s *BlkioGroup) Name() string {
return "blkio"
}
func (s *BlkioGroup) Apply(d *cgroupData) error {
_, err := d.join("blkio")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *BlkioGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -74,10 +70,6 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *BlkioGroup) Remove(d *cgroupData) error {
return removePath(d.path("blkio"))
}
/*
examples:

View File

@ -21,17 +21,7 @@ func (s *CpuGroup) Name() string {
return "cpu"
}
func (s *CpuGroup) Apply(d *cgroupData) error {
// We always want to join the cpu group, to allow fair cpu scheduling
// on a container basis
path, err := d.path("cpu")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return s.ApplyDir(path, d.config, d.pid)
}
func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error {
func (s *CpuGroup) Apply(path string, d *cgroupData) error {
// This might happen if we have no cpu cgroup mounted.
// Just do nothing and don't fail.
if path == "" {
@ -43,12 +33,12 @@ func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error
// We should set the real-Time group scheduling settings before moving
// in the process because if the process is already in SCHED_RR mode
// and no RT bandwidth is set, adding it will fail.
if err := s.SetRtSched(path, cgroup); err != nil {
if err := s.SetRtSched(path, d.config); err != nil {
return err
}
// because we are not using d.join we need to place the pid into the procs file
// unlike the other subsystems
return cgroups.WriteCgroupProc(path, pid)
// Since we are not using join(), we need to place the pid
// into the procs file unlike other subsystems.
return cgroups.WriteCgroupProc(path, d.pid)
}
func (s *CpuGroup) SetRtSched(path string, cgroup *configs.Cgroup) error {
@ -96,10 +86,6 @@ func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
return s.SetRtSched(path, cgroup)
}
func (s *CpuGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpu"))
}
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
f, err := os.Open(filepath.Join(path, "cpu.stat"))
if err != nil {

View File

@ -182,7 +182,9 @@ func TestCpuSetRtSchedAtApply(t *testing.T) {
helper.CgroupData.config.Resources.CpuRtRuntime = rtRuntimeAfter
helper.CgroupData.config.Resources.CpuRtPeriod = rtPeriodAfter
cpu := &CpuGroup{}
if err := cpu.ApplyDir(helper.CgroupPath, helper.CgroupData.config, 1234); err != nil {
helper.CgroupData.pid = 1234
if err := cpu.Apply(helper.CgroupPath, helper.CgroupData); err != nil {
t.Fatal(err)
}

View File

@ -40,24 +40,18 @@ func (s *CpuacctGroup) Name() string {
return "cpuacct"
}
func (s *CpuacctGroup) Apply(d *cgroupData) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *CpuacctGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *CpuacctGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpuacct"))
}
func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
if !cgroups.PathExists(path) {
return nil
}
userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
if err != nil {
return err

View File

@ -23,12 +23,8 @@ func (s *CpusetGroup) Name() string {
return "cpuset"
}
func (s *CpusetGroup) Apply(d *cgroupData) error {
dir, err := d.path("cpuset")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return s.ApplyDir(dir, d.config, d.pid)
func (s *CpusetGroup) Apply(path string, d *cgroupData) error {
return s.ApplyDir(path, d.config, d.pid)
}
func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -45,10 +41,6 @@ func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *CpusetGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpuset"))
}
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -22,14 +22,16 @@ func (s *DevicesGroup) Name() string {
return "devices"
}
func (s *DevicesGroup) Apply(d *cgroupData) error {
_, err := d.join("devices")
if err != nil {
// We will return error even it's `not found` error, devices
// cgroup is hard requirement for container's security.
return err
}
func (s *DevicesGroup) Apply(path string, d *cgroupData) error {
if d.config.SkipDevices {
return nil
}
if path == "" {
// Return error here, since devices cgroup
// is a hard requirement for container's security.
return errSubsystemDoesNotExist
}
return join(path, d.pid)
}
func loadEmulator(path string) (*devices.Emulator, error) {
@ -52,7 +54,7 @@ func buildEmulator(rules []*configs.DeviceRule) (*devices.Emulator, error) {
}
func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
if system.RunningInUserNS() {
if system.RunningInUserNS() || cgroup.SkipDevices {
return nil
}
@ -103,10 +105,6 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *DevicesGroup) Remove(d *cgroupData) error {
return removePath(d.path("devices"))
}
func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -22,12 +22,8 @@ func (s *FreezerGroup) Name() string {
return "freezer"
}
func (s *FreezerGroup) Apply(d *cgroupData) error {
_, err := d.join("freezer")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *FreezerGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -61,10 +57,6 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *FreezerGroup) Remove(d *cgroupData) error {
return removePath(d.path("freezer"))
}
func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -18,7 +18,7 @@ import (
)
var (
subsystemsLegacy = subsystemSet{
subsystems = []subsystem{
&CpusetGroup{},
&DevicesGroup{},
&MemoryGroup{},
@ -38,26 +38,13 @@ var (
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
type subsystemSet []subsystem
func (s subsystemSet) Get(name string) (subsystem, error) {
for _, ss := range s {
if ss.Name() == name {
return ss, nil
}
}
return nil, errSubsystemDoesNotExist
}
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Removes the cgroup represented by 'cgroupData'.
Remove(*cgroupData) error
// Creates and joins the cgroup represented by 'cgroupData'.
Apply(*cgroupData) error
Apply(path string, c *cgroupData) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
}
@ -81,6 +68,56 @@ func NewManager(cg *configs.Cgroup, paths map[string]string, rootless bool) cgro
var cgroupRootLock sync.Mutex
var cgroupRoot string
const defaultCgroupRoot = "/sys/fs/cgroup"
func tryDefaultCgroupRoot() string {
var st, pst unix.Stat_t
// (1) it should be a directory...
err := unix.Lstat(defaultCgroupRoot, &st)
if err != nil || st.Mode&unix.S_IFDIR == 0 {
return ""
}
// (2) ... and a mount point ...
err = unix.Lstat(filepath.Dir(defaultCgroupRoot), &pst)
if err != nil {
return ""
}
if st.Dev == pst.Dev {
// parent dir has the same dev -- not a mount point
return ""
}
// (3) ... of 'tmpfs' fs type.
var fst unix.Statfs_t
err = unix.Statfs(defaultCgroupRoot, &fst)
if err != nil || fst.Type != unix.TMPFS_MAGIC {
return ""
}
// (4) it should have at least 1 entry ...
dir, err := os.Open(defaultCgroupRoot)
if err != nil {
return ""
}
names, err := dir.Readdirnames(1)
if err != nil {
return ""
}
if len(names) < 1 {
return ""
}
// ... which is a cgroup mount point.
err = unix.Statfs(filepath.Join(defaultCgroupRoot, names[0]), &fst)
if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
return ""
}
return defaultCgroupRoot
}
// Gets the cgroupRoot.
func getCgroupRoot() (string, error) {
cgroupRootLock.Lock()
@ -90,6 +127,14 @@ func getCgroupRoot() (string, error) {
return cgroupRoot, nil
}
// fast path
cgroupRoot = tryDefaultCgroupRoot()
if cgroupRoot != "" {
return cgroupRoot, nil
}
// slow path: parse mountinfo, find the first mount where fs=cgroup
// (e.g. "/sys/fs/cgroup/memory"), use its parent.
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", err
@ -166,10 +211,6 @@ func isIgnorableError(rootless bool, err error) bool {
return false
}
func (m *manager) getSubsystems() subsystemSet {
return subsystemsLegacy
}
func (m *manager) Apply(pid int) (err error) {
if m.cgroups == nil {
return nil
@ -199,19 +240,19 @@ func (m *manager) Apply(pid int) (err error) {
return cgroups.EnterPid(m.paths, pid)
}
for _, sys := range m.getSubsystems() {
for _, sys := range subsystems {
p, err := d.path(sys.Name())
if err != nil {
// The non-presence of the devices subsystem is
// considered fatal for security reasons.
if cgroups.IsNotFound(err) && sys.Name() != "devices" {
if cgroups.IsNotFound(err) && (c.SkipDevices || sys.Name() != "devices") {
continue
}
return err
}
m.paths[sys.Name()] = p
if err := sys.Apply(d); err != nil {
if err := sys.Apply(p, d); err != nil {
// In the case of rootless (including euid=0 in userns), where an
// explicit cgroup path hasn't been set, we don't bail on error in
// case of permission problems. Cases where limits have been set
@ -233,11 +274,7 @@ func (m *manager) Destroy() error {
}
m.mu.Lock()
defer m.mu.Unlock()
if err := cgroups.RemovePaths(m.paths); err != nil {
return err
}
m.paths = make(map[string]string)
return nil
return cgroups.RemovePaths(m.paths)
}
func (m *manager) Path(subsys string) string {
@ -250,9 +287,9 @@ func (m *manager) GetStats() (*cgroups.Stats, error) {
m.mu.Lock()
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.paths {
sys, err := m.getSubsystems().Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
for _, sys := range subsystems {
path := m.paths[sys.Name()]
if path == "" {
continue
}
if err := sys.GetStats(path, stats); err != nil {
@ -275,7 +312,7 @@ func (m *manager) Set(container *configs.Config) error {
m.mu.Lock()
defer m.mu.Unlock()
for _, sys := range m.getSubsystems() {
for _, sys := range subsystems {
path := m.paths[sys.Name()]
if err := sys.Set(path, container.Cgroups); err != nil {
if m.rootless && sys.Name() == "devices" {
@ -298,7 +335,7 @@ func (m *manager) Set(container *configs.Config) error {
// Freeze toggles the container's freezer cgroup depending on the state
// provided
func (m *manager) Freeze(state configs.FreezerState) (Err error) {
func (m *manager) Freeze(state configs.FreezerState) error {
path := m.Path("freezer")
if m.cgroups == nil || path == "" {
return errors.New("cannot toggle freezer: cgroups not configured for container")
@ -306,17 +343,9 @@ func (m *manager) Freeze(state configs.FreezerState) (Err error) {
prevState := m.cgroups.Resources.Freezer
m.cgroups.Resources.Freezer = state
defer func() {
if Err != nil {
m.cgroups.Resources.Freezer = prevState
}
}()
freezer, err := m.getSubsystems().Get("freezer")
if err != nil {
return err
}
freezer := &FreezerGroup{}
if err := freezer.Set(path, m.cgroups); err != nil {
m.cgroups.Resources.Freezer = prevState
return err
}
return nil
@ -359,14 +388,14 @@ func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) {
}
func (raw *cgroupData) path(subsystem string) (string, error) {
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
if filepath.IsAbs(raw.innerPath) {
mnt, err := cgroups.FindCgroupMountpoint(raw.root, subsystem)
// If we didn't mount the subsystem, there is no point we make the path.
if err != nil {
return "", err
}
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
if filepath.IsAbs(raw.innerPath) {
// Sometimes subsystems can be mounted together as 'cpu,cpuacct'.
return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil
}
@ -382,18 +411,14 @@ func (raw *cgroupData) path(subsystem string) (string, error) {
return filepath.Join(parentPath, raw.innerPath), nil
}
func (raw *cgroupData) join(subsystem string) (string, error) {
path, err := raw.path(subsystem)
if err != nil {
return "", err
func join(path string, pid int) error {
if path == "" {
return nil
}
if err := os.MkdirAll(path, 0755); err != nil {
return "", err
return err
}
if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil {
return "", err
}
return path, nil
return cgroups.WriteCgroupProc(path, pid)
}
func removePath(p string, err error) error {
@ -418,13 +443,12 @@ func (m *manager) GetCgroups() (*configs.Cgroup, error) {
func (m *manager) GetFreezerState() (configs.FreezerState, error) {
dir := m.Path("freezer")
freezer, err := m.getSubsystems().Get("freezer")
// If the container doesn't have the freezer cgroup, say it's undefined.
if err != nil || dir == "" {
if dir == "" {
return configs.Undefined, nil
}
return freezer.(*FreezerGroup).GetState(dir)
freezer := &FreezerGroup{}
return freezer.GetState(dir)
}
func (m *manager) Exists() bool {

View File

@ -15,18 +15,63 @@ func TestInvalidCgroupPath(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
t.Fatalf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Path: "../../../../../../../../../../some/path",
testCases := []struct {
test string
path, name, parent string
}{
{
test: "invalid cgroup path",
path: "../../../../../../../../../../some/path",
},
{
test: "invalid absolute cgroup path",
path: "/../../../../../../../../../../some/path",
},
{
test: "invalid cgroup parent",
parent: "../../../../../../../../../../some/path",
name: "name",
},
{
test: "invalid absolute cgroup parent",
parent: "/../../../../../../../../../../some/path",
name: "name",
},
{
test: "invalid cgroup name",
parent: "parent",
name: "../../../../../../../../../../some/path",
},
{
test: "invalid absolute cgroup name",
parent: "parent",
name: "/../../../../../../../../../../some/path",
},
{
test: "invalid cgroup name and parent",
parent: "../../../../../../../../../../some/path",
name: "../../../../../../../../../../some/path",
},
{
test: "invalid absolute cgroup name and parent",
parent: "/../../../../../../../../../../some/path",
name: "/../../../../../../../../../../some/path",
},
}
for _, tc := range testCases {
t.Run(tc.test, func(t *testing.T) {
config := &configs.Cgroup{Path: tc.path, Name: tc.name, Parent: tc.parent}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
t.Fatalf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
@ -38,260 +83,24 @@ func TestInvalidCgroupPath(t *testing.T) {
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
t.Fatalf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
})
}
}
func TestInvalidAbsoluteCgroupPath(t *testing.T) {
func TestTryDefaultCgroupRoot(t *testing.T) {
res := tryDefaultCgroupRoot()
exp := defaultCgroupRoot
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
// checking that tryDefaultCgroupRoot does return ""
// in case /sys/fs/cgroup is not cgroup v1 root dir.
exp = ""
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Path: "/../../../../../../../../../../some/path",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
}
// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
func TestInvalidCgroupParent(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Parent: "../../../../../../../../../../some/path",
Name: "name",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
}
// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
func TestInvalidAbsoluteCgroupParent(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Parent: "/../../../../../../../../../../some/path",
Name: "name",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
}
// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
func TestInvalidCgroupName(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Parent: "parent",
Name: "../../../../../../../../../../some/path",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
}
// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
func TestInvalidAbsoluteCgroupName(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Parent: "parent",
Name: "/../../../../../../../../../../some/path",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
}
// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
func TestInvalidCgroupNameAndParent(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Parent: "../../../../../../../../../../some/path",
Name: "../../../../../../../../../../some/path",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
}
}
// XXX: Remove me after we get rid of configs.Cgroup.Name and configs.Cgroup.Parent.
func TestInvalidAbsoluteCgroupNameAndParent(t *testing.T) {
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
root, err := getCgroupRoot()
if err != nil {
t.Errorf("couldn't get cgroup root: %v", err)
}
config := &configs.Cgroup{
Parent: "/../../../../../../../../../../some/path",
Name: "/../../../../../../../../../../some/path",
}
data, err := getCgroupData(config, 0)
if err != nil {
t.Errorf("couldn't get cgroup data: %v", err)
}
// Make sure the final innerPath doesn't go outside the cgroup mountpoint.
if strings.HasPrefix(data.innerPath, "..") {
t.Errorf("SECURITY: cgroup innerPath is outside cgroup mountpoint!")
}
// Double-check, using an actual cgroup.
deviceRoot := filepath.Join(root, "devices")
devicePath, err := data.path("devices")
if err != nil {
t.Errorf("couldn't get cgroup path: %v", err)
}
if !strings.HasPrefix(devicePath, deviceRoot) {
t.Errorf("SECURITY: cgroup path() is outside cgroup mountpoint!")
if res != exp {
t.Errorf("tryDefaultCgroupRoot: want %q, got %q", exp, res)
}
}

View File

@ -19,12 +19,8 @@ func (s *HugetlbGroup) Name() string {
return "hugetlb"
}
func (s *HugetlbGroup) Apply(d *cgroupData) error {
_, err := d.join("hugetlb")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *HugetlbGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -37,12 +33,11 @@ func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *HugetlbGroup) Remove(d *cgroupData) error {
return removePath(d.path("hugetlb"))
}
func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
hugetlbStats := cgroups.HugetlbStats{}
if !cgroups.PathExists(path) {
return nil
}
for _, pageSize := range HugePageSizes {
usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".")
value, err := fscommon.GetCgroupParamUint(path, usage)

View File

@ -37,11 +37,8 @@ func (s *MemoryGroup) Name() string {
return "memory"
}
func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
path, err := d.path("memory")
if err != nil && !cgroups.IsNotFound(err) {
return err
} else if path == "" {
func (s *MemoryGroup) Apply(path string, d *cgroupData) (err error) {
if path == "" {
return nil
}
if memoryAssigned(d.config) {
@ -66,11 +63,7 @@ func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
// We need to join memory cgroup after set memory limits, because
// kmem.limit_in_bytes can only be set when the cgroup is empty.
_, err = d.join("memory")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
return join(path, d.pid)
}
func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
@ -165,10 +158,6 @@ func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *MemoryGroup) Remove(d *cgroupData) error {
return removePath(d.path("memory"))
}
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
// Set stats from memory.stat.
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))

View File

@ -16,10 +16,10 @@ func (s *NameGroup) Name() string {
return s.GroupName
}
func (s *NameGroup) Apply(d *cgroupData) error {
func (s *NameGroup) Apply(path string, d *cgroupData) error {
if s.Join {
// ignore errors if the named cgroup does not exist
d.join(s.GroupName)
join(path, d.pid)
}
return nil
}
@ -28,13 +28,6 @@ func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NameGroup) Remove(d *cgroupData) error {
if s.Join {
removePath(d.path(s.GroupName))
}
return nil
}
func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -17,12 +17,8 @@ func (s *NetClsGroup) Name() string {
return "net_cls"
}
func (s *NetClsGroup) Apply(d *cgroupData) error {
_, err := d.join("net_cls")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *NetClsGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -35,10 +31,6 @@ func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NetClsGroup) Remove(d *cgroupData) error {
return removePath(d.path("net_cls"))
}
func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -15,12 +15,8 @@ func (s *NetPrioGroup) Name() string {
return "net_prio"
}
func (s *NetPrioGroup) Apply(d *cgroupData) error {
_, err := d.join("net_prio")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *NetPrioGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -33,10 +29,6 @@ func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NetPrioGroup) Remove(d *cgroupData) error {
return removePath(d.path("net_prio"))
}
func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -14,22 +14,14 @@ func (s *PerfEventGroup) Name() string {
return "perf_event"
}
func (s *PerfEventGroup) Apply(d *cgroupData) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *PerfEventGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *PerfEventGroup) Remove(d *cgroupData) error {
return removePath(d.path("perf_event"))
}
func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -19,12 +19,8 @@ func (s *PidsGroup) Name() string {
return "pids"
}
func (s *PidsGroup) Apply(d *cgroupData) error {
_, err := d.join("pids")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
func (s *PidsGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -44,11 +40,10 @@ func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *PidsGroup) Remove(d *cgroupData) error {
return removePath(d.path("pids"))
}
func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error {
if !cgroups.PathExists(path) {
return nil
}
current, err := fscommon.GetCgroupParamUint(path, "pids.current")
if err != nil {
return fmt.Errorf("failed to parse pids.current - %s", err)

View File

@ -37,6 +37,9 @@ func canSkipEBPFError(cgroup *configs.Cgroup) bool {
}
func setDevices(dirPath string, cgroup *configs.Cgroup) error {
if cgroup.SkipDevices {
return nil
}
// XXX: This is currently a white-list (but all callers pass a blacklist of
// devices). This is bad for a whole variety of reasons, but will need
// to be fixed with co-ordinated effort with downstreams.

View File

@ -4,14 +4,12 @@ package fs2
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
type manager struct {
@ -157,45 +155,8 @@ func (m *manager) Freeze(state configs.FreezerState) error {
return nil
}
func rmdir(path string) error {
err := unix.Rmdir(path)
if err == nil || err == unix.ENOENT {
return nil
}
return &os.PathError{Op: "rmdir", Path: path, Err: err}
}
// removeCgroupPath aims to remove cgroup path recursively
// Because there may be subcgroups in it.
func removeCgroupPath(path string) error {
// try the fast path first
if err := rmdir(path); err == nil {
return nil
}
infos, err := ioutil.ReadDir(path)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return err
}
for _, info := range infos {
if info.IsDir() {
// We should remove subcgroups dir first
if err = removeCgroupPath(filepath.Join(path, info.Name())); err != nil {
break
}
}
}
if err == nil {
err = rmdir(path)
}
return err
}
func (m *manager) Destroy() error {
return removeCgroupPath(m.dirPath)
return cgroups.RemovePath(m.dirPath)
}
func (m *manager) Path(_ string) string {

View File

@ -27,6 +27,9 @@ var (
versionOnce sync.Once
version int
versionErr error
isRunningSystemdOnce sync.Once
isRunningSystemd bool
)
// NOTE: This function comes from package github.com/coreos/go-systemd/util
@ -37,11 +40,11 @@ var (
// checks whether /run/systemd/system/ exists and is a directory.
// http://www.freedesktop.org/software/systemd/man/sd_booted.html
func IsRunningSystemd() bool {
isRunningSystemdOnce.Do(func() {
fi, err := os.Lstat("/run/systemd/system")
if err != nil {
return false
}
return fi.IsDir()
isRunningSystemd = err == nil && fi.IsDir()
})
return isRunningSystemd
}
// systemd represents slice hierarchy using `-`, so we need to follow suit when

View File

@ -41,18 +41,7 @@ type subsystem interface {
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
type subsystemSet []subsystem
func (s subsystemSet) Get(name string) (subsystem, error) {
for _, ss := range s {
if ss.Name() == name {
return ss, nil
}
}
return nil, errSubsystemDoesNotExist
}
var legacySubsystems = subsystemSet{
var legacySubsystems = []subsystem{
&fs.CpusetGroup{},
&fs.DevicesGroup{},
&fs.MemoryGroup{},
@ -222,11 +211,15 @@ func (m *legacyManager) Destroy() error {
return err
}
unitName := getUnitName(m.cgroups)
if err := stopUnit(dbusConnection, unitName); err != nil {
stopErr := stopUnit(dbusConnection, unitName)
// Both on success and on error, cleanup all the cgroups we are aware of.
// Some of them were created directly by Apply() and are not managed by systemd.
if err := cgroups.RemovePaths(m.paths); err != nil {
return err
}
m.paths = make(map[string]string)
return nil
return stopErr
}
func (m *legacyManager) Path(subsys string) string {
@ -319,10 +312,7 @@ func (m *legacyManager) Freeze(state configs.FreezerState) error {
}
prevState := m.cgroups.Resources.Freezer
m.cgroups.Resources.Freezer = state
freezer, err := legacySubsystems.Get("freezer")
if err != nil {
return err
}
freezer := &fs.FreezerGroup{}
err = freezer.Set(path, m.cgroups)
if err != nil {
m.cgroups.Resources.Freezer = prevState
@ -351,9 +341,9 @@ func (m *legacyManager) GetStats() (*cgroups.Stats, error) {
m.mu.Lock()
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.paths {
sys, err := legacySubsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
for _, sys := range legacySubsystems {
path := m.paths[sys.Name()]
if path == "" {
continue
}
if err := sys.GetStats(path, stats); err != nil {
@ -379,9 +369,17 @@ func (m *legacyManager) Set(container *configs.Config) error {
return err
}
// We have to freeze the container while systemd sets the cgroup settings.
// The reason for this is that systemd's application of DeviceAllow rules
// is done disruptively, resulting in spurrious errors to common devices
// (unlike our fs driver, they will happily write deny-all rules to running
// containers). So we freeze the container to avoid them hitting the cgroup
// error. But if the freezer cgroup isn't supported, we just warn about it.
targetFreezerState := configs.Undefined
if !m.cgroups.SkipDevices {
// Figure out the current freezer state, so we can revert to it after we
// temporarily freeze the container.
targetFreezerState, err := m.GetFreezerState()
targetFreezerState, err = m.GetFreezerState()
if err != nil {
return err
}
@ -389,15 +387,10 @@ func (m *legacyManager) Set(container *configs.Config) error {
targetFreezerState = configs.Thawed
}
// We have to freeze the container while systemd sets the cgroup settings.
// The reason for this is that systemd's application of DeviceAllow rules
// is done disruptively, resulting in spurrious errors to common devices
// (unlike our fs driver, they will happily write deny-all rules to running
// containers). So we freeze the container to avoid them hitting the cgroup
// error. But if the freezer cgroup isn't supported, we just warn about it.
if err := m.Freeze(configs.Frozen); err != nil {
logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
}
}
if err := dbusConnection.SetUnitProperties(getUnitName(container.Cgroups), true, properties...); err != nil {
_ = m.Freeze(targetFreezerState)
@ -458,11 +451,8 @@ func (m *legacyManager) GetFreezerState() (configs.FreezerState, error) {
if err != nil && !cgroups.IsNotFound(err) {
return configs.Undefined, err
}
freezer, err := legacySubsystems.Get("freezer")
if err != nil {
return configs.Undefined, err
}
return freezer.(*fs.FreezerGroup).GetState(path)
freezer := &fs.FreezerGroup{}
return freezer.GetState(path)
}
func (m *legacyManager) Exists() bool {

View File

@ -298,9 +298,17 @@ func (m *unifiedManager) Set(container *configs.Config) error {
return err
}
// We have to freeze the container while systemd sets the cgroup settings.
// The reason for this is that systemd's application of DeviceAllow rules
// is done disruptively, resulting in spurrious errors to common devices
// (unlike our fs driver, they will happily write deny-all rules to running
// containers). So we freeze the container to avoid them hitting the cgroup
// error. But if the freezer cgroup isn't supported, we just warn about it.
targetFreezerState := configs.Undefined
if !m.cgroups.SkipDevices {
// Figure out the current freezer state, so we can revert to it after we
// temporarily freeze the container.
targetFreezerState, err := m.GetFreezerState()
targetFreezerState, err = m.GetFreezerState()
if err != nil {
return err
}
@ -308,15 +316,10 @@ func (m *unifiedManager) Set(container *configs.Config) error {
targetFreezerState = configs.Thawed
}
// We have to freeze the container while systemd sets the cgroup settings.
// The reason for this is that systemd's application of DeviceAllow rules
// is done disruptively, resulting in spurrious errors to common devices
// (unlike our fs driver, they will happily write deny-all rules to running
// containers). So we freeze the container to avoid them hitting the cgroup
// error. But if the freezer cgroup isn't supported, we just warn about it.
if err := m.Freeze(configs.Frozen); err != nil {
logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
}
}
if err := dbusConnection.SetUnitProperties(getUnitName(m.cgroups), true, properties...); err != nil {
_ = m.Freeze(targetFreezerState)

View File

@ -16,6 +16,7 @@ import (
"time"
units "github.com/docker/go-units"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@ -207,20 +208,66 @@ func EnterPid(cgroupPaths map[string]string, pid int) error {
return nil
}
func rmdir(path string) error {
err := unix.Rmdir(path)
if err == nil || err == unix.ENOENT {
return nil
}
return &os.PathError{Op: "rmdir", Path: path, Err: err}
}
// RemovePath aims to remove cgroup path. It does so recursively,
// by removing any subdirectories (sub-cgroups) first.
func RemovePath(path string) error {
// try the fast path first
if err := rmdir(path); err == nil {
return nil
}
infos, err := ioutil.ReadDir(path)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return err
}
for _, info := range infos {
if info.IsDir() {
// We should remove subcgroups dir first
if err = RemovePath(filepath.Join(path, info.Name())); err != nil {
break
}
}
}
if err == nil {
err = rmdir(path)
}
return err
}
// RemovePaths iterates over the provided paths removing them.
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
func RemovePaths(paths map[string]string) (err error) {
const retries = 5
delay := 10 * time.Millisecond
for i := 0; i < 5; i++ {
for i := 0; i < retries; i++ {
if i != 0 {
time.Sleep(delay)
delay *= 2
}
for s, p := range paths {
os.RemoveAll(p)
// TODO: here probably should be logging
if err := RemovePath(p); err != nil {
// do not log intermediate iterations
switch i {
case 0:
logrus.WithError(err).Warnf("Failed to remove cgroup (will retry)")
case retries - 1:
logrus.WithError(err).Error("Failed to remove cgroup")
}
}
_, err := os.Stat(p)
// We need this strange way of checking cgroups existence because
// RemoveAll almost always returns error, even on already removed
@ -230,6 +277,7 @@ func RemovePaths(paths map[string]string) (err error) {
}
}
if len(paths) == 0 {
paths = make(map[string]string)
return nil
}
}

View File

@ -8,6 +8,10 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
securejoin "github.com/cyphar/filepath-securejoin"
"golang.org/x/sys/unix"
)
// Code in this source file are specific to cgroup v1,
@ -15,6 +19,7 @@ import (
const (
CgroupNamePrefix = "name="
defaultPrefix = "/sys/fs/cgroup"
)
var (
@ -43,11 +48,59 @@ func IsNotFound(err error) bool {
return ok
}
func tryDefaultPath(cgroupPath, subsystem string) string {
if !strings.HasPrefix(defaultPrefix, cgroupPath) {
return ""
}
// remove possible prefix
subsystem = strings.TrimPrefix(subsystem, CgroupNamePrefix)
// Make sure we're still under defaultPrefix, and resolve
// a possible symlink (like cpu -> cpu,cpuacct).
path, err := securejoin.SecureJoin(defaultPrefix, subsystem)
if err != nil {
return ""
}
// (1) path should be a directory.
st, err := os.Lstat(path)
if err != nil || !st.IsDir() {
return ""
}
// (2) path should be a mount point.
pst, err := os.Lstat(filepath.Dir(path))
if err != nil {
return ""
}
if st.Sys().(*syscall.Stat_t).Dev == pst.Sys().(*syscall.Stat_t).Dev {
// parent dir has the same dev -- path is not a mount point
return ""
}
// (3) path should have 'cgroup' fs type.
fst := unix.Statfs_t{}
err = unix.Statfs(path, &fst)
if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
return ""
}
return path
}
// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) {
if IsCgroup2UnifiedMode() {
return "", errUnified
}
// Avoid parsing mountinfo by trying the default path first, if possible.
if path := tryDefaultPath(cgroupPath, subsystem); path != "" {
return path, nil
}
mnt, _, err := FindCgroupMountpointAndRoot(cgroupPath, subsystem)
return mnt, err
}
@ -57,9 +110,7 @@ func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string,
return "", "", errUnified
}
// We are not using mount.GetMounts() because it's super-inefficient,
// parsing it directly sped up x10 times because of not using Sscanf.
// It was one of two major performance drawbacks in container start.
// Avoid parsing mountinfo by checking if subsystem is valid/available.
if !isSubsystemAvailable(subsystem) {
return "", "", NewNotFoundError(subsystem)
}

View File

@ -126,4 +126,11 @@ type Resources struct {
// CpuWeight sets a proportional bandwidth limit.
CpuWeight uint64 `json:"cpu_weight"`
// SkipDevices allows to skip configuring device permissions.
// Used by e.g. kubelet while creating a parent cgroup (kubepods)
// common for many containers.
//
// NOTE it is impossible to start a container which has this flag set.
SkipDevices bool `json:"skip_devices"`
}

View File

@ -239,15 +239,6 @@ const (
Poststop = "poststop"
)
// TODO move this to runtime-spec
// See: https://github.com/opencontainers/runtime-spec/pull/1046
const (
Creating = "creating"
Created = "created"
Running = "running"
Stopped = "stopped"
)
type Capabilities struct {
// Bounding is the set of capabilities checked by the kernel.
Bounding []string

View File

@ -1,20 +1,15 @@
package configs
import (
"errors"
"fmt"
"os"
"strconv"
"golang.org/x/sys/unix"
)
const (
Wildcard = -1
)
// TODO Windows: This can be factored out in the future
type Device struct {
DeviceRule
@ -173,10 +168,3 @@ func (d *DeviceRule) CgroupString() string {
}
return fmt.Sprintf("%c %s:%s %s", d.Type, major, minor, d.Permissions)
}
func (d *DeviceRule) Mkdev() (uint64, error) {
if d.Major == Wildcard || d.Minor == Wildcard {
return 0, errors.New("cannot mkdev() device with wildcards")
}
return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil
}

View File

@ -0,0 +1,16 @@
// +build !windows
package configs
import (
"errors"
"golang.org/x/sys/unix"
)
func (d *DeviceRule) Mkdev() (uint64, error) {
if d.Major == Wildcard || d.Minor == Wildcard {
return 0, errors.New("cannot mkdev() device with wildcards")
}
return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil
}

View File

@ -0,0 +1,5 @@
package configs
func (d *DeviceRule) Mkdev() (uint64, error) {
return 0, nil
}

View File

@ -251,6 +251,9 @@ func (c *linuxContainer) Set(config configs.Config) error {
func (c *linuxContainer) Start(process *Process) error {
c.m.Lock()
defer c.m.Unlock()
if c.config.Cgroups.Resources.SkipDevices {
return newGenericError(errors.New("can't start container with SkipDevices set"), ConfigInvalid)
}
if process.Init {
if err := c.createExecFifo(); err != nil {
return err
@ -761,6 +764,7 @@ func (c *linuxContainer) checkCriuVersion(minVersion int) error {
}
criu := criu.MakeCriu()
criu.SetCriuPath(c.criuPath)
var err error
c.criuVersion, err = criu.GetCriuVersion()
if err != nil {
@ -833,6 +837,79 @@ func (c *linuxContainer) handleCriuConfigurationFile(rpcOpts *criurpc.CriuOpts)
}
}
func (c *linuxContainer) criuSupportsExtNS(t configs.NamespaceType) bool {
var minVersion int
switch t {
case configs.NEWNET:
// CRIU supports different external namespace with different released CRIU versions.
// For network namespaces to work we need at least criu 3.11.0 => 31100.
minVersion = 31100
case configs.NEWPID:
// For PID namespaces criu 31500 is needed.
minVersion = 31500
default:
return false
}
return c.checkCriuVersion(minVersion) == nil
}
func criuNsToKey(t configs.NamespaceType) string {
return "extRoot" + strings.Title(configs.NsName(t)) + "NS"
}
func (c *linuxContainer) handleCheckpointingExternalNamespaces(rpcOpts *criurpc.CriuOpts, t configs.NamespaceType) error {
if !c.criuSupportsExtNS(t) {
return nil
}
nsPath := c.config.Namespaces.PathOf(t)
if nsPath == "" {
return nil
}
// CRIU expects the information about an external namespace
// like this: --external <TYPE>[<inode>]:<key>
// This <key> is always 'extRoot<TYPE>NS'.
var ns unix.Stat_t
if err := unix.Stat(nsPath, &ns); err != nil {
return err
}
criuExternal := fmt.Sprintf("%s[%d]:%s", configs.NsName(t), ns.Ino, criuNsToKey(t))
rpcOpts.External = append(rpcOpts.External, criuExternal)
return nil
}
func (c *linuxContainer) handleRestoringExternalNamespaces(rpcOpts *criurpc.CriuOpts, extraFiles *[]*os.File, t configs.NamespaceType) error {
if !c.criuSupportsExtNS(t) {
return nil
}
nsPath := c.config.Namespaces.PathOf(t)
if nsPath == "" {
return nil
}
// CRIU wants the information about an existing namespace
// like this: --inherit-fd fd[<fd>]:<key>
// The <key> needs to be the same as during checkpointing.
// We are always using 'extRoot<TYPE>NS' as the key in this.
nsFd, err := os.Open(nsPath)
if err != nil {
logrus.Errorf("If a specific network namespace is defined it must exist: %s", err)
return fmt.Errorf("Requested network namespace %v does not exist", nsPath)
}
inheritFd := &criurpc.InheritFd{
Key: proto.String(criuNsToKey(t)),
// The offset of four is necessary because 0, 1, 2 and 3 are
// already used by stdin, stdout, stderr, 'criu swrk' socket.
Fd: proto.Int32(int32(4 + len(*extraFiles))),
}
rpcOpts.InheritFd = append(rpcOpts.InheritFd, inheritFd)
// All open FDs need to be transferred to CRIU via extraFiles
*extraFiles = append(*extraFiles, nsFd)
return nil
}
func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
c.m.Lock()
defer c.m.Unlock()
@ -906,25 +983,13 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
// will expect that the namespace exists during restore.
// This basically means that CRIU will ignore the namespace
// and expect to be setup correctly.
nsPath := c.config.Namespaces.PathOf(configs.NEWNET)
if nsPath != "" {
// For this to work we need at least criu 3.11.0 => 31100.
// As there was already a successful version check we will
// not error out if it fails. runc will just behave as it used
// to do and ignore external network namespaces.
err := c.checkCriuVersion(31100)
if err == nil {
// CRIU expects the information about an external namespace
// like this: --external net[<inode>]:<key>
// This <key> is always 'extRootNetNS'.
var netns unix.Stat_t
err = unix.Stat(nsPath, &netns)
if err != nil {
if err := c.handleCheckpointingExternalNamespaces(&rpcOpts, configs.NEWNET); err != nil {
return err
}
criuExternal := fmt.Sprintf("net[%d]:extRootNetNS", netns.Ino)
rpcOpts.External = append(rpcOpts.External, criuExternal)
}
// Same for possible external PID namespaces
if err := c.handleCheckpointingExternalNamespaces(&rpcOpts, configs.NEWPID); err != nil {
return err
}
// CRIU can use cgroup freezer; when rpcOpts.FreezeCgroup
@ -1248,33 +1313,13 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
// Same as during checkpointing. If the container has a specific network namespace
// assigned to it, this now expects that the checkpoint will be restored in a
// already created network namespace.
nsPath := c.config.Namespaces.PathOf(configs.NEWNET)
if nsPath != "" {
// For this to work we need at least criu 3.11.0 => 31100.
// As there was already a successful version check we will
// not error out if it fails. runc will just behave as it used
// to do and ignore external network namespaces.
err := c.checkCriuVersion(31100)
if err == nil {
// CRIU wants the information about an existing network namespace
// like this: --inherit-fd fd[<fd>]:<key>
// The <key> needs to be the same as during checkpointing.
// We are always using 'extRootNetNS' as the key in this.
netns, err := os.Open(nsPath)
if err != nil {
logrus.Errorf("If a specific network namespace is defined it must exist: %s", err)
return fmt.Errorf("Requested network namespace %v does not exist", nsPath)
}
defer netns.Close()
inheritFd := new(criurpc.InheritFd)
inheritFd.Key = proto.String("extRootNetNS")
// The offset of four is necessary because 0, 1, 2 and 3 is already
// used by stdin, stdout, stderr, 'criu swrk' socket.
inheritFd.Fd = proto.Int32(int32(4 + len(extraFiles)))
req.Opts.InheritFd = append(req.Opts.InheritFd, inheritFd)
// All open FDs need to be transferred to CRIU via extraFiles
extraFiles = append(extraFiles, netns)
if err := c.handleRestoringExternalNamespaces(req.Opts, &extraFiles, configs.NEWNET); err != nil {
return err
}
// Same for PID namespaces.
if err := c.handleRestoringExternalNamespaces(req.Opts, &extraFiles, configs.NEWPID); err != nil {
return err
}
// This will modify the rootfs of the container in the same way runc
@ -1342,7 +1387,14 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
req.Opts.InheritFd = append(req.Opts.InheritFd, inheritFd)
}
}
return c.criuSwrk(process, req, criuOpts, extraFiles)
err = c.criuSwrk(process, req, criuOpts, extraFiles)
// Now that CRIU is done let's close all opened FDs CRIU needed.
for _, fd := range extraFiles {
fd.Close()
}
return err
}
func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
@ -1860,7 +1912,7 @@ func (c *linuxContainer) currentOCIState() (*specs.State, error) {
if err != nil {
return nil, err
}
state.Status = status.String()
state.Status = specs.ContainerState(status.String())
if status != Stopped {
if c.initProcess != nil {
state.Pid = c.initProcess.pid()

View File

@ -1,14 +1,6 @@
package libcontainer
// cgroup restoring strategy provided by criu
type cgMode uint32
const (
CRIU_CG_MODE_SOFT cgMode = 3 + iota // restore cgroup properties if only dir created by criu
CRIU_CG_MODE_FULL // always restore all cgroups and their properties
CRIU_CG_MODE_STRICT // restore all, requiring them to not present in the system
CRIU_CG_MODE_DEFAULT // the same as CRIU_CG_MODE_SOFT
)
import criu "github.com/checkpoint-restore/go-criu/v4/rpc"
type CriuPageServerInfo struct {
Address string // IP address of CRIU page server
@ -32,7 +24,7 @@ type CriuOpts struct {
PreDump bool // call criu predump to perform iterative checkpoint
PageServer CriuPageServerInfo // allow to dump to criu page server
VethPairs []VethPairName // pass the veth to criu when restore
ManageCgroupsMode cgMode // dump or restore cgroup mode
ManageCgroupsMode criu.CriuCgMode // dump or restore cgroup mode
EmptyNs uint32 // don't c/r properties for namespace from this mask
AutoDedup bool // auto deduplication for incremental dumps
LazyPages bool // restore memory pages lazily using userfaultfd

View File

@ -37,12 +37,12 @@ func DeviceFromPath(path, permissions string) (*configs.Device, error) {
major = unix.Major(devNumber)
minor = unix.Minor(devNumber)
)
switch {
case mode&unix.S_IFBLK == unix.S_IFBLK:
switch mode & unix.S_IFMT {
case unix.S_IFBLK:
devType = configs.BlockDevice
case mode&unix.S_IFCHR == unix.S_IFCHR:
case unix.S_IFCHR:
devType = configs.CharDevice
case mode&unix.S_IFIFO == unix.S_IFIFO:
case unix.S_IFIFO:
devType = configs.FifoDevice
default:
return nil, ErrNotADevice
@ -104,6 +104,9 @@ func GetDevices(path string) ([]*configs.Device, error) {
}
return nil, err
}
if device.Type == configs.FifoDevice {
continue
}
out = append(out, device)
}
return out, nil

View File

@ -2,12 +2,19 @@ package devices
import (
"errors"
"io/ioutil"
"os"
"testing"
"github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/sys/unix"
)
func cleanupTest() {
unixLstat = unix.Lstat
ioutilReadDir = ioutil.ReadDir
}
func TestDeviceFromPathLstatFailure(t *testing.T) {
testError := errors.New("test error")
@ -15,6 +22,7 @@ func TestDeviceFromPathLstatFailure(t *testing.T) {
unixLstat = func(path string, stat *unix.Stat_t) error {
return testError
}
defer cleanupTest()
_, err := DeviceFromPath("", "")
if err != testError {
@ -29,6 +37,7 @@ func TestHostDevicesIoutilReadDirFailure(t *testing.T) {
ioutilReadDir = func(dirname string) ([]os.FileInfo, error) {
return nil, testError
}
defer cleanupTest()
_, err := HostDevices()
if err != testError {
@ -55,9 +64,41 @@ func TestHostDevicesIoutilReadDirDeepFailure(t *testing.T) {
return []os.FileInfo{fi}, nil
}
defer cleanupTest()
_, err := HostDevices()
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}
func TestHostDevicesAllValid(t *testing.T) {
devices, err := HostDevices()
if err != nil {
t.Fatalf("failed to get host devices: %v", err)
}
for _, device := range devices {
// Devices can't have major number 0.
if device.Major == 0 {
t.Errorf("device entry %+v has zero major number", device)
}
// Devices should only have file modes that correspond to their type.
var expectedType os.FileMode
switch device.Type {
case configs.BlockDevice:
expectedType = unix.S_IFBLK
case configs.CharDevice:
expectedType = unix.S_IFCHR
case configs.FifoDevice:
t.Logf("fifo devices shouldn't show up from HostDevices")
fallthrough
default:
t.Errorf("device entry %+v has unexpected type %v", device, device.Type)
}
gotType := device.FileMode & unix.S_IFMT
if expectedType != gotType {
t.Errorf("device entry %+v has mismatched types (expected %#x, got %#x)", device, expectedType, gotType)
}
}
}

View File

@ -184,6 +184,9 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error {
return err
}
// After we return from here, we don't need the console anymore.
defer pty.Close()
if config.ConsoleHeight != 0 && config.ConsoleWidth != 0 {
err = pty.Resize(console.WinSize{
Height: config.ConsoleHeight,
@ -195,9 +198,6 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error {
}
}
// After we return from here, we don't need the console anymore.
defer pty.Close()
// Mount the console inside our rootfs.
if mount {
if err := mountConsole(slavePath); err != nil {

View File

@ -207,9 +207,6 @@ func TestEnter(t *testing.T) {
if testing.Short() {
return
}
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
rootfs, err := newRootfs()
ok(t, err)
@ -516,9 +513,6 @@ func testFreeze(t *testing.T, systemd bool) {
if testing.Short() {
return
}
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
rootfs, err := newRootfs()
ok(t, err)
@ -574,7 +568,7 @@ func testCpuShares(t *testing.T, systemd bool) {
return
}
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
t.Skip("cgroup v2 does not support CpuShares")
}
rootfs, err := newRootfs()
@ -608,9 +602,6 @@ func testPids(t *testing.T, systemd bool) {
if testing.Short() {
return
}
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
}
rootfs, err := newRootfs()
ok(t, err)
@ -695,7 +686,7 @@ func testRunWithKernelMemory(t *testing.T, systemd bool) {
return
}
if cgroups.IsCgroup2UnifiedMode() {
t.Skip("cgroup v2 is not supported")
t.Skip("cgroup v2 does not support kernel memory limit")
}
rootfs, err := newRootfs()

View File

@ -117,7 +117,7 @@ func newTemplateConfig(rootfs string) *configs.Config {
{Type: configs.NEWNET},
}),
Cgroups: &configs.Cgroup{
Path: "integration/test",
Path: "/sys/fs/cgroup/",
Resources: &configs.Resources{
MemorySwappiness: nil,
Devices: allowedDevices,

View File

@ -19,7 +19,7 @@ import (
"github.com/opencontainers/runc/libcontainer/logs"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@ -400,7 +400,7 @@ func (p *initProcess) start() (retErr error) {
}
// initProcessStartTime hasn't been set yet.
s.Pid = p.cmd.Process.Pid
s.Status = configs.Creating
s.Status = specs.StateCreating
hooks := p.config.Config.Hooks
if err := hooks[configs.Prestart].RunHooks(s); err != nil {
@ -433,7 +433,7 @@ func (p *initProcess) start() (retErr error) {
}
// initProcessStartTime hasn't been set yet.
s.Pid = p.cmd.Process.Pid
s.Status = configs.Creating
s.Status = specs.StateCreating
hooks := p.config.Config.Hooks
if err := hooks[configs.Prestart].RunHooks(s); err != nil {

View File

@ -20,6 +20,7 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system"
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"golang.org/x/sys/unix"
@ -100,7 +101,7 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
s := iConfig.SpecState
s.Pid = unix.Getpid()
s.Status = configs.Creating
s.Status = specs.StateCreating
if err := iConfig.Config.Hooks[configs.CreateContainer].RunHooks(s); err != nil {
return err
}
@ -110,7 +111,7 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
} else if config.Namespaces.Contains(configs.NEWNS) {
err = pivotRoot(config.Rootfs)
} else {
err = chroot(config.Rootfs)
err = chroot()
}
if err != nil {
return newSystemErrorWithCause(err, "jailing process inside rootfs")
@ -836,10 +837,10 @@ func msMoveRoot(rootfs string) error {
if err := unix.Mount(rootfs, "/", "", unix.MS_MOVE, ""); err != nil {
return err
}
return chroot(rootfs)
return chroot()
}
func chroot(rootfs string) error {
func chroot() error {
if err := unix.Chroot("."); err != nil {
return err
}

View File

@ -13,9 +13,9 @@ import (
"github.com/opencontainers/runc/libcontainer/keys"
"github.com/opencontainers/runc/libcontainer/seccomp"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@ -210,7 +210,7 @@ func (l *linuxStandardInit) Init() error {
s := l.config.SpecState
s.Pid = unix.Getpid()
s.Status = configs.Created
s.Status = specs.StateCreated
if err := l.config.Config.Hooks[configs.StartContainer].RunHooks(s); err != nil {
return err
}

View File

@ -8,7 +8,7 @@ import (
"path/filepath"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@ -70,7 +70,7 @@ func runPoststopHooks(c *linuxContainer) error {
if err != nil {
return err
}
s.Status = configs.Stopped
s.Status = specs.StateStopped
if err := hooks[configs.Poststop].RunHooks(s); err != nil {
return err

View File

@ -60,7 +60,7 @@ type Group struct {
// groupFromOS converts an os/user.(*Group) to local Group
//
// (This does not include Pass, Shell or Gecos)
// (This does not include Pass or List)
func groupFromOS(g *user.Group) (Group, error) {
newGroup := Group{
Name: g.Name,

2
ps.go
View File

@ -57,7 +57,7 @@ var psCommand = cli.Command{
}
// [1:] is to remove command name, ex:
// context.Args(): [containet_id ps_arg1 ps_arg2 ...]
// context.Args(): [container_id ps_arg1 ps_arg2 ...]
// psArgs: [ps_arg1 ps_arg2 ...]
//
psArgs := context.Args()[1:]

View File

@ -3,7 +3,7 @@
load helpers
function teardown() {
rm -f $BATS_TMPDIR/runc-cgroups-integration-test.json
rm -f "$BATS_TMPDIR"/runc-cgroups-integration-test.json
teardown_running_container test_cgroups_kmem
teardown_running_container test_cgroups_permissions
teardown_busybox
@ -21,10 +21,10 @@ function setup() {
set_cgroups_path "$BUSYBOX_BUNDLE"
# Set some initial known values
update_config '.linux.resources.memory |= {"kernel": 16777216, "kernelTCP": 11534336}' ${BUSYBOX_BUNDLE}
update_config '.linux.resources.memory |= {"kernel": 16777216, "kernelTCP": 11534336}' "${BUSYBOX_BUNDLE}"
# run a detached busybox to work with
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_kmem
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_kmem
[ "$status" -eq 0 ]
check_cgroup_value "memory.kmem.limit_in_bytes" 16777216
@ -48,14 +48,14 @@ function setup() {
set_cgroups_path "$BUSYBOX_BUNDLE"
# run a detached busybox to work with
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_kmem
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_kmem
[ "$status" -eq 0 ]
# update kernel memory limit
runc update test_cgroups_kmem --kernel-memory 50331648
# Since kernel 4.6, we can update kernel memory without initialization
# because it's accounted by default.
if [ "$KERNEL_MAJOR" -lt 4 ] || [ "$KERNEL_MAJOR" -eq 4 -a "$KERNEL_MINOR" -le 5 ]; then
if [[ "$KERNEL_MAJOR" -lt 4 || ( "$KERNEL_MAJOR" -eq 4 && "$KERNEL_MINOR" -le 5 ) ]]; then
[ ! "$status" -eq 0 ]
else
[ "$status" -eq 0 ]
@ -64,7 +64,7 @@ function setup() {
}
@test "runc create (no limits + no cgrouppath + no permission) succeeds" {
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
[ "$status" -eq 0 ]
}
@ -76,7 +76,7 @@ function setup() {
set_cgroups_path "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
[ "$status" -eq 1 ]
[[ ${lines[1]} == *"permission denied"* ]]
}
@ -89,7 +89,7 @@ function setup() {
set_resources_limit "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
[ "$status" -eq 1 ]
[[ ${lines[1]} == *"rootless needs no limits + no cgrouppath when no permission is granted for cgroups"* ]] || [[ ${lines[1]} == *"cannot set pids limit: container could not join or create cgroup"* ]]
}
@ -100,13 +100,14 @@ function setup() {
set_cgroups_path "$BUSYBOX_BUNDLE"
set_resources_limit "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
[ "$status" -eq 0 ]
if [ "$CGROUP_UNIFIED" != "no" ]; then
if [ -n "${RUNC_USE_SYSTEMD}" ] ; then
if [ $(id -u) = "0" ]; then
if [ "$(id -u)" = "0" ]; then
check_cgroup_value "cgroup.controllers" "$(cat /sys/fs/cgroup/machine.slice/cgroup.controllers)"
else
# shellcheck disable=SC2046
check_cgroup_value "cgroup.controllers" "$(cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers)"
fi
else
@ -121,7 +122,7 @@ function setup() {
set_cgroups_path "$BUSYBOX_BUNDLE"
set_resources_limit "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
[ "$status" -eq 0 ]
runc exec test_cgroups_permissions echo "cgroups_exec"
@ -135,7 +136,7 @@ function setup() {
set_cgroups_path "$BUSYBOX_BUNDLE"
set_cgroup_mount_writable "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_group
runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_group
[ "$status" -eq 0 ]
runc exec test_cgroups_group cat /sys/fs/cgroup/cgroup.controllers
@ -169,6 +170,7 @@ function setup() {
[[ ${lines[0]} == "0::/foo" ]]
# teardown: remove "/foo"
# shellcheck disable=SC2016
runc exec test_cgroups_group sh -uxc 'echo -memory > /sys/fs/cgroup/cgroup.subtree_control; for f in $(cat /sys/fs/cgroup/foo/cgroup.procs); do echo $f > /sys/fs/cgroup/cgroup.procs; done; rmdir /sys/fs/cgroup/foo'
runc exec test_cgroups_group test ! -d /sys/fs/cgroup/foo
[ "$status" -eq 0 ]

View File

@ -12,53 +12,68 @@ function setup() {
function teardown() {
teardown_busybox
local pid fd
for pid in "${PIDS_TO_KILL[@]}"; do
kill -9 "$pid" || true
done
PIDS_TO_KILL=()
for fd in "${FDS_TO_CLOSE[@]}"; do
exec {fd}>&-
done
FDS_TO_CLOSE=()
}
function setup_pipes() {
# The changes to 'terminal' are needed for running in detached mode
# shellcheck disable=SC2016
update_config ' (.. | select(.terminal? != null)) .terminal |= false
| (.. | select(.[]? == "sh")) += ["-c", "for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"]'
# Create two sets of pipes
# for stdout/stderr
exec 52<> <(:)
exec 50</proc/self/fd/52
exec 51>/proc/self/fd/52
exec 52>&-
exec {pipe}<> <(:)
exec {out_r}</proc/self/fd/$pipe
exec {out_w}>/proc/self/fd/$pipe
exec {pipe}>&-
# ... and stdin
exec 62<> <(:)
exec 60</proc/self/fd/62
exec 61>/proc/self/fd/62
exec 62>&-
exec {pipe}<> <(:)
exec {in_r}</proc/self/fd/$pipe
exec {in_w}>/proc/self/fd/$pipe
exec {pipe}>&-
# shellcheck disable=SC2206
FDS_TO_CLOSE=($in_r $in_w $out_r $out_w)
}
function check_pipes() {
echo Ping >&61
exec 61>&-
exec 51>&-
run cat <&50
echo Ping >&${in_w}
exec {in_w}>&-
exec {out_w}>&-
run cat <&${out_r}
[ "$status" -eq 0 ]
[[ "${output}" == *"ponG Ping"* ]]
}
function simple_cr() {
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
for i in `seq 2`; do
# shellcheck disable=SC2034
for i in $(seq 2); do
# checkpoint the running container
runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
cat ./work-dir/dump.log | grep -B 5 Error || true
grep -B 5 Error ./work-dir/dump.log || true
[ "$status" -eq 0 ]
# after checkpoint busybox is no longer running
testcontainer test_busybox checkpointed
# restore from checkpoint
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
cat ./work-dir/restore.log | grep -B 5 Error || true
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket "$CONSOLE_SOCKET" test_busybox
grep -B 5 Error ./work-dir/restore.log || true
[ "$status" -eq 0 ]
# busybox should be back up and running
@ -72,7 +87,7 @@ function simple_cr() {
@test "checkpoint and restore (cgroupns)" {
# cgroupv2 already enables cgroupns so this case was tested above already
requires cgroups_v1
requires cgroups_v1 cgroupns
# enable CGROUPNS
update_config '.linux.namespaces += [{"type": "cgroup"}]'
@ -84,8 +99,7 @@ function simple_cr() {
setup_pipes
# run busybox
__runc run -d test_busybox <&60 >&51 2>&51
[ $? -eq 0 ]
__runc run -d test_busybox <&${in_r} >&${out_w} 2>&${out_w}
testcontainer test_busybox running
@ -101,16 +115,16 @@ function simple_cr() {
mkdir image-dir
mkdir work-dir
runc --criu "$CRIU" checkpoint --parent-path ./parent-dir --work-path ./work-dir --image-path ./image-dir test_busybox
cat ./work-dir/dump.log | grep -B 5 Error || true
grep -B 5 Error ./work-dir/dump.log || true
[ "$status" -eq 0 ]
# after checkpoint busybox is no longer running
testcontainer test_busybox checkpointed
# restore from checkpoint
__runc --criu "$CRIU" restore -d --work-path ./work-dir --image-path ./image-dir test_busybox <&60 >&51 2>&51
ret=$?
cat ./work-dir/restore.log | grep -B 5 Error || true
ret=0
__runc --criu "$CRIU" restore -d --work-path ./work-dir --image-path ./image-dir test_busybox <&${in_r} >&${out_w} 2>&${out_w} || ret=$?
grep -B 5 Error ./work-dir/restore.log || true
[ $ret -eq 0 ]
# busybox should be back up and running
@ -125,22 +139,18 @@ function simple_cr() {
@test "checkpoint --lazy-pages and restore" {
# check if lazy-pages is supported
run ${CRIU} check --feature uffd-noncoop
run "${CRIU}" check --feature uffd-noncoop
if [ "$status" -eq 1 ]; then
skip "this criu does not support lazy migration"
fi
setup_pipes
# This should not be necessary: https://github.com/checkpoint-restore/criu/issues/575
update_config '(.. | select(.readonly? != null)) .readonly |= false'
# TCP port for lazy migration
port=27277
# run busybox
__runc run -d test_busybox <&60 >&51 2>&51
[ $? -eq 0 ]
__runc run -d test_busybox <&${in_r} >&${out_w} 2>&${out_w}
testcontainer test_busybox running
@ -150,16 +160,22 @@ function simple_cr() {
# For lazy migration we need to know when CRIU is ready to serve
# the memory pages via TCP.
exec 72<> <(:)
exec 70</proc/self/fd/72 71>/proc/self/fd/72
exec 72>&-
exec {pipe}<> <(:)
# shellcheck disable=SC2094
exec {lazy_r}</proc/self/fd/$pipe {lazy_w}>/proc/self/fd/$pipe
exec {pipe}>&-
# shellcheck disable=SC2206
FDS_TO_CLOSE+=($lazy_r $lazy_w)
__runc --criu "$CRIU" checkpoint --lazy-pages --page-server 0.0.0.0:${port} --status-fd 71 --work-path ./work-dir --image-path ./image-dir test_busybox &
__runc --criu "$CRIU" checkpoint --lazy-pages --page-server 0.0.0.0:${port} --status-fd ${lazy_w} --work-path ./work-dir --image-path ./image-dir test_busybox &
cpt_pid=$!
# shellcheck disable=SC2206
PIDS_TO_KILL=($cpt_pid)
# wait for lazy page server to be ready
out=$(timeout 2 dd if=/proc/self/fd/70 bs=1 count=1 2>/dev/null | od)
exec 71>&-
out=$(timeout 2 dd if=/proc/self/fd/${lazy_r} bs=1 count=1 2>/dev/null | od)
exec {lazy_w}>&-
# shellcheck disable=SC2116,SC2086
out=$(echo $out) # rm newlines
# show errors if there are any before we fail
grep -B5 Error ./work-dir/dump.log || true
@ -172,6 +188,8 @@ function simple_cr() {
# Start CRIU in lazy-daemon mode
${CRIU} lazy-pages --page-server --address 127.0.0.1 --port ${port} -D image-dir &
lp_pid=$!
# shellcheck disable=SC2206
PIDS_TO_KILL+=($lp_pid)
# Restore lazily from checkpoint.
# The restored container needs a different name as the checkpointed
@ -179,9 +197,9 @@ function simple_cr() {
# in time when the last page is lazily transferred to the destination.
# Killing the CRIU on the checkpoint side will let the container
# continue to run if the migration failed at some point.
__runc --criu "$CRIU" restore -d --work-path ./image-dir --image-path ./image-dir --lazy-pages test_busybox_restore <&60 >&51 2>&51
ret=$?
cat ./work-dir/restore.log | grep -B 5 Error || true
ret=0
__runc --criu "$CRIU" restore -d --work-path ./image-dir --image-path ./image-dir --lazy-pages test_busybox_restore <&${in_r} >&${out_w} 2>&${out_w} || ret=$?
grep -B 5 Error ./work-dir/restore.log || true
[ $ret -eq 0 ]
# busybox should be back up and running
@ -192,81 +210,78 @@ function simple_cr() {
[[ ${output} == "ok" ]]
wait $cpt_pid
[ $? -eq 0 ]
wait $lp_pid
[ $? -eq 0 ]
PIDS_TO_KILL=()
check_pipes
}
@test "checkpoint and restore in external network namespace" {
# check if external_net_ns is supported; only with criu 3.10++
run ${CRIU} check --feature external_net_ns
run "${CRIU}" check --feature external_net_ns
if [ "$status" -eq 1 ]; then
# this criu does not support external_net_ns; skip the test
skip "this criu does not support external network namespaces"
fi
# create a temporary name for the test network namespace
tmp=`mktemp`
rm -f $tmp
ns_name=`basename $tmp`
tmp=$(mktemp)
rm -f "$tmp"
ns_name=$(basename "$tmp")
# create network namespace
ip netns add $ns_name
ns_path=`ip netns add $ns_name 2>&1 | sed -e 's/.*"\(.*\)".*/\1/'`
ns_inode=`ls -iL $ns_path | awk '{ print $1 }'`
ip netns add "$ns_name"
ns_path=$(ip netns add "$ns_name" 2>&1 | sed -e 's/.*"\(.*\)".*/\1/')
# shellcheck disable=SC2012
ns_inode=$(ls -iL "$ns_path" | awk '{ print $1 }')
# tell runc which network namespace to use
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
for i in `seq 2`; do
# shellcheck disable=SC2034
for i in $(seq 2); do
# checkpoint the running container; this automatically tells CRIU to
# handle the network namespace defined in config.json as an external
runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
# if you are having problems getting criu to work uncomment the following dump:
#cat /run/opencontainer/containers/test_busybox/criu.work/dump.log
cat ./work-dir/dump.log | grep -B 5 Error || true
grep -B 5 Error ./work-dir/dump.log || true
[ "$status" -eq 0 ]
# after checkpoint busybox is no longer running
testcontainer test_busybox checkpointed
# restore from checkpoint; this should restore the container into the existing network namespace
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
ret=$?
cat ./work-dir/restore.log | grep -B 5 Error || true
[ "$ret" -eq 0 ]
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket "$CONSOLE_SOCKET" test_busybox
grep -B 5 Error ./work-dir/restore.log || true
[ "$status" -eq 0 ]
# busybox should be back up and running
testcontainer test_busybox running
# container should be running in same network namespace as before
pid=`__runc state test_busybox | jq '.pid'`
ns_inode_new=`readlink /proc/$pid/ns/net | sed -e 's/.*\[\(.*\)\]/\1/'`
pid=$(__runc state test_busybox | jq '.pid')
ns_inode_new=$(readlink /proc/"$pid"/ns/net | sed -e 's/.*\[\(.*\)\]/\1/')
echo "old network namespace inode $ns_inode"
echo "new network namespace inode $ns_inode_new"
[ "$ns_inode" -eq "$ns_inode_new" ]
done
ip netns del $ns_name
ip netns del "$ns_name"
}
@test "checkpoint and restore with container specific CRIU config" {
tmp=`mktemp /tmp/runc-criu-XXXXXX.conf`
tmp=$(mktemp /tmp/runc-criu-XXXXXX.conf)
# This is the file we write to /etc/criu/default.conf
tmplog1=`mktemp /tmp/runc-criu-log-XXXXXX.log`
unlink $tmplog1
tmplog1=`basename $tmplog1`
tmplog1=$(mktemp /tmp/runc-criu-log-XXXXXX.log)
unlink "$tmplog1"
tmplog1=$(basename "$tmplog1")
# That is the actual configuration file to be used
tmplog2=`mktemp /tmp/runc-criu-log-XXXXXX.log`
unlink $tmplog2
tmplog2=`basename $tmplog2`
tmplog2=$(mktemp /tmp/runc-criu-log-XXXXXX.log)
unlink "$tmplog2"
tmplog2=$(basename "$tmplog2")
# This adds the annotation 'org.criu.config' to set a container
# specific CRIU config file.
update_config '.annotations += {"org.criu.config": "'"$tmp"'"}'
@ -275,34 +290,34 @@ function simple_cr() {
mkdir -p /etc/criu
echo "log-file=$tmplog1" > /etc/criu/default.conf
# Make sure the RPC defined configuration file overwrites the previous
echo "log-file=$tmplog2" > $tmp
echo "log-file=$tmplog2" > "$tmp"
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
# checkpoint the running container
runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
cat ./work-dir/dump.log | grep -B 5 Error || true
grep -B 5 Error ./work-dir/dump.log || true
[ "$status" -eq 0 ]
! test -f ./work-dir/$tmplog1
test -f ./work-dir/$tmplog2
! test -f ./work-dir/"$tmplog1"
test -f ./work-dir/"$tmplog2"
# after checkpoint busybox is no longer running
testcontainer test_busybox checkpointed
test -f ./work-dir/$tmplog2 && unlink ./work-dir/$tmplog2
test -f ./work-dir/"$tmplog2" && unlink ./work-dir/"$tmplog2"
# restore from checkpoint
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
cat ./work-dir/restore.log | grep -B 5 Error || true
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket "$CONSOLE_SOCKET" test_busybox
grep -B 5 Error ./work-dir/restore.log || true
[ "$status" -eq 0 ]
! test -f ./work-dir/$tmplog1
test -f ./work-dir/$tmplog2
! test -f ./work-dir/"$tmplog1"
test -f ./work-dir/"$tmplog2"
# busybox should be back up and running
testcontainer test_busybox running
unlink $tmp
test -f ./work-dir/$tmplog2 && unlink ./work-dir/$tmplog2
unlink "$tmp"
test -f ./work-dir/"$tmplog2" && unlink ./work-dir/"$tmplog2"
}

View File

@ -12,7 +12,7 @@ function teardown() {
}
@test "runc create" {
runc create --console-socket $CONSOLE_SOCKET test_busybox
runc create --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
@ -25,7 +25,7 @@ function teardown() {
}
@test "runc create exec" {
runc create --console-socket $CONSOLE_SOCKET test_busybox
runc create --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
@ -43,7 +43,7 @@ function teardown() {
}
@test "runc create --pid-file" {
runc create --pid-file pid.txt --console-socket $CONSOLE_SOCKET test_busybox
runc create --pid-file pid.txt --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
@ -69,7 +69,7 @@ function teardown() {
run cd pid_file
[ "$status" -eq 0 ]
runc create --pid-file pid.txt -b $BUSYBOX_BUNDLE --console-socket $CONSOLE_SOCKET test_busybox
runc create --pid-file pid.txt -b "$BUSYBOX_BUNDLE" --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created

View File

@ -12,29 +12,29 @@ function teardown() {
}
@test "runc delete" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" testbusyboxdelete
[ "$status" -eq 0 ]
# check state
testcontainer test_busybox running
testcontainer testbusyboxdelete running
runc kill test_busybox KILL
runc kill testbusyboxdelete KILL
[ "$status" -eq 0 ]
# wait for busybox to be in the destroyed state
retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'"
retry 10 1 eval "__runc state testbusyboxdelete | grep -q 'stopped'"
# delete test_busybox
runc delete test_busybox
runc delete testbusyboxdelete
[ "$status" -eq 0 ]
runc state test_busybox
runc state testbusyboxdelete
[ "$status" -ne 0 ]
run find /sys/fs/cgroup -wholename '*testbusyboxdelete*' -type d
[ "$status" -eq 0 ]
[ "$output" = "" ] || fail "cgroup not cleaned up correctly: $output"
}
@test "runc delete --force" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -52,13 +52,67 @@ function teardown() {
[ "$status" -eq 0 ]
}
@test "runc delete --force in cgroupv1 with subcgroups" {
requires cgroups_v1 root cgroupns
set_cgroups_path "$BUSYBOX_BUNDLE"
set_cgroup_mount_writable "$BUSYBOX_BUNDLE"
# enable cgroupns
update_config '.linux.namespaces += [{"type": "cgroup"}]'
local subsystems="memory freezer"
for i in $(seq 1); do
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
__runc exec -d test_busybox sleep 1d
# find the pid of sleep
pid=$(__runc exec test_busybox ps -a | grep 1d | awk '{print $1}')
[[ ${pid} =~ [0-9]+ ]]
# create a sub-cgroup
cat <<EOF | runc exec test_busybox sh
set -e -u -x
for s in ${subsystems}; do
cd /sys/fs/cgroup/\$s
mkdir foo
cd foo
echo ${pid} > tasks
cat tasks
done
EOF
[ "$status" -eq 0 ]
[[ "$output" =~ [0-9]+ ]]
for s in ${subsystems}; do
name=CGROUP_${s^^}
eval path=\$${name}/foo
echo $path
[ -d ${path} ] || fail "test failed to create memory sub-cgroup"
done
runc delete --force test_busybox
runc state test_busybox
[ "$status" -ne 0 ]
run find /sys/fs/cgroup -wholename '*testbusyboxdelete*' -type d
[ "$status" -eq 0 ]
[ "$output" = "" ] || fail "cgroup not cleaned up correctly: $output"
done
}
@test "runc delete --force in cgroupv2 with subcgroups" {
requires cgroups_v2 root
set_cgroups_path "$BUSYBOX_BUNDLE"
set_cgroup_mount_writable "$BUSYBOX_BUNDLE"
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -82,12 +136,12 @@ function teardown() {
echo ${pid} > cgroup.threads
cat cgroup.threads
EOF
cat nest.sh | runc exec test_busybox sh
runc exec test_busybox sh < nest.sh
[ "$status" -eq 0 ]
[[ "$output" =~ [0-9]+ ]]
# check create subcgroups success
[ -d $CGROUP_PATH/foo ]
[ -d "$CGROUP_PATH"/foo ]
# force delete test_busybox
runc delete --force test_busybox
@ -96,5 +150,5 @@ EOF
[ "$status" -ne 0 ]
# check delete subcgroups success
[ ! -d $CGROUP_PATH/foo ]
[ ! -d "$CGROUP_PATH"/foo ]
}

View File

@ -17,7 +17,7 @@ function teardown() {
init_cgroup_paths
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# generate stats
@ -33,7 +33,7 @@ function teardown() {
init_cgroup_paths
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# spawn two sub processes (shells)
@ -61,7 +61,7 @@ function teardown() {
init_cgroup_paths
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# spawn two sub processes (shells)
@ -88,7 +88,7 @@ function teardown() {
init_cgroup_paths
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
#prove there is no carry over of events.log from a prior test
@ -118,10 +118,10 @@ function teardown() {
init_cgroup_paths
# we need the container to hit OOM, so disable swap
update_config '(.. | select(.resources? != null)) .resources.memory |= {"limit": 33554432, "swap": 33554432}' ${BUSYBOX_BUNDLE}
update_config '(.. | select(.resources? != null)) .resources.memory |= {"limit": 33554432, "swap": 33554432}' "${BUSYBOX_BUNDLE}"
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# spawn two sub processes (shells)
@ -131,6 +131,7 @@ function teardown() {
(__runc events test_busybox > events.log) &
(
retry 10 1 eval "grep -q 'test_busybox' events.log"
# shellcheck disable=SC2016
__runc exec -d test_busybox sh -c 'test=$(dd if=/dev/urandom ibs=5120k)'
retry 10 1 eval "grep -q 'oom' events.log"
__runc delete -f test_busybox

View File

@ -13,7 +13,7 @@ function teardown() {
@test "runc exec" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox echo Hello from exec
@ -24,7 +24,7 @@ function teardown() {
@test "runc exec --pid-file" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec --pid-file pid.txt test_busybox echo Hello from exec
@ -49,7 +49,7 @@ function teardown() {
[ "$status" -eq 0 ]
# run busybox detached
runc run -d -b $BUSYBOX_BUNDLE --console-socket $CONSOLE_SOCKET test_busybox
runc run -d -b "$BUSYBOX_BUNDLE" --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec --pid-file pid.txt test_busybox echo Hello from exec
@ -68,7 +68,7 @@ function teardown() {
@test "runc exec ls -la" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox ls -la
@ -80,7 +80,7 @@ function teardown() {
@test "runc exec ls -la with --cwd" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec --cwd /bin test_busybox pwd
@ -90,7 +90,7 @@ function teardown() {
@test "runc exec --env" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec --env RUNC_EXEC_TEST=true test_busybox env
@ -104,7 +104,7 @@ function teardown() {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec --user 1000:1000 test_busybox id
@ -117,7 +117,7 @@ function teardown() {
requires root
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
wait_for_container 15 1 test_busybox
@ -130,7 +130,7 @@ function teardown() {
@test "runc exec --preserve-fds" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
run bash -c "cat hello > preserve-fds.test; exec 3<preserve-fds.test; $RUNC ${RUNC_USE_SYSTEMD:+--systemd-cgroup} --log /proc/self/fd/2 --root $ROOT exec --preserve-fds=1 test_busybox cat /proc/self/fd/3"

View File

@ -286,6 +286,11 @@ function requires() {
skip_me=1
fi
;;
cgroupns)
if [ ! -e "/proc/self/ns/cgroup" ]; then
skip_me=1
fi
;;
cgroups_v1)
init_cgroup_paths
if [ "$CGROUP_UNIFIED" != "no" ]; then

View File

@ -9,16 +9,16 @@ HOOKLIBCC=librunc-hooks-create-container.so
LIBPATH="$DEBIAN_BUNDLE/rootfs/lib/"
function setup() {
umount $LIBPATH/$HOOKLIBCR.1.0.0 &> /dev/null || true
umount $LIBPATH/$HOOKLIBCC.1.0.0 &> /dev/null || true
umount "$LIBPATH"/$HOOKLIBCR.1.0.0 &> /dev/null || true
umount "$LIBPATH"/$HOOKLIBCC.1.0.0 &> /dev/null || true
teardown_debian
setup_debian
}
function teardown() {
umount $LIBPATH/$HOOKLIBCR.1.0.0 &> /dev/null || true
umount $LIBPATH/$HOOKLIBCC.1.0.0 &> /dev/null || true
umount "$LIBPATH"/$HOOKLIBCR.1.0.0 &> /dev/null || true
umount "$LIBPATH"/$HOOKLIBCC.1.0.0 &> /dev/null || true
rm -f $HOOKLIBCR.1.0.0 $HOOKLIBCC.1.0.0
teardown_debian
@ -39,7 +39,8 @@ function teardown() {
pid=\$(cat - | jq -r '.pid')
touch "$LIBPATH/$HOOKLIBCR.1.0.0"
nsenter -m \$ns -t \$pid mount --bind "$current_pwd/$HOOKLIBCR.1.0.0" "$LIBPATH/$HOOKLIBCR.1.0.0"
EOF)
EOF
)
create_container_hook="touch ./lib/$HOOKLIBCC.1.0.0 && mount --bind $current_pwd/$HOOKLIBCC.1.0.0 ./lib/$HOOKLIBCC.1.0.0"
@ -47,17 +48,15 @@ function teardown() {
.hooks |= . + {"createRuntime": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", $create_runtime_hook]}]} |
.hooks |= . + {"createContainer": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", $create_container_hook]}]} |
.hooks |= . + {"startContainer": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", "ldconfig"]}]} |
.process.args = ["/bin/sh", "-c", "ldconfig -p | grep librunc"]' $DEBIAN_BUNDLE/config.json)
.process.args = ["/bin/sh", "-c", "ldconfig -p | grep librunc"]' "$DEBIAN_BUNDLE"/config.json)
echo "${CONFIG}" > config.json
runc run test_debian
[ "$status" -eq 0 ]
echo "Checking create-runtime library"
echo $output | grep $HOOKLIBCR
[ "$?" -eq 0 ]
echo "$output" | grep $HOOKLIBCR
echo "Checking create-container library"
echo $output | grep $HOOKLIBCC
[ "$?" -eq 0 ]
echo "$output" | grep $HOOKLIBCC
}

View File

@ -14,7 +14,7 @@ function teardown() {
@test "kill detached busybox" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state

View File

@ -3,29 +3,29 @@
load helpers
function setup() {
teardown_running_container_inroot test_box1 $HELLO_BUNDLE
teardown_running_container_inroot test_box2 $HELLO_BUNDLE
teardown_running_container_inroot test_box3 $HELLO_BUNDLE
teardown_running_container_inroot test_box1 "$HELLO_BUNDLE"
teardown_running_container_inroot test_box2 "$HELLO_BUNDLE"
teardown_running_container_inroot test_box3 "$HELLO_BUNDLE"
teardown_busybox
setup_busybox
}
function teardown() {
teardown_running_container_inroot test_box1 $HELLO_BUNDLE
teardown_running_container_inroot test_box2 $HELLO_BUNDLE
teardown_running_container_inroot test_box3 $HELLO_BUNDLE
teardown_running_container_inroot test_box1 "$HELLO_BUNDLE"
teardown_running_container_inroot test_box2 "$HELLO_BUNDLE"
teardown_running_container_inroot test_box3 "$HELLO_BUNDLE"
teardown_busybox
}
@test "list" {
# run a few busyboxes detached
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_box1
ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_box1
[ "$status" -eq 0 ]
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_box2
ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_box2
[ "$status" -eq 0 ]
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_box3
ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_box3
[ "$status" -eq 0 ]
ROOT=$HELLO_BUNDLE runc list

View File

@ -20,7 +20,7 @@ function teardown() {
@test "mask paths [file]" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox cat /testfile
@ -38,7 +38,7 @@ function teardown() {
@test "mask paths [directory]" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox ls /testdir

View File

@ -17,5 +17,5 @@ function teardown() {
runc run test_bind_mount
[ "$status" -eq 0 ]
[[ "${lines[0]}" =~ '/tmp/bind/config.json' ]]
[[ "${lines[0]}" == *'/tmp/bind/config.json'* ]]
}

View File

@ -20,7 +20,7 @@ function teardown() {
requires cgroups_freezer
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
@ -49,7 +49,7 @@ function teardown() {
requires cgroups_freezer
# run test_busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running

View File

@ -16,7 +16,7 @@ function teardown() {
requires root
# start busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -33,7 +33,7 @@ function teardown() {
requires root
# start busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -49,7 +49,7 @@ function teardown() {
requires root
# start busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -67,7 +67,7 @@ function teardown() {
set_cgroups_path "$BUSYBOX_BUNDLE"
# start busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state

View File

@ -3,23 +3,23 @@
load helpers
function setup() {
teardown_running_container_inroot test_dotbox $HELLO_BUNDLE
teardown_running_container_inroot test_dotbox "$HELLO_BUNDLE"
teardown_busybox
setup_busybox
}
function teardown() {
teardown_running_container_inroot test_dotbox $HELLO_BUNDLE
teardown_running_container_inroot test_dotbox "$HELLO_BUNDLE"
teardown_busybox
}
@test "global --root" {
# run busybox detached using $HELLO_BUNDLE for state
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_dotbox
ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_dotbox
[ "$status" -eq 0 ]
# run busybox detached in default root
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
runc state test_busybox

View File

@ -4,18 +4,15 @@ load helpers
function setup() {
# initial cleanup in case a prior test exited and did not cleanup
cd "$INTEGRATION_ROOT"
run rm -f -r "$HELLO_BUNDLE"
rm -rf "$HELLO_BUNDLE"
# setup hello-world for spec generation testing
run mkdir "$HELLO_BUNDLE"
run mkdir "$HELLO_BUNDLE"/rootfs
run tar -C "$HELLO_BUNDLE"/rootfs -xf "$HELLO_IMAGE"
mkdir -p "$HELLO_BUNDLE"/rootfs
tar -C "$HELLO_BUNDLE"/rootfs -xf "$HELLO_IMAGE"
}
function teardown() {
cd "$INTEGRATION_ROOT"
run rm -f -r "$HELLO_BUNDLE"
rm -rf "$HELLO_BUNDLE"
}
@test "spec generation cwd" {
@ -58,7 +55,7 @@ function teardown() {
[ -e "$HELLO_BUNDLE"/config.json ]
# change the default args parameter from sh to hello
update_config '(.. | select(.? == "sh")) |= "/hello"' $HELLO_BUNDLE
update_config '(.. | select(.? == "sh")) |= "/hello"' "$HELLO_BUNDLE"
# ensure the generated spec works by running hello-world
runc run --bundle "$HELLO_BUNDLE" test_hello
@ -72,12 +69,12 @@ function teardown() {
run git clone https://github.com/opencontainers/runtime-spec.git src/runtime-spec
[ "$status" -eq 0 ]
SPEC_VERSION=$(grep 'github.com/opencontainers/runtime-spec' ${TESTDIR}/../../go.mod | cut -d ' ' -f 2)
SPEC_VERSION=$(grep 'github.com/opencontainers/runtime-spec' "${TESTDIR}"/../../go.mod | cut -d ' ' -f 2)
# Will look like this when not pinned to spesific tag: "v0.0.0-20190207185410-29686dbc5559", otherwise "v1.0.0"
SPEC_COMMIT=$(cut -d "-" -f 3 <<< $SPEC_VERSION)
SPEC_COMMIT=$(cut -d "-" -f 3 <<< "$SPEC_VERSION")
SPEC_REF=$([[ -z "$SPEC_COMMIT" ]] && echo $SPEC_VERSION || echo $SPEC_COMMIT)
SPEC_REF=$([[ -z "$SPEC_COMMIT" ]] && echo "$SPEC_VERSION" || echo "$SPEC_COMMIT")
run bash -c "cd src/runtime-spec && git reset --hard ${SPEC_REF}"

View File

@ -12,7 +12,7 @@ function teardown() {
}
@test "runc start" {
runc create --console-socket $CONSOLE_SOCKET test_busybox
runc create --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created

View File

@ -13,7 +13,7 @@ function teardown() {
@test "runc run detached" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -30,7 +30,7 @@ function teardown() {
| (.. | select(.gid? == 0)) .gid |= 100'
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -39,7 +39,7 @@ function teardown() {
@test "runc run detached --pid-file" {
# run busybox detached
runc run --pid-file pid.txt -d --console-socket $CONSOLE_SOCKET test_busybox
runc run --pid-file pid.txt -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -61,7 +61,7 @@ function teardown() {
[ "$status" -eq 0 ]
# run busybox detached
runc run --pid-file pid.txt -d -b $BUSYBOX_BUNDLE --console-socket $CONSOLE_SOCKET test_busybox
runc run --pid-file pid.txt -d -b "$BUSYBOX_BUNDLE" --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state

View File

@ -16,7 +16,7 @@ function teardown() {
[ "$status" -ne 0 ]
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state
@ -44,7 +44,7 @@ function teardown() {
[ "$status" -ne 0 ]
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# check state

View File

@ -13,6 +13,7 @@ function teardown() {
@test "runc run [tty ptsname]" {
# Replace sh script with readlink.
# shellcheck disable=SC2016
update_config '(.. | select(.[]? == "sh")) += ["-c", "for file in /proc/self/fd/[012]; do readlink $file; done"]'
# run busybox
@ -29,6 +30,7 @@ function teardown() {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
# Replace sh script with stat.
# shellcheck disable=SC2016
update_config '(.. | select(.[]? == "sh")) += ["-c", "stat -c %u:%g $(tty) | tr : \\\\n"]'
# run busybox
@ -46,6 +48,7 @@ function teardown() {
# replace "uid": 0 with "uid": 1000
# and do a similar thing for gid.
# Replace sh script with stat.
# shellcheck disable=SC2016
update_config ' (.. | select(.uid? == 0)) .uid |= 1000
| (.. | select(.gid? == 0)) .gid |= 100
| (.. | select(.[]? == "sh")) += ["-c", "stat -c %u:%g $(tty) | tr : \\\\n"]'
@ -60,13 +63,14 @@ function teardown() {
@test "runc exec [tty ptsname]" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# make sure we're running
testcontainer test_busybox running
# run the exec
# shellcheck disable=SC2016
runc exec -t test_busybox sh -c 'for file in /proc/self/fd/[012]; do readlink $file; done'
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ /dev/pts/+ ]]
@ -80,13 +84,14 @@ function teardown() {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# make sure we're running
testcontainer test_busybox running
# run the exec
# shellcheck disable=SC2016
runc exec -t test_busybox sh -c 'stat -c %u:%g $(tty) | tr : \\n'
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ 0 ]]
@ -99,17 +104,19 @@ function teardown() {
# replace "uid": 0 with "uid": 1000
# and do a similar thing for gid.
# shellcheck disable=SC2016
update_config ' (.. | select(.uid? == 0)) .uid |= 1000
| (.. | select(.gid? == 0)) .gid |= 100'
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# make sure we're running
testcontainer test_busybox running
# run the exec
# shellcheck disable=SC2016
runc exec -t test_busybox sh -c 'stat -c %u:%g $(tty) | tr : \\n'
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ 1000 ]]
@ -121,7 +128,7 @@ function teardown() {
update_config '(.. | select(.readonly? != null)) .readonly |= false'
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# make sure we're running
@ -145,14 +152,14 @@ EOF
)
# run the exec
runc exec -t --pid-file pid.txt -d --console-socket $CONSOLE_SOCKET -p <( echo $tty_info_with_consize_size ) test_busybox
runc exec -t --pid-file pid.txt -d --console-socket "$CONSOLE_SOCKET" -p <( echo "$tty_info_with_consize_size" ) test_busybox
[ "$status" -eq 0 ]
# check the pid was generated
[ -e pid.txt ]
#wait user process to finish
timeout 1 tail --pid=$(head -n 1 pid.txt) -f /dev/null
timeout 1 tail --pid="$(head -n 1 pid.txt)" -f /dev/null
tty_info=$( cat <<EOF
{
@ -166,7 +173,7 @@ EOF
)
# run the exec
runc exec -t -p <( echo $tty_info ) test_busybox
runc exec -t -p <( echo "$tty_info" ) test_busybox
[ "$status" -eq 0 ]
# test tty width and height against original process.json

View File

@ -3,7 +3,7 @@
load helpers
function teardown() {
rm -f $BATS_TMPDIR/runc-cgroups-integration-test.json
rm -f "$BATS_TMPDIR"/runc-cgroups-integration-test.json
teardown_running_container test_update
teardown_running_container test_update_rt
teardown_busybox
@ -18,7 +18,7 @@ function setup() {
# Set some initial known values
update_config ' .linux.resources.memory |= {"limit": 33554432, "reservation": 25165824}
| .linux.resources.cpu |= {"shares": 100, "quota": 500000, "period": 1000000, "cpus": "0"}
| .linux.resources.pids |= {"limit": 20}' ${BUSYBOX_BUNDLE}
| .linux.resources.pids |= {"limit": 20}' "${BUSYBOX_BUNDLE}"
}
# Tests whatever limits are (more or less) common between cgroup
@ -29,7 +29,7 @@ function setup() {
file="/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers"
# NOTE: delegation of cpuset requires systemd >= 244 (Fedora >= 32, Ubuntu >= 20.04).
for f in memory pids cpuset; do
if grep -qwv $f $file; then
if grep -qwv $f "$file"; then
skip "$f is not enabled in $file"
fi
done
@ -37,7 +37,7 @@ function setup() {
init_cgroup_paths
# run a few busyboxes detached
runc run -d --console-socket $CONSOLE_SOCKET test_update
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
# Set a few variables to make the code below work for both v1 and v2
@ -68,7 +68,7 @@ function setup() {
esac
SD_UNLIMITED="infinity"
SD_VERSION=$(systemctl --version | awk '{print $2; exit}')
if [ $SD_VERSION -lt 227 ]; then
if [ "$SD_VERSION" -lt 227 ]; then
SD_UNLIMITED="18446744073709551615"
fi
@ -89,7 +89,7 @@ function setup() {
# update cpuset if supported (i.e. we're running on a multicore cpu)
cpu_count=$(grep -c '^processor' /proc/cpuinfo)
if [ $cpu_count -gt 1 ]; then
if [ "$cpu_count" -gt 1 ]; then
runc update test_update --cpuset-cpus "1"
[ "$status" -eq 0 ]
check_cgroup_value "cpuset.cpus" 1
@ -208,7 +208,7 @@ EOF
check_systemd_value "TasksMax" 10
# reset to initial test value via json file
cat << EOF > $BATS_TMPDIR/runc-cgroups-integration-test.json
cat << EOF > "$BATS_TMPDIR"/runc-cgroups-integration-test.json
{
"memory": {
"limit": 33554432,
@ -226,7 +226,7 @@ EOF
}
EOF
runc update -r $BATS_TMPDIR/runc-cgroups-integration-test.json test_update
runc update -r "$BATS_TMPDIR"/runc-cgroups-integration-test.json test_update
[ "$status" -eq 0 ]
check_cgroup_value "cpuset.cpus" 0
@ -252,10 +252,10 @@ function check_cpu_quota() {
check_cgroup_value "cpu.max" "$quota $period"
else
check_cgroup_value "cpu.cfs_quota_us" $quota
check_cgroup_value "cpu.cfs_period_us" $period
check_cgroup_value "cpu.cfs_period_us" "$period"
fi
# systemd values are the same for v1 and v2
check_systemd_value "CPUQuotaPerSecUSec" $sd_quota
check_systemd_value "CPUQuotaPerSecUSec" "$sd_quota"
# CPUQuotaPeriodUSec requires systemd >= v242
[ "$(systemd_version)" -lt 242 ] && return
@ -276,8 +276,8 @@ function check_cpu_shares() {
check_cgroup_value "cpu.weight" $weight
check_systemd_value "CPUWeight" $weight
else
check_cgroup_value "cpu.shares" $shares
check_systemd_value "CPUShares" $shares
check_cgroup_value "cpu.shares" "$shares"
check_systemd_value "CPUShares" "$shares"
fi
}
@ -285,7 +285,7 @@ function check_cpu_shares() {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
# run a few busyboxes detached
runc run -d --console-socket $CONSOLE_SOCKET test_update
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
# check that initial values were properly set
@ -338,7 +338,7 @@ EOF
check_cpu_quota -1 100000 "infinity"
# reset to initial test value via json file
cat << EOF > $BATS_TMPDIR/runc-cgroups-integration-test.json
cat << EOF > "$BATS_TMPDIR"/runc-cgroups-integration-test.json
{
"cpu": {
"shares": 100,
@ -348,7 +348,7 @@ EOF
}
EOF
runc update -r $BATS_TMPDIR/runc-cgroups-integration-test.json test_update
runc update -r "$BATS_TMPDIR"/runc-cgroups-integration-test.json test_update
[ "$status" -eq 0 ]
check_cpu_quota 500000 1000000 "500ms"
check_cpu_shares 100
@ -357,9 +357,9 @@ EOF
@test "set cpu period with no quota" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
update_config '.linux.resources.cpu |= { "period": 1000000 }' ${BUSYBOX_BUNDLE}
update_config '.linux.resources.cpu |= { "period": 1000000 }' "${BUSYBOX_BUNDLE}"
runc run -d --console-socket $CONSOLE_SOCKET test_update
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
check_cpu_quota -1 1000000 "infinity"
@ -368,9 +368,9 @@ EOF
@test "set cpu quota with no period" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
update_config '.linux.resources.cpu |= { "quota": 5000 }' ${BUSYBOX_BUNDLE}
update_config '.linux.resources.cpu |= { "quota": 5000 }' "${BUSYBOX_BUNDLE}"
runc run -d --console-socket $CONSOLE_SOCKET test_update
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
check_cpu_quota 5000 100000 "50ms"
}
@ -378,9 +378,9 @@ EOF
@test "update cpu period with no previous period/quota set" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
update_config '.linux.resources.cpu |= {}' ${BUSYBOX_BUNDLE}
update_config '.linux.resources.cpu |= {}' "${BUSYBOX_BUNDLE}"
runc run -d --console-socket $CONSOLE_SOCKET test_update
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
# update the period alone, no old values were set
@ -392,9 +392,9 @@ EOF
@test "update cpu quota with no previous period/quota set" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
update_config '.linux.resources.cpu |= {}' ${BUSYBOX_BUNDLE}
update_config '.linux.resources.cpu |= {}' "${BUSYBOX_BUNDLE}"
runc run -d --console-socket $CONSOLE_SOCKET test_update
runc run -d --console-socket "$CONSOLE_SOCKET" test_update
[ "$status" -eq 0 ]
# update the quota alone, no old values were set
@ -419,10 +419,11 @@ EOF
#
# TODO: support systemd
mkdir -p "$CGROUP_CPU"
local root_period=$(cat "${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us")
local root_runtime=$(cat "${CGROUP_CPU_BASE_PATH}/cpu.rt_runtime_us")
local root_period root_runtime
root_period=$(cat "${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us")
root_runtime=$(cat "${CGROUP_CPU_BASE_PATH}/cpu.rt_runtime_us")
# the following IFS magic sets dirs=("runc-cgroups-integration-test" "test-cgroup")
IFS='/' read -r -a dirs <<< $(echo ${CGROUP_CPU} | sed -e s@^${CGROUP_CPU_BASE_PATH}/@@)
IFS='/' read -r -a dirs <<< "${CGROUP_CPU//${CGROUP_CPU_BASE_PATH}/}"
for (( i = 0; i < ${#dirs[@]}; i++ )); do
local target="$CGROUP_CPU_BASE_PATH"
for (( j = 0; j <= i; j++ )); do
@ -437,7 +438,7 @@ EOF
done
# run a detached busybox
runc run -d --console-socket $CONSOLE_SOCKET test_update_rt
runc run -d --console-socket "$CONSOLE_SOCKET" test_update_rt
[ "$status" -eq 0 ]
runc update -r - test_update_rt <<EOF

View File

@ -47,14 +47,13 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
cgroupManager = libcontainer.RootlessCgroupfs
}
if context.GlobalBool("systemd-cgroup") {
if systemd.IsRunningSystemd() {
if !systemd.IsRunningSystemd() {
return nil, errors.New("systemd cgroup flag passed, but systemd support for managing cgroups is not available")
}
cgroupManager = libcontainer.SystemdCgroups
if rootlessCg {
cgroupManager = libcontainer.RootlessSystemdCgroups
}
} else {
return nil, errors.New("systemd cgroup flag passed, but systemd support for managing cgroups is not available")
}
}
intelRdtManager := libcontainer.IntelRdtFs
@ -423,13 +422,11 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
}
if notifySocket != nil {
err := notifySocket.setupSocketDirectory()
if err != nil {
if err := notifySocket.setupSocketDirectory(); err != nil {
return -1, err
}
if action == CT_ACT_RUN {
err := notifySocket.bindSocket()
if err != nil {
if err := notifySocket.bindSocket(); err != nil {
return -1, err
}
}

View File

@ -1,5 +1,5 @@
language: go
sudo: required
dist: bionic
os:
- linux
go:
@ -13,6 +13,7 @@ env:
install:
- sudo apt-get update
- sudo apt-get install -y libprotobuf-dev libprotobuf-c0-dev protobuf-c-compiler protobuf-compiler python-protobuf libnl-3-dev libnet-dev libcap-dev
- make install.tools
- go get github.com/checkpoint-restore/go-criu
- git clone --single-branch -b ${CRIU_BRANCH} https://github.com/checkpoint-restore/criu.git
- cd criu; make
@ -20,6 +21,8 @@ install:
- cd ..
script:
# This builds the code without running the tests.
- make build phaul test/test test/phaul test/piggie
- make lint build phaul test/test test/phaul test/piggie
# Run actual test as root as it uses CRIU.
- sudo make test phaul-test
# This builds crit-go
- make -C crit-go/magic-gen lint build magicgen test

View File

@ -12,7 +12,7 @@ endif
all: build test phaul phaul-test
lint:
@golint . test phaul
@golint -set_exit_status . test phaul
build:
@$(GO) build -v

View File

@ -1,20 +1,2 @@
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=

View File

@ -16,11 +16,20 @@ import (
type Criu struct {
swrkCmd *exec.Cmd
swrkSk *os.File
swrkPath string
}
// MakeCriu returns the Criu object required for most operations
func MakeCriu() *Criu {
return &Criu{}
return &Criu{
swrkPath: "criu",
}
}
// SetCriuPath allows setting the path to the CRIU binary
// if it is in a non standard location
func (c *Criu) SetCriuPath(path string) {
c.swrkPath = path
}
// Prepare sets up everything for the RPC communication to CRIU
@ -36,7 +45,7 @@ func (c *Criu) Prepare() error {
defer srv.Close()
args := []string{"swrk", strconv.Itoa(fds[1])}
cmd := exec.Command("criu", args...)
cmd := exec.Command(c.swrkPath, args...)
err = cmd.Start()
if err != nil {

15
vendor/github.com/cilium/ebpf/abi.go generated vendored
View File

@ -3,14 +3,13 @@ package ebpf
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"syscall"
"github.com/cilium/ebpf/internal"
"golang.org/x/xerrors"
)
// MapABI are the attributes of a Map which are available across all supported kernels.
@ -35,7 +34,7 @@ func newMapABIFromSpec(spec *MapSpec) *MapABI {
func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) {
info, err := bpfGetMapInfoByFD(fd)
if err != nil {
if xerrors.Is(err, syscall.EINVAL) {
if errors.Is(err, syscall.EINVAL) {
abi, err := newMapABIFromProc(fd)
return "", abi, err
}
@ -98,7 +97,7 @@ func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI {
func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) {
info, err := bpfGetProgInfoByFD(fd)
if err != nil {
if xerrors.Is(err, syscall.EINVAL) {
if errors.Is(err, syscall.EINVAL) {
return newProgramABIFromProc(fd)
}
@ -127,7 +126,7 @@ func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) {
"prog_type": &abi.Type,
"prog_tag": &name,
})
if xerrors.Is(err, errMissingFields) {
if errors.Is(err, errMissingFields) {
return "", nil, &internal.UnsupportedFeatureError{
Name: "reading ABI from /proc/self/fdinfo",
MinimumVersion: internal.Version{4, 11, 0},
@ -153,12 +152,12 @@ func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
defer fh.Close()
if err := scanFdInfoReader(fh, fields); err != nil {
return xerrors.Errorf("%s: %w", fh.Name(), err)
return fmt.Errorf("%s: %w", fh.Name(), err)
}
return nil
}
var errMissingFields = xerrors.New("missing fields")
var errMissingFields = errors.New("missing fields")
func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
var (
@ -179,7 +178,7 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
}
if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 {
return xerrors.Errorf("can't parse field %s: %v", name, err)
return fmt.Errorf("can't parse field %s: %v", name, err)
}
scanned++

View File

@ -2,13 +2,11 @@ package asm
import (
"encoding/binary"
"errors"
"fmt"
"github.com/cilium/ebpf/internal"
"io"
"math"
"strings"
"golang.org/x/xerrors"
)
// InstructionSize is the size of a BPF instruction in bytes
@ -40,10 +38,12 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
}
ins.OpCode = bi.OpCode
ins.Dst = bi.Registers.Dst()
ins.Src = bi.Registers.Src()
ins.Offset = bi.Offset
ins.Constant = int64(bi.Constant)
ins.Dst, ins.Src, err = bi.Registers.Unmarshal(bo)
if err != nil {
return 0, fmt.Errorf("can't unmarshal registers: %s", err)
}
if !bi.OpCode.isDWordLoad() {
return InstructionSize, nil
@ -52,10 +52,10 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
var bi2 bpfInstruction
if err := binary.Read(r, bo, &bi2); err != nil {
// No Wrap, to avoid io.EOF clash
return 0, xerrors.New("64bit immediate is missing second half")
return 0, errors.New("64bit immediate is missing second half")
}
if bi2.OpCode != 0 || bi2.Offset != 0 || bi2.Registers != 0 {
return 0, xerrors.New("64bit immediate has non-zero fields")
return 0, errors.New("64bit immediate has non-zero fields")
}
ins.Constant = int64(uint64(uint32(bi2.Constant))<<32 | uint64(uint32(bi.Constant)))
@ -65,7 +65,7 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
// Marshal encodes a BPF instruction.
func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
if ins.OpCode == InvalidOpCode {
return 0, xerrors.New("invalid opcode")
return 0, errors.New("invalid opcode")
}
isDWordLoad := ins.OpCode.isDWordLoad()
@ -76,9 +76,14 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
cons = int32(uint32(ins.Constant))
}
regs, err := newBPFRegisters(ins.Dst, ins.Src, bo)
if err != nil {
return 0, fmt.Errorf("can't marshal registers: %s", err)
}
bpfi := bpfInstruction{
ins.OpCode,
newBPFRegisters(ins.Dst, ins.Src),
regs,
ins.Offset,
cons,
}
@ -107,11 +112,11 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
// Returns an error if the instruction doesn't load a map.
func (ins *Instruction) RewriteMapPtr(fd int) error {
if !ins.OpCode.isDWordLoad() {
return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode)
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}
if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
return xerrors.New("not a load from a map")
return errors.New("not a load from a map")
}
// Preserve the offset value for direct map loads.
@ -130,11 +135,11 @@ func (ins *Instruction) mapPtr() uint32 {
// Returns an error if the instruction is not a direct load.
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
if !ins.OpCode.isDWordLoad() {
return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode)
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}
if ins.Src != PseudoMapValue {
return xerrors.New("not a direct load from a map")
return errors.New("not a direct load from a map")
}
fd := uint64(ins.Constant) & math.MaxUint32
@ -245,7 +250,7 @@ func (insns Instructions) String() string {
// Returns an error if the symbol isn't used, see IsUnreferencedSymbol.
func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
if symbol == "" {
return xerrors.New("empty symbol")
return errors.New("empty symbol")
}
found := false
@ -280,7 +285,7 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
}
if _, ok := offsets[ins.Symbol]; ok {
return nil, xerrors.Errorf("duplicate symbol %s", ins.Symbol)
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
}
offsets[ins.Symbol] = i
@ -318,7 +323,7 @@ func (insns Instructions) marshalledOffsets() (map[string]int, error) {
}
if _, ok := symbols[ins.Symbol]; ok {
return nil, xerrors.Errorf("duplicate symbol %s", ins.Symbol)
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
}
symbols[ins.Symbol] = currentPos
@ -399,7 +404,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
// Rewrite bpf to bpf call
offset, ok := absoluteOffsets[ins.Reference]
if !ok {
return xerrors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
}
ins.Constant = int64(offset - num - 1)
@ -408,7 +413,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
// Rewrite jump to label
offset, ok := absoluteOffsets[ins.Reference]
if !ok {
return xerrors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
}
ins.Offset = int16(offset - num - 1)
@ -416,7 +421,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
n, err := ins.Marshal(w, bo)
if err != nil {
return xerrors.Errorf("instruction %d: %w", i, err)
return fmt.Errorf("instruction %d: %w", i, err)
}
num += int(n / InstructionSize)
@ -433,27 +438,25 @@ type bpfInstruction struct {
type bpfRegisters uint8
func newBPFRegisters(dst, src Register) bpfRegisters {
if internal.NativeEndian == binary.LittleEndian {
return bpfRegisters((src << 4) | (dst & 0xF))
} else {
return bpfRegisters((dst << 4) | (src & 0xF))
func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) {
switch bo {
case binary.LittleEndian:
return bpfRegisters((src << 4) | (dst & 0xF)), nil
case binary.BigEndian:
return bpfRegisters((dst << 4) | (src & 0xF)), nil
default:
return 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
}
func (r bpfRegisters) Dst() Register {
if internal.NativeEndian == binary.LittleEndian {
return Register(r & 0xF)
}else {
return Register(r >> 4)
}
}
func (r bpfRegisters) Src() Register {
if internal.NativeEndian == binary.LittleEndian {
return Register(r >> 4)
} else {
return Register(r & 0xf)
func (r bpfRegisters) Unmarshal(bo binary.ByteOrder) (dst, src Register, err error) {
switch bo {
case binary.LittleEndian:
return Register(r & 0xF), Register(r >> 4), nil
case binary.BigEndian:
return Register(r >> 4), Register(r & 0xf), nil
default:
return 0, 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
}

View File

@ -225,7 +225,7 @@ func (op OpCode) String() string {
}
default:
fmt.Fprintf(&f, "%#x", op)
fmt.Fprintf(&f, "OpCode(%#x)", uint8(op))
}
return f.String()

View File

@ -1,12 +1,13 @@
package ebpf
import (
"errors"
"fmt"
"math"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"golang.org/x/xerrors"
)
// CollectionOptions control loading a collection into the kernel.
@ -64,12 +65,12 @@ func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
// Not all programs need to use the map
default:
return xerrors.Errorf("program %s: %w", progName, err)
return fmt.Errorf("program %s: %w", progName, err)
}
}
if !seen {
return xerrors.Errorf("map %s not referenced by any programs", symbol)
return fmt.Errorf("map %s not referenced by any programs", symbol)
}
// Prevent NewCollection from creating rewritten maps
@ -96,21 +97,21 @@ func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
rodata := cs.Maps[".rodata"]
if rodata == nil {
return xerrors.New("missing .rodata section")
return errors.New("missing .rodata section")
}
if rodata.BTF == nil {
return xerrors.New(".rodata section has no BTF")
return errors.New(".rodata section has no BTF")
}
if n := len(rodata.Contents); n != 1 {
return xerrors.Errorf("expected one key in .rodata, found %d", n)
return fmt.Errorf("expected one key in .rodata, found %d", n)
}
kv := rodata.Contents[0]
value, ok := kv.Value.([]byte)
if !ok {
return xerrors.Errorf("first value in .rodata is %T not []byte", kv.Value)
return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value)
}
buf := make([]byte, len(value))
@ -185,14 +186,14 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
var handle *btf.Handle
if mapSpec.BTF != nil {
handle, err = loadBTF(btf.MapSpec(mapSpec.BTF))
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, err
}
}
m, err := newMapWithBTF(mapSpec, handle)
if err != nil {
return nil, xerrors.Errorf("map %s: %w", mapName, err)
return nil, fmt.Errorf("map %s: %w", mapName, err)
}
maps[mapName] = m
}
@ -216,29 +217,29 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
m := maps[ins.Reference]
if m == nil {
return nil, xerrors.Errorf("program %s: missing map %s", progName, ins.Reference)
return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference)
}
fd := m.FD()
if fd < 0 {
return nil, xerrors.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
}
if err := ins.RewriteMapPtr(m.FD()); err != nil {
return nil, xerrors.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
}
}
var handle *btf.Handle
if progSpec.BTF != nil {
handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF))
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, err
}
}
prog, err := newProgramWithBTF(progSpec, handle, opts.Programs)
if err != nil {
return nil, xerrors.Errorf("program %s: %w", progName, err)
return nil, fmt.Errorf("program %s: %w", progName, err)
}
progs[progName] = prog
}

View File

@ -4,6 +4,8 @@ import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
@ -13,8 +15,6 @@ import (
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
"golang.org/x/xerrors"
)
type elfCode struct {
@ -35,7 +35,7 @@ func LoadCollectionSpec(file string) (*CollectionSpec, error) {
spec, err := LoadCollectionSpecFromReader(f)
if err != nil {
return nil, xerrors.Errorf("file %s: %w", file, err)
return nil, fmt.Errorf("file %s: %w", file, err)
}
return spec, nil
}
@ -50,7 +50,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
symbols, err := f.Symbols()
if err != nil {
return nil, xerrors.Errorf("load symbols: %v", err)
return nil, fmt.Errorf("load symbols: %v", err)
}
ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0}
@ -79,13 +79,13 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
dataSections[elf.SectionIndex(i)] = sec
case sec.Type == elf.SHT_REL:
if int(sec.Info) >= len(ec.Sections) {
return nil, xerrors.Errorf("found relocation section %v for missing section %v", i, sec.Info)
return nil, fmt.Errorf("found relocation section %v for missing section %v", i, sec.Info)
}
// Store relocations under the section index of the target
idx := elf.SectionIndex(sec.Info)
if relSections[idx] != nil {
return nil, xerrors.Errorf("section %d has multiple relocation sections", sec.Info)
return nil, fmt.Errorf("section %d has multiple relocation sections", sec.Info)
}
relSections[idx] = sec
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
@ -95,44 +95,52 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
ec.license, err = loadLicense(licenseSection)
if err != nil {
return nil, xerrors.Errorf("load license: %w", err)
return nil, fmt.Errorf("load license: %w", err)
}
ec.version, err = loadVersion(versionSection, ec.ByteOrder)
if err != nil {
return nil, xerrors.Errorf("load version: %w", err)
return nil, fmt.Errorf("load version: %w", err)
}
btfSpec, err := btf.LoadSpecFromReader(rd)
if err != nil {
return nil, xerrors.Errorf("load BTF: %w", err)
return nil, fmt.Errorf("load BTF: %w", err)
}
relocations, referencedSections, err := ec.loadRelocations(relSections)
if err != nil {
return nil, fmt.Errorf("load relocations: %w", err)
}
maps := make(map[string]*MapSpec)
if err := ec.loadMaps(maps, mapSections); err != nil {
return nil, xerrors.Errorf("load maps: %w", err)
return nil, fmt.Errorf("load maps: %w", err)
}
if len(btfMaps) > 0 {
if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil {
return nil, xerrors.Errorf("load BTF maps: %w", err)
return nil, fmt.Errorf("load BTF maps: %w", err)
}
}
if len(dataSections) > 0 {
if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil {
return nil, xerrors.Errorf("load data sections: %w", err)
for idx := range dataSections {
if !referencedSections[idx] {
// Prune data sections which are not referenced by any
// instructions.
delete(dataSections, idx)
}
}
relocations, err := ec.loadRelocations(relSections)
if err != nil {
return nil, xerrors.Errorf("load relocations: %w", err)
if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil {
return nil, fmt.Errorf("load data sections: %w", err)
}
}
progs, err := ec.loadPrograms(progSections, relocations, btfSpec)
if err != nil {
return nil, xerrors.Errorf("load programs: %w", err)
return nil, fmt.Errorf("load programs: %w", err)
}
return &CollectionSpec{maps, progs}, nil
@ -140,11 +148,12 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
func loadLicense(sec *elf.Section) (string, error) {
if sec == nil {
return "", xerrors.New("missing license section")
return "", nil
}
data, err := sec.Data()
if err != nil {
return "", xerrors.Errorf("section %s: %v", sec.Name, err)
return "", fmt.Errorf("section %s: %v", sec.Name, err)
}
return string(bytes.TrimRight(data, "\000")), nil
}
@ -156,12 +165,12 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
var version uint32
if err := binary.Read(sec.Open(), bo, &version); err != nil {
return 0, xerrors.Errorf("section %s: %v", sec.Name, err)
return 0, fmt.Errorf("section %s: %v", sec.Name, err)
}
return version, nil
}
func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btf *btf.Spec) (map[string]*ProgramSpec, error) {
func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btfSpec *btf.Spec) (map[string]*ProgramSpec, error) {
var (
progs []*ProgramSpec
libs []*ProgramSpec
@ -170,34 +179,36 @@ func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section,
for idx, sec := range progSections {
syms := ec.symbolsPerSection[idx]
if len(syms) == 0 {
return nil, xerrors.Errorf("section %v: missing symbols", sec.Name)
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
}
funcSym, ok := syms[0]
if !ok {
return nil, xerrors.Errorf("section %v: no label at start", sec.Name)
return nil, fmt.Errorf("section %v: no label at start", sec.Name)
}
insns, length, err := ec.loadInstructions(sec, syms, relocations[idx])
if err != nil {
return nil, xerrors.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err)
return nil, fmt.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err)
}
progType, attachType := getProgType(sec.Name)
progType, attachType, attachTo := getProgType(sec.Name)
spec := &ProgramSpec{
Name: funcSym.Name,
Type: progType,
AttachType: attachType,
AttachTo: attachTo,
License: ec.license,
KernelVersion: ec.version,
Instructions: insns,
ByteOrder: ec.ByteOrder,
}
if btf != nil {
spec.BTF, err = btf.Program(sec.Name, length)
if err != nil {
return nil, xerrors.Errorf("BTF for section %s (program %s): %w", sec.Name, funcSym.Name, err)
if btfSpec != nil {
spec.BTF, err = btfSpec.Program(sec.Name, length)
if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
}
}
@ -215,7 +226,7 @@ func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section,
for _, prog := range progs {
err := link(prog, libs)
if err != nil {
return nil, xerrors.Errorf("program %s: %w", prog.Name, err)
return nil, fmt.Errorf("program %s: %w", prog.Name, err)
}
res[prog.Name] = prog
}
@ -236,14 +247,14 @@ func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations m
return insns, offset, nil
}
if err != nil {
return nil, 0, xerrors.Errorf("offset %d: %w", offset, err)
return nil, 0, fmt.Errorf("offset %d: %w", offset, err)
}
ins.Symbol = symbols[offset].Name
if rel, ok := relocations[offset]; ok {
if err = ec.relocateInstruction(&ins, rel); err != nil {
return nil, 0, xerrors.Errorf("offset %d: can't relocate instruction: %w", offset, err)
return nil, 0, fmt.Errorf("offset %d: can't relocate instruction: %w", offset, err)
}
}
@ -264,7 +275,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
// from the section itself.
idx := int(rel.Section)
if idx > len(ec.Sections) {
return xerrors.New("out-of-bounds section index")
return errors.New("out-of-bounds section index")
}
name = ec.Sections[idx].Name
@ -284,7 +295,7 @@ outer:
// section. Weirdly, the offset of the real symbol in the
// section is encoded in the instruction stream.
if bind != elf.STB_LOCAL {
return xerrors.Errorf("direct load: %s: unsupported relocation %s", name, bind)
return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
}
// For some reason, clang encodes the offset of the symbol its
@ -306,13 +317,13 @@ outer:
case elf.STT_OBJECT:
if bind != elf.STB_GLOBAL {
return xerrors.Errorf("load: %s: unsupported binding: %s", name, bind)
return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
}
ins.Src = asm.PseudoMapFD
default:
return xerrors.Errorf("load: %s: unsupported relocation: %s", name, typ)
return fmt.Errorf("load: %s: unsupported relocation: %s", name, typ)
}
// Mark the instruction as needing an update when creating the
@ -323,18 +334,18 @@ outer:
case ins.OpCode.JumpOp() == asm.Call:
if ins.Src != asm.PseudoCall {
return xerrors.Errorf("call: %s: incorrect source register", name)
return fmt.Errorf("call: %s: incorrect source register", name)
}
switch typ {
case elf.STT_NOTYPE, elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
return xerrors.Errorf("call: %s: unsupported binding: %s", name, bind)
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
return xerrors.Errorf("call: %s: unsupported binding: %s", name, bind)
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
// The function we want to call is in the indicated section,
@ -343,23 +354,23 @@ outer:
// A value of -1 references the first instruction in the section.
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
if offset < 0 {
return xerrors.Errorf("call: %s: invalid offset %d", name, offset)
return fmt.Errorf("call: %s: invalid offset %d", name, offset)
}
sym, ok := ec.symbolsPerSection[rel.Section][uint64(offset)]
if !ok {
return xerrors.Errorf("call: %s: no symbol at offset %d", name, offset)
return fmt.Errorf("call: %s: no symbol at offset %d", name, offset)
}
ins.Constant = -1
name = sym.Name
default:
return xerrors.Errorf("call: %s: invalid symbol type %s", name, typ)
return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
}
default:
return xerrors.Errorf("relocation for unsupported instruction: %s", ins.OpCode)
return fmt.Errorf("relocation for unsupported instruction: %s", ins.OpCode)
}
ins.Reference = name
@ -370,11 +381,11 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
for idx, sec := range mapSections {
syms := ec.symbolsPerSection[idx]
if len(syms) == 0 {
return xerrors.Errorf("section %v: no symbols", sec.Name)
return fmt.Errorf("section %v: no symbols", sec.Name)
}
if sec.Size%uint64(len(syms)) != 0 {
return xerrors.Errorf("section %v: map descriptors are not of equal size", sec.Name)
return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name)
}
var (
@ -384,11 +395,11 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
mapSym, ok := syms[offset]
if !ok {
return xerrors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
}
if maps[mapSym.Name] != nil {
return xerrors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym)
}
lr := io.LimitReader(r, int64(size))
@ -398,19 +409,19 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
}
switch {
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
return xerrors.Errorf("map %v: missing type", mapSym)
return fmt.Errorf("map %v: missing type", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
return xerrors.Errorf("map %v: missing key size", mapSym)
return fmt.Errorf("map %v: missing key size", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
return xerrors.Errorf("map %v: missing value size", mapSym)
return fmt.Errorf("map %v: missing value size", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
return xerrors.Errorf("map %v: missing max entries", mapSym)
return fmt.Errorf("map %v: missing max entries", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
return xerrors.Errorf("map %v: missing flags", mapSym)
return fmt.Errorf("map %v: missing flags", mapSym)
}
if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
return xerrors.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
}
maps[mapSym.Name] = &spec
@ -422,84 +433,116 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
if spec == nil {
return xerrors.Errorf("missing BTF")
return fmt.Errorf("missing BTF")
}
for idx, sec := range mapSections {
syms := ec.symbolsPerSection[idx]
if len(syms) == 0 {
return xerrors.Errorf("section %v: no symbols", sec.Name)
return fmt.Errorf("section %v: no symbols", sec.Name)
}
for _, sym := range syms {
name := sym.Name
if maps[name] != nil {
return xerrors.Errorf("section %v: map %v already exists", sec.Name, sym)
return fmt.Errorf("section %v: map %v already exists", sec.Name, sym)
}
btfMap, btfMapMembers, err := spec.Map(name)
mapSpec, err := mapSpecFromBTF(spec, name)
if err != nil {
return xerrors.Errorf("map %v: can't get BTF: %w", name, err)
return fmt.Errorf("map %v: %w", name, err)
}
spec, err := mapSpecFromBTF(btfMap, btfMapMembers)
if err != nil {
return xerrors.Errorf("map %v: %w", name, err)
}
maps[name] = spec
maps[name] = mapSpec
}
}
return nil
}
func mapSpecFromBTF(btfMap *btf.Map, btfMapMembers []btf.Member) (*MapSpec, error) {
func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) {
btfMap, btfMapMembers, err := spec.Map(name)
if err != nil {
return nil, fmt.Errorf("can't get BTF: %w", err)
}
keyType := btf.MapKey(btfMap)
size, err := btf.Sizeof(keyType)
if err != nil {
return nil, fmt.Errorf("can't get size of BTF key: %w", err)
}
keySize := uint32(size)
valueType := btf.MapValue(btfMap)
size, err = btf.Sizeof(valueType)
if err != nil {
return nil, fmt.Errorf("can't get size of BTF value: %w", err)
}
valueSize := uint32(size)
var (
mapType, flags, maxEntries uint32
err error
)
for _, member := range btfMapMembers {
switch member.Name {
case "type":
mapType, err = uintFromBTF(member.Type)
if err != nil {
return nil, xerrors.Errorf("can't get type: %w", err)
return nil, fmt.Errorf("can't get type: %w", err)
}
case "map_flags":
flags, err = uintFromBTF(member.Type)
if err != nil {
return nil, xerrors.Errorf("can't get BTF map flags: %w", err)
return nil, fmt.Errorf("can't get BTF map flags: %w", err)
}
case "max_entries":
maxEntries, err = uintFromBTF(member.Type)
if err != nil {
return nil, xerrors.Errorf("can't get BTF map max entries: %w", err)
return nil, fmt.Errorf("can't get BTF map max entries: %w", err)
}
case "key":
case "value":
case "key_size":
if _, isVoid := keyType.(*btf.Void); !isVoid {
return nil, errors.New("both key and key_size given")
}
keySize, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get BTF key size: %w", err)
}
case "value_size":
if _, isVoid := valueType.(*btf.Void); !isVoid {
return nil, errors.New("both value and value_size given")
}
valueSize, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get BTF value size: %w", err)
}
case "pinning":
pinning, err := uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get pinning: %w", err)
}
if pinning != 0 {
return nil, fmt.Errorf("'pinning' attribute not supported: %w", ErrNotSupported)
}
case "key", "value":
default:
return nil, xerrors.Errorf("unrecognized field %s in BTF map definition", member.Name)
return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name)
}
}
keySize, err := btf.Sizeof(btf.MapKey(btfMap))
if err != nil {
return nil, xerrors.Errorf("can't get size of BTF key: %w", err)
}
valueSize, err := btf.Sizeof(btf.MapValue(btfMap))
if err != nil {
return nil, xerrors.Errorf("can't get size of BTF value: %w", err)
}
return &MapSpec{
Type: MapType(mapType),
KeySize: uint32(keySize),
ValueSize: uint32(valueSize),
KeySize: keySize,
ValueSize: valueSize,
MaxEntries: maxEntries,
Flags: flags,
BTF: btfMap,
@ -511,12 +554,12 @@ func mapSpecFromBTF(btfMap *btf.Map, btfMapMembers []btf.Member) (*MapSpec, erro
func uintFromBTF(typ btf.Type) (uint32, error) {
ptr, ok := typ.(*btf.Pointer)
if !ok {
return 0, xerrors.Errorf("not a pointer: %v", typ)
return 0, fmt.Errorf("not a pointer: %v", typ)
}
arr, ok := ptr.Target.(*btf.Array)
if !ok {
return 0, xerrors.Errorf("not a pointer to array: %v", typ)
return 0, fmt.Errorf("not a pointer to array: %v", typ)
}
return arr.Nelems, nil
@ -524,7 +567,7 @@ func uintFromBTF(typ btf.Type) (uint32, error) {
func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
if spec == nil {
return xerrors.New("data sections require BTF")
return errors.New("data sections require BTF, make sure all consts are marked as static")
}
for _, sec := range dataSections {
@ -535,11 +578,11 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[e
data, err := sec.Data()
if err != nil {
return xerrors.Errorf("data section %s: can't get contents: %w", sec.Name, err)
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
}
if uint64(len(data)) > math.MaxUint32 {
return xerrors.Errorf("data section %s: contents exceed maximum size", sec.Name)
return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
}
mapSpec := &MapSpec{
@ -566,91 +609,79 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[e
return nil
}
func getProgType(v string) (ProgramType, AttachType) {
types := map[string]ProgramType{
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568
"socket": SocketFilter,
"seccomp": SocketFilter,
"kprobe/": Kprobe,
"uprobe/": Kprobe,
"kretprobe/": Kprobe,
"uretprobe/": Kprobe,
"tracepoint/": TracePoint,
"raw_tracepoint/": RawTracepoint,
"xdp": XDP,
"perf_event": PerfEvent,
"lwt_in": LWTIn,
"lwt_out": LWTOut,
"lwt_xmit": LWTXmit,
"lwt_seg6local": LWTSeg6Local,
"sockops": SockOps,
"sk_skb": SkSKB,
"sk_msg": SkMsg,
"lirc_mode2": LircMode2,
"flow_dissector": FlowDissector,
func getProgType(sectionName string) (ProgramType, AttachType, string) {
types := map[string]struct {
progType ProgramType
attachType AttachType
}{
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
"socket": {SocketFilter, AttachNone},
"seccomp": {SocketFilter, AttachNone},
"kprobe/": {Kprobe, AttachNone},
"uprobe/": {Kprobe, AttachNone},
"kretprobe/": {Kprobe, AttachNone},
"uretprobe/": {Kprobe, AttachNone},
"tracepoint/": {TracePoint, AttachNone},
"raw_tracepoint/": {RawTracepoint, AttachNone},
"xdp": {XDP, AttachNone},
"perf_event": {PerfEvent, AttachNone},
"lwt_in": {LWTIn, AttachNone},
"lwt_out": {LWTOut, AttachNone},
"lwt_xmit": {LWTXmit, AttachNone},
"lwt_seg6local": {LWTSeg6Local, AttachNone},
"sockops": {SockOps, AttachCGroupSockOps},
"sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser},
"sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser},
"sk_msg": {SkMsg, AttachSkSKBStreamVerdict},
"lirc_mode2": {LircMode2, AttachLircMode2},
"flow_dissector": {FlowDissector, AttachFlowDissector},
"iter/": {Tracing, AttachTraceIter},
"cgroup_skb/": CGroupSKB,
"cgroup/dev": CGroupDevice,
"cgroup/skb": CGroupSKB,
"cgroup/sock": CGroupSock,
"cgroup/post_bind": CGroupSock,
"cgroup/bind": CGroupSockAddr,
"cgroup/connect": CGroupSockAddr,
"cgroup/sendmsg": CGroupSockAddr,
"cgroup/recvmsg": CGroupSockAddr,
"cgroup/sysctl": CGroupSysctl,
"cgroup/getsockopt": CGroupSockopt,
"cgroup/setsockopt": CGroupSockopt,
"classifier": SchedCLS,
"action": SchedACT,
}
attachTypes := map[string]AttachType{
"cgroup_skb/ingress": AttachCGroupInetIngress,
"cgroup_skb/egress": AttachCGroupInetEgress,
"cgroup/sock": AttachCGroupInetSockCreate,
"cgroup/post_bind4": AttachCGroupInet4PostBind,
"cgroup/post_bind6": AttachCGroupInet6PostBind,
"cgroup/dev": AttachCGroupDevice,
"sockops": AttachCGroupSockOps,
"sk_skb/stream_parser": AttachSkSKBStreamParser,
"sk_skb/stream_verdict": AttachSkSKBStreamVerdict,
"sk_msg": AttachSkSKBStreamVerdict,
"lirc_mode2": AttachLircMode2,
"flow_dissector": AttachFlowDissector,
"cgroup/bind4": AttachCGroupInet4Bind,
"cgroup/bind6": AttachCGroupInet6Bind,
"cgroup/connect4": AttachCGroupInet4Connect,
"cgroup/connect6": AttachCGroupInet6Connect,
"cgroup/sendmsg4": AttachCGroupUDP4Sendmsg,
"cgroup/sendmsg6": AttachCGroupUDP6Sendmsg,
"cgroup/recvmsg4": AttachCGroupUDP4Recvmsg,
"cgroup/recvmsg6": AttachCGroupUDP6Recvmsg,
"cgroup/sysctl": AttachCGroupSysctl,
"cgroup/getsockopt": AttachCGroupGetsockopt,
"cgroup/setsockopt": AttachCGroupSetsockopt,
}
attachType := AttachNone
for k, t := range attachTypes {
if strings.HasPrefix(v, k) {
attachType = t
}
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress},
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress},
"cgroup/dev": {CGroupDevice, AttachCGroupDevice},
"cgroup/skb": {CGroupSKB, AttachNone},
"cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate},
"cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind},
"cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind},
"cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind},
"cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind},
"cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect},
"cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect},
"cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg},
"cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg},
"cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg},
"cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg},
"cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl},
"cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt},
"cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt},
"classifier": {SchedCLS, AttachNone},
"action": {SchedACT, AttachNone},
}
for k, t := range types {
if strings.HasPrefix(v, k) {
return t, attachType
for prefix, t := range types {
if !strings.HasPrefix(sectionName, prefix) {
continue
}
if !strings.HasSuffix(prefix, "/") {
return t.progType, t.attachType, ""
}
return UnspecifiedProgram, AttachNone
return t.progType, t.attachType, sectionName[len(prefix):]
}
return UnspecifiedProgram, AttachNone, ""
}
func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, error) {
func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, map[elf.SectionIndex]bool, error) {
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
targets := make(map[elf.SectionIndex]bool)
for idx, sec := range sections {
rels := make(map[uint64]elf.Symbol)
if sec.Entsize < 16 {
return nil, xerrors.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
return nil, nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
}
r := sec.Open()
@ -659,20 +690,22 @@ func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (
var rel elf.Rel64
if binary.Read(ent, ec.ByteOrder, &rel) != nil {
return nil, xerrors.Errorf("can't parse relocation at offset %v", off)
return nil, nil, fmt.Errorf("can't parse relocation at offset %v", off)
}
symNo := int(elf.R_SYM64(rel.Info) - 1)
if symNo >= len(ec.symbols) {
return nil, xerrors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
return nil, nil, fmt.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
}
symbol := ec.symbols[symNo]
targets[symbol.Section] = true
rels[rel.Off] = ec.symbols[symNo]
}
result[idx] = rels
}
return result, nil
return result, targets, nil
}
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol {

View File

@ -1,8 +1,5 @@
module github.com/cilium/ebpf
go 1.12
go 1.13
require (
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
)
require golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9

View File

@ -1,6 +1,2 @@
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -4,16 +4,18 @@ import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"reflect"
"sync"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
"golang.org/x/xerrors"
)
const btfMagic = 0xeB9F
@ -21,6 +23,8 @@ const btfMagic = 0xeB9F
// Errors returned by BTF functions.
var (
ErrNotSupported = internal.ErrNotSupported
ErrNotFound = errors.New("not found")
ErrNoExtendedInfo = errors.New("no extended info")
)
// Spec represents decoded BTF.
@ -30,6 +34,7 @@ type Spec struct {
types map[string][]Type
funcInfos map[string]extInfo
lineInfos map[string]extInfo
byteOrder binary.ByteOrder
}
type btfHeader struct {
@ -72,7 +77,7 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
}
if sec.Size > math.MaxUint32 {
return nil, xerrors.Errorf("section %s exceeds maximum size", sec.Name)
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
}
sectionSizes[sec.Name] = uint32(sec.Size)
@ -85,7 +90,7 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
symbols, err := file.Symbols()
if err != nil {
return nil, xerrors.Errorf("can't read symbols: %v", err)
return nil, fmt.Errorf("can't read symbols: %v", err)
}
variableOffsets := make(map[variable]uint32)
@ -101,13 +106,31 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
}
if symbol.Value > math.MaxUint32 {
return nil, xerrors.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
}
variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
}
rawTypes, rawStrings, err := parseBTF(btfSection.Open(), file.ByteOrder)
spec, err := loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
if err != nil {
return nil, err
}
if btfExtSection == nil {
return spec, nil
}
spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
if err != nil {
return nil, fmt.Errorf("can't read ext info: %w", err)
}
return spec, nil
}
func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
rawTypes, rawStrings, err := parseBTF(btf, bo)
if err != nil {
return nil, err
}
@ -122,76 +145,99 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
return nil, err
}
var (
funcInfos = make(map[string]extInfo)
lineInfos = make(map[string]extInfo)
)
if btfExtSection != nil {
funcInfos, lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, rawStrings)
if err != nil {
return nil, xerrors.Errorf("can't read ext info: %w", err)
}
}
return &Spec{
rawTypes: rawTypes,
types: types,
strings: rawStrings,
funcInfos: funcInfos,
lineInfos: lineInfos,
byteOrder: bo,
}, nil
}
var kernelBTF struct {
sync.Mutex
*Spec
}
// LoadKernelSpec returns the current kernel's BTF information.
//
// Requires a >= 5.5 kernel with CONFIG_DEBUG_INFO_BTF enabled. Returns
// ErrNotSupported if BTF is not enabled.
func LoadKernelSpec() (*Spec, error) {
kernelBTF.Lock()
defer kernelBTF.Unlock()
if kernelBTF.Spec != nil {
return kernelBTF.Spec, nil
}
var err error
kernelBTF.Spec, err = loadKernelSpec()
return kernelBTF.Spec, err
}
func loadKernelSpec() (*Spec, error) {
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if os.IsNotExist(err) {
return nil, fmt.Errorf("can't open kernel BTF at /sys/kernel/btf/vmlinux: %w", ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("can't read kernel BTF: %s", err)
}
defer fh.Close()
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
}
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
rawBTF, err := ioutil.ReadAll(btf)
if err != nil {
return nil, nil, xerrors.Errorf("can't read BTF: %v", err)
return nil, nil, fmt.Errorf("can't read BTF: %v", err)
}
rd := bytes.NewReader(rawBTF)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, nil, xerrors.Errorf("can't read header: %v", err)
return nil, nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, xerrors.Errorf("incorrect magic value %v", header.Magic)
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, xerrors.Errorf("unexpected version %v", header.Version)
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, xerrors.Errorf("unsupported flags %v", header.Flags)
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, xerrors.New("header is too short")
return nil, nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, nil, xerrors.Errorf("header padding: %v", err)
return nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, nil, xerrors.Errorf("can't seek to start of string section: %v", err)
return nil, nil, fmt.Errorf("can't seek to start of string section: %v", err)
}
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
if err != nil {
return nil, nil, xerrors.Errorf("can't read type names: %w", err)
return nil, nil, fmt.Errorf("can't read type names: %w", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
return nil, nil, xerrors.Errorf("can't seek to start of type section: %v", err)
return nil, nil, fmt.Errorf("can't seek to start of type section: %v", err)
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
if err != nil {
return nil, nil, xerrors.Errorf("can't read types: %w", err)
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
return rawTypes, rawStrings, nil
@ -213,9 +259,13 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s
return err
}
if name == ".kconfig" || name == ".ksym" {
return fmt.Errorf("reference to %s: %w", name, ErrNotSupported)
}
size, ok := sectionSizes[name]
if !ok {
return xerrors.Errorf("data section %s: missing size", name)
return fmt.Errorf("data section %s: missing size", name)
}
rawTypes[i].SizeType = size
@ -224,17 +274,17 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s
for j, secInfo := range secinfos {
id := int(secInfo.Type - 1)
if id >= len(rawTypes) {
return xerrors.Errorf("data section %s: invalid type id %d for variable %d", name, id, j)
return fmt.Errorf("data section %s: invalid type id %d for variable %d", name, id, j)
}
varName, err := rawStrings.Lookup(rawTypes[id].NameOff)
if err != nil {
return xerrors.Errorf("data section %s: can't get name for type %d: %w", name, id, err)
return fmt.Errorf("data section %s: can't get name for type %d: %w", name, id, err)
}
offset, ok := variableOffsets[variable{name, varName}]
if !ok {
return xerrors.Errorf("data section %s: missing offset for variable %s", name, varName)
return fmt.Errorf("data section %s: missing offset for variable %s", name, varName)
}
secinfos[j].Offset = offset
@ -244,7 +294,12 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s
return nil
}
func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
type marshalOpts struct {
ByteOrder binary.ByteOrder
StripFuncLinkage bool
}
func (s *Spec) marshal(opts marshalOpts) ([]byte, error) {
var (
buf bytes.Buffer
header = new(btfHeader)
@ -256,9 +311,14 @@ func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
_, _ = buf.Write(make([]byte, headerLen))
// Write type section, just after the header.
for _, typ := range s.rawTypes {
if err := typ.Marshal(&buf, bo); err != nil {
return nil, xerrors.Errorf("can't marshal BTF: %w", err)
for _, raw := range s.rawTypes {
switch {
case opts.StripFuncLinkage && raw.Kind() == kindFunc:
raw.SetLinkage(linkageStatic)
}
if err := raw.Marshal(&buf, opts.ByteOrder); err != nil {
return nil, fmt.Errorf("can't marshal BTF: %w", err)
}
}
@ -280,9 +340,9 @@ func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
}
raw := buf.Bytes()
err := binary.Write(sliceWriter(raw[:headerLen]), bo, header)
err := binary.Write(sliceWriter(raw[:headerLen]), opts.ByteOrder, header)
if err != nil {
return nil, xerrors.Errorf("can't write header: %v", err)
return nil, fmt.Errorf("can't write header: %v", err)
}
return raw, nil
@ -292,7 +352,7 @@ type sliceWriter []byte
func (sw sliceWriter) Write(p []byte) (int, error) {
if len(p) != len(sw) {
return 0, xerrors.New("size doesn't match")
return 0, errors.New("size doesn't match")
}
return copy(sw, p), nil
@ -302,17 +362,22 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
//
// Length is the number of bytes in the raw BPF instruction stream.
//
// Returns an error if there is no BTF.
// Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't
// contain extended BTF info.
func (s *Spec) Program(name string, length uint64) (*Program, error) {
if length == 0 {
return nil, xerrors.New("length musn't be zero")
return nil, errors.New("length musn't be zero")
}
if s.funcInfos == nil && s.lineInfos == nil {
return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo)
}
funcInfos, funcOK := s.funcInfos[name]
lineInfos, lineOK := s.lineInfos[name]
if !funcOK && !lineOK {
return nil, xerrors.Errorf("no BTF for program %s", name)
return nil, fmt.Errorf("no extended BTF info for section %s", name)
}
return &Program{s, length, funcInfos, lineInfos}, nil
@ -329,7 +394,7 @@ func (s *Spec) Map(name string) (*Map, []Member, error) {
mapStruct, ok := mapVar.Type.(*Struct)
if !ok {
return nil, nil, xerrors.Errorf("expected struct, have %s", mapVar.Type)
return nil, nil, fmt.Errorf("expected struct, have %s", mapVar.Type)
}
var key, value Type
@ -344,11 +409,11 @@ func (s *Spec) Map(name string) (*Map, []Member, error) {
}
if key == nil {
return nil, nil, xerrors.Errorf("map %s: missing 'key' in type", name)
key = (*Void)(nil)
}
if value == nil {
return nil, nil, xerrors.Errorf("map %s: missing 'value' in type", name)
value = (*Void)(nil)
}
return &Map{s, key, value}, mapStruct.Members, nil
@ -358,19 +423,18 @@ func (s *Spec) Map(name string) (*Map, []Member, error) {
func (s *Spec) Datasec(name string) (*Map, error) {
var datasec Datasec
if err := s.FindType(name, &datasec); err != nil {
return nil, xerrors.Errorf("data section %s: can't get BTF: %w", name, err)
return nil, fmt.Errorf("data section %s: can't get BTF: %w", name, err)
}
return &Map{s, &Void{}, &datasec}, nil
}
var errNotFound = xerrors.New("not found")
// FindType searches for a type with a specific name.
//
// hint determines the type of the returned Type.
//
// Returns an error if there is no or multiple matches.
// Returns an error wrapping ErrNotFound if no matching
// type exists in spec.
func (s *Spec) FindType(name string, typ Type) error {
var (
wanted = reflect.TypeOf(typ)
@ -383,14 +447,14 @@ func (s *Spec) FindType(name string, typ Type) error {
}
if candidate != nil {
return xerrors.Errorf("type %s: multiple candidates for %T", name, typ)
return fmt.Errorf("type %s: multiple candidates for %T", name, typ)
}
candidate = typ
}
if candidate == nil {
return xerrors.Errorf("type %s: %w", name, errNotFound)
return fmt.Errorf("type %s: %w", name, ErrNotFound)
}
value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
@ -411,13 +475,20 @@ func NewHandle(spec *Spec) (*Handle, error) {
return nil, err
}
btf, err := spec.marshal(internal.NativeEndian)
if spec.byteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s BTF on %s", spec.byteOrder, internal.NativeEndian)
}
btf, err := spec.marshal(marshalOpts{
ByteOrder: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
})
if err != nil {
return nil, xerrors.Errorf("can't marshal BTF: %w", err)
return nil, fmt.Errorf("can't marshal BTF: %w", err)
}
if uint64(len(btf)) > math.MaxUint32 {
return nil, xerrors.New("BTF exceeds the maximum size")
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &bpfLoadBTFAttr{
@ -501,12 +572,12 @@ func ProgramSpec(s *Program) *Spec {
func ProgramAppend(s, other *Program) error {
funcInfos, err := s.funcInfos.append(other.funcInfos, s.length)
if err != nil {
return xerrors.Errorf("func infos: %w", err)
return fmt.Errorf("func infos: %w", err)
}
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
if err != nil {
return xerrors.Errorf("line infos: %w", err)
return fmt.Errorf("line infos: %w", err)
}
s.length += other.length
@ -560,26 +631,36 @@ func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
return internal.NewFD(uint32(fd)), nil
}
func minimalBTF(bo binary.ByteOrder) []byte {
func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
const minHeaderLength = 24
typesLen := uint32(binary.Size(types))
header := btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typesLen,
StringOff: typesLen,
StringLen: uint32(len(strings)),
}
buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, types)
buf.Write(strings)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() (bool, error) {
var (
types struct {
Integer btfType
Var btfType
btfVar struct{ Linkage uint32 }
}
typLen = uint32(binary.Size(&types))
strings = []byte{0, 'a', 0}
header = btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typLen,
StringOff: typLen,
StringLen: uint32(len(strings)),
}
)
// We use a BTF_KIND_VAR here, to make sure that
@ -590,16 +671,8 @@ func minimalBTF(bo binary.ByteOrder) []byte {
types.Var.SetKind(kindVar)
types.Var.SizeType = 1
buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, &types)
buf.Write(strings)
btf := marshalBTF(&types, strings, internal.NativeEndian)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
btf := minimalBTF(internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
@ -609,5 +682,35 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
}
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return !xerrors.Is(err, unix.EINVAL)
return !errors.Is(err, unix.EINVAL), nil
})
var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() (bool, error) {
var (
types struct {
FuncProto btfType
Func btfType
}
strings = []byte{0, 'a', 0}
)
types.FuncProto.SetKind(kindFuncProto)
types.Func.SetKind(kindFunc)
types.Func.SizeType = 1 // aka FuncProto
types.Func.NameOff = 1
types.Func.SetLinkage(linkageGlobal)
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
})
if err == nil {
fd.Close()
}
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return !errors.Is(err, unix.EINVAL), nil
})

View File

@ -4,8 +4,6 @@ import (
"encoding/binary"
"fmt"
"io"
"golang.org/x/xerrors"
)
// btfKind describes a Type.
@ -33,6 +31,14 @@ const (
kindDatasec
)
type btfFuncLinkage uint8
const (
linkageStatic btfFuncLinkage = iota
linkageGlobal
linkageExtern
)
const (
btfTypeKindShift = 24
btfTypeKindLen = 4
@ -44,7 +50,7 @@ const (
type btfType struct {
NameOff uint32
/* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members)
* bits 0-15: vlen (e.g. # of struct's members), linkage
* bits 16-23: unused
* bits 24-27: kind (e.g. int, ptr, array...etc)
* bits 28-30: unused
@ -130,6 +136,14 @@ func (bt *btfType) SetVlen(vlen int) {
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Linkage() btfFuncLinkage {
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetLinkage(linkage btfFuncLinkage) {
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Type() TypeID {
// TODO: Panic here if wrong kind?
return TypeID(bt.SizeType)
@ -179,6 +193,16 @@ type btfVariable struct {
Linkage uint32
}
type btfEnum struct {
NameOff uint32
Val int32
}
type btfParam struct {
NameOff uint32
Type TypeID
}
func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
var (
header btfType
@ -189,14 +213,13 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
if err := binary.Read(r, bo, &header); err == io.EOF {
return types, nil
} else if err != nil {
return nil, xerrors.Errorf("can't read type info for id %v: %v", id, err)
return nil, fmt.Errorf("can't read type info for id %v: %v", id, err)
}
var data interface{}
switch header.Kind() {
case kindInt:
// sizeof(uint32)
data = make([]byte, 4)
data = new(uint32)
case kindPointer:
case kindArray:
data = new(btfArray)
@ -205,8 +228,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
case kindUnion:
data = make([]btfMember, header.Vlen())
case kindEnum:
// sizeof(struct btf_enum)
data = make([]byte, header.Vlen()*4*2)
data = make([]btfEnum, header.Vlen())
case kindForward:
case kindTypedef:
case kindVolatile:
@ -214,14 +236,13 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
case kindRestrict:
case kindFunc:
case kindFuncProto:
// sizeof(struct btf_param)
data = make([]byte, header.Vlen()*4*2)
data = make([]btfParam, header.Vlen())
case kindVar:
data = new(btfVariable)
case kindDatasec:
data = make([]btfVarSecinfo, header.Vlen())
default:
return nil, xerrors.Errorf("type id %v: unknown kind: %v", id, header.Kind())
return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
}
if data == nil {
@ -230,7 +251,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
}
if err := binary.Read(r, bo, data); err != nil {
return nil, xerrors.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
}
types = append(types, rawType{header, data})

View File

@ -3,13 +3,13 @@ package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"golang.org/x/xerrors"
)
type btfExtHeader struct {
@ -27,49 +27,49 @@ type btfExtHeader struct {
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) {
var header btfExtHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, nil, xerrors.Errorf("can't read header: %v", err)
return nil, nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, xerrors.Errorf("incorrect magic value %v", header.Magic)
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, xerrors.Errorf("unexpected version %v", header.Version)
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, xerrors.Errorf("unsupported flags %v", header.Flags)
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, xerrors.New("header is too short")
return nil, nil, errors.New("header is too short")
}
// Of course, the .BTF.ext header has different semantics than the
// .BTF ext header. We need to ignore non-null values.
_, err = io.CopyN(ioutil.Discard, r, remainder)
if err != nil {
return nil, nil, xerrors.Errorf("header padding: %v", err)
return nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
return nil, nil, xerrors.Errorf("can't seek to function info section: %v", err)
return nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
}
funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings)
if err != nil {
return nil, nil, xerrors.Errorf("function info: %w", err)
return nil, nil, fmt.Errorf("function info: %w", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
return nil, nil, xerrors.Errorf("can't seek to line info section: %v", err)
return nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
}
lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings)
if err != nil {
return nil, nil, xerrors.Errorf("line info: %w", err)
return nil, nil, fmt.Errorf("line info: %w", err)
}
return funcInfo, lineInfo, nil
@ -92,7 +92,7 @@ type extInfo struct {
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
if other.recordSize != ei.recordSize {
return extInfo{}, xerrors.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
}
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
@ -117,7 +117,7 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
// while the ELF tracks it in bytes.
insnOff := uint32(info.InsnOff / asm.InstructionSize)
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
return nil, xerrors.Errorf("can't write instruction offset: %v", err)
return nil, fmt.Errorf("can't write instruction offset: %v", err)
}
buf.Write(info.Opaque)
@ -129,12 +129,12 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, xerrors.Errorf("can't read record size: %v", err)
return nil, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
// Need at least insnOff
return nil, xerrors.New("record size too short")
return nil, errors.New("record size too short")
}
result := make(map[string]extInfo)
@ -143,32 +143,32 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st
if err := binary.Read(r, bo, &infoHeader); err == io.EOF {
return result, nil
} else if err != nil {
return nil, xerrors.Errorf("can't read ext info header: %v", err)
return nil, fmt.Errorf("can't read ext info header: %v", err)
}
secName, err := strings.Lookup(infoHeader.SecNameOff)
if err != nil {
return nil, xerrors.Errorf("can't get section name: %w", err)
return nil, fmt.Errorf("can't get section name: %w", err)
}
if infoHeader.NumInfo == 0 {
return nil, xerrors.Errorf("section %s has invalid number of records", secName)
return nil, fmt.Errorf("section %s has invalid number of records", secName)
}
var records []extInfoRecord
for i := uint32(0); i < infoHeader.NumInfo; i++ {
var byteOff uint32
if err := binary.Read(r, bo, &byteOff); err != nil {
return nil, xerrors.Errorf("section %v: can't read extended info offset: %v", secName, err)
return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
}
buf := make([]byte, int(recordSize-4))
if _, err := io.ReadFull(r, buf); err != nil {
return nil, xerrors.Errorf("section %v: can't read record: %v", secName, err)
return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
}
if byteOff%asm.InstructionSize != 0 {
return nil, xerrors.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
}
records = append(records, extInfoRecord{uint64(byteOff), buf})

View File

@ -2,10 +2,10 @@ package btf
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"golang.org/x/xerrors"
)
type stringTable []byte
@ -13,19 +13,19 @@ type stringTable []byte
func readStringTable(r io.Reader) (stringTable, error) {
contents, err := ioutil.ReadAll(r)
if err != nil {
return nil, xerrors.Errorf("can't read string table: %v", err)
return nil, fmt.Errorf("can't read string table: %v", err)
}
if len(contents) < 1 {
return nil, xerrors.New("string table is empty")
return nil, errors.New("string table is empty")
}
if contents[0] != '\x00' {
return nil, xerrors.New("first item in string table is non-empty")
return nil, errors.New("first item in string table is non-empty")
}
if contents[len(contents)-1] != '\x00' {
return nil, xerrors.New("string table isn't null terminated")
return nil, errors.New("string table isn't null terminated")
}
return stringTable(contents), nil
@ -33,22 +33,22 @@ func readStringTable(r io.Reader) (stringTable, error) {
func (st stringTable) Lookup(offset uint32) (string, error) {
if int64(offset) > int64(^uint(0)>>1) {
return "", xerrors.Errorf("offset %d overflows int", offset)
return "", fmt.Errorf("offset %d overflows int", offset)
}
pos := int(offset)
if pos >= len(st) {
return "", xerrors.Errorf("offset %d is out of bounds", offset)
return "", fmt.Errorf("offset %d is out of bounds", offset)
}
if pos > 0 && st[pos-1] != '\x00' {
return "", xerrors.Errorf("offset %d isn't start of a string", offset)
return "", fmt.Errorf("offset %d isn't start of a string", offset)
}
str := st[pos:]
end := bytes.IndexByte(str, '\x00')
if end == -1 {
return "", xerrors.Errorf("offset %d isn't null terminated", offset)
return "", fmt.Errorf("offset %d isn't null terminated", offset)
}
return string(str[:end]), nil

View File

@ -1,9 +1,9 @@
package btf
import (
"errors"
"fmt"
"math"
"golang.org/x/xerrors"
)
const maxTypeDepth = 32
@ -38,9 +38,10 @@ func (n Name) name() string {
// Void is the unit type of BTF.
type Void struct{}
func (v Void) ID() TypeID { return 0 }
func (v Void) copy() Type { return Void{} }
func (v Void) walk(*copyStack) {}
func (v *Void) ID() TypeID { return 0 }
func (v *Void) size() uint32 { return 0 }
func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*copyStack) {}
// Int is an integer of a given length.
type Int struct {
@ -310,7 +311,7 @@ func Sizeof(typ Type) (int, error) {
switch v := typ.(type) {
case *Array:
if n > 0 && int64(v.Nelems) > math.MaxInt64/n {
return 0, xerrors.New("overflow")
return 0, errors.New("overflow")
}
// Arrays may be of zero length, which allows
@ -336,22 +337,22 @@ func Sizeof(typ Type) (int, error) {
continue
default:
return 0, xerrors.Errorf("unrecognized type %T", typ)
return 0, fmt.Errorf("unrecognized type %T", typ)
}
if n > 0 && elem > math.MaxInt64/n {
return 0, xerrors.New("overflow")
return 0, errors.New("overflow")
}
size := n * elem
if int64(int(size)) != size {
return 0, xerrors.New("overflow")
return 0, errors.New("overflow")
}
return int(size), nil
}
return 0, xerrors.New("exceeded type depth")
return 0, errors.New("exceeded type depth")
}
// copy a Type recursively.
@ -433,7 +434,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
for i, btfMember := range raw {
name, err := rawStrings.LookupName(btfMember.NameOff)
if err != nil {
return nil, xerrors.Errorf("can't get name for member %d: %w", i, err)
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
}
members = append(members, Member{
Name: name,
@ -447,7 +448,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
}
types := make([]Type, 0, len(rawTypes))
types = append(types, Void{})
types = append(types, (*Void)(nil))
namedTypes = make(map[string][]Type)
for i, raw := range rawTypes {
@ -460,7 +461,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
name, err := rawStrings.LookupName(raw.NameOff)
if err != nil {
return nil, xerrors.Errorf("can't get name for type id %d: %w", id, err)
return nil, fmt.Errorf("can't get name for type id %d: %w", id, err)
}
switch raw.Kind() {
@ -484,14 +485,14 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
case kindStruct:
members, err := convertMembers(raw.data.([]btfMember))
if err != nil {
return nil, xerrors.Errorf("struct %s (id %d): %w", name, id, err)
return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
}
typ = &Struct{id, name, raw.Size(), members}
case kindUnion:
members, err := convertMembers(raw.data.([]btfMember))
if err != nil {
return nil, xerrors.Errorf("union %s (id %d): %w", name, id, err)
return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
}
typ = &Union{id, name, raw.Size(), members}
@ -551,7 +552,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
typ = &Datasec{id, name, raw.SizeType, vars}
default:
return nil, xerrors.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
return nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
}
types = append(types, typ)
@ -566,7 +567,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
for _, fixup := range fixups {
i := int(fixup.id)
if i >= len(types) {
return nil, xerrors.Errorf("reference to invalid type id: %d", fixup.id)
return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id)
}
// Default void (id 0) to unknown
@ -576,7 +577,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
}
if expected := fixup.expectedKind; expected != kindUnknown && rawKind != expected {
return nil, xerrors.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind)
return nil, fmt.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind)
}
*fixup.typ = types[i]

View File

@ -2,11 +2,11 @@ package internal
import (
"bytes"
"errors"
"fmt"
"strings"
"github.com/cilium/ebpf/internal/unix"
"golang.org/x/xerrors"
)
// ErrorWithLog returns an error that includes logs from the
@ -16,7 +16,7 @@ import (
// the log. It is used to check for truncation of the output.
func ErrorWithLog(err error, log []byte, logErr error) error {
logStr := strings.Trim(CString(log), "\t\r\n ")
if xerrors.Is(logErr, unix.ENOSPC) {
if errors.Is(logErr, unix.ENOSPC) {
logStr += " (truncated...)"
}

View File

@ -1,15 +1,16 @@
package internal
import (
"errors"
"fmt"
"os"
"runtime"
"strconv"
"github.com/cilium/ebpf/internal/unix"
"golang.org/x/xerrors"
)
var ErrClosedFd = xerrors.New("use of closed file descriptor")
var ErrClosedFd = errors.New("use of closed file descriptor")
type FD struct {
raw int64
@ -56,8 +57,13 @@ func (fd *FD) Dup() (*FD, error) {
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
if err != nil {
return nil, xerrors.Errorf("can't dup fd: %v", err)
return nil, fmt.Errorf("can't dup fd: %v", err)
}
return NewFD(uint32(dup)), nil
}
func (fd *FD) File(name string) *os.File {
fd.Forget()
return os.NewFile(uintptr(fd.raw), name)
}

View File

@ -1,14 +1,13 @@
package internal
import (
"errors"
"fmt"
"sync"
"golang.org/x/xerrors"
)
// ErrNotSupported indicates that a feature is not supported by the current kernel.
var ErrNotSupported = xerrors.New("not supported")
var ErrNotSupported = errors.New("not supported")
// UnsupportedFeatureError is returned by FeatureTest() functions.
type UnsupportedFeatureError struct {
@ -29,33 +28,63 @@ func (ufe *UnsupportedFeatureError) Is(target error) bool {
return target == ErrNotSupported
}
type featureTest struct {
sync.Mutex
successful bool
result error
}
// FeatureTestFn is used to determine whether the kernel supports
// a certain feature.
//
// The return values have the following semantics:
//
// err != nil: the test couldn't be executed
// err == nil && available: the feature is available
// err == nil && !available: the feature isn't available
type FeatureTestFn func() (available bool, err error)
// FeatureTest wraps a function so that it is run at most once.
//
// name should identify the tested feature, while version must be in the
// form Major.Minor[.Patch].
//
// Returns a descriptive UnsupportedFeatureError if the feature is not available.
func FeatureTest(name, version string, fn func() bool) func() error {
// Returns an error wrapping ErrNotSupported if the feature is not supported.
func FeatureTest(name, version string, fn FeatureTestFn) func() error {
v, err := NewVersion(version)
if err != nil {
return func() error { return err }
}
var (
once sync.Once
result error
)
ft := new(featureTest)
return func() error {
once.Do(func() {
if !fn() {
result = &UnsupportedFeatureError{
ft.Lock()
defer ft.Unlock()
if ft.successful {
return ft.result
}
available, err := fn()
if errors.Is(err, ErrNotSupported) {
// The feature test aborted because a dependent feature
// is missing, which we should cache.
available = false
} else if err != nil {
// We couldn't execute the feature test to a point
// where it could make a determination.
// Don't cache the result, just return it.
return fmt.Errorf("can't detect support for %s: %w", name, err)
}
ft.successful = true
if !available {
ft.result = &UnsupportedFeatureError{
MinimumVersion: v,
Name: name,
}
}
})
return result
return ft.result
}
}
@ -69,7 +98,7 @@ func NewVersion(ver string) (Version, error) {
var major, minor, patch uint16
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
if n < 2 {
return Version{}, xerrors.Errorf("invalid version: %s", ver)
return Version{}, fmt.Errorf("invalid version: %s", ver)
}
return Version{major, minor, patch}, nil
}

View File

@ -1,6 +1,6 @@
package internal
import "golang.org/x/xerrors"
import "errors"
// DiscardZeroes makes sure that all written bytes are zero
// before discarding them.
@ -9,7 +9,7 @@ type DiscardZeroes struct{}
func (DiscardZeroes) Write(p []byte) (int, error) {
for _, b := range p {
if b != 0 {
return 0, xerrors.New("encountered non-zero byte")
return 0, errors.New("encountered non-zero byte")
}
}
return len(p), nil

View File

@ -1,16 +1,61 @@
package internal
import (
"fmt"
"path/filepath"
"runtime"
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
//go:generate stringer -output syscall_string.go -type=BPFCmd
// BPFCmd identifies a subcommand of the bpf syscall.
type BPFCmd int
// Well known BPF commands.
const (
BPF_MAP_CREATE BPFCmd = iota
BPF_MAP_LOOKUP_ELEM
BPF_MAP_UPDATE_ELEM
BPF_MAP_DELETE_ELEM
BPF_MAP_GET_NEXT_KEY
BPF_PROG_LOAD
BPF_OBJ_PIN
BPF_OBJ_GET
BPF_PROG_ATTACH
BPF_PROG_DETACH
BPF_PROG_TEST_RUN
BPF_PROG_GET_NEXT_ID
BPF_MAP_GET_NEXT_ID
BPF_PROG_GET_FD_BY_ID
BPF_MAP_GET_FD_BY_ID
BPF_OBJ_GET_INFO_BY_FD
BPF_PROG_QUERY
BPF_RAW_TRACEPOINT_OPEN
BPF_BTF_LOAD
BPF_BTF_GET_FD_BY_ID
BPF_TASK_FD_QUERY
BPF_MAP_LOOKUP_AND_DELETE_ELEM
BPF_MAP_FREEZE
BPF_BTF_GET_NEXT_ID
BPF_MAP_LOOKUP_BATCH
BPF_MAP_LOOKUP_AND_DELETE_BATCH
BPF_MAP_UPDATE_BATCH
BPF_MAP_DELETE_BATCH
BPF_LINK_CREATE
BPF_LINK_UPDATE
BPF_LINK_GET_FD_BY_ID
BPF_LINK_GET_NEXT_ID
BPF_ENABLE_STATS
BPF_ITER_CREATE
)
// BPF wraps SYS_BPF.
//
// Any pointers contained in attr must use the Pointer type from this package.
func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr)
@ -21,3 +66,74 @@ func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
return r1, err
}
type BPFProgAttachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
AttachFlags uint32
ReplaceBpfFd uint32
}
func BPFProgAttach(attr *BPFProgAttachAttr) error {
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type BPFProgDetachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
}
func BPFProgDetach(attr *BPFProgDetachAttr) error {
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type bpfObjAttr struct {
fileName Pointer
fd uint32
fileFlags uint32
}
const bpfFSType = 0xcafe4a11
// BPFObjPin wraps BPF_OBJ_PIN.
func BPFObjPin(fileName string, fd *FD) error {
dirName := filepath.Dir(fileName)
var statfs unix.Statfs_t
if err := unix.Statfs(dirName, &statfs); err != nil {
return err
}
if uint64(statfs.Type) != bpfFSType {
return fmt.Errorf("%s is not on a bpf filesystem", fileName)
}
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
fd: value,
}
_, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("pin object %s: %w", fileName, err)
}
return nil
}
// BPFObjGet wraps BPF_OBJ_GET.
func BPFObjGet(fileName string) (*FD, error) {
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
}
ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, fmt.Errorf("get object %s: %w", fileName, err)
}
return NewFD(uint32(ptr)), nil
}

View File

@ -0,0 +1,56 @@
// Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT.
package internal
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[BPF_MAP_CREATE-0]
_ = x[BPF_MAP_LOOKUP_ELEM-1]
_ = x[BPF_MAP_UPDATE_ELEM-2]
_ = x[BPF_MAP_DELETE_ELEM-3]
_ = x[BPF_MAP_GET_NEXT_KEY-4]
_ = x[BPF_PROG_LOAD-5]
_ = x[BPF_OBJ_PIN-6]
_ = x[BPF_OBJ_GET-7]
_ = x[BPF_PROG_ATTACH-8]
_ = x[BPF_PROG_DETACH-9]
_ = x[BPF_PROG_TEST_RUN-10]
_ = x[BPF_PROG_GET_NEXT_ID-11]
_ = x[BPF_MAP_GET_NEXT_ID-12]
_ = x[BPF_PROG_GET_FD_BY_ID-13]
_ = x[BPF_MAP_GET_FD_BY_ID-14]
_ = x[BPF_OBJ_GET_INFO_BY_FD-15]
_ = x[BPF_PROG_QUERY-16]
_ = x[BPF_RAW_TRACEPOINT_OPEN-17]
_ = x[BPF_BTF_LOAD-18]
_ = x[BPF_BTF_GET_FD_BY_ID-19]
_ = x[BPF_TASK_FD_QUERY-20]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21]
_ = x[BPF_MAP_FREEZE-22]
_ = x[BPF_BTF_GET_NEXT_ID-23]
_ = x[BPF_MAP_LOOKUP_BATCH-24]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25]
_ = x[BPF_MAP_UPDATE_BATCH-26]
_ = x[BPF_MAP_DELETE_BATCH-27]
_ = x[BPF_LINK_CREATE-28]
_ = x[BPF_LINK_UPDATE-29]
_ = x[BPF_LINK_GET_FD_BY_ID-30]
_ = x[BPF_LINK_GET_NEXT_ID-31]
_ = x[BPF_ENABLE_STATS-32]
_ = x[BPF_ITER_CREATE-33]
}
const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE"
var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617}
func (i BPFCmd) String() string {
if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) {
return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]]
}

View File

@ -10,11 +10,13 @@ import (
const (
ENOENT = linux.ENOENT
EEXIST = linux.EEXIST
EAGAIN = linux.EAGAIN
ENOSPC = linux.ENOSPC
EINVAL = linux.EINVAL
EPOLLIN = linux.EPOLLIN
EINTR = linux.EINTR
EPERM = linux.EPERM
ESRCH = linux.ESRCH
ENODEV = linux.ENODEV
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG

View File

@ -12,10 +12,12 @@ var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime
const (
ENOENT = syscall.ENOENT
EEXIST = syscall.EEXIST
EAGAIN = syscall.EAGAIN
ENOSPC = syscall.ENOSPC
EINVAL = syscall.EINVAL
EINTR = syscall.EINTR
EPERM = syscall.EPERM
ESRCH = syscall.ESRCH
ENODEV = syscall.ENODEV
BPF_F_RDONLY_PROG = 0

View File

@ -1,10 +1,10 @@
package ebpf
import (
"fmt"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal/btf"
"golang.org/x/xerrors"
)
// link resolves bpf-to-bpf calls.
@ -28,7 +28,7 @@ func link(prog *ProgramSpec, libs []*ProgramSpec) error {
needed, err := needSection(insns, lib.Instructions)
if err != nil {
return xerrors.Errorf("linking %s: %w", lib.Name, err)
return fmt.Errorf("linking %s: %w", lib.Name, err)
}
if !needed {
@ -41,7 +41,7 @@ func link(prog *ProgramSpec, libs []*ProgramSpec) error {
if prog.BTF != nil && lib.BTF != nil {
if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil {
return xerrors.Errorf("linking BTF of %s: %w", lib.Name, err)
return fmt.Errorf("linking BTF of %s: %w", lib.Name, err)
}
}
}

108
vendor/github.com/cilium/ebpf/map.go generated vendored
View File

@ -1,20 +1,20 @@
package ebpf
import (
"errors"
"fmt"
"strings"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
"golang.org/x/xerrors"
)
// Errors returned by Map and MapIterator methods.
var (
ErrKeyNotExist = xerrors.New("key does not exist")
ErrIterationAborted = xerrors.New("iteration aborted")
ErrKeyNotExist = errors.New("key does not exist")
ErrKeyExist = errors.New("key already exists")
ErrIterationAborted = errors.New("iteration aborted")
)
// MapID represents the unique ID of an eBPF map
@ -91,7 +91,7 @@ type Map struct {
// You should not use fd after calling this function.
func NewMapFromFD(fd int) (*Map, error) {
if fd < 0 {
return nil, xerrors.New("invalid fd")
return nil, errors.New("invalid fd")
}
bpfFd := internal.NewFD(uint32(fd))
@ -107,14 +107,18 @@ func NewMapFromFD(fd int) (*Map, error) {
//
// Creating a map for the first time will perform feature detection
// by creating small, temporary maps.
//
// The caller is responsible for ensuring the process' rlimit is set
// sufficiently high for locking memory during map creation. This can be done
// by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMap.
func NewMap(spec *MapSpec) (*Map, error) {
if spec.BTF == nil {
return newMapWithBTF(spec, nil)
}
handle, err := btf.NewHandle(btf.MapSpec(spec.BTF))
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
return nil, xerrors.Errorf("can't load BTF: %w", err)
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, fmt.Errorf("can't load BTF: %w", err)
}
return newMapWithBTF(spec, handle)
@ -126,7 +130,7 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
}
if spec.InnerMap == nil {
return nil, xerrors.Errorf("%s requires InnerMap", spec.Type)
return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
}
template, err := createMap(spec.InnerMap, nil, handle)
@ -150,25 +154,25 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
}
if abi.ValueSize != 0 && abi.ValueSize != 4 {
return nil, xerrors.New("ValueSize must be zero or four for map of map")
return nil, errors.New("ValueSize must be zero or four for map of map")
}
abi.ValueSize = 4
case PerfEventArray:
if abi.KeySize != 0 && abi.KeySize != 4 {
return nil, xerrors.New("KeySize must be zero or four for perf event array")
return nil, errors.New("KeySize must be zero or four for perf event array")
}
abi.KeySize = 4
if abi.ValueSize != 0 && abi.ValueSize != 4 {
return nil, xerrors.New("ValueSize must be zero or four for perf event array")
return nil, errors.New("ValueSize must be zero or four for perf event array")
}
abi.ValueSize = 4
if abi.MaxEntries == 0 {
n, err := internal.PossibleCPUs()
if err != nil {
return nil, xerrors.Errorf("perf event array: %w", err)
return nil, fmt.Errorf("perf event array: %w", err)
}
abi.MaxEntries = uint32(n)
}
@ -176,7 +180,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
if err := haveMapMutabilityModifiers(); err != nil {
return nil, xerrors.Errorf("map create: %w", err)
return nil, fmt.Errorf("map create: %w", err)
}
}
@ -192,7 +196,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
var err error
attr.innerMapFd, err = inner.Value()
if err != nil {
return nil, xerrors.Errorf("map create: %w", err)
return nil, fmt.Errorf("map create: %w", err)
}
}
@ -208,7 +212,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
fd, err := bpfMapCreate(&attr)
if err != nil {
return nil, xerrors.Errorf("map create: %w", err)
return nil, fmt.Errorf("map create: %w", err)
}
m, err := newMap(fd, spec.Name, abi)
@ -218,13 +222,13 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
if err := m.populate(spec.Contents); err != nil {
m.Close()
return nil, xerrors.Errorf("map create: can't set initial contents: %w", err)
return nil, fmt.Errorf("map create: can't set initial contents: %w", err)
}
if spec.Freeze {
if err := m.Freeze(); err != nil {
m.Close()
return nil, xerrors.Errorf("can't freeze map: %w", err)
return nil, fmt.Errorf("can't freeze map: %w", err)
}
}
@ -296,9 +300,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
*value = m
return nil
case *Map:
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
case Map:
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
case **Program:
p, err := unmarshalProgram(valueBytes)
@ -310,9 +314,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
*value = p
return nil
case *Program:
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
case Program:
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
default:
return unmarshalBytes(valueOut, valueBytes)
@ -327,11 +331,11 @@ func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return xerrors.Errorf("can't marshal key: %w", err)
return fmt.Errorf("can't marshal key: %w", err)
}
if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil {
return xerrors.Errorf("lookup and delete failed: %w", err)
return fmt.Errorf("lookup and delete failed: %w", err)
}
return unmarshalBytes(valueOut, valueBytes)
@ -345,7 +349,7 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
valuePtr := internal.NewSlicePointer(valueBytes)
err := m.lookup(key, valuePtr)
if xerrors.Is(err, ErrKeyNotExist) {
if errors.Is(err, ErrKeyNotExist) {
return nil, nil
}
@ -355,11 +359,11 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return xerrors.Errorf("can't marshal key: %w", err)
return fmt.Errorf("can't marshal key: %w", err)
}
if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil {
return xerrors.Errorf("lookup failed: %w", err)
return fmt.Errorf("lookup failed: %w", err)
}
return nil
}
@ -389,7 +393,7 @@ func (m *Map) Put(key, value interface{}) error {
func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return xerrors.Errorf("can't marshal key: %w", err)
return fmt.Errorf("can't marshal key: %w", err)
}
var valuePtr internal.Pointer
@ -399,11 +403,11 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
}
if err != nil {
return xerrors.Errorf("can't marshal value: %w", err)
return fmt.Errorf("can't marshal value: %w", err)
}
if err = bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)); err != nil {
return xerrors.Errorf("update failed: %w", err)
return fmt.Errorf("update failed: %w", err)
}
return nil
@ -415,11 +419,11 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
func (m *Map) Delete(key interface{}) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return xerrors.Errorf("can't marshal key: %w", err)
return fmt.Errorf("can't marshal key: %w", err)
}
if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil {
return xerrors.Errorf("delete failed: %w", err)
return fmt.Errorf("delete failed: %w", err)
}
return nil
}
@ -441,7 +445,7 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
}
if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil {
return xerrors.Errorf("can't unmarshal next key: %w", err)
return fmt.Errorf("can't unmarshal next key: %w", err)
}
return nil
}
@ -458,7 +462,7 @@ func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
nextKeyPtr := internal.NewSlicePointer(nextKey)
err := m.nextKey(key, nextKeyPtr)
if xerrors.Is(err, ErrKeyNotExist) {
if errors.Is(err, ErrKeyNotExist) {
return nil, nil
}
@ -474,12 +478,12 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
if key != nil {
keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return xerrors.Errorf("can't marshal key: %w", err)
return fmt.Errorf("can't marshal key: %w", err)
}
}
if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil {
return xerrors.Errorf("next key failed: %w", err)
return fmt.Errorf("next key failed: %w", err)
}
return nil
}
@ -532,7 +536,7 @@ func (m *Map) Clone() (*Map, error) {
dup, err := m.fd.Dup()
if err != nil {
return nil, xerrors.Errorf("can't clone map: %w", err)
return nil, fmt.Errorf("can't clone map: %w", err)
}
return newMap(dup, m.name, &m.abi)
@ -542,7 +546,7 @@ func (m *Map) Clone() (*Map, error) {
//
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
func (m *Map) Pin(fileName string) error {
return bpfPinObject(fileName, m.fd)
return internal.BPFObjPin(fileName, m.fd)
}
// Freeze prevents a map to be modified from user space.
@ -550,11 +554,11 @@ func (m *Map) Pin(fileName string) error {
// It makes no changes to kernel-side restrictions.
func (m *Map) Freeze() error {
if err := haveMapMutabilityModifiers(); err != nil {
return xerrors.Errorf("can't freeze map: %w", err)
return fmt.Errorf("can't freeze map: %w", err)
}
if err := bpfMapFreeze(m.fd); err != nil {
return xerrors.Errorf("can't freeze map: %w", err)
return fmt.Errorf("can't freeze map: %w", err)
}
return nil
}
@ -562,7 +566,7 @@ func (m *Map) Freeze() error {
func (m *Map) populate(contents []MapKV) error {
for _, kv := range contents {
if err := m.Put(kv.Key, kv.Value); err != nil {
return xerrors.Errorf("key %v: %w", kv.Key, err)
return fmt.Errorf("key %v: %w", kv.Key, err)
}
}
return nil
@ -573,7 +577,7 @@ func (m *Map) populate(contents []MapKV) error {
// The function is not compatible with nested maps.
// Use LoadPinnedMapExplicit in these situations.
func LoadPinnedMap(fileName string) (*Map, error) {
fd, err := bpfGetObject(fileName)
fd, err := internal.BPFObjGet(fileName)
if err != nil {
return nil, err
}
@ -587,7 +591,7 @@ func LoadPinnedMap(fileName string) (*Map, error) {
// LoadPinnedMapExplicit loads a map with explicit parameters.
func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
fd, err := bpfGetObject(fileName)
fd, err := internal.BPFObjGet(fileName)
if err != nil {
return nil, err
}
@ -596,7 +600,7 @@ func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
func unmarshalMap(buf []byte) (*Map, error) {
if len(buf) != 4 {
return nil, xerrors.New("map id requires 4 byte value")
return nil, errors.New("map id requires 4 byte value")
}
// Looking up an entry in a nested map or prog array returns an id,
@ -621,12 +625,12 @@ func patchValue(value []byte, typ btf.Type, replacements map[string]interface{})
replaced := make(map[string]bool)
replace := func(name string, offset, size int, replacement interface{}) error {
if offset+size > len(value) {
return xerrors.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size)
return fmt.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size)
}
buf, err := marshalBytes(replacement, size)
if err != nil {
return xerrors.Errorf("marshal %s: %w", name, err)
return fmt.Errorf("marshal %s: %w", name, err)
}
copy(value[offset:offset+size], buf)
@ -650,7 +654,7 @@ func patchValue(value []byte, typ btf.Type, replacements map[string]interface{})
}
default:
return xerrors.Errorf("patching %T is not supported", typ)
return fmt.Errorf("patching %T is not supported", typ)
}
if len(replaced) == len(replacements) {
@ -665,10 +669,10 @@ func patchValue(value []byte, typ btf.Type, replacements map[string]interface{})
}
if len(missing) == 1 {
return xerrors.Errorf("unknown field: %s", missing[0])
return fmt.Errorf("unknown field: %s", missing[0])
}
return xerrors.Errorf("unknown fields: %s", strings.Join(missing, ","))
return fmt.Errorf("unknown fields: %s", strings.Join(missing, ","))
}
// MapIterator iterates a Map.
@ -726,7 +730,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
mi.prevKey = mi.prevBytes
mi.err = mi.target.Lookup(nextBytes, valueOut)
if xerrors.Is(mi.err, ErrKeyNotExist) {
if errors.Is(mi.err, ErrKeyNotExist) {
// Even though the key should be valid, we couldn't look up
// its value. If we're iterating a hash map this is probably
// because a concurrent delete removed the value before we
@ -745,7 +749,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
return mi.err == nil
}
mi.err = xerrors.Errorf("%w", ErrIterationAborted)
mi.err = fmt.Errorf("%w", ErrIterationAborted)
return false
}
@ -762,7 +766,7 @@ func (mi *MapIterator) Err() error {
//
// Returns ErrNotExist, if there is no next eBPF map.
func MapGetNextID(startID MapID) (MapID, error) {
id, err := objGetNextID(_MapGetNextID, uint32(startID))
id, err := objGetNextID(internal.BPF_MAP_GET_NEXT_ID, uint32(startID))
return MapID(id), err
}
@ -770,7 +774,7 @@ func MapGetNextID(startID MapID) (MapID, error) {
//
// Returns ErrNotExist, if there is no eBPF map with the given id.
func NewMapFromID(id MapID) (*Map, error) {
fd, err := bpfObjGetFDByID(_MapGetFDByID, uint32(id))
fd, err := bpfObjGetFDByID(internal.BPF_MAP_GET_FD_BY_ID, uint32(id))
if err != nil {
return nil, err
}

View File

@ -4,13 +4,13 @@ import (
"bytes"
"encoding"
"encoding/binary"
"errors"
"fmt"
"reflect"
"runtime"
"unsafe"
"github.com/cilium/ebpf/internal"
"golang.org/x/xerrors"
)
func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
@ -18,7 +18,7 @@ func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
if length == 0 {
return internal.NewPointer(nil), nil
}
return internal.Pointer{}, xerrors.New("can't use nil as key of map")
return internal.Pointer{}, errors.New("can't use nil as key of map")
}
if ptr, ok := data.(unsafe.Pointer); ok {
@ -42,12 +42,12 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
case []byte:
buf = value
case unsafe.Pointer:
err = xerrors.New("can't marshal from unsafe.Pointer")
err = errors.New("can't marshal from unsafe.Pointer")
default:
var wr bytes.Buffer
err = binary.Write(&wr, internal.NativeEndian, value)
if err != nil {
err = xerrors.Errorf("encoding %T: %v", value, err)
err = fmt.Errorf("encoding %T: %v", value, err)
}
buf = wr.Bytes()
}
@ -56,7 +56,7 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
}
if len(buf) != length {
return nil, xerrors.Errorf("%T doesn't marshal to %d bytes", data, length)
return nil, fmt.Errorf("%T doesn't marshal to %d bytes", data, length)
}
return buf, nil
}
@ -92,13 +92,13 @@ func unmarshalBytes(data interface{}, buf []byte) error {
*value = buf
return nil
case string:
return xerrors.New("require pointer to string")
return errors.New("require pointer to string")
case []byte:
return xerrors.New("require pointer to []byte")
return errors.New("require pointer to []byte")
default:
rd := bytes.NewReader(buf)
if err := binary.Read(rd, internal.NativeEndian, value); err != nil {
return xerrors.Errorf("decoding %T: %v", value, err)
return fmt.Errorf("decoding %T: %v", value, err)
}
return nil
}
@ -113,7 +113,7 @@ func unmarshalBytes(data interface{}, buf []byte) error {
func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, error) {
sliceType := reflect.TypeOf(slice)
if sliceType.Kind() != reflect.Slice {
return internal.Pointer{}, xerrors.New("per-CPU value requires slice")
return internal.Pointer{}, errors.New("per-CPU value requires slice")
}
possibleCPUs, err := internal.PossibleCPUs()
@ -124,7 +124,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
sliceValue := reflect.ValueOf(slice)
sliceLen := sliceValue.Len()
if sliceLen > possibleCPUs {
return internal.Pointer{}, xerrors.Errorf("per-CPU value exceeds number of CPUs")
return internal.Pointer{}, fmt.Errorf("per-CPU value exceeds number of CPUs")
}
alignedElemLength := align(elemLength, 8)
@ -151,7 +151,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error {
slicePtrType := reflect.TypeOf(slicePtr)
if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice {
return xerrors.Errorf("per-cpu value requires pointer to slice")
return fmt.Errorf("per-cpu value requires pointer to slice")
}
possibleCPUs, err := internal.PossibleCPUs()
@ -170,7 +170,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
step := len(buf) / possibleCPUs
if step < elemLength {
return xerrors.Errorf("per-cpu element length is larger than available data")
return fmt.Errorf("per-cpu element length is larger than available data")
}
for i := 0; i < possibleCPUs; i++ {
var elem interface{}
@ -188,7 +188,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
err := unmarshalBytes(elem, elemBytes)
if err != nil {
return xerrors.Errorf("cpu %d: %w", i, err)
return fmt.Errorf("cpu %d: %w", i, err)
}
buf = buf[step:]

158
vendor/github.com/cilium/ebpf/prog.go generated vendored
View File

@ -2,18 +2,17 @@ package ebpf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"strings"
"time"
"unsafe"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
"golang.org/x/xerrors"
)
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
@ -48,16 +47,32 @@ type ProgramSpec struct {
// Name is passed to the kernel as a debug aid. Must only contain
// alpha numeric and '_' characters.
Name string
// Type determines at which hook in the kernel a program will run.
Type ProgramType
AttachType AttachType
// Name of a kernel data structure to attach to. It's interpretation
// depends on Type and AttachType.
AttachTo string
Instructions asm.Instructions
// License of the program. Some helpers are only available if
// the license is deemed compatible with the GPL.
//
// See https://www.kernel.org/doc/html/latest/process/license-rules.html#id1
License string
// Version used by tracing programs.
//
// Deprecated: superseded by BTF.
KernelVersion uint32
// The BTF associated with this program. Changing Instructions
// will most likely invalidate the contained data, and may
// result in errors when attempting to load it into the kernel.
BTF *btf.Program
// The byte order this program was compiled for, may be nil.
ByteOrder binary.ByteOrder
}
// Copy returns a copy of the spec.
@ -83,6 +98,7 @@ type Program struct {
fd *internal.FD
name string
abi ProgramABI
attachType AttachType
}
// NewProgram creates a new Program.
@ -103,8 +119,8 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
}
handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF))
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
return nil, xerrors.Errorf("can't load BTF: %w", err)
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, fmt.Errorf("can't load BTF: %w", err)
}
return newProgramWithBTF(spec, handle, opts)
@ -148,7 +164,7 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
}
err = internal.ErrorWithLog(err, logBuf, logErr)
return nil, xerrors.Errorf("can't load program: %w", err)
return nil, fmt.Errorf("can't load program: %w", err)
}
// NewProgramFromFD creates a program from a raw fd.
@ -158,7 +174,7 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
// Requires at least Linux 4.11.
func NewProgramFromFD(fd int) (*Program, error) {
if fd < 0 {
return nil, xerrors.New("invalid fd")
return nil, errors.New("invalid fd")
}
bpfFd := internal.NewFD(uint32(fd))
@ -181,11 +197,15 @@ func newProgram(fd *internal.FD, name string, abi *ProgramABI) *Program {
func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr, error) {
if len(spec.Instructions) == 0 {
return nil, xerrors.New("Instructions cannot be empty")
return nil, errors.New("Instructions cannot be empty")
}
if len(spec.License) == 0 {
return nil, xerrors.New("License cannot be empty")
return nil, errors.New("License cannot be empty")
}
if spec.ByteOrder != nil && spec.ByteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
}
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
@ -214,7 +234,7 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
recSize, bytes, err := btf.ProgramLineInfos(spec.BTF)
if err != nil {
return nil, xerrors.Errorf("can't get BTF line infos: %w", err)
return nil, fmt.Errorf("can't get BTF line infos: %w", err)
}
attr.lineInfoRecSize = recSize
attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
@ -222,13 +242,23 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF)
if err != nil {
return nil, xerrors.Errorf("can't get BTF function infos: %w", err)
return nil, fmt.Errorf("can't get BTF function infos: %w", err)
}
attr.funcInfoRecSize = recSize
attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
attr.funcInfo = internal.NewSlicePointer(bytes)
}
if spec.AttachTo != "" {
target, err := resolveBTFType(spec.AttachTo, spec.Type, spec.AttachType)
if err != nil {
return nil, err
}
if target != nil {
attr.attachBTFID = target.ID()
}
}
return attr, nil
}
@ -270,7 +300,7 @@ func (p *Program) Clone() (*Program, error) {
dup, err := p.fd.Dup()
if err != nil {
return nil, xerrors.Errorf("can't clone program: %w", err)
return nil, fmt.Errorf("can't clone program: %w", err)
}
return newProgram(dup, p.name, &p.abi), nil
@ -280,8 +310,8 @@ func (p *Program) Clone() (*Program, error) {
//
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
func (p *Program) Pin(fileName string) error {
if err := bpfPinObject(fileName, p.fd); err != nil {
return xerrors.Errorf("can't pin program: %w", err)
if err := internal.BPFObjPin(fileName, p.fd); err != nil {
return fmt.Errorf("can't pin program: %w", err)
}
return nil
}
@ -305,7 +335,7 @@ func (p *Program) Close() error {
func (p *Program) Test(in []byte) (uint32, []byte, error) {
ret, out, _, err := p.testRun(in, 1, nil)
if err != nil {
return ret, nil, xerrors.Errorf("can't test program: %w", err)
return ret, nil, fmt.Errorf("can't test program: %w", err)
}
return ret, out, nil
}
@ -324,12 +354,12 @@ func (p *Program) Test(in []byte) (uint32, []byte, error) {
func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) {
ret, _, total, err := p.testRun(in, repeat, reset)
if err != nil {
return ret, total, xerrors.Errorf("can't benchmark program: %w", err)
return ret, total, fmt.Errorf("can't benchmark program: %w", err)
}
return ret, total, nil
}
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() bool {
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() (bool, error) {
prog, err := NewProgram(&ProgramSpec{
Type: SocketFilter,
Instructions: asm.Instructions{
@ -340,28 +370,23 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() b
})
if err != nil {
// This may be because we lack sufficient permissions, etc.
return false
return false, err
}
defer prog.Close()
fd, err := prog.fd.Value()
if err != nil {
return false
}
// Programs require at least 14 bytes input
in := make([]byte, 14)
attr := bpfProgTestRunAttr{
fd: fd,
fd: uint32(prog.FD()),
dataSizeIn: uint32(len(in)),
dataIn: internal.NewSlicePointer(in),
}
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
err = bpfProgTestRun(&attr)
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return !xerrors.Is(err, unix.EINVAL)
return !errors.Is(err, unix.EINVAL), nil
})
func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) {
@ -403,19 +428,19 @@ func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte,
}
for {
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
err = bpfProgTestRun(&attr)
if err == nil {
break
}
if xerrors.Is(err, unix.EINTR) {
if errors.Is(err, unix.EINTR) {
if reset != nil {
reset()
}
continue
}
return 0, nil, 0, xerrors.Errorf("can't run test: %w", err)
return 0, nil, 0, fmt.Errorf("can't run test: %w", err)
}
if int(attr.dataSizeOut) > cap(out) {
@ -431,7 +456,7 @@ func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte,
func unmarshalProgram(buf []byte) (*Program, error) {
if len(buf) != 4 {
return nil, xerrors.New("program id requires 4 byte value")
return nil, errors.New("program id requires 4 byte value")
}
// Looking up an entry in a nested map or prog array returns an id,
@ -452,10 +477,12 @@ func (p *Program) MarshalBinary() ([]byte, error) {
return buf, nil
}
// Attach a Program to a container object fd
// Attach a Program.
//
// Deprecated: use link.RawAttachProgram instead.
func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
if fd < 0 {
return xerrors.New("invalid fd")
return errors.New("invalid fd")
}
pfd, err := p.fd.Value()
@ -463,20 +490,26 @@ func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
return err
}
attr := bpfProgAlterAttr{
targetFd: uint32(fd),
attachBpfFd: pfd,
attachType: uint32(typ),
attachFlags: uint32(flags),
attr := internal.BPFProgAttachAttr{
TargetFd: uint32(fd),
AttachBpfFd: pfd,
AttachType: uint32(typ),
AttachFlags: uint32(flags),
}
return bpfProgAlter(_ProgAttach, &attr)
return internal.BPFProgAttach(&attr)
}
// Detach a Program from a container object fd
// Detach a Program.
//
// Deprecated: use link.RawDetachProgram instead.
func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
if fd < 0 {
return xerrors.New("invalid fd")
return errors.New("invalid fd")
}
if flags != 0 {
return errors.New("flags must be zero")
}
pfd, err := p.fd.Value()
@ -484,21 +517,20 @@ func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
return err
}
attr := bpfProgAlterAttr{
targetFd: uint32(fd),
attachBpfFd: pfd,
attachType: uint32(typ),
attachFlags: uint32(flags),
attr := internal.BPFProgDetachAttr{
TargetFd: uint32(fd),
AttachBpfFd: pfd,
AttachType: uint32(typ),
}
return bpfProgAlter(_ProgDetach, &attr)
return internal.BPFProgDetach(&attr)
}
// LoadPinnedProgram loads a Program from a BPF file.
//
// Requires at least Linux 4.11.
func LoadPinnedProgram(fileName string) (*Program, error) {
fd, err := bpfGetObject(fileName)
fd, err := internal.BPFObjGet(fileName)
if err != nil {
return nil, err
}
@ -506,7 +538,7 @@ func LoadPinnedProgram(fileName string) (*Program, error) {
name, abi, err := newProgramABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, xerrors.Errorf("can't get ABI for %s: %w", fileName, err)
return nil, fmt.Errorf("can't get ABI for %s: %w", fileName, err)
}
return newProgram(fd, name, abi), nil
@ -532,7 +564,7 @@ func SanitizeName(name string, replacement rune) string {
//
// Returns ErrNotExist, if there is no next eBPF program.
func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
id, err := objGetNextID(_ProgGetNextID, uint32(startID))
id, err := objGetNextID(internal.BPF_PROG_GET_NEXT_ID, uint32(startID))
return ProgramID(id), err
}
@ -540,7 +572,7 @@ func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
//
// Returns ErrNotExist, if there is no eBPF program with the given id.
func NewProgramFromID(id ProgramID) (*Program, error) {
fd, err := bpfObjGetFDByID(_ProgGetFDByID, uint32(id))
fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
if err != nil {
return nil, err
}
@ -562,3 +594,29 @@ func (p *Program) ID() (ProgramID, error) {
}
return ProgramID(info.id), nil
}
func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
kernel, err := btf.LoadKernelSpec()
if err != nil {
return nil, fmt.Errorf("can't resolve BTF type %s: %w", name, err)
}
type match struct {
p ProgramType
a AttachType
}
target := match{progType, attachType}
switch target {
case match{Tracing, AttachTraceIter}:
var target btf.Func
if err := kernel.FindType("bpf_iter_"+name, &target); err != nil {
return nil, fmt.Errorf("can't resolve BTF for iterator %s: %w", name, err)
}
return &target, nil
default:
return nil, nil
}
}

Some files were not shown because too many files have changed in this diff Show More