Compare commits
246 Commits
Author | SHA1 | Date |
---|---|---|
|
7c6167725b | |
|
b63cf81bb5 | |
|
e86b33e7c5 | |
|
71fa86684d | |
|
9d8d33bd67 | |
|
bfdd693beb | |
|
2fe6ea2d9f | |
|
4590fdba26 | |
|
a028edf9cb | |
|
22a4f30f48 | |
|
34b21b6dc3 | |
|
63fefe0f20 | |
|
591153cb8f | |
|
8a24390d18 | |
|
97f2983630 | |
|
e2a50af30e | |
|
1a717caef7 | |
|
d363e25d2a | |
|
bdb2268d7b | |
|
c5e4904edc | |
|
630a09f34e | |
|
51eaa377ce | |
|
b8a02cc18f | |
|
6c8ecb124e | |
|
6a2d670f51 | |
|
e9fdef4dbf | |
|
76a55c7ad9 | |
|
e6400f5b96 | |
|
bad4003415 | |
|
a26fed5314 | |
|
1a9b6396ef | |
|
b03cedfb33 | |
|
1326c0606a | |
|
898f7b190e | |
|
3b4191616c | |
|
ff7d010baf | |
|
7af78de4f7 | |
|
00e2d7245d | |
|
f72ed843c0 | |
|
0bc7da7ab1 | |
|
51015d1966 | |
|
f9b9e536f8 | |
|
2f5237395e | |
|
b897a7d464 | |
|
7a6c9d3a3d | |
|
86b2c1c2cc | |
|
266bdfe05f | |
|
1ec8ce61a5 | |
|
ba91441159 | |
|
5553f90b1c | |
|
681179f1bd | |
|
b486e8181e | |
|
cd907412d5 | |
|
969537eb7a | |
|
ec31ff1e86 | |
|
fe7a74ceed | |
|
01ca720a78 | |
|
57e5d60a3f | |
|
062cbb9697 | |
|
12a6b5a7ef | |
|
d7918b036c | |
|
8b59acfd3d | |
|
3082eed8f1 | |
|
a4c0f838cd | |
|
fff84e808d | |
|
5ed5002d6c | |
|
ad9b137be0 | |
|
1c78fe1b15 | |
|
a7bafcb864 | |
|
8cdee7b11e | |
|
e8f359faac | |
|
c6dd74b3e7 | |
|
86c18727bb | |
|
162cf0d113 | |
|
c23a2764ac | |
|
3cbd5e812a | |
|
e8062f8abe | |
|
a12ae46058 | |
|
a868c45bf8 | |
|
32c22ee33f | |
|
560bae97b8 | |
|
8c709c228c | |
|
d560060775 | |
|
21d936bbef | |
|
fb98b83d20 | |
|
61c7d317ea | |
|
55a5929566 | |
|
e98e224fab | |
|
56d0907d7d | |
|
9e495e0a80 | |
|
9745218df4 | |
|
16395c5ed7 | |
|
8a9af6861b | |
|
8c5ed87d79 | |
|
5d4d04f251 | |
|
ce7d972ba4 | |
|
8c4b4dcc6b | |
|
32e2606d27 | |
|
38f6ab6090 | |
|
5e3793b640 | |
|
57823f9c42 | |
|
4cc0634fc1 | |
|
1c8eaf3085 | |
|
d525ee9695 | |
|
37fbd3020f | |
|
cf1b3ce101 | |
|
3b5d827ce8 | |
|
967fea3e8e | |
|
39ea9e139f | |
|
6fdae39069 | |
|
28d462ed74 | |
|
4e983ea172 | |
|
89503c8f6b | |
|
ab5d270372 | |
|
c5eecd3be7 | |
|
d131bebcca | |
|
c3a431d834 | |
|
3718eaafa4 | |
|
b09d71ad89 | |
|
9ca3f9f7b5 | |
|
3280db1a60 | |
|
f1cc6af344 | |
|
00e4b496e1 | |
|
28f1b41688 | |
|
82113f6b3a | |
|
b9068bfb01 | |
|
3f6c6065a8 | |
|
7021239842 | |
|
bed5713b5a | |
|
19d707a515 | |
|
9ee6f0a8ba | |
|
bc40b01a56 | |
|
8cc1f7aedd | |
|
bd33a9cdce | |
|
e6accd4cfe | |
|
544c991c77 | |
|
1003459cbc | |
|
36a96f5d5a | |
|
8013aee634 | |
|
a742ba49c8 | |
|
cc4650c778 | |
|
872fc48dce | |
|
c9476b811f | |
|
d6a8b8c7bf | |
|
b7030ae105 | |
|
2258f0a2ce | |
|
ee91033618 | |
|
2543f4f6e5 | |
|
245cc45ad4 | |
|
934aee1fc5 | |
|
3fee319eaf | |
|
e33c59f550 | |
|
ad1d706bc1 | |
|
0dd7377070 | |
|
1ac29b4441 | |
|
4de230431d | |
|
e3e0f2acff | |
|
623923ef48 | |
|
bdb5040dc4 | |
|
afc6afabe4 | |
|
c57f0ab6d2 | |
|
5c855cb55a | |
|
1244014eee | |
|
ef71fe9e52 | |
|
998f008301 | |
|
5d1ff36303 | |
|
7327b3c07e | |
|
a5abc427ed | |
|
7513108440 | |
|
7f40ff45a2 | |
|
81d698b67b | |
|
8b16fc7912 | |
|
f49c1ba592 | |
|
aac3eb4fa5 | |
|
c378484c29 | |
|
65ccce260b | |
|
6385f4e518 | |
|
641cabfc84 | |
|
7a25813f29 | |
|
bd2d0c048e | |
|
9fac5591f3 | |
|
ca92d4d70e | |
|
4f6665442d | |
|
76f6883c86 | |
|
4a30d950c6 | |
|
e568b43963 | |
|
511749e593 | |
|
70c739725d | |
|
1910bb0fdb | |
|
70364b44d1 | |
|
b8769db559 | |
|
feff03e6dc | |
|
f5cea64ac7 | |
|
b059fab415 | |
|
a076a6ff65 | |
|
c16213717f | |
|
8a276e7fe6 | |
|
feb3681126 | |
|
32107099d0 | |
|
a5a1db612a | |
|
89e1fbb867 | |
|
6eb2ec5d88 | |
|
53f03f5398 | |
|
ba736858dd | |
|
bddafc93a9 | |
|
9fcc943fd4 | |
|
50e7112d7d | |
|
ab48870ff9 | |
|
4e42394b75 | |
|
4a24d7e884 | |
|
febb2eb0f0 | |
|
040dd03b90 | |
|
8c656034bf | |
|
f7e344f8ce | |
|
1dd355b45c | |
|
73f4c1da35 | |
|
3b5f1881e1 | |
|
e86c7e6cb1 | |
|
2150d6524a | |
|
e6400c50dc | |
|
6ab18cde1b | |
|
9f10df075c | |
|
9acbb81149 | |
|
f6ca81985f | |
|
c5e3818aab | |
|
23836342cf | |
|
9ea9e07037 | |
|
100ce3a292 | |
|
c7df7c4f65 | |
|
817da261f9 | |
|
b2bc06aba8 | |
|
f0d6307b00 | |
|
b14a9214a3 | |
|
2bee3bb8aa | |
|
1d6c433812 | |
|
ade24a0cf7 | |
|
85b2b3704d | |
|
33b553f78f | |
|
9b805a9a37 | |
|
1cf35fbdc5 | |
|
9c75b4cdcd | |
|
9f0e26560a | |
|
544d5124de | |
|
54693c9b5c | |
|
33508af811 | |
|
e1c55123fd |
73
.cirrus.yml
73
.cirrus.yml
|
@ -10,9 +10,9 @@ env:
|
|||
HOME: "/root" # not set by default
|
||||
GOCACHE: "${HOME}/.cache/go-build"
|
||||
|
||||
# VM Images are maintained in the libpod repo.
|
||||
_BUILT_IMAGE_SUFFIX: "libpod-6224667180531712" # From the packer output of 'build_vm_images_script'
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-32-${_BUILT_IMAGE_SUFFIX}"
|
||||
# VM Image built in containers/automation_images
|
||||
IMAGE_SUFFIX: "c20250324t111922z-f41f40d13"
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
|
||||
|
||||
# Must be defined true when testing w/in containers
|
||||
CONTAINER: "false"
|
||||
|
@ -21,21 +21,11 @@ env:
|
|||
gcp_credentials: ENCRYPTED[2ba9cffb563741f8538eab6d4a8b2d4684c0de23693a8ade80aced34596669a87a6c01e45ce45fad7f7db1d995e8c777]
|
||||
|
||||
|
||||
# Default VM to use unless set or modified by task
|
||||
gce_instance:
|
||||
image_project: "${IMAGE_PROJECT}"
|
||||
zone: "us-central1-c" # Required by Cirrus for the time being
|
||||
cpu: 2
|
||||
memory: "4Gb"
|
||||
disk: 200 # Required for performance reasons
|
||||
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||
|
||||
|
||||
# Update metadata on VM images referenced by this repository state
|
||||
meta_task:
|
||||
|
||||
container:
|
||||
image: "quay.io/libpod/imgts:master" # maintained in libpod repo
|
||||
image: "quay.io/libpod/imgts:latest"
|
||||
cpu: 1
|
||||
memory: 1
|
||||
|
||||
|
@ -58,49 +48,27 @@ meta_task:
|
|||
vendor_task:
|
||||
|
||||
container:
|
||||
image: golang:1.14
|
||||
image: golang:1.23
|
||||
|
||||
script:
|
||||
- make vendor
|
||||
|
||||
|
||||
# build binary and docs
|
||||
build_task:
|
||||
|
||||
# Uses VM via default gce_instance (above)
|
||||
gce_instance:
|
||||
matrix:
|
||||
image_name: $FEDORA_CACHE_IMAGE_NAME
|
||||
|
||||
# Avoid downloading this stuff every time
|
||||
gocache_cache:
|
||||
folder: "${GOCACHE}"
|
||||
fingerprint_script: $SCRIPT_BASE/cache_fingerprint.sh
|
||||
|
||||
# Avoid needless rebuilding of tooling binaries
|
||||
gopath_cache:
|
||||
folder: "${GOPATH}/bin"
|
||||
fingerprint_script: $SCRIPT_BASE/cache_fingerprint.sh
|
||||
|
||||
# Avoid needless rebuilding of source binaries
|
||||
gosrc_bin_cache:
|
||||
folder: "${CIRRUS_WORKING_DIR}/bin"
|
||||
fingerprint_script: $SCRIPT_BASE/cache_fingerprint.sh
|
||||
|
||||
script:
|
||||
- $SCRIPT_BASE/setup.sh
|
||||
- make binary
|
||||
- make install.tools
|
||||
- make validate
|
||||
- make docs
|
||||
|
||||
|
||||
# run unit and integration tests
|
||||
test_task:
|
||||
build_and_test_task:
|
||||
|
||||
gce_instance:
|
||||
matrix:
|
||||
image_name: $FEDORA_CACHE_IMAGE_NAME
|
||||
image_project: "${IMAGE_PROJECT}"
|
||||
zone: "us-central1-c" # Required by Cirrus for the time being
|
||||
cpu: 2
|
||||
memory: "4Gb"
|
||||
disk: 200 # Required for performance reasons
|
||||
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||
|
||||
matrix:
|
||||
- name: "Test on Fedora"
|
||||
gce_instance:
|
||||
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||
|
||||
# Avoid downloading this stuff every time
|
||||
gocache_cache:
|
||||
|
@ -125,10 +93,17 @@ test_task:
|
|||
script:
|
||||
- $SCRIPT_BASE/setup.sh
|
||||
- make binary
|
||||
- make install.tools
|
||||
- make validate
|
||||
- make docs
|
||||
- sudo make PREFIX=/usr install
|
||||
- make test-unit
|
||||
- sudo make test-integration
|
||||
|
||||
# The hook runs in the background, errors will be in the journal.
|
||||
always:
|
||||
journal_script: journalctl -b
|
||||
|
||||
binaries_artifacts:
|
||||
path: "bin/*"
|
||||
type: "application/octet-stream"
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Renovate is a service similar to GitHub Dependabot, but with
|
||||
(fantastically) more configuration options. So many options
|
||||
in fact, if you're new I recommend glossing over this cheat-sheet
|
||||
prior to the official documentation:
|
||||
|
||||
https://www.augmentedmind.de/2021/07/25/renovate-bot-cheat-sheet
|
||||
|
||||
Configuration Update/Change Procedure:
|
||||
1. Make changes
|
||||
2. Manually validate changes (from repo-root):
|
||||
|
||||
podman run -it \
|
||||
-v ./.github/renovate.json5:/usr/src/app/renovate.json5:z \
|
||||
docker.io/renovate/renovate:latest \
|
||||
renovate-config-validator
|
||||
3. Commit.
|
||||
|
||||
Configuration Reference:
|
||||
https://docs.renovatebot.com/configuration-options/
|
||||
|
||||
Monitoring Dashboard:
|
||||
https://app.renovatebot.com/dashboard#github/containers
|
||||
|
||||
Note: The Renovate bot will create/manage it's business on
|
||||
branches named 'renovate/*'. Otherwise, and by
|
||||
default, the only the copy of this file that matters
|
||||
is the one on the `main` branch. No other branches
|
||||
will be monitored or touched in any way.
|
||||
*/
|
||||
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
|
||||
/*************************************************
|
||||
****** Global/general configuration options *****
|
||||
*************************************************/
|
||||
|
||||
// Re-use predefined sets of configuration options to DRY
|
||||
"extends": [
|
||||
// https://github.com/containers/automation/blob/main/renovate/defaults.json5
|
||||
"github>containers/automation//renovate/defaults.json5"
|
||||
],
|
||||
|
||||
// Permit automatic rebasing when base-branch changes by more than
|
||||
// one commit.
|
||||
"rebaseWhen": "behind-base-branch",
|
||||
|
||||
/*************************************************
|
||||
*** Repository-specific configuration options ***
|
||||
*************************************************/
|
||||
|
||||
"assignees": ["vrothberg", "rhatdan"],
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
## The oci-seccomp-bpf-hook Project Community Code of Conduct
|
||||
|
||||
The oci-seccomp-bpf-hook project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/master/CODE-OF-CONDUCT.md).
|
||||
The oci-seccomp-bpf-hook project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md).
|
||||
|
|
12
Makefile
12
Makefile
|
@ -6,6 +6,7 @@ GO_BUILD=$(GO) build
|
|||
ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true)
|
||||
GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor
|
||||
endif
|
||||
BUILDDIR ?= .
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
|
||||
|
@ -46,11 +47,11 @@ docs:
|
|||
|
||||
.PHONY: binary
|
||||
binary:
|
||||
$(GO_BUILD) -mod=vendor -o bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
||||
$(GO_BUILD) -mod=vendor -o $(BUILDDIR)/bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
||||
|
||||
.PHONY: validate
|
||||
validate:
|
||||
$(GOBIN)/golangci-lint run
|
||||
./build/golangci-lint run
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
|
@ -73,10 +74,9 @@ test-unit:
|
|||
.PHONY: install.tools
|
||||
install.tools: .install.golangci-lint .install.md2man
|
||||
|
||||
.install.golangci-lint: VERSION=v1.60.3
|
||||
.install.golangci-lint:
|
||||
if [ ! -x "$(GOBIN)/golangci-lint" ]; then \
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOBIN)/ v1.18.0; \
|
||||
fi
|
||||
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/$(VERSION)/install.sh | sh -s -- -b ./build $(VERSION)
|
||||
|
||||
.install.md2man:
|
||||
if [ -z "$(shell type -P go-md2man)" ]; then \
|
||||
|
@ -95,7 +95,7 @@ install.docs:
|
|||
install-nobuild: install.docs-nobuild
|
||||
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_BIN_DIR)
|
||||
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_DIR)
|
||||
install $(SELINUXOPT) -m 755 bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
||||
install $(SELINUXOPT) -m 755 $(BUILDDIR)/bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
||||
install $(SELINUXOPT) -m 644 oci-seccomp-bpf-hook.json $(DESTDIR)$(HOOK_DIR)
|
||||
sed -i 's|HOOK_BIN_DIR|$(HOOK_BIN_DIR)|g' $(DESTDIR)$(HOOK_DIR)/oci-seccomp-bpf-hook.json
|
||||
|
||||
|
|
12
README.md
12
README.md
|
@ -1,16 +1,16 @@
|
|||
[](https://cirrus-ci.com/github/containers/oci-seccomp-bpf-hook/master)
|
||||
[](https://cirrus-ci.com/github/containers/oci-seccomp-bpf-hook/main)
|
||||
|
||||
# oci-seccomp-bpf-hook
|
||||
|
||||
OCI hooks to generate seccomp profiles by tracing the syscalls made by the container. The generated profile would whitelist all the syscalls made and blacklist every other syscall.
|
||||
This project provides an OCI hook to generate seccomp profiles by tracing the syscalls made by the container. The generated profile would allow all the syscalls made and deny every other syscall.
|
||||
|
||||
The syscalls are traced by launching a binary by using the prestart OCI hook. The binary started spawns a child process which attaches function `enter_trace` to the `raw_syscalls:sys_enter` tracepoint using eBPF. The function looks at all the syscalls made on the system and writes the syscalls which have the same PID namespace as the container to the perf buffer. The perf buffer is read by the process in the userspace and generates a seccomp profile when the container exits.
|
||||
|
||||
There are a few limitations to this approach:
|
||||
|
||||
* Needs CAP_SYS_ADMIN to run
|
||||
* Needs `CAP_SYS_ADMIN` to run
|
||||
* Compiles C code on the fly
|
||||
* Cannot use podman run --rm along with this ability
|
||||
* Cannot use `podman run --rm` along with this ability
|
||||
|
||||
To build it, we need extra dependencies namely bcc-devel and kernel-headers for Fedora and bcc-tools and linux-headers-[..] for Ubuntu.
|
||||
|
||||
|
@ -21,3 +21,7 @@ sudo podman run --annotation io.containers.trace-syscall="if:[absolute path to t
|
|||
```
|
||||
|
||||
The profile will be created at the output path provided to the annotation. Providing `of:` is mandatory, while `if:` is optional. An input file can be used to create a baseline and newly recorded syscalls will be added to the set and written to the output. If a syscall is blocked in the base profile, then it will remain blocked in the output file even if it is recorded while tracing.
|
||||
|
||||
Please refer to an article on [Enable Sysadmin](https://www.redhat.com/sysadmin/container-security-seccomp) for more details.
|
||||
|
||||
`Copyright {2018-2022} {containers/oci-seccomp-bpf-hook maintainers}`
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
## Security and Disclosure Information Policy for the oci-seccomp-bpf-hook Project
|
||||
|
||||
The oci-seccomp-bpf-hook Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/master/SECURITY.md) for the Containers Projects.
|
||||
The oci-seccomp-bpf-hook Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM fedora:31
|
||||
FROM fedora:latest
|
||||
|
||||
ENV GOPATH=/var/tmp/go
|
||||
ENV GOSRC=$GOPATH/src/github.com/containers/oci-seccomp-bpf-hook
|
||||
|
|
|
@ -1,61 +1,50 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Library of common, shared utility functions. This file is intended
|
||||
# to be sourced by other scripts, not called directly.
|
||||
|
||||
# BEGIN Global export of all variables
|
||||
set -a
|
||||
|
||||
# Due to differences across platforms and runtime execution environments,
|
||||
# handling of the (otherwise) default shell setup is non-uniform. Rather
|
||||
# than attempt to workaround differences, simply force-load/set required
|
||||
# items every time this library is utilized.
|
||||
USER="$(whoami)"
|
||||
HOME="$(getent passwd $USER | cut -d : -f 6)"
|
||||
# Some platforms set and make this read-only
|
||||
[[ -n "$UID" ]] || \
|
||||
UID=$(getent passwd $USER | cut -d : -f 3)
|
||||
|
||||
# Automation library installed at image-build time,
|
||||
# defining $AUTOMATION_LIB_PATH in this file.
|
||||
if [[ -r "/etc/automation_environment" ]]; then
|
||||
source /etc/automation_environment
|
||||
fi
|
||||
# shellcheck disable=SC2154
|
||||
if [[ -n "$AUTOMATION_LIB_PATH" ]]; then
|
||||
# shellcheck source=/usr/share/automation/lib/common_lib.sh
|
||||
source $AUTOMATION_LIB_PATH/common_lib.sh
|
||||
else
|
||||
(
|
||||
echo "WARNING: It does not appear that containers/automation was installed."
|
||||
echo " Functionality of most of this library will be negatively impacted"
|
||||
echo " This ${BASH_SOURCE[0]} was loaded by ${BASH_SOURCE[1]}"
|
||||
) > /dev/stderr
|
||||
fi
|
||||
|
||||
GOSRC="${GOSRC:-$(realpath $(dirname $0)/../../)}"
|
||||
SCRIPT_BASE="${SCRIPT_BASE:-./contrib/cirrus}"
|
||||
|
||||
# GCE image-name compatible string representation of distribution name
|
||||
OS_RELEASE_ID="$(source /etc/os-release; echo $ID)"
|
||||
# GCE image-name compatible string representation of distribution _major_ version
|
||||
OS_RELEASE_VER="$(source /etc/os-release; echo $VERSION_ID | cut -d '.' -f 1)"
|
||||
# Combined to ease soe usage
|
||||
OS_REL_VER="${OS_RELEASE_ID}-${OS_RELEASE_VER}"
|
||||
# Set 'true' when operating inside a container
|
||||
CONTAINER="${CONTAINER:-false}"
|
||||
|
||||
if type -P go &> /dev/null; then
|
||||
set -a && eval "$(go env)" && set +a
|
||||
eval "$(go env)"
|
||||
fi
|
||||
|
||||
die() {
|
||||
echo "************************************************"
|
||||
echo ">>>>> ${1:-FATAL ERROR (but no message given!) in ${FUNCNAME[1]}()}"
|
||||
echo "************************************************"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Pass in a list of one or more envariable names; exit non-zero with
|
||||
# helpful error message if any value is empty
|
||||
req_env_var() {
|
||||
# Provide context. If invoked from function use its name; else script name
|
||||
local caller=${FUNCNAME[1]}
|
||||
if [[ -n "$caller" ]]; then
|
||||
# Indicate that it's a function name
|
||||
caller="$caller()"
|
||||
else
|
||||
# Not called from a function: use script name
|
||||
caller=$(basename $0)
|
||||
fi
|
||||
|
||||
# Usage check
|
||||
[[ -n "$1" ]] || die "FATAL: req_env_var: invoked without arguments"
|
||||
|
||||
# Each input arg is an envariable name, e.g. HOME PATH etc. Expand each.
|
||||
# If any is empty, bail out and explain why.
|
||||
for i; do
|
||||
if [[ -z "${!i}" ]]; then
|
||||
die "FATAL: $caller requires \$$i to be non-empty"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Helper/wrapper script to only show stderr/stdout on non-zero exit
|
||||
install_ooe() {
|
||||
req_env_var GOSRC SCRIPT_BASE
|
||||
echo "Installing script to mask stdout/stderr unless non-zero exit."
|
||||
sudo install -D -m 755 "$GOSRC/$SCRIPT_BASE/ooe.sh" /usr/local/bin/ooe.sh
|
||||
}
|
||||
# END Global export of all variables
|
||||
set +a
|
||||
|
||||
bad_os_id_ver() {
|
||||
echo "Unknown/Unsupported distro. $OS_RELEASE_ID and/or version $OS_RELEASE_VER for $(basename $0)"
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script executes a command while logging all output to a temporary
|
||||
# file. If the command exits non-zero, then all output is sent to the console,
|
||||
# before returning the exit code. If the script itself fails, the exit code 121
|
||||
# is returned.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_BASEDIR="$(basename $0)"
|
||||
|
||||
badusage() {
|
||||
echo "Incorrect usage: $SCRIPT_BASEDIR) <command> [options]" > /dev/stderr
|
||||
echo "ERROR: $1"
|
||||
exit 121
|
||||
}
|
||||
|
||||
COMMAND="$@"
|
||||
[[ -n "$COMMAND" ]] || badusage "No command specified"
|
||||
|
||||
OUTPUT_TMPFILE="$(mktemp -p '' ${SCRIPT_BASEDIR}_output_XXXX)"
|
||||
output_on_error() {
|
||||
RET=$?
|
||||
set +e
|
||||
if [[ "$RET" -ne "0" ]]
|
||||
then
|
||||
echo "---------------------------"
|
||||
cat "$OUTPUT_TMPFILE"
|
||||
echo "[$(date --iso-8601=second)] <exit $RET> $COMMAND"
|
||||
fi
|
||||
rm -f "$OUTPUT_TMPFILE"
|
||||
}
|
||||
trap "output_on_error" EXIT
|
||||
|
||||
"$@" 2>&1 | while IFS='' read LINE # Preserve leading/trailing whitespace
|
||||
do
|
||||
# Every stdout and (copied) stderr line
|
||||
echo "[$(date --iso-8601=second)] $LINE"
|
||||
done >> "$OUTPUT_TMPFILE"
|
|
@ -6,10 +6,9 @@ set -e
|
|||
|
||||
source $(dirname $0)/lib.sh
|
||||
|
||||
cd $GOSRC
|
||||
req_env_vars OS_RELEASE_ID GOSRC
|
||||
|
||||
# Only Output on Error wrapper
|
||||
install_ooe
|
||||
cd $GOSRC
|
||||
|
||||
CRITICAL_PKGS=()
|
||||
INSTALL_PACKAGES=()
|
||||
|
@ -25,7 +24,7 @@ case "$OS_RELEASE_ID" in
|
|||
ooe.sh dnf update -y
|
||||
fi
|
||||
|
||||
# Installed AND separetly querried/displayed
|
||||
# Installed AND separately querried/displayed
|
||||
CRITICAL_PKGS+=(\
|
||||
bcc
|
||||
bcc-devel
|
||||
|
@ -93,7 +92,7 @@ case "$OS_RELEASE_ID" in
|
|||
python
|
||||
python3-dateutil
|
||||
python3-psutil
|
||||
python3-pytoml
|
||||
python3-toml
|
||||
selinux-policy-devel
|
||||
unzip
|
||||
vim
|
||||
|
@ -101,14 +100,6 @@ case "$OS_RELEASE_ID" in
|
|||
xz
|
||||
zip
|
||||
)
|
||||
# Some small differences between 30 and 31
|
||||
case "$OS_RELEASE_VER" in
|
||||
32)
|
||||
INSTALL_PACKAGES+=(crun)
|
||||
;;
|
||||
*)
|
||||
bad_os_id_ver ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
bad_os_id_ver
|
||||
|
@ -120,12 +111,6 @@ if [[ "${#INSTALL_PACKAGES[@]}" -gt "0" ]]; then
|
|||
ooe.sh $INSTALL_COMMAND ${INSTALL_PACKAGES[@]}
|
||||
fi
|
||||
|
||||
if [[ "$OS_RELEASE_ID" == "fedora" ]] && [[ -r "/usr/libexec/podman/conmon" ]]
|
||||
then
|
||||
echo "Warning: Working around podman 1.5 w/ embedded conmon."
|
||||
rm -vf '/usr/libexec/podman/conmon'
|
||||
fi
|
||||
|
||||
# Some variables change after package install
|
||||
source $(dirname $0)/lib.sh
|
||||
|
||||
|
@ -135,3 +120,7 @@ ooe.sh make install.tools
|
|||
echo "Names and versions of critical packages"
|
||||
NOT_INSTALLED_RE='(package .+ is not installed)|(no packages found matching .+)'
|
||||
$LIST_COMMAND ${CRITICAL_PKGS[@]} | sed -r -e "s/$NOT_INSTALLED_RE/ > > \0/" | sort
|
||||
|
||||
show_env_vars
|
||||
|
||||
setenforce 0
|
||||
|
|
|
@ -4,14 +4,16 @@ set -e
|
|||
|
||||
source $(dirname $0)/lib.sh
|
||||
|
||||
req_env_vars GOSRC
|
||||
|
||||
BIN=bin/oci-seccomp-bpf-hook
|
||||
|
||||
cd $GOSRC
|
||||
|
||||
# This should have been populated by cache, but if not just build again
|
||||
if [[ ! -x "$BIN" ]]; then
|
||||
echo "Warning: $BIN not found, expecting to find it cached as 'gosrc' from build_task"
|
||||
echo "Re-building binaries"
|
||||
warn "$BIN not found, expecting to find it cached as 'gosrc' from build_task"
|
||||
msg "Re-building binaries"
|
||||
make
|
||||
fi
|
||||
|
||||
|
@ -20,8 +22,8 @@ fi
|
|||
|
||||
ls -la bin
|
||||
|
||||
echo "Installing oci-seccomp-bpf-hook"
|
||||
msg "Installing oci-seccomp-bpf-hook"
|
||||
make install PREFIX=/usr
|
||||
|
||||
echo "Executing integration tests"
|
||||
msg "Executing integration tests"
|
||||
make test-integration BATS_OPTS=--tap
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
set -e
|
||||
|
||||
set -a && eval "$(go env)" && set +a
|
||||
source $(dirname $0)/lib.sh
|
||||
|
||||
req_env_vars GOSRC
|
||||
|
||||
cd $GOSRC
|
||||
|
||||
make validate
|
||||
|
|
|
@ -4,6 +4,8 @@ set -e
|
|||
|
||||
source $(dirname $0)/lib.sh
|
||||
|
||||
req_env_vars GOSRC
|
||||
|
||||
cd $GOSRC
|
||||
|
||||
make vendor
|
||||
|
|
44
ebpf.go
44
ebpf.go
|
@ -14,11 +14,10 @@ type event struct {
|
|||
|
||||
// the source is a bpf program compiled at runtime. Some macro's like
|
||||
// BPF_HASH and BPF_PERF_OUTPUT are expanded during compilation
|
||||
// by bcc. $PARENT_PID get's replaced before compilation with the PID of the container
|
||||
// by bcc. $PARENT_PID gets replaced before compilation with the PID of the container
|
||||
// Complete documentation is available at
|
||||
// https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
|
||||
const source string = `
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/pid_namespace.h>
|
||||
#include <linux/ns_common.h>
|
||||
|
@ -38,6 +37,8 @@ struct mnt_namespace {
|
|||
// of the processes inside the container.
|
||||
BPF_HASH(parent_namespace, u64, unsigned int);
|
||||
|
||||
BPF_HASH(seen_syscalls, int, u64);
|
||||
|
||||
// Opens a custom BPF table to push data to user space via perf ring buffer
|
||||
BPF_PERF_OUTPUT(events);
|
||||
|
||||
|
@ -68,9 +69,10 @@ int enter_trace(struct tracepoint__raw_syscalls__sys_enter* args)
|
|||
struct task_struct *task;
|
||||
struct nsproxy *nsproxy;
|
||||
struct mnt_namespace *mnt_ns;
|
||||
int id = (int)args->id;
|
||||
|
||||
data.pid = bpf_get_current_pid_tgid();
|
||||
data.id = (int)args->id;
|
||||
data.id = id;
|
||||
bpf_get_current_comm(&data.comm, sizeof(data.comm));
|
||||
|
||||
task = (struct task_struct *)bpf_get_current_task();
|
||||
|
@ -84,12 +86,46 @@ int enter_trace(struct tracepoint__raw_syscalls__sys_enter* args)
|
|||
}
|
||||
unsigned int* parent_inum = parent_namespace.lookup_or_init(&key, &zero);
|
||||
|
||||
if (*parent_inum != inum) {
|
||||
if (parent_inum != NULL && *parent_inum != inum) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 seen = 0, *tmp = seen_syscalls.lookup(&id);
|
||||
if (tmp != NULL)
|
||||
seen = *tmp;
|
||||
// Syscalls are not recorded until prctl() is called. The first
|
||||
// invocation of prctl is guaranteed to happen by the supported
|
||||
// OCI runtimes (i.e., runc and crun) as it's being called when
|
||||
// setting the seccomp profile.
|
||||
if (id == __NR_prctl) {
|
||||
// The syscall was already notified.
|
||||
if (seen > 1)
|
||||
return 0;
|
||||
|
||||
// The first time we see prctl, we record it without generating
|
||||
// any event.
|
||||
if (seen == 0) {
|
||||
goto record_and_exit;
|
||||
}
|
||||
} else {
|
||||
// The syscall was already notified.
|
||||
if (seen > 0)
|
||||
return 0;
|
||||
|
||||
// if prctl was not seen, ignore the current syscall.
|
||||
u64 prctl = __NR_prctl;
|
||||
u64 *prctl_seen = seen_syscalls.lookup(&prctl);
|
||||
if (prctl_seen == NULL) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
data.stopTracing = false;
|
||||
events.perf_submit(args, &data, sizeof(data));
|
||||
|
||||
record_and_exit:
|
||||
seen++;
|
||||
seen_syscalls.update(&id, &seen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
34
go.mod
34
go.mod
|
@ -1,16 +1,28 @@
|
|||
module github.com/containers/oci-seccomp-bpf-hook
|
||||
|
||||
go 1.12
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.8
|
||||
|
||||
require (
|
||||
github.com/docker/docker v1.13.1
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/iovisor/gobpf v0.0.0-20200504095308-90dbbdfb1358
|
||||
github.com/opencontainers/runtime-spec v1.0.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/seccomp/libseccomp-golang v0.9.1
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
||||
github.com/containers/common v0.62.3
|
||||
github.com/containers/storage v1.58.0
|
||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4
|
||||
github.com/opencontainers/runtime-spec v1.2.1
|
||||
github.com/seccomp/libseccomp-golang v0.10.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/moby/sys/capability v0.4.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
81
go.sum
81
go.sum
|
@ -1,36 +1,55 @@
|
|||
github.com/containers/common v0.62.3 h1:aOGryqXfW6aKBbHbqOveH7zB+ihavUN03X/2pUSvWFI=
|
||||
github.com/containers/common v0.62.3/go.mod h1:3R8kDox2prC9uj/a2hmXj/YjZz5sBEUNrcDiw51S0Lo=
|
||||
github.com/containers/storage v1.58.0 h1:Q7SyyCCjqgT3wYNgRNIL8o/wUS92heIj2/cc8Sewvcc=
|
||||
github.com/containers/storage v1.58.0/go.mod h1:w7Jl6oG+OpeLGLzlLyOZPkmUso40kjpzgrHUk5tyBlo=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/iovisor/gobpf v0.0.0-20200504095308-90dbbdfb1358 h1:Jwlf8GAKb98CrR9WZbUdpe82++USxerHAHqLVt3C8zc=
|
||||
github.com/iovisor/gobpf v0.0.0-20200504095308-90dbbdfb1358/go.mod h1:+5U5qu5UOu8YJ5oHVLvWKH7/Dr5QNHU7mZ2RfPEeXg8=
|
||||
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/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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4 h1:WpizD4VUT5V+VcaQSvW5BlvFpQYrd2974H9KbiGa5/0=
|
||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4/go.mod h1:WSY9Jj5RhdgC3ci1QaacvbFdQ8cbrEjrpiZbLHLt2s4=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20241108202711-f7e3563b0271 h1:TPj0pMLCTy1CKwmrat3hqTxoZfqOuTy0asG0ccpGk8Q=
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20241108202711-f7e3563b0271/go.mod h1:oIH6VwKkaDOO+SIYZpdwrC/0wKYqrfO6E1sG1j3UVws=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY=
|
||||
github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,217 +1,64 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# For help and usage information, simply execute the script w/o any arguments.
|
||||
#
|
||||
# This script is intended to be run by Red Hat oci-seccomp-bpf-hook developers
|
||||
# who need to debug problems specifically related to Cirrus-CI automated testing.
|
||||
# It requires that you have been granted prior access to create VMs in
|
||||
# google-cloud. For non-Red Hat contributors, VMs are available as-needed,
|
||||
# with supervision upon request.
|
||||
|
||||
set -e
|
||||
|
||||
RED="\e[1;36;41m"
|
||||
YEL="\e[1;33;44m"
|
||||
NOR="\e[0m"
|
||||
USAGE_WARNING="
|
||||
${YEL}WARNING: This will not work without local sudo access to run podman,${NOR}
|
||||
${YEL}and prior authorization to use the oci-seccomp-bpf-hook GCP project. Also,${NOR}
|
||||
${YEL}possession of the proper ssh private key is required.${NOR}
|
||||
"
|
||||
# TODO: Many/most of these values should come from .cirrus.yml
|
||||
ZONE="us-central1-c"
|
||||
CPUS="2"
|
||||
MEMORY="4Gb"
|
||||
DISK="200"
|
||||
PROJECT="oci-seccomp-bpf-hook"
|
||||
GOSRC="/var/tmp/go/src/github.com/containers/oci-seccomp-bpf-hook"
|
||||
GCLOUD_IMAGE=${GCLOUD_IMAGE:-quay.io/cevich/gcloud_centos:latest}
|
||||
GCLOUD_SUDO=${GCLOUD_SUDO-sudo}
|
||||
SSHUSER="root"
|
||||
SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}")
|
||||
SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH")
|
||||
REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../")
|
||||
|
||||
# Shared tmp directory between container and us
|
||||
TMPDIR=$(mktemp -d --tmpdir $(basename $0)_tmpdir_XXXXXX)
|
||||
|
||||
SECCOMPHOOKROOT=$(realpath "$(dirname $0)/../")
|
||||
# else: Assume $PWD is the root of the oci-seccomp-bpf-hook repository
|
||||
[[ "$SECCOMPHOOKROOT" != "/" ]] || SECCOMPHOOKROOT=$PWD
|
||||
|
||||
# Command shortcuts save some typing (asumes $SECCOMPHOOKROOT is subdir of $HOME)
|
||||
PGCLOUD="$GCLOUD_SUDO podman run -it --rm -e AS_ID=$UID -e AS_USER=$USER --security-opt label=disable -v $TMPDIR:$HOME -v $HOME/.config/gcloud:$HOME/.config/gcloud -v $HOME/.config/gcloud/ssh:$HOME/.ssh -v $SECCOMPHOOKROOT:$SECCOMPHOOKROOT $GCLOUD_IMAGE --configuration=oci-seccomp-bpf-hook --project=$PROJECT"
|
||||
SCP_CMD="$PGCLOUD compute scp --zone=$ZONE"
|
||||
|
||||
|
||||
showrun() {
|
||||
if [[ "$1" == "--background" ]]
|
||||
then
|
||||
shift
|
||||
# Properly escape any nested spaces, so command can be copy-pasted
|
||||
echo '+ '$(printf " %q" "$@")' &' > /dev/stderr
|
||||
"$@" &
|
||||
echo -e "${RED}<backgrounded>${NOR}"
|
||||
else
|
||||
echo '+ '$(printf " %q" "$@") > /dev/stderr
|
||||
"$@"
|
||||
# Help detect if we were called by get_ci_vm container
|
||||
GET_CI_VM="${GET_CI_VM:-0}"
|
||||
in_get_ci_vm() {
|
||||
if ((GET_CI_VM==0)); then
|
||||
echo "Error: $1 is not intended for use in this context"
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
RET=$?
|
||||
set +e
|
||||
wait
|
||||
|
||||
# set GCLOUD_DEBUG to leave tmpdir behind for postmortem
|
||||
test -z "$GCLOUD_DEBUG" && rm -rf $TMPDIR
|
||||
|
||||
# Not always called from an exit handler, but should always exit when called
|
||||
exit $RET
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
delvm() {
|
||||
echo -e "\n"
|
||||
echo -e "\n${YEL}Offering to Delete $VMNAME ${RED}(Might take a minute or two)${NOR}"
|
||||
echo -e "\n${YEL}Note: It's safe to answer N, then re-run script again later.${NOR}"
|
||||
showrun $CLEANUP_CMD # prompts for Yes/No
|
||||
cleanup
|
||||
}
|
||||
|
||||
image_hints() {
|
||||
_BIS=$(egrep -m 1 '_BUILT_IMAGE_SUFFIX:[[:space:]+"[[:print:]]+"' \
|
||||
"$SECCOMPHOOKROOT/.cirrus.yml" | cut -d: -f 2 | tr -d '"[:blank:]')
|
||||
egrep '[[:space:]]+[[:alnum:]].+_CACHE_IMAGE_NAME:[[:space:]+"[[:print:]]+"' \
|
||||
"$SECCOMPHOOKROOT/.cirrus.yml" | cut -d: -f 2 | tr -d '"[:blank:]' | \
|
||||
sed -r -e "s/\\\$[{]_BUILT_IMAGE_SUFFIX[}]/$_BIS/" | sort -u
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
echo -e "\n${RED}ERROR: $1${NOR}"
|
||||
echo -e "${YEL}Usage: $(basename $0) <image_name>${NOR}"
|
||||
echo ""
|
||||
if [[ -r ".cirrus.yml" ]]
|
||||
then
|
||||
echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}"
|
||||
image_hints
|
||||
echo ""
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
get_env_vars() {
|
||||
python3 -c '
|
||||
import yaml
|
||||
env=yaml.load(open(".cirrus.yml"), Loader=yaml.SafeLoader)["env"]
|
||||
keys=[k for k in env if "ENCRYPTED" not in str(env[k])]
|
||||
for k,v in env.items():
|
||||
v=str(v)
|
||||
if "ENCRYPTED" not in v:
|
||||
print("{0}=\"{1}\"".format(k, v))
|
||||
'
|
||||
}
|
||||
|
||||
parse_args(){
|
||||
echo -e "$USAGE_WARNING"
|
||||
|
||||
if [[ "$USER" =~ "root" ]]
|
||||
then
|
||||
show_usage "This script must be run as a regular user."
|
||||
fi
|
||||
|
||||
ENVS="$(get_env_vars)"
|
||||
IMAGE_NAME="$1"
|
||||
if [[ -z "$IMAGE_NAME" ]]
|
||||
then
|
||||
show_usage "No image-name specified."
|
||||
fi
|
||||
|
||||
ENVS="$ENVS SPECIALMODE=\"$SPECIALMODE\""
|
||||
SETUP_CMD="env $ENVS $GOSRC/contrib/cirrus/setup.sh"
|
||||
VMNAME="${VMNAME:-${USER}-${IMAGE_NAME}}"
|
||||
CREATE_CMD="$PGCLOUD compute instances create --zone=$ZONE --image-project=libpod-218412 --image=${IMAGE_NAME} --custom-cpu=$CPUS --custom-memory=$MEMORY --boot-disk-size=$DISK --labels=in-use-by=$USER $VMNAME"
|
||||
SSH_CMD="$PGCLOUD compute ssh --zone=$ZONE $SSHUSER@$VMNAME"
|
||||
CLEANUP_CMD="$PGCLOUD compute instances delete --zone=$ZONE --delete-disks=all $VMNAME"
|
||||
}
|
||||
|
||||
##### main
|
||||
|
||||
[[ "${SECCOMPHOOKROOT%%${SECCOMPHOOKROOT##$HOME}}" == "$HOME" ]] || \
|
||||
show_usage "Repo clone must be sub-dir of $HOME: $SECCOMPHOOKROOT"
|
||||
|
||||
cd "$SECCOMPHOOKROOT"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
# Ensure mount-points and data directories exist on host as $USER. Also prevents
|
||||
# permission-denied errors during cleanup() b/c `sudo podman` created mount-points
|
||||
# owned by root.
|
||||
mkdir -p $TMPDIR/${SECCOMPHOOKROOT##$HOME}
|
||||
mkdir -p $TMPDIR/.ssh
|
||||
mkdir -p {$HOME,$TMPDIR}/.config/gcloud/ssh
|
||||
chmod 700 {$HOME,$TMPDIR}/.config/gcloud/ssh $TMPDIR/.ssh
|
||||
|
||||
cd $SECCOMPHOOKROOT
|
||||
|
||||
# Attempt to determine if named 'oci-seccomp-bpf-hook' gcloud configuration exists
|
||||
showrun $PGCLOUD info > $TMPDIR/gcloud-info
|
||||
if egrep -q "Account:.*None" $TMPDIR/gcloud-info
|
||||
then
|
||||
echo -e "\n${YEL}WARNING: Can't find gcloud configuration for 'oci-seccomp-bpf-hook', running init.${NOR}"
|
||||
echo -e " ${RED}Please choose '#1: Re-initialize' and 'login' if asked.${NOR}"
|
||||
echo -e " ${RED}Please set Compute Region and Zone (if asked) to 'us-central1-b'.${NOR}"
|
||||
echo -e " ${RED}DO NOT set any password for the generated ssh key.${NOR}"
|
||||
showrun $PGCLOUD init --project=$PROJECT --console-only --skip-diagnostics
|
||||
|
||||
# Verify it worked (account name == someone@example.com)
|
||||
$PGCLOUD info > $TMPDIR/gcloud-info-after-init
|
||||
if egrep -q "Account:.*None" $TMPDIR/gcloud-info-after-init
|
||||
then
|
||||
echo -e "${RED}ERROR: Could not initialize 'oci-seccomp-bpf-hook' configuration in gcloud.${NOR}"
|
||||
exit 5
|
||||
fi
|
||||
|
||||
# If this is the only config, make it the default to avoid persistent warnings from gcloud
|
||||
[[ -r "$HOME/.config/gcloud/configurations/config_default" ]] || \
|
||||
ln "$HOME/.config/gcloud/configurations/config_oci-seccomp-bpf-hook" \
|
||||
"$HOME/.config/gcloud/configurations/config_default"
|
||||
# get_ci_vm APIv1 container entrypoint calls into this script
|
||||
# to obtain required repo. specific configuration options.
|
||||
if [[ "$1" == "--config" ]]; then
|
||||
in_get_ci_vm "$1"
|
||||
cat <<EOF
|
||||
DESTDIR="/var/tmp/go/src/github.com/containers/oci-seccomp-bpf-hook"
|
||||
UPSTREAM_REPO="https://github.com/containers/oci-seccomp-bpf-hook.git"
|
||||
GCLOUD_PROJECT="oci-seccomp-bpf-hook"
|
||||
GCLOUD_IMGPROJECT="libpod-218412"
|
||||
GCLOUD_CFG="oci-seccomp-bpf-hook"
|
||||
GCLOUD_ZONE="${GCLOUD_ZONE:-us-central1-c}"
|
||||
GCLOUD_CPUS="2"
|
||||
GCLOUD_MEMORY="4Gb"
|
||||
GCLOUD_DISK="200"
|
||||
EOF
|
||||
elif [[ "$1" == "--setup" ]]; then
|
||||
in_get_ci_vm "$1"
|
||||
# get_ci_vm container entrypoint calls us with this option on the
|
||||
# Cirrus-CI environment instance, to perform repo.-specific setup.
|
||||
cd $REPO_DIRPATH
|
||||
echo "+ Loading ./contrib/cirrus/lib.sh" > /dev/stderr
|
||||
source ./contrib/cirrus/lib.sh
|
||||
echo "+ Running environment setup" > /dev/stderr
|
||||
./contrib/cirrus/setup.sh
|
||||
else
|
||||
# Create and access VM for specified Cirrus-CI task
|
||||
mkdir -p $HOME/.config/gcloud/ssh
|
||||
podman run -it --rm \
|
||||
--tz=local \
|
||||
-e NAME="$USER" \
|
||||
-e SRCDIR=/src \
|
||||
-e GCLOUD_ZONE="$GCLOUD_ZONE" \
|
||||
-e DEBUG="${DEBUG:-0}" \
|
||||
-v $REPO_DIRPATH:/src:O \
|
||||
-v $HOME/.config/gcloud:/root/.config/gcloud:z \
|
||||
-v $HOME/.config/gcloud/ssh:/root/.ssh:z \
|
||||
quay.io/libpod/get_ci_vm:latest "$@"
|
||||
fi
|
||||
|
||||
# Couldn't make rsync work with gcloud's ssh wrapper: ssh-keys generated on the fly
|
||||
TARBALL=$VMNAME.tar.bz2
|
||||
echo -e "\n${YEL}Packing up local repository into a tarball.${NOR}"
|
||||
showrun --background tar cjf $TMPDIR/$TARBALL --warning=no-file-changed --exclude-vcs-ignores -C $SECCOMPHOOKROOT .
|
||||
|
||||
trap delvm INT # Allow deleting VM if CTRL-C during create
|
||||
# This fails if VM already exists: permit this usage to re-init
|
||||
echo -e "\n${YEL}Trying to create a VM named $VMNAME\n${RED}(might take a minute/two. Errors ignored).${NOR}"
|
||||
showrun $CREATE_CMD || true # allow re-running commands below when "delete: N"
|
||||
|
||||
# Any subsequent failure should prompt for VM deletion
|
||||
trap delvm EXIT
|
||||
|
||||
echo -e "\n${YEL}Retrying for 30s for ssh port to open (may give some errors)${NOR}"
|
||||
trap 'COUNT=9999' INT
|
||||
ATTEMPTS=10
|
||||
for (( COUNT=1 ; COUNT <= $ATTEMPTS ; COUNT++ ))
|
||||
do
|
||||
if $SSH_CMD --command "true"; then break; else sleep 3s; fi
|
||||
done
|
||||
if (( COUNT > $ATTEMPTS ))
|
||||
then
|
||||
echo -e "\n${RED}Failed${NOR}"
|
||||
exit 7
|
||||
fi
|
||||
echo -e "${YEL}Got it${NOR}"
|
||||
|
||||
echo -e "\n${YEL}Removing and re-creating $GOSRC on $VMNAME.${NOR}"
|
||||
showrun $SSH_CMD --command "rm -rf $GOSRC"
|
||||
showrun $SSH_CMD --command "mkdir -p $GOSRC"
|
||||
|
||||
echo -e "\n${YEL}Transfering tarball to $VMNAME.${NOR}"
|
||||
wait
|
||||
showrun $SCP_CMD $HOME/$TARBALL $SSHUSER@$VMNAME:/tmp/$TARBALL
|
||||
|
||||
echo -e "\n${YEL}Unpacking tarball into $GOSRC on $VMNAME.${NOR}"
|
||||
showrun $SSH_CMD --command "tar xjf /tmp/$TARBALL -C $GOSRC"
|
||||
|
||||
echo -e "\n${YEL}Removing tarball on $VMNAME.${NOR}"
|
||||
showrun $SSH_CMD --command "rm -f /tmp/$TARBALL"
|
||||
|
||||
echo -e "\n${YEL}Executing environment setup${NOR}"
|
||||
showrun $SSH_CMD --command "$SETUP_CMD"
|
||||
|
||||
VMIP=$($PGCLOUD compute instances describe $VMNAME --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
|
||||
|
||||
echo -e "\n${YEL}Connecting to $VMNAME${NOR}\nPublic IP Address: $VMIP\n${RED}(option to delete VM upon logout).${NOR}\n"
|
||||
showrun $SSH_CMD -- -t "cd $GOSRC && exec env $ENVS bash -il"
|
||||
|
|
|
@ -5,14 +5,15 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -20,10 +21,10 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
types "github.com/containers/common/pkg/seccomp"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/iovisor/gobpf/bcc"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
seccomp "github.com/seccomp/libseccomp-golang"
|
||||
"github.com/sirupsen/logrus"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
|
@ -56,6 +57,11 @@ func main() {
|
|||
logrus.AddHook(hook)
|
||||
}
|
||||
|
||||
if os.Getuid() != 0 || unshare.IsRootless() {
|
||||
logrus.Errorf("running the hook requires root privileges")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
runBPF := flag.Int("r", 0, "Trace the specified PID")
|
||||
outputFile := flag.String("o", "", "Path of the output file")
|
||||
inputFile := flag.String("i", "", "Path of the input file")
|
||||
|
@ -108,9 +114,9 @@ func modprobe(module string) error {
|
|||
// detachAndTrace re-executes the current executable to "fork" in go-ish way and
|
||||
// traces the provided PID.
|
||||
func detachAndTrace() error {
|
||||
logrus.Info("Loading kheaders module")
|
||||
logrus.Info("Trying to load `kheaders` module")
|
||||
if err := modprobe("kheaders"); err != nil {
|
||||
return errors.Wrap(err, "error loading kheaders module")
|
||||
logrus.Infof("Loading `kheaders` failed, continuing in hope kernel headers reside on disk: %v", err)
|
||||
}
|
||||
|
||||
// Read the State spec from stdin and unmarshal it.
|
||||
|
@ -123,7 +129,7 @@ func detachAndTrace() error {
|
|||
|
||||
// Sanity check the PID.
|
||||
if s.Pid <= 0 {
|
||||
return errors.Errorf("invalid PID %d (must be greater than 0)", s.Pid)
|
||||
return fmt.Errorf("invalid PID %d (must be greater than 0)", s.Pid)
|
||||
}
|
||||
|
||||
// Parse the State's annotation.
|
||||
|
@ -158,12 +164,12 @@ func detachAndTrace() error {
|
|||
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot determine executable")
|
||||
return fmt.Errorf("cannot determine executable: %v", err)
|
||||
}
|
||||
|
||||
process, err := os.StartProcess(executable, []string{"oci-seccomp-bpf-hook", "-r", strconv.Itoa(s.Pid), "-o", outputFile, "-i", inputFile}, attr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot re-execute")
|
||||
return fmt.Errorf("cannot re-execute: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := process.Release(); err != nil {
|
||||
|
@ -182,7 +188,7 @@ func detachAndTrace() error {
|
|||
case syscall.SIGUSR2:
|
||||
return errors.New("error while tracing")
|
||||
default:
|
||||
return errors.Errorf("unexpected signal %v", s)
|
||||
return fmt.Errorf("unexpected signal %v", s)
|
||||
}
|
||||
|
||||
// The timeout kicked in. Kill the child and return the sad news.
|
||||
|
@ -190,7 +196,7 @@ func detachAndTrace() error {
|
|||
if err := process.Kill(); err != nil {
|
||||
logrus.Errorf("error killing child process: %v", err)
|
||||
}
|
||||
return errors.Errorf("BPF program didn't compile and attach within %d seconds", BPFTimeout)
|
||||
return fmt.Errorf("BPF program didn't compile and attach within %d seconds", BPFTimeout)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -203,7 +209,7 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
|||
ppid := os.Getppid()
|
||||
parentProcess, err := os.FindProcess(ppid)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot find parent process %d", ppid)
|
||||
return fmt.Errorf("cannot find parent process %d: %v", ppid, err)
|
||||
}
|
||||
logrus.Infof("Running floating process PID to attach: %d", pid)
|
||||
|
||||
|
@ -222,46 +228,40 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
|||
m := bcc.NewModule(src, []string{})
|
||||
defer m.Close()
|
||||
|
||||
logrus.Info("Loading enter tracepoint")
|
||||
enterTrace, err := m.LoadTracepoint("enter_trace")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error loading tracepoint")
|
||||
}
|
||||
logrus.Info("Loading exit tracepoint")
|
||||
checkExit, err := m.LoadTracepoint("check_exit")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error loading tracepoint")
|
||||
}
|
||||
logrus.Info("Loaded tracepoints")
|
||||
|
||||
if err := m.AttachTracepoint("raw_syscalls:sys_enter", enterTrace); err != nil {
|
||||
return errors.Wrap(err, "error attaching to tracepoint")
|
||||
}
|
||||
if err := m.AttachTracepoint("sched:sched_process_exit", checkExit); err != nil {
|
||||
return errors.Wrap(err, "error attaching to tracepoint")
|
||||
}
|
||||
|
||||
// Send a signal to the parent process to indicate the compilation has
|
||||
// been completed.
|
||||
if err := parentProcess.Signal(syscall.SIGUSR1); err != nil {
|
||||
return err
|
||||
}
|
||||
signaledParent = true
|
||||
|
||||
table := bcc.NewTable(m.TableId("events"), m)
|
||||
channel := make(chan []byte)
|
||||
perfMap, err := bcc.InitPerfMap(table, channel, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error initializing perf map")
|
||||
return fmt.Errorf("error initializing perf map: %v", err)
|
||||
}
|
||||
|
||||
logrus.Info("Loading enter tracepoint")
|
||||
enterTrace, err := m.LoadTracepoint("enter_trace")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading tracepoint: %v", err)
|
||||
}
|
||||
logrus.Info("Loading exit tracepoint")
|
||||
checkExit, err := m.LoadTracepoint("check_exit")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading tracepoint: %v", err)
|
||||
}
|
||||
logrus.Info("Loaded tracepoints")
|
||||
|
||||
if err := m.AttachTracepoint("raw_syscalls:sys_enter", enterTrace); err != nil {
|
||||
return fmt.Errorf("error attaching to tracepoint: %v", err)
|
||||
}
|
||||
if err := m.AttachTracepoint("sched:sched_process_exit", checkExit); err != nil {
|
||||
return fmt.Errorf("error attaching to tracepoint: %v", err)
|
||||
}
|
||||
|
||||
// Initialize the wait group used to wait for the tracing to be finished.
|
||||
wg.Add(1)
|
||||
var events []event
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
recordSyscalls := false
|
||||
var e event
|
||||
local := []event{}
|
||||
for data := range channel {
|
||||
var e event
|
||||
if err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &e); err != nil {
|
||||
// Return in case of an error. Otherwise, we
|
||||
// could miss stop event and run into an
|
||||
|
@ -273,39 +273,49 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
|||
// The BPF program is done tracing, so we can stop
|
||||
// reading from the perf buffer.
|
||||
if e.StopTracing {
|
||||
// Pointing events at the very end should relax
|
||||
// the memory management a bit as we don't have
|
||||
// to constantly sync across routines.
|
||||
events = local
|
||||
return
|
||||
}
|
||||
|
||||
name, err := syscallIDtoName(e.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting the name for syscall ID %d", e.ID)
|
||||
}
|
||||
// Syscalls are not recorded until prctl() is called. The first
|
||||
// invocation of prctl is guaranteed to happen by the supported
|
||||
// OCI runtimes (i.e., runc and crun) as it's being called when
|
||||
// setting the seccomp profile.
|
||||
if name == "prctl" {
|
||||
recordSyscalls = true
|
||||
}
|
||||
if recordSyscalls {
|
||||
syscalls[name]++
|
||||
}
|
||||
// We are in a hurry to not lose messages, so defer
|
||||
// processing the events when we're done tracing.
|
||||
local = append(local, e)
|
||||
}
|
||||
}()
|
||||
logrus.Info("PerfMap Start")
|
||||
perfMap.Start()
|
||||
|
||||
// Send a signal to the parent process to indicate the compilation has
|
||||
// been completed.
|
||||
if err := parentProcess.Signal(syscall.SIGUSR1); err != nil {
|
||||
return err
|
||||
}
|
||||
signaledParent = true
|
||||
|
||||
// Waiting for the goroutine which is reading the perf buffer to be done
|
||||
// The goroutine will exit when the container exits
|
||||
wg.Wait()
|
||||
logrus.Info("BPF progam has finished")
|
||||
logrus.Info("BPF program has finished")
|
||||
|
||||
// Post-process the recorded events and extract the syscall names.
|
||||
for _, e := range events {
|
||||
name, err := syscallIDtoName(e.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting the name for syscall ID %d", e.ID)
|
||||
continue
|
||||
}
|
||||
syscalls[name]++
|
||||
}
|
||||
|
||||
logrus.Info("PerfMap Stop")
|
||||
perfMap.Stop()
|
||||
go perfMap.Stop()
|
||||
|
||||
logrus.Infof("Writing seccomp profile to %q", profilePath)
|
||||
if err := generateProfile(syscalls, profilePath, inputFile); err != nil {
|
||||
return errors.Wrap(err, "error generating final seccomp profile")
|
||||
return fmt.Errorf("error generating final seccomp profile: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -317,13 +327,13 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
|||
inputProfile := types.Seccomp{}
|
||||
|
||||
if inputFile != "" {
|
||||
input, err := ioutil.ReadFile(inputFile)
|
||||
input, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reading input file")
|
||||
return fmt.Errorf("error reading input file: %v", err)
|
||||
}
|
||||
err = json.Unmarshal(input, &inputProfile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing input file")
|
||||
return fmt.Errorf("error parsing input file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +350,10 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
|||
outputProfile = inputProfile
|
||||
outputProfile.DefaultAction = types.ActErrno
|
||||
|
||||
if err := appendArchIfNotAlreadyIncluded(runtime.GOARCH, &outputProfile); err != nil {
|
||||
return fmt.Errorf("appending architecture to output profile: %v", err)
|
||||
}
|
||||
|
||||
outputProfile.Syscalls = append(outputProfile.Syscalls, &types.Syscall{
|
||||
Action: types.ActAllow,
|
||||
Names: names,
|
||||
|
@ -348,10 +362,10 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
|||
|
||||
sJSON, err := json.Marshal(outputProfile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error writing seccomp profile")
|
||||
return fmt.Errorf("error writing seccomp profile: %v", err)
|
||||
}
|
||||
if err := ioutil.WriteFile(profilePath, sJSON, 0644); err != nil {
|
||||
return errors.Wrap(err, "error writing seccomp profile")
|
||||
if err := os.WriteFile(profilePath, sJSON, 0644); err != nil {
|
||||
return fmt.Errorf("error writing seccomp profile: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -361,7 +375,7 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
|||
func parseAnnotation(annotation string) (outputFile string, inputFile string, err error) {
|
||||
annotationSplit := strings.Split(annotation, ";")
|
||||
if len(annotationSplit) > 2 {
|
||||
return "", "", errors.Wrapf(errInvalidAnnotation, "more than one semi-colon: %q", annotation)
|
||||
return "", "", fmt.Errorf("%v: more than one semi-colon: %q", errInvalidAnnotation, annotation)
|
||||
}
|
||||
for _, path := range annotationSplit {
|
||||
switch {
|
||||
|
@ -369,33 +383,33 @@ func parseAnnotation(annotation string) (outputFile string, inputFile string, er
|
|||
case strings.HasPrefix(path, "if:"):
|
||||
inputFile = strings.TrimSpace(strings.TrimPrefix(path, InputPrefix))
|
||||
if !filepath.IsAbs(inputFile) {
|
||||
return "", "", errors.Wrapf(errInvalidAnnotation, "paths must be absolute: %q", inputFile)
|
||||
return "", "", fmt.Errorf("%v: input file path must be absolute: %q", errInvalidAnnotation, inputFile)
|
||||
}
|
||||
inputProfile := types.Seccomp{}
|
||||
input, err := ioutil.ReadFile(inputFile)
|
||||
input, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return "", "", errors.Wrapf(errInvalidAnnotation, "error reading input file: %q", inputFile)
|
||||
return "", "", fmt.Errorf("%v: error reading input file: %q", errInvalidAnnotation, inputFile)
|
||||
}
|
||||
err = json.Unmarshal(input, &inputProfile)
|
||||
if err != nil {
|
||||
return "", "", errors.Wrapf(errInvalidAnnotation, "error parsing input file: %q", inputFile)
|
||||
return "", "", fmt.Errorf("%v: error parsing input file: %q", errInvalidAnnotation, inputFile)
|
||||
}
|
||||
|
||||
// Output profile
|
||||
case strings.HasPrefix(path, "of:"):
|
||||
outputFile = strings.TrimSpace(strings.TrimPrefix(path, OutputPrefix))
|
||||
if !filepath.IsAbs(outputFile) {
|
||||
return "", "", errors.Wrapf(errInvalidAnnotation, "paths must be absolute: %q", inputFile)
|
||||
return "", "", fmt.Errorf("%v: output file path must be absolute: %q", errInvalidAnnotation, outputFile)
|
||||
}
|
||||
|
||||
// Unsupported default
|
||||
default:
|
||||
return "", "", errors.Wrapf(errInvalidAnnotation, "must start %q or %q prefix", InputPrefix, OutputPrefix)
|
||||
return "", "", fmt.Errorf("%v: must start %q or %q prefix", errInvalidAnnotation, InputPrefix, OutputPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
if outputFile == "" {
|
||||
return "", "", errors.Wrap(errInvalidAnnotation, "providing output file is mandatory")
|
||||
return "", "", fmt.Errorf("%v: providing output file is mandatory", errInvalidAnnotation)
|
||||
}
|
||||
|
||||
return outputFile, inputFile, nil
|
||||
|
@ -420,3 +434,18 @@ func syscallInProfile(profile *types.Seccomp, syscall string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func appendArchIfNotAlreadyIncluded(goArch string, profile *types.Seccomp) error {
|
||||
targetArch, err := types.GoArchToSeccompArch(goArch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("determine target architecture: %v", err)
|
||||
}
|
||||
for _, arch := range profile.Architectures {
|
||||
if arch == targetArch {
|
||||
// architecture already part of the profile
|
||||
return nil
|
||||
}
|
||||
}
|
||||
profile.Architectures = append(profile.Architectures, targetArch)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"when": {
|
||||
"annotations": {
|
||||
"io.containers.trace-syscall": ".*"
|
||||
"^io\\.containers\\.trace-syscall$": ".*"
|
||||
}
|
||||
},
|
||||
"stages": [
|
||||
|
|
|
@ -71,7 +71,7 @@ export BUILDTAGS=""
|
|||
BUILDTAGS=$BUILDTAGS make
|
||||
|
||||
%install
|
||||
%{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} OCI-SECCOMP-BPF_VERSION=%{version} install
|
||||
%{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} install
|
||||
|
||||
%check
|
||||
%if 0%{?with_check} && 0%{?with_unit_test} && 0%{?with_devel}
|
||||
|
@ -102,6 +102,9 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath}
|
|||
%{_mandir}/man1/%{name}.1*
|
||||
|
||||
%changelog
|
||||
* Wed Aug 12 2020 Sascha Grunert <sgrunert@suse.com>
|
||||
- remove unused version env var on build
|
||||
|
||||
* Mon Sep 23 2019 Jindrich Novy <jnovy@redhat.com> - 0.0.1-0.1.gite200a9ed
|
||||
- fix spec file and build
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
types "github.com/containers/common/pkg/seccomp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@ func TestParseAnnotation(t *testing.T) {
|
|||
testProfile := types.Seccomp{}
|
||||
testProfile.DefaultAction = types.ActErrno
|
||||
|
||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "input-*.json")
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), "input-*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create temporary file")
|
||||
}
|
||||
|
@ -52,3 +52,55 @@ func TestParseAnnotation(t *testing.T) {
|
|||
assert.NotNil(t, err)
|
||||
}
|
||||
}
|
||||
func TestAppendArchIfNotAlreadyIncluded(t *testing.T) {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
t.Skip("Test runs only reliable on amd64 arch")
|
||||
}
|
||||
|
||||
currentArch, err := types.GoArchToSeccompArch(runtime.GOARCH)
|
||||
assert.Nil(t, err)
|
||||
for _, tc := range []struct {
|
||||
profile types.Seccomp
|
||||
goArch string
|
||||
expect func(error, types.Seccomp)
|
||||
}{
|
||||
{
|
||||
profile: types.Seccomp{},
|
||||
goArch: runtime.GOARCH,
|
||||
expect: func(err error, profile types.Seccomp) {
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, profile.Architectures, 1)
|
||||
},
|
||||
},
|
||||
{
|
||||
profile: types.Seccomp{
|
||||
Architectures: []types.Arch{currentArch},
|
||||
},
|
||||
goArch: runtime.GOARCH,
|
||||
expect: func(err error, profile types.Seccomp) {
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, profile.Architectures, 1)
|
||||
},
|
||||
},
|
||||
{
|
||||
profile: types.Seccomp{
|
||||
Architectures: []types.Arch{types.ArchMIPS, types.ArchARM},
|
||||
},
|
||||
goArch: runtime.GOARCH,
|
||||
expect: func(err error, profile types.Seccomp) {
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, profile.Architectures, 3)
|
||||
},
|
||||
},
|
||||
{
|
||||
profile: types.Seccomp{},
|
||||
goArch: "wrong",
|
||||
expect: func(err error, profile types.Seccomp) {
|
||||
assert.NotNil(t, err)
|
||||
assert.Empty(t, profile.Architectures)
|
||||
},
|
||||
},
|
||||
} {
|
||||
tc.expect(appendArchIfNotAlreadyIncluded(tc.goArch, &tc.profile), tc.profile)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,13 @@ load helpers
|
|||
}
|
||||
|
||||
@test "Version check" {
|
||||
# First, we don't have a VERSION file in a system-test environment
|
||||
# Second, /usr/libexec/oci/hooks.d/oci-seccomp-bpf-hook --version
|
||||
# produces no output.
|
||||
if [ ! -d .git ]; then
|
||||
skip "This test only makes sense in a source-tree environment"
|
||||
fi
|
||||
|
||||
local version
|
||||
version=$(cat ./VERSION)
|
||||
run ./bin/oci-seccomp-bpf-hook --version
|
||||
|
@ -78,7 +85,7 @@ load helpers
|
|||
echo "Podman output: ${lines[*]}"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run podman run --net=host --security-opt seccomp=${tmpFile} ${ALPINE} ping -c3 google.com
|
||||
run podman run --net=host --security-opt seccomp=${tmpFile} ${ALPINE} ping -c3 $PINGABLE_HOST
|
||||
echo "Podman output: ${lines[*]}"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
@ -86,7 +93,7 @@ load helpers
|
|||
@test "Extend existing seccomp profile" {
|
||||
local tmpFile1
|
||||
local tmpFile2
|
||||
local size
|
||||
local size
|
||||
|
||||
tmpFile1=$(mktemp)
|
||||
tmpFile2=$(mktemp)
|
||||
|
@ -102,15 +109,15 @@ load helpers
|
|||
echo "Size of the first generated file: ${size}"
|
||||
[ "${size}" -gt 0 ]
|
||||
|
||||
run podman run --net=host --security-opt seccomp=${tmpFile1} ${ALPINE} ping -c3 google.com
|
||||
run podman run --net=host --security-opt seccomp=${tmpFile1} ${ALPINE} ping -c3 $PINGABLE_HOST
|
||||
echo "Podman output: ${lines[*]}"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run podman run --net=host --annotation io.containers.trace-syscall="if:${tmpFile1};of:${tmpFile2}" ${ALPINE} ping -c3 google.com
|
||||
run podman run --net=host --annotation io.containers.trace-syscall="if:${tmpFile1};of:${tmpFile2}" ${ALPINE} ping -c3 $PINGABLE_HOST
|
||||
echo "Podman output: ${lines[*]}"
|
||||
[ "$status" -eq 0 ]
|
||||
sleep 2 # sleep two seconds to let the hook finish writing the file
|
||||
|
||||
|
||||
size=$(du -b ${tmpFile2} | awk '{ print $1 }')
|
||||
echo "Size of the second generated file: ${size}"
|
||||
[ "${size}" -gt 0 ]
|
||||
|
@ -119,14 +126,14 @@ load helpers
|
|||
echo "Podman output: ${lines[*]}"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run podman run --net=host --security-opt seccomp=${tmpFile2} ${ALPINE} ping -c3 google.com
|
||||
run podman run --net=host --security-opt seccomp=${tmpFile2} ${ALPINE} ping -c3 $PINGABLE_HOST
|
||||
echo "Podman output: ${lines[*]}"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "Syscall blocked in input profile remains blocked in output profile" {
|
||||
local tmpFile
|
||||
local size
|
||||
local size
|
||||
|
||||
tmpFile=$(mktemp)
|
||||
echo "Temporary file : ${tmpFile}"
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
ALPINE="docker.io/library/alpine:latest"
|
||||
BLOCK_MKDIR=$(realpath ./test/fixtures/block-mkdir.json)
|
||||
ALPINE="quay.io/libpod/alpine:latest"
|
||||
BLOCK_MKDIR=$(realpath $(dirname ${BASH_SOURCE[0]})/fixtures/block-mkdir.json)
|
||||
|
||||
# Hostname that should be ping'able from any environment in which we run tests
|
||||
PINGABLE_HOST=github.com
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
// NOTE: this package has originally been copied from
|
||||
// github.com/opencontainers/runc and modified to work for other use cases
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
var (
|
||||
goArchToSeccompArchMap = map[string]Arch{
|
||||
"386": ArchX86,
|
||||
"amd64": ArchX86_64,
|
||||
"amd64p32": ArchX32,
|
||||
"arm": ArchARM,
|
||||
"arm64": ArchAARCH64,
|
||||
"mips": ArchMIPS,
|
||||
"mips64": ArchMIPS64,
|
||||
"mips64le": ArchMIPSEL64,
|
||||
"mips64p32": ArchMIPS64N32,
|
||||
"mips64p32le": ArchMIPSEL64N32,
|
||||
"mipsle": ArchMIPSEL,
|
||||
"ppc": ArchPPC,
|
||||
"ppc64": ArchPPC64,
|
||||
"ppc64le": ArchPPC64LE,
|
||||
"s390": ArchS390,
|
||||
"s390x": ArchS390X,
|
||||
}
|
||||
specArchToLibseccompArchMap = map[specs.Arch]string{
|
||||
specs.ArchX86: "x86",
|
||||
specs.ArchX86_64: "amd64",
|
||||
specs.ArchX32: "x32",
|
||||
specs.ArchARM: "arm",
|
||||
specs.ArchAARCH64: "arm64",
|
||||
specs.ArchMIPS: "mips",
|
||||
specs.ArchMIPS64: "mips64",
|
||||
specs.ArchMIPS64N32: "mips64n32",
|
||||
specs.ArchMIPSEL: "mipsel",
|
||||
specs.ArchMIPSEL64: "mipsel64",
|
||||
specs.ArchMIPSEL64N32: "mipsel64n32",
|
||||
specs.ArchPPC: "ppc",
|
||||
specs.ArchPPC64: "ppc64",
|
||||
specs.ArchPPC64LE: "ppc64le",
|
||||
specs.ArchS390: "s390",
|
||||
specs.ArchS390X: "s390x",
|
||||
}
|
||||
specArchToSeccompArchMap = map[specs.Arch]Arch{
|
||||
specs.ArchX86: ArchX86,
|
||||
specs.ArchX86_64: ArchX86_64,
|
||||
specs.ArchX32: ArchX32,
|
||||
specs.ArchARM: ArchARM,
|
||||
specs.ArchAARCH64: ArchAARCH64,
|
||||
specs.ArchMIPS: ArchMIPS,
|
||||
specs.ArchMIPS64: ArchMIPS64,
|
||||
specs.ArchMIPS64N32: ArchMIPS64N32,
|
||||
specs.ArchMIPSEL: ArchMIPSEL,
|
||||
specs.ArchMIPSEL64: ArchMIPSEL64,
|
||||
specs.ArchMIPSEL64N32: ArchMIPSEL64N32,
|
||||
specs.ArchPPC: ArchPPC,
|
||||
specs.ArchPPC64: ArchPPC64,
|
||||
specs.ArchPPC64LE: ArchPPC64LE,
|
||||
specs.ArchS390: ArchS390,
|
||||
specs.ArchS390X: ArchS390X,
|
||||
}
|
||||
specActionToSeccompActionMap = map[specs.LinuxSeccompAction]Action{
|
||||
specs.ActKill: ActKill,
|
||||
// TODO: wait for this PR to get merged:
|
||||
// https://github.com/opencontainers/runtime-spec/pull/1064
|
||||
// specs.ActKillProcess ActKillProcess,
|
||||
// specs.ActKillThread ActKillThread,
|
||||
specs.ActErrno: ActErrno,
|
||||
specs.ActTrap: ActTrap,
|
||||
specs.ActAllow: ActAllow,
|
||||
specs.ActTrace: ActTrace,
|
||||
specs.ActLog: ActLog,
|
||||
specs.ActNotify: ActNotify,
|
||||
}
|
||||
specOperatorToSeccompOperatorMap = map[specs.LinuxSeccompOperator]Operator{
|
||||
specs.OpNotEqual: OpNotEqual,
|
||||
specs.OpLessThan: OpLessThan,
|
||||
specs.OpLessEqual: OpLessEqual,
|
||||
specs.OpEqualTo: OpEqualTo,
|
||||
specs.OpGreaterEqual: OpGreaterEqual,
|
||||
specs.OpGreaterThan: OpGreaterThan,
|
||||
specs.OpMaskedEqual: OpMaskedEqual,
|
||||
}
|
||||
)
|
||||
|
||||
// GoArchToSeccompArch converts a runtime.GOARCH to a seccomp `Arch`. The
|
||||
// function returns an error if the architecture conversion is not supported.
|
||||
func GoArchToSeccompArch(goArch string) (Arch, error) {
|
||||
arch, ok := goArchToSeccompArchMap[goArch]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported go arch provided: %s", goArch)
|
||||
}
|
||||
return arch, nil
|
||||
}
|
||||
|
||||
// specToSeccomp converts a `LinuxSeccomp` spec into a `Seccomp` struct.
|
||||
func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
||||
res := &Seccomp{
|
||||
Syscalls: []*Syscall{},
|
||||
}
|
||||
|
||||
for _, arch := range spec.Architectures {
|
||||
newArch, err := specArchToSeccompArch(arch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert spec arch: %w", err)
|
||||
}
|
||||
res.Architectures = append(res.Architectures, newArch)
|
||||
}
|
||||
|
||||
// Convert default action
|
||||
newDefaultAction, err := specActionToSeccompAction(spec.DefaultAction)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert default action: %w", err)
|
||||
}
|
||||
res.DefaultAction = newDefaultAction
|
||||
res.DefaultErrnoRet = spec.DefaultErrnoRet
|
||||
|
||||
// Loop through all syscall blocks and convert them to the internal format
|
||||
for _, call := range spec.Syscalls {
|
||||
newAction, err := specActionToSeccompAction(call.Action)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert action: %w", err)
|
||||
}
|
||||
|
||||
for _, name := range call.Names {
|
||||
newCall := Syscall{
|
||||
Name: name,
|
||||
Action: newAction,
|
||||
ErrnoRet: call.ErrnoRet,
|
||||
Args: []*Arg{},
|
||||
}
|
||||
|
||||
// Loop through all the arguments of the syscall and convert them
|
||||
for _, arg := range call.Args {
|
||||
newOp, err := specOperatorToSeccompOperator(arg.Op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert operator: %w", err)
|
||||
}
|
||||
|
||||
newArg := Arg{
|
||||
Index: arg.Index,
|
||||
Value: arg.Value,
|
||||
ValueTwo: arg.ValueTwo,
|
||||
Op: newOp,
|
||||
}
|
||||
|
||||
newCall.Args = append(newCall.Args, &newArg)
|
||||
}
|
||||
res.Syscalls = append(res.Syscalls, &newCall)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// specArchToLibseccompArch converts a spec arch into a libseccomp one.
|
||||
func specArchToLibseccompArch(arch specs.Arch) (string, error) {
|
||||
if res, ok := specArchToLibseccompArchMap[arch]; ok {
|
||||
return res, nil
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"architecture %q is not valid for libseccomp", arch,
|
||||
)
|
||||
}
|
||||
|
||||
// specArchToSeccompArch converts a spec arch into an internal one.
|
||||
func specArchToSeccompArch(arch specs.Arch) (Arch, error) {
|
||||
if res, ok := specArchToSeccompArchMap[arch]; ok {
|
||||
return res, nil
|
||||
}
|
||||
return "", fmt.Errorf("architecture %q is not valid", arch)
|
||||
}
|
||||
|
||||
// specActionToSeccompAction converts a spec action into a seccomp one.
|
||||
func specActionToSeccompAction(action specs.LinuxSeccompAction) (Action, error) {
|
||||
if res, ok := specActionToSeccompActionMap[action]; ok {
|
||||
return res, nil
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"spec action %q is not valid internal action", action,
|
||||
)
|
||||
}
|
||||
|
||||
// specOperatorToSeccompOperator converts a spec operator into a seccomp one.
|
||||
func specOperatorToSeccompOperator(operator specs.LinuxSeccompOperator) (Operator, error) {
|
||||
if op, ok := specOperatorToSeccompOperatorMap[operator]; ok {
|
||||
return op, nil
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"spec operator %q is not a valid internal operator", operator,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,953 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func arches() []Architecture {
|
||||
return []Architecture{
|
||||
{
|
||||
Arch: ArchX86_64,
|
||||
SubArches: []Arch{ArchX86, ArchX32},
|
||||
},
|
||||
{
|
||||
Arch: ArchAARCH64,
|
||||
SubArches: []Arch{ArchARM},
|
||||
},
|
||||
{
|
||||
Arch: ArchMIPS64,
|
||||
SubArches: []Arch{ArchMIPS, ArchMIPS64N32},
|
||||
},
|
||||
{
|
||||
Arch: ArchMIPS64N32,
|
||||
SubArches: []Arch{ArchMIPS, ArchMIPS64},
|
||||
},
|
||||
{
|
||||
Arch: ArchMIPSEL64,
|
||||
SubArches: []Arch{ArchMIPSEL, ArchMIPSEL64N32},
|
||||
},
|
||||
{
|
||||
Arch: ArchMIPSEL64N32,
|
||||
SubArches: []Arch{ArchMIPSEL, ArchMIPSEL64},
|
||||
},
|
||||
{
|
||||
Arch: ArchS390X,
|
||||
SubArches: []Arch{ArchS390},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultProfile defines the allowlist for the default seccomp profile.
|
||||
func DefaultProfile() *Seccomp {
|
||||
einval := uint(unix.EINVAL)
|
||||
enosys := uint(unix.ENOSYS)
|
||||
eperm := uint(unix.EPERM)
|
||||
|
||||
syscalls := []*Syscall{
|
||||
{
|
||||
Names: []string{
|
||||
"bdflush",
|
||||
"cachestat",
|
||||
"futex_requeue",
|
||||
"futex_wait",
|
||||
"futex_waitv",
|
||||
"futex_wake",
|
||||
"io_pgetevents",
|
||||
"io_pgetevents_time64",
|
||||
"kexec_file_load",
|
||||
"kexec_load",
|
||||
"map_shadow_stack",
|
||||
"migrate_pages",
|
||||
"move_pages",
|
||||
"nfsservctl",
|
||||
"nice",
|
||||
"oldfstat",
|
||||
"oldlstat",
|
||||
"oldolduname",
|
||||
"oldstat",
|
||||
"olduname",
|
||||
"pciconfig_iobase",
|
||||
"pciconfig_read",
|
||||
"pciconfig_write",
|
||||
"sgetmask",
|
||||
"ssetmask",
|
||||
"swapoff",
|
||||
"swapon",
|
||||
"syscall",
|
||||
"sysfs",
|
||||
"uselib",
|
||||
"userfaultfd",
|
||||
"ustat",
|
||||
"vm86",
|
||||
"vm86old",
|
||||
"vmsplice",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"_llseek",
|
||||
"_newselect",
|
||||
"accept",
|
||||
"accept4",
|
||||
"access",
|
||||
"adjtimex",
|
||||
"alarm",
|
||||
"bind",
|
||||
"brk",
|
||||
"capget",
|
||||
"capset",
|
||||
"chdir",
|
||||
"chmod",
|
||||
"chown",
|
||||
"chown32",
|
||||
"clock_adjtime",
|
||||
"clock_adjtime64",
|
||||
"clock_getres",
|
||||
"clock_getres_time64",
|
||||
"clock_gettime",
|
||||
"clock_gettime64",
|
||||
"clock_nanosleep",
|
||||
"clock_nanosleep_time64",
|
||||
"clone",
|
||||
"clone3",
|
||||
"close",
|
||||
"close_range",
|
||||
"connect",
|
||||
"copy_file_range",
|
||||
"creat",
|
||||
"dup",
|
||||
"dup2",
|
||||
"dup3",
|
||||
"epoll_create",
|
||||
"epoll_create1",
|
||||
"epoll_ctl",
|
||||
"epoll_ctl_old",
|
||||
"epoll_pwait",
|
||||
"epoll_pwait2",
|
||||
"epoll_wait",
|
||||
"epoll_wait_old",
|
||||
"eventfd",
|
||||
"eventfd2",
|
||||
"execve",
|
||||
"execveat",
|
||||
"exit",
|
||||
"exit_group",
|
||||
"faccessat",
|
||||
"faccessat2",
|
||||
"fadvise64",
|
||||
"fadvise64_64",
|
||||
"fallocate",
|
||||
"fanotify_mark",
|
||||
"fchdir",
|
||||
"fchmod",
|
||||
"fchmodat",
|
||||
"fchmodat2",
|
||||
"fchown",
|
||||
"fchown32",
|
||||
"fchownat",
|
||||
"fcntl",
|
||||
"fcntl64",
|
||||
"fdatasync",
|
||||
"fgetxattr",
|
||||
"flistxattr",
|
||||
"flock",
|
||||
"fork",
|
||||
"fremovexattr",
|
||||
"fsconfig",
|
||||
"fsetxattr",
|
||||
"fsmount",
|
||||
"fsopen",
|
||||
"fspick",
|
||||
"fstat",
|
||||
"fstat64",
|
||||
"fstatat64",
|
||||
"fstatfs",
|
||||
"fstatfs64",
|
||||
"fsync",
|
||||
"ftruncate",
|
||||
"ftruncate64",
|
||||
"futex",
|
||||
"futex_time64",
|
||||
"futimesat",
|
||||
"get_mempolicy",
|
||||
"get_robust_list",
|
||||
"get_thread_area",
|
||||
"getcpu",
|
||||
"getcwd",
|
||||
"getdents",
|
||||
"getdents64",
|
||||
"getegid",
|
||||
"getegid32",
|
||||
"geteuid",
|
||||
"geteuid32",
|
||||
"getgid",
|
||||
"getgid32",
|
||||
"getgroups",
|
||||
"getgroups32",
|
||||
"getitimer",
|
||||
"getpeername",
|
||||
"getpgid",
|
||||
"getpgrp",
|
||||
"getpid",
|
||||
"getppid",
|
||||
"getpriority",
|
||||
"getrandom",
|
||||
"getresgid",
|
||||
"getresgid32",
|
||||
"getresuid",
|
||||
"getresuid32",
|
||||
"getrlimit",
|
||||
"getrusage",
|
||||
"getsid",
|
||||
"getsockname",
|
||||
"getsockopt",
|
||||
"gettid",
|
||||
"gettimeofday",
|
||||
"getuid",
|
||||
"getuid32",
|
||||
"getxattr",
|
||||
"inotify_add_watch",
|
||||
"inotify_init",
|
||||
"inotify_init1",
|
||||
"inotify_rm_watch",
|
||||
"io_cancel",
|
||||
"io_destroy",
|
||||
"io_getevents",
|
||||
"io_setup",
|
||||
"io_submit",
|
||||
"ioctl",
|
||||
"ioprio_get",
|
||||
"ioprio_set",
|
||||
"ipc",
|
||||
"keyctl",
|
||||
"kill",
|
||||
"landlock_add_rule",
|
||||
"landlock_create_ruleset",
|
||||
"landlock_restrict_self",
|
||||
"lchown",
|
||||
"lchown32",
|
||||
"lgetxattr",
|
||||
"link",
|
||||
"linkat",
|
||||
"listen",
|
||||
"listxattr",
|
||||
"llistxattr",
|
||||
"lremovexattr",
|
||||
"lseek",
|
||||
"lsetxattr",
|
||||
"lstat",
|
||||
"lstat64",
|
||||
"madvise",
|
||||
"mbind",
|
||||
"membarrier",
|
||||
"memfd_create",
|
||||
"memfd_secret",
|
||||
"mincore",
|
||||
"mkdir",
|
||||
"mkdirat",
|
||||
"mknod",
|
||||
"mknodat",
|
||||
"mlock",
|
||||
"mlock2",
|
||||
"mlockall",
|
||||
"mmap",
|
||||
"mmap2",
|
||||
"mount",
|
||||
"mount_setattr",
|
||||
"move_mount",
|
||||
"mprotect",
|
||||
"mq_getsetattr",
|
||||
"mq_notify",
|
||||
"mq_open",
|
||||
"mq_timedreceive",
|
||||
"mq_timedreceive_time64",
|
||||
"mq_timedsend",
|
||||
"mq_timedsend_time64",
|
||||
"mq_unlink",
|
||||
"mremap",
|
||||
"msgctl",
|
||||
"msgget",
|
||||
"msgrcv",
|
||||
"msgsnd",
|
||||
"msync",
|
||||
"munlock",
|
||||
"munlockall",
|
||||
"munmap",
|
||||
"name_to_handle_at",
|
||||
"nanosleep",
|
||||
"newfstatat",
|
||||
"open",
|
||||
"open_tree",
|
||||
"openat",
|
||||
"openat2",
|
||||
"pause",
|
||||
"pidfd_getfd",
|
||||
"pidfd_open",
|
||||
"pidfd_send_signal",
|
||||
"pipe",
|
||||
"pipe2",
|
||||
"pivot_root",
|
||||
"pkey_alloc",
|
||||
"pkey_free",
|
||||
"pkey_mprotect",
|
||||
"poll",
|
||||
"ppoll",
|
||||
"ppoll_time64",
|
||||
"prctl",
|
||||
"pread64",
|
||||
"preadv",
|
||||
"preadv2",
|
||||
"prlimit64",
|
||||
"process_mrelease",
|
||||
"process_vm_readv",
|
||||
"process_vm_writev",
|
||||
"pselect6",
|
||||
"pselect6_time64",
|
||||
"ptrace",
|
||||
"pwrite64",
|
||||
"pwritev",
|
||||
"pwritev2",
|
||||
"read",
|
||||
"readahead",
|
||||
"readlink",
|
||||
"readlinkat",
|
||||
"readv",
|
||||
"reboot",
|
||||
"recv",
|
||||
"recvfrom",
|
||||
"recvmmsg",
|
||||
"recvmmsg_time64",
|
||||
"recvmsg",
|
||||
"remap_file_pages",
|
||||
"removexattr",
|
||||
"rename",
|
||||
"renameat",
|
||||
"renameat2",
|
||||
"restart_syscall",
|
||||
"rmdir",
|
||||
"rseq",
|
||||
"rt_sigaction",
|
||||
"rt_sigpending",
|
||||
"rt_sigprocmask",
|
||||
"rt_sigqueueinfo",
|
||||
"rt_sigreturn",
|
||||
"rt_sigsuspend",
|
||||
"rt_sigtimedwait",
|
||||
"rt_sigtimedwait_time64",
|
||||
"rt_tgsigqueueinfo",
|
||||
"sched_get_priority_max",
|
||||
"sched_get_priority_min",
|
||||
"sched_getaffinity",
|
||||
"sched_getattr",
|
||||
"sched_getparam",
|
||||
"sched_getscheduler",
|
||||
"sched_rr_get_interval",
|
||||
"sched_rr_get_interval_time64",
|
||||
"sched_setaffinity",
|
||||
"sched_setattr",
|
||||
"sched_setparam",
|
||||
"sched_setscheduler",
|
||||
"sched_yield",
|
||||
"seccomp",
|
||||
"select",
|
||||
"semctl",
|
||||
"semget",
|
||||
"semop",
|
||||
"semtimedop",
|
||||
"semtimedop_time64",
|
||||
"send",
|
||||
"sendfile",
|
||||
"sendfile64",
|
||||
"sendmmsg",
|
||||
"sendmsg",
|
||||
"sendto",
|
||||
"set_mempolicy",
|
||||
"set_robust_list",
|
||||
"set_thread_area",
|
||||
"set_tid_address",
|
||||
"setfsgid",
|
||||
"setfsgid32",
|
||||
"setfsuid",
|
||||
"setfsuid32",
|
||||
"setgid",
|
||||
"setgid32",
|
||||
"setgroups",
|
||||
"setgroups32",
|
||||
"setitimer",
|
||||
"setns",
|
||||
"setpgid",
|
||||
"setpriority",
|
||||
"setregid",
|
||||
"setregid32",
|
||||
"setresgid",
|
||||
"setresgid32",
|
||||
"setresuid",
|
||||
"setresuid32",
|
||||
"setreuid",
|
||||
"setreuid32",
|
||||
"setrlimit",
|
||||
"setsid",
|
||||
"setsockopt",
|
||||
"setuid",
|
||||
"setuid32",
|
||||
"setxattr",
|
||||
"shmat",
|
||||
"shmctl",
|
||||
"shmdt",
|
||||
"shmget",
|
||||
"shutdown",
|
||||
"sigaltstack",
|
||||
"signal",
|
||||
"signalfd",
|
||||
"signalfd4",
|
||||
"sigprocmask",
|
||||
"sigreturn",
|
||||
"socketcall",
|
||||
"socketpair",
|
||||
"splice",
|
||||
"stat",
|
||||
"stat64",
|
||||
"statfs",
|
||||
"statfs64",
|
||||
"statx",
|
||||
"symlink",
|
||||
"symlinkat",
|
||||
"sync",
|
||||
"sync_file_range",
|
||||
"syncfs",
|
||||
"sysinfo",
|
||||
"syslog",
|
||||
"tee",
|
||||
"tgkill",
|
||||
"time",
|
||||
"timer_create",
|
||||
"timer_delete",
|
||||
"timer_getoverrun",
|
||||
"timer_gettime",
|
||||
"timer_gettime64",
|
||||
"timer_settime",
|
||||
"timer_settime64",
|
||||
"timerfd_create",
|
||||
"timerfd_gettime",
|
||||
"timerfd_gettime64",
|
||||
"timerfd_settime",
|
||||
"timerfd_settime64",
|
||||
"times",
|
||||
"tkill",
|
||||
"truncate",
|
||||
"truncate64",
|
||||
"ugetrlimit",
|
||||
"umask",
|
||||
"umount",
|
||||
"umount2",
|
||||
"uname",
|
||||
"unlink",
|
||||
"unlinkat",
|
||||
"unshare",
|
||||
"utime",
|
||||
"utimensat",
|
||||
"utimensat_time64",
|
||||
"utimes",
|
||||
"vfork",
|
||||
"wait4",
|
||||
"waitid",
|
||||
"waitpid",
|
||||
"write",
|
||||
"writev",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
},
|
||||
{
|
||||
Names: []string{"personality"},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: 0x0,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{"personality"},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: 0x0008,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{"personality"},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: 0x20000,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{"personality"},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: 0x20008,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{"personality"},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: 0xffffffff,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"sync_file_range2",
|
||||
"swapcontext",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"ppc64le"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"arm_fadvise64_64",
|
||||
"arm_sync_file_range",
|
||||
"breakpoint",
|
||||
"cacheflush",
|
||||
"set_tls",
|
||||
"sync_file_range2",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"arm", "arm64"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"arch_prctl",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"amd64", "x32"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"modify_ldt",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"amd64", "x32", "x86"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"s390_pci_mmio_read",
|
||||
"s390_pci_mmio_write",
|
||||
"s390_runtime_instr",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"s390", "s390x"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"riscv_flush_icache",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"riscv64"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"open_by_handle_at",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_DAC_READ_SEARCH"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"open_by_handle_at",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_DAC_READ_SEARCH"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
"setdomainname",
|
||||
"sethostname",
|
||||
"setns",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_ADMIN"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
"setdomainname",
|
||||
"sethostname",
|
||||
"setns",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_ADMIN"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"chroot",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_CHROOT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"chroot",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_CHROOT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"delete_module",
|
||||
"finit_module",
|
||||
"init_module",
|
||||
"query_module",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_MODULE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"delete_module",
|
||||
"finit_module",
|
||||
"init_module",
|
||||
"query_module",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_MODULE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"acct",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_PACCT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"acct",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_PACCT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"kcmp",
|
||||
"process_madvise",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"kcmp",
|
||||
"process_madvise",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"ioperm",
|
||||
"iopl",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_RAWIO"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"ioperm",
|
||||
"iopl",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_RAWIO"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"clock_settime",
|
||||
"clock_settime64",
|
||||
"settimeofday",
|
||||
"stime",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_TIME"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"clock_settime",
|
||||
"clock_settime64",
|
||||
"settimeofday",
|
||||
"stime",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_TIME"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"vhangup",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_SYS_TTY_CONFIG"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"vhangup",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_TTY_CONFIG"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"socket",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EINVAL",
|
||||
ErrnoRet: &einval,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: unix.AF_NETLINK,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
{
|
||||
Index: 2,
|
||||
Value: unix.NETLINK_AUDIT,
|
||||
Op: OpEqualTo,
|
||||
},
|
||||
},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_AUDIT_WRITE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"socket",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 2,
|
||||
Value: unix.NETLINK_AUDIT,
|
||||
Op: OpNotEqual,
|
||||
},
|
||||
},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_AUDIT_WRITE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"socket",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 0,
|
||||
Value: unix.AF_NETLINK,
|
||||
Op: OpNotEqual,
|
||||
},
|
||||
},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_AUDIT_WRITE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"socket",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{
|
||||
{
|
||||
Index: 2,
|
||||
Value: unix.NETLINK_AUDIT,
|
||||
Op: OpNotEqual,
|
||||
},
|
||||
},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_AUDIT_WRITE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"socket",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_AUDIT_WRITE"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"bpf",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_ADMIN", "CAP_BPF"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"bpf",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_BPF"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"perf_event_open",
|
||||
},
|
||||
Action: ActErrno,
|
||||
Errno: "EPERM",
|
||||
ErrnoRet: &eperm,
|
||||
Args: []*Arg{},
|
||||
Excludes: Filter{
|
||||
Caps: []string{"CAP_SYS_ADMIN", "CAP_BPF"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"perf_event_open",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Caps: []string{"CAP_PERFMON"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &Seccomp{
|
||||
DefaultAction: ActErrno,
|
||||
DefaultErrno: "ENOSYS",
|
||||
DefaultErrnoRet: &enosys,
|
||||
ArchMap: arches(),
|
||||
Syscalls: syscalls,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//go:build linux && seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Error table
|
||||
var errnoArch = map[string]uint{
|
||||
"EPERM": uint(unix.EPERM),
|
||||
"ENOENT": uint(unix.ENOENT),
|
||||
"ESRCH": uint(unix.ESRCH),
|
||||
"EIO": uint(unix.EIO),
|
||||
"ENXIO": uint(unix.ENXIO),
|
||||
"E2BIG": uint(unix.E2BIG),
|
||||
"ENOEXEC": uint(unix.ENOEXEC),
|
||||
"EBADF": uint(unix.EBADF),
|
||||
"ECHILD": uint(unix.ECHILD),
|
||||
"EDEADLK": uint(unix.EDEADLK),
|
||||
"ENOMEM": uint(unix.ENOMEM),
|
||||
"EACCES": uint(unix.EACCES),
|
||||
"EFAULT": uint(unix.EFAULT),
|
||||
"ENOTBLK": uint(unix.ENOTBLK),
|
||||
"EBUSY": uint(unix.EBUSY),
|
||||
"EEXIST": uint(unix.EEXIST),
|
||||
"EXDEV": uint(unix.EXDEV),
|
||||
"ENODEV": uint(unix.ENODEV),
|
||||
"ENOTDIR": uint(unix.ENOTDIR),
|
||||
"EISDIR": uint(unix.EISDIR),
|
||||
"EINVAL": uint(unix.EINVAL),
|
||||
"ENFILE": uint(unix.ENFILE),
|
||||
"EMFILE": uint(unix.EMFILE),
|
||||
"ENOTTY": uint(unix.ENOTTY),
|
||||
"ETXTBSY": uint(unix.ETXTBSY),
|
||||
"EFBIG": uint(unix.EFBIG),
|
||||
"ENOSPC": uint(unix.ENOSPC),
|
||||
"ESPIPE": uint(unix.ESPIPE),
|
||||
"EROFS": uint(unix.EROFS),
|
||||
"EMLINK": uint(unix.EMLINK),
|
||||
"EPIPE": uint(unix.EPIPE),
|
||||
"EDOM": uint(unix.EDOM),
|
||||
"ERANGE": uint(unix.ERANGE),
|
||||
"EAGAIN": uint(unix.EAGAIN),
|
||||
"EINPROGRESS": uint(unix.EINPROGRESS),
|
||||
"EALREADY": uint(unix.EALREADY),
|
||||
"ENOTSOCK": uint(unix.ENOTSOCK),
|
||||
"EDESTADDRREQ": uint(unix.EDESTADDRREQ),
|
||||
"EMSGSIZE": uint(unix.EMSGSIZE),
|
||||
"EPROTOTYPE": uint(unix.EPROTOTYPE),
|
||||
"ENOPROTOOPT": uint(unix.ENOPROTOOPT),
|
||||
"EPROTONOSUPPORT": uint(unix.EPROTONOSUPPORT),
|
||||
"ESOCKTNOSUPPORT": uint(unix.ESOCKTNOSUPPORT),
|
||||
"EOPNOTSUPP": uint(unix.EOPNOTSUPP),
|
||||
"EPFNOSUPPORT": uint(unix.EPFNOSUPPORT),
|
||||
"EAFNOSUPPORT": uint(unix.EAFNOSUPPORT),
|
||||
"EADDRINUSE": uint(unix.EADDRINUSE),
|
||||
"EADDRNOTAVAIL": uint(unix.EADDRNOTAVAIL),
|
||||
"ENETDOWN": uint(unix.ENETDOWN),
|
||||
"ENETUNREACH": uint(unix.ENETUNREACH),
|
||||
"ENETRESET": uint(unix.ENETRESET),
|
||||
"ECONNABORTED": uint(unix.ECONNABORTED),
|
||||
"ECONNRESET": uint(unix.ECONNRESET),
|
||||
"ENOBUFS": uint(unix.ENOBUFS),
|
||||
"EISCONN": uint(unix.EISCONN),
|
||||
"ENOTCONN": uint(unix.ENOTCONN),
|
||||
"ESHUTDOWN": uint(unix.ESHUTDOWN),
|
||||
"ETOOMANYREFS": uint(unix.ETOOMANYREFS),
|
||||
"ETIMEDOUT": uint(unix.ETIMEDOUT),
|
||||
"ECONNREFUSED": uint(unix.ECONNREFUSED),
|
||||
"ELOOP": uint(unix.ELOOP),
|
||||
"ENAMETOOLONG": uint(unix.ENAMETOOLONG),
|
||||
"EHOSTDOWN": uint(unix.EHOSTDOWN),
|
||||
"EHOSTUNREACH": uint(unix.EHOSTUNREACH),
|
||||
"ENOTEMPTY": uint(unix.ENOTEMPTY),
|
||||
"EUSERS": uint(unix.EUSERS),
|
||||
"EDQUOT": uint(unix.EDQUOT),
|
||||
"ESTALE": uint(unix.ESTALE),
|
||||
"EREMOTE": uint(unix.EREMOTE),
|
||||
"ENOLCK": uint(unix.ENOLCK),
|
||||
"ENOSYS": uint(unix.ENOSYS),
|
||||
"EILSEQ": uint(unix.EILSEQ),
|
||||
"ENOMEDIUM": uint(unix.ENOMEDIUM),
|
||||
"EMEDIUMTYPE": uint(unix.EMEDIUMTYPE),
|
||||
"EOVERFLOW": uint(unix.EOVERFLOW),
|
||||
"ECANCELED": uint(unix.ECANCELED),
|
||||
"EIDRM": uint(unix.EIDRM),
|
||||
"ENOMSG": uint(unix.ENOMSG),
|
||||
"ENOTSUP": uint(unix.ENOTSUP),
|
||||
"EBADMSG": uint(unix.EBADMSG),
|
||||
"ENOTRECOVERABLE": uint(unix.ENOTRECOVERABLE),
|
||||
"EOWNERDEAD": uint(unix.EOWNERDEAD),
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
//go:build seccomp
|
||||
|
||||
// NOTE: this package has originally been copied from
|
||||
// github.com/opencontainers/runc and modified to work for other use cases
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NOTE: this package has originally been copied from
|
||||
// github.com/opencontainers/runc and modified to work for other use cases
|
||||
|
||||
var (
|
||||
// ErrSpecNil is a possible return error from BuildFilter() and occurs if
|
||||
// the provided spec is nil.
|
||||
ErrSpecNil = errors.New("spec is nil")
|
||||
|
||||
// ErrSpecEmpty is a possible return error from BuildFilter() and occurs if
|
||||
// the provided spec has neither a DefaultAction nor any syscalls.
|
||||
ErrSpecEmpty = errors.New("spec contains neither a default action nor any syscalls")
|
||||
)
|
||||
|
||||
// BuildFilter does a basic validation for the provided seccomp profile
|
||||
// string and returns a filter for it.
|
||||
func BuildFilter(spec *specs.LinuxSeccomp) (*libseccomp.ScmpFilter, error) {
|
||||
// Sanity checking to allow consumers to act accordingly
|
||||
if spec == nil {
|
||||
return nil, ErrSpecNil
|
||||
}
|
||||
if spec.DefaultAction == "" && len(spec.Syscalls) == 0 {
|
||||
return nil, ErrSpecEmpty
|
||||
}
|
||||
|
||||
profile, err := specToSeccomp(spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert spec to seccomp profile: %w", err)
|
||||
}
|
||||
|
||||
defaultAction, err := toAction(profile.DefaultAction, profile.DefaultErrnoRet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert default action %s: %w", profile.DefaultAction, err)
|
||||
}
|
||||
|
||||
filter, err := libseccomp.NewFilter(defaultAction)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create filter for default action %s: %w", defaultAction, err)
|
||||
}
|
||||
|
||||
// Add extra architectures
|
||||
for _, arch := range spec.Architectures {
|
||||
libseccompArch, err := specArchToLibseccompArch(arch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert spec arch: %w", err)
|
||||
}
|
||||
|
||||
scmpArch, err := libseccomp.GetArchFromString(libseccompArch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validate Seccomp architecture %s: %w", arch, err)
|
||||
}
|
||||
|
||||
if err := filter.AddArch(scmpArch); err != nil {
|
||||
return nil, fmt.Errorf("add architecture to seccomp filter: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Unset no new privs bit
|
||||
if err := filter.SetNoNewPrivsBit(false); err != nil {
|
||||
return nil, fmt.Errorf("set no new privileges flag: %w", err)
|
||||
}
|
||||
|
||||
// Add a rule for each syscall
|
||||
for _, call := range profile.Syscalls {
|
||||
if call == nil {
|
||||
return nil, errors.New("encountered nil syscall while initializing seccomp")
|
||||
}
|
||||
|
||||
if err = matchSyscall(filter, call); err != nil {
|
||||
return nil, fmt.Errorf("filter matches syscall: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
|
||||
if call == nil || filter == nil {
|
||||
return errors.New("cannot use nil as syscall to block")
|
||||
}
|
||||
|
||||
if call.Name == "" {
|
||||
return errors.New("empty string is not a valid syscall")
|
||||
}
|
||||
|
||||
// If we can't resolve the syscall, assume it's not supported on this kernel
|
||||
// Ignore it, don't error out
|
||||
callNum, err := libseccomp.GetSyscallFromName(call.Name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert the call's action to the libseccomp equivalent
|
||||
callAct, err := toAction(call.Action, call.ErrnoRet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert action %s: %w", call.Action, err)
|
||||
}
|
||||
|
||||
// Unconditional match - just add the rule
|
||||
if len(call.Args) == 0 {
|
||||
if err = filter.AddRule(callNum, callAct); err != nil {
|
||||
return fmt.Errorf("add seccomp filter rule for syscall %s: %w", call.Name, err)
|
||||
}
|
||||
} else {
|
||||
// Linux system calls can have at most 6 arguments
|
||||
const syscallMaxArguments int = 6
|
||||
|
||||
// If two or more arguments have the same condition,
|
||||
// Revert to old behavior, adding each condition as a separate rule
|
||||
argCounts := make([]uint, syscallMaxArguments)
|
||||
conditions := []libseccomp.ScmpCondition{}
|
||||
|
||||
for _, cond := range call.Args {
|
||||
newCond, err := toCondition(cond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create seccomp syscall condition for syscall %s: %w", call.Name, err)
|
||||
}
|
||||
|
||||
argCounts[cond.Index]++
|
||||
|
||||
conditions = append(conditions, newCond)
|
||||
}
|
||||
|
||||
hasMultipleArgs := false
|
||||
for _, count := range argCounts {
|
||||
if count > 1 {
|
||||
hasMultipleArgs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasMultipleArgs {
|
||||
// Revert to old behavior
|
||||
// Add each condition attached to a separate rule
|
||||
for _, cond := range conditions {
|
||||
condArr := []libseccomp.ScmpCondition{cond}
|
||||
|
||||
if err = filter.AddRuleConditional(callNum, callAct, condArr); err != nil {
|
||||
return fmt.Errorf("add seccomp rule for syscall %s: %w", call.Name, err)
|
||||
}
|
||||
}
|
||||
} else if err = filter.AddRuleConditional(callNum, callAct, conditions); err != nil {
|
||||
// No conditions share same argument
|
||||
// Use new, proper behavior
|
||||
return fmt.Errorf("add seccomp rule for syscall %s: %w", call.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// toAction converts an internal `Action` type to a `libseccomp.ScmpAction`
|
||||
// type.
|
||||
func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
|
||||
switch act {
|
||||
case ActKill:
|
||||
// lint was not passing until this was changed from ActKill to ActKilThread.
|
||||
return libseccomp.ActKillThread, nil
|
||||
case ActKillProcess:
|
||||
return libseccomp.ActKillProcess, nil
|
||||
case ActErrno:
|
||||
if errnoRet != nil {
|
||||
return libseccomp.ActErrno.SetReturnCode(int16(*errnoRet)), nil
|
||||
}
|
||||
return libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM)), nil
|
||||
case ActTrap:
|
||||
return libseccomp.ActTrap, nil
|
||||
case ActAllow:
|
||||
return libseccomp.ActAllow, nil
|
||||
case ActTrace:
|
||||
if errnoRet != nil {
|
||||
return libseccomp.ActTrace.SetReturnCode(int16(*errnoRet)), nil
|
||||
}
|
||||
return libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM)), nil
|
||||
case ActLog:
|
||||
return libseccomp.ActLog, nil
|
||||
default:
|
||||
return libseccomp.ActInvalid, fmt.Errorf("invalid action %s", act)
|
||||
}
|
||||
}
|
||||
|
||||
// toCondition converts an internal `Arg` type to a `libseccomp.ScmpCondition`
|
||||
// type.
|
||||
func toCondition(arg *Arg) (cond libseccomp.ScmpCondition, err error) {
|
||||
if arg == nil {
|
||||
return cond, errors.New("cannot convert nil to syscall condition")
|
||||
}
|
||||
|
||||
op, err := toCompareOp(arg.Op)
|
||||
if err != nil {
|
||||
return cond, fmt.Errorf("convert compare operator: %w", err)
|
||||
}
|
||||
|
||||
condition, err := libseccomp.MakeCondition(
|
||||
arg.Index, op, arg.Value, arg.ValueTwo,
|
||||
)
|
||||
if err != nil {
|
||||
return cond, fmt.Errorf("make condition: %w", err)
|
||||
}
|
||||
|
||||
return condition, nil
|
||||
}
|
||||
|
||||
// toCompareOp converts an internal `Operator` type to a
|
||||
// `libseccomp.ScmpCompareOp`.
|
||||
func toCompareOp(op Operator) (libseccomp.ScmpCompareOp, error) {
|
||||
switch op {
|
||||
case OpEqualTo:
|
||||
return libseccomp.CompareEqual, nil
|
||||
case OpNotEqual:
|
||||
return libseccomp.CompareNotEqual, nil
|
||||
case OpGreaterThan:
|
||||
return libseccomp.CompareGreater, nil
|
||||
case OpGreaterEqual:
|
||||
return libseccomp.CompareGreaterEqual, nil
|
||||
case OpLessThan:
|
||||
return libseccomp.CompareLess, nil
|
||||
case OpLessEqual:
|
||||
return libseccomp.CompareLessOrEqual, nil
|
||||
case OpMaskedEqual:
|
||||
return libseccomp.CompareMaskedEqual, nil
|
||||
default:
|
||||
return libseccomp.CompareInvalid, fmt.Errorf("invalid operator %s", op)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,225 @@
|
|||
//go:build seccomp
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
//go:generate go run -tags 'seccomp' generate.go
|
||||
|
||||
// GetDefaultProfile returns the default seccomp profile.
|
||||
func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
return setupSeccomp(DefaultProfile(), rs)
|
||||
}
|
||||
|
||||
// LoadProfile takes a json string and decodes the seccomp profile.
|
||||
func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
var config Seccomp
|
||||
if err := json.Unmarshal([]byte(body), &config); err != nil {
|
||||
return nil, fmt.Errorf("decoding seccomp profile failed: %v", err)
|
||||
}
|
||||
return setupSeccomp(&config, rs)
|
||||
}
|
||||
|
||||
// LoadProfileFromBytes takes a byte slice and decodes the seccomp profile.
|
||||
func LoadProfileFromBytes(body []byte, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
config := &Seccomp{}
|
||||
if err := json.Unmarshal(body, config); err != nil {
|
||||
return nil, fmt.Errorf("decoding seccomp profile failed: %v", err)
|
||||
}
|
||||
return setupSeccomp(config, rs)
|
||||
}
|
||||
|
||||
// LoadProfileFromConfig takes a Seccomp struct and a spec to retrieve a LinuxSeccomp
|
||||
func LoadProfileFromConfig(config *Seccomp, specgen *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
return setupSeccomp(config, specgen)
|
||||
}
|
||||
|
||||
var nativeToSeccomp = map[string]Arch{
|
||||
"amd64": ArchX86_64,
|
||||
"arm64": ArchAARCH64,
|
||||
"mips64": ArchMIPS64,
|
||||
"mips64n32": ArchMIPS64N32,
|
||||
"mipsel64": ArchMIPSEL64,
|
||||
"mipsel64n32": ArchMIPSEL64N32,
|
||||
"s390x": ArchS390X,
|
||||
}
|
||||
|
||||
// inSlice tests whether a string is contained in a slice of strings or not.
|
||||
// Comparison is case sensitive
|
||||
func inSlice(slice []string, s string) bool {
|
||||
for _, ss := range slice {
|
||||
if s == ss {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getArchitectures(config *Seccomp, newConfig *specs.LinuxSeccomp) error {
|
||||
if len(config.Architectures) != 0 && len(config.ArchMap) != 0 {
|
||||
return errors.New("'architectures' and 'archMap' were specified in the seccomp profile, use either 'architectures' or 'archMap'")
|
||||
}
|
||||
|
||||
// if config.Architectures == 0 then libseccomp will figure out the architecture to use
|
||||
if len(config.Architectures) != 0 {
|
||||
for _, a := range config.Architectures {
|
||||
newConfig.Architectures = append(newConfig.Architectures, specs.Arch(a))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getErrno(errno string, def *uint) (*uint, error) {
|
||||
if errno == "" {
|
||||
return def, nil
|
||||
}
|
||||
v, err := strconv.ParseUint(errno, 10, 32)
|
||||
if err == nil {
|
||||
v2 := uint(v)
|
||||
return &v2, nil
|
||||
}
|
||||
|
||||
v2, found := errnoArch[errno]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unknown errno %s", errno)
|
||||
}
|
||||
return &v2, nil
|
||||
}
|
||||
|
||||
func setupSeccomp(config *Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// No default action specified, no syscalls listed, assume seccomp disabled
|
||||
if config.DefaultAction == "" && len(config.Syscalls) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newConfig := &specs.LinuxSeccomp{}
|
||||
|
||||
var arch string
|
||||
native, err := libseccomp.GetNativeArch()
|
||||
if err == nil {
|
||||
arch = native.String()
|
||||
}
|
||||
|
||||
if err := getArchitectures(config, newConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, flag := range config.Flags {
|
||||
newConfig.Flags = append(newConfig.Flags, specs.LinuxSeccompFlag(flag))
|
||||
}
|
||||
|
||||
newConfig.ListenerPath = config.ListenerPath
|
||||
newConfig.ListenerMetadata = config.ListenerMetadata
|
||||
|
||||
if len(config.ArchMap) != 0 {
|
||||
for _, a := range config.ArchMap {
|
||||
seccompArch, ok := nativeToSeccomp[arch]
|
||||
if ok {
|
||||
if a.Arch == seccompArch {
|
||||
newConfig.Architectures = append(newConfig.Architectures, specs.Arch(a.Arch))
|
||||
for _, sa := range a.SubArches {
|
||||
newConfig.Architectures = append(newConfig.Architectures, specs.Arch(sa))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newConfig.DefaultAction = specs.LinuxSeccompAction(config.DefaultAction)
|
||||
|
||||
newConfig.DefaultErrnoRet, err = getErrno(config.DefaultErrno, config.DefaultErrnoRet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Loop:
|
||||
// Loop through all syscall blocks and convert them to libcontainer format after filtering them
|
||||
for _, call := range config.Syscalls {
|
||||
if len(call.Excludes.Arches) > 0 {
|
||||
if inSlice(call.Excludes.Arches, arch) {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
if len(call.Excludes.Caps) > 0 {
|
||||
for _, c := range call.Excludes.Caps {
|
||||
if rs != nil && rs.Process != nil && rs.Process.Capabilities != nil && inSlice(rs.Process.Capabilities.Bounding, c) {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(call.Includes.Arches) > 0 {
|
||||
if !inSlice(call.Includes.Arches, arch) {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
if len(call.Includes.Caps) > 0 {
|
||||
for _, c := range call.Includes.Caps {
|
||||
if rs != nil && rs.Process != nil && rs.Process.Capabilities != nil && !inSlice(rs.Process.Capabilities.Bounding, c) {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if call.Name != "" && len(call.Names) != 0 {
|
||||
return nil, errors.New("'name' and 'names' were specified in the seccomp profile, use either 'name' or 'names'")
|
||||
}
|
||||
|
||||
errno, err := getErrno(call.Errno, call.ErrnoRet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if call.Name != "" {
|
||||
newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall([]string{call.Name}, call.Action, call.Args, errno))
|
||||
}
|
||||
|
||||
if len(call.Names) > 0 {
|
||||
newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall(call.Names, call.Action, call.Args, errno))
|
||||
}
|
||||
}
|
||||
|
||||
return newConfig, nil
|
||||
}
|
||||
|
||||
func createSpecsSyscall(names []string, action Action, args []*Arg, errnoRet *uint) specs.LinuxSyscall {
|
||||
newCall := specs.LinuxSyscall{
|
||||
Names: names,
|
||||
Action: specs.LinuxSeccompAction(action),
|
||||
ErrnoRet: errnoRet,
|
||||
}
|
||||
|
||||
// Loop through all the arguments of the syscall and convert them
|
||||
for _, arg := range args {
|
||||
newArg := specs.LinuxSeccompArg{
|
||||
Index: arg.Index,
|
||||
Value: arg.Value,
|
||||
ValueTwo: arg.ValueTwo,
|
||||
Op: specs.LinuxSeccompOperator(arg.Op),
|
||||
}
|
||||
|
||||
newCall.Args = append(newCall.Args, newArg)
|
||||
}
|
||||
return newCall
|
||||
}
|
||||
|
||||
// IsEnabled returns true if seccomp is enabled for the host.
|
||||
func IsEnabled() bool {
|
||||
return IsSupported()
|
||||
}
|
46
vendor/github.com/containers/common/pkg/seccomp/seccomp_unsupported.go
generated
vendored
Normal file
46
vendor/github.com/containers/common/pkg/seccomp/seccomp_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
//go:build !linux || !seccomp
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
var errNotSupported = errors.New("seccomp not enabled in this build")
|
||||
|
||||
// LoadProfile returns an error on unsupported systems
|
||||
func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// GetDefaultProfile returns an error on unsupported systems
|
||||
func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// LoadProfileFromBytes takes a byte slice and decodes the seccomp profile.
|
||||
func LoadProfileFromBytes(body []byte, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// LoadProfileFromConfig takes a Seccomp struct and a spec to retrieve a LinuxSeccomp
|
||||
func LoadProfileFromConfig(config *Seccomp, specgen *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// IsEnabled returns true if seccomp is enabled for the host.
|
||||
func IsEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSupported returns true if the system has been configured to support
|
||||
// seccomp.
|
||||
func IsSupported() bool {
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
//go:build linux && seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
supported bool
|
||||
supOnce sync.Once
|
||||
)
|
||||
|
||||
// IsSupported returns true if the system has been configured to support
|
||||
// seccomp (including the check for CONFIG_SECCOMP_FILTER kernel option).
|
||||
func IsSupported() bool {
|
||||
// Excerpts from prctl(2), section ERRORS:
|
||||
//
|
||||
// EACCES
|
||||
// option is PR_SET_SECCOMP and arg2 is SECCOMP_MODE_FILTER, but
|
||||
// the process does not have the CAP_SYS_ADMIN capability or has
|
||||
// not set the no_new_privs attribute <...>.
|
||||
// <...>
|
||||
// EFAULT
|
||||
// option is PR_SET_SECCOMP, arg2 is SECCOMP_MODE_FILTER, the
|
||||
// system was built with CONFIG_SECCOMP_FILTER, and arg3 is an
|
||||
// invalid address.
|
||||
// <...>
|
||||
// EINVAL
|
||||
// option is PR_SET_SECCOMP or PR_GET_SECCOMP, and the kernel
|
||||
// was not configured with CONFIG_SECCOMP.
|
||||
//
|
||||
// EINVAL
|
||||
// option is PR_SET_SECCOMP, arg2 is SECCOMP_MODE_FILTER,
|
||||
// and the kernel was not configured with CONFIG_SECCOMP_FILTER.
|
||||
// <end of quote>
|
||||
//
|
||||
// Meaning, in case these kernel options are set (this is what we check
|
||||
// for here), we will get some other error (most probably EACCES or
|
||||
// EFAULT). IOW, EINVAL means "seccomp not supported", any other error
|
||||
// means it is supported.
|
||||
|
||||
supOnce.Do(func() {
|
||||
supported = unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0) != unix.EINVAL
|
||||
})
|
||||
return supported
|
||||
}
|
|
@ -1,16 +1,28 @@
|
|||
package types
|
||||
package seccomp
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
// Seccomp represents the config for a seccomp profile for syscall restriction.
|
||||
type Seccomp struct {
|
||||
DefaultAction Action `json:"defaultAction"`
|
||||
|
||||
// DefaultErrnoRet is obsolete, please use DefaultErrno
|
||||
DefaultErrnoRet *uint `json:"defaultErrnoRet,omitempty"`
|
||||
DefaultErrno string `json:"defaultErrno,omitempty"`
|
||||
|
||||
// Architectures is kept to maintain backward compatibility with the old
|
||||
// seccomp profile.
|
||||
Architectures []Arch `json:"architectures,omitempty"`
|
||||
ArchMap []Architecture `json:"archMap,omitempty"`
|
||||
Syscalls []*Syscall `json:"syscalls"`
|
||||
Architectures []Arch `json:"architectures,omitempty"`
|
||||
ArchMap []Architecture `json:"archMap,omitempty"`
|
||||
Syscalls []*Syscall `json:"syscalls"`
|
||||
Flags []string `json:"flags,omitempty"`
|
||||
ListenerPath string `json:"listenerPath,omitempty"`
|
||||
ListenerMetadata string `json:"listenerMetadata,omitempty"`
|
||||
}
|
||||
|
||||
// Architecture is used to represent an specific architecture
|
||||
// Architecture is used to represent a specific architecture
|
||||
// and its sub-architectures
|
||||
type Architecture struct {
|
||||
Arch Arch `json:"architecture"`
|
||||
|
@ -23,6 +35,7 @@ type Arch string
|
|||
// Additional architectures permitted to be used for system calls
|
||||
// By default only the native architecture of the kernel is permitted
|
||||
const (
|
||||
ArchNative Arch = "SCMP_ARCH_NATIVE"
|
||||
ArchX86 Arch = "SCMP_ARCH_X86"
|
||||
ArchX86_64 Arch = "SCMP_ARCH_X86_64"
|
||||
ArchX32 Arch = "SCMP_ARCH_X32"
|
||||
|
@ -39,6 +52,9 @@ const (
|
|||
ArchPPC64LE Arch = "SCMP_ARCH_PPC64LE"
|
||||
ArchS390 Arch = "SCMP_ARCH_S390"
|
||||
ArchS390X Arch = "SCMP_ARCH_S390X"
|
||||
ArchPARISC Arch = "SCMP_ARCH_PARISC"
|
||||
ArchPARISC64 Arch = "SCMP_ARCH_PARISC64"
|
||||
ArchRISCV64 Arch = "SCMP_ARCH_RISCV64"
|
||||
)
|
||||
|
||||
// Action taken upon Seccomp rule match
|
||||
|
@ -46,11 +62,20 @@ type Action string
|
|||
|
||||
// Define actions for Seccomp rules
|
||||
const (
|
||||
ActKill Action = "SCMP_ACT_KILL"
|
||||
ActTrap Action = "SCMP_ACT_TRAP"
|
||||
ActErrno Action = "SCMP_ACT_ERRNO"
|
||||
ActTrace Action = "SCMP_ACT_TRACE"
|
||||
ActAllow Action = "SCMP_ACT_ALLOW"
|
||||
// ActKill results in termination of the thread that made the system call.
|
||||
ActKill Action = "SCMP_ACT_KILL"
|
||||
// ActKillProcess results in termination of the entire process.
|
||||
ActKillProcess Action = "SCMP_ACT_KILL_PROCESS"
|
||||
// ActKillThread kills the thread that violated the rule. It is the same as
|
||||
// ActKill. All other threads from the same thread group will continue to
|
||||
// execute.
|
||||
ActKillThread Action = "SCMP_ACT_KILL_THREAD"
|
||||
ActTrap Action = "SCMP_ACT_TRAP"
|
||||
ActErrno Action = "SCMP_ACT_ERRNO"
|
||||
ActTrace Action = "SCMP_ACT_TRACE"
|
||||
ActAllow Action = "SCMP_ACT_ALLOW"
|
||||
ActLog Action = "SCMP_ACT_LOG"
|
||||
ActNotify Action = "SCMP_ACT_NOTIFY"
|
||||
)
|
||||
|
||||
// Operator used to match syscall arguments in Seccomp
|
||||
|
@ -90,4 +115,7 @@ type Syscall struct {
|
|||
Comment string `json:"comment"`
|
||||
Includes Filter `json:"includes"`
|
||||
Excludes Filter `json:"excludes"`
|
||||
// ErrnoRet is obsolete, please use Errno
|
||||
ErrnoRet *uint `json:"errnoRet,omitempty"`
|
||||
Errno string `json:"errno,omitempty"`
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//go:build seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ValidateProfile does a basic validation for the provided seccomp profile
|
||||
// string.
|
||||
func ValidateProfile(content string) error {
|
||||
profile := &Seccomp{}
|
||||
if err := json.Unmarshal([]byte(content), &profile); err != nil {
|
||||
return fmt.Errorf("decoding seccomp profile: %w", err)
|
||||
}
|
||||
|
||||
spec, err := setupSeccomp(profile, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create seccomp spec: %w", err)
|
||||
}
|
||||
|
||||
if _, err := BuildFilter(spec); err != nil {
|
||||
return fmt.Errorf("build seccomp filter: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
147
vendor/github.com/docker/docker/AUTHORS → vendor/github.com/containers/storage/AUTHORS
generated
vendored
147
vendor/github.com/docker/docker/AUTHORS → vendor/github.com/containers/storage/AUTHORS
generated
vendored
|
@ -12,17 +12,11 @@ Abhijeet Kasurde <akasurde@redhat.com>
|
|||
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
||||
Abhishek Chanda <abhishek.becs@gmail.com>
|
||||
Abin Shahab <ashahab@altiscale.com>
|
||||
Adam Avilla <aavilla@yp.com>
|
||||
Adam Kunk <adam.kunk@tiaa-cref.org>
|
||||
Adam Miller <admiller@redhat.com>
|
||||
Adam Mills <adam@armills.info>
|
||||
Adam Singer <financeCoding@gmail.com>
|
||||
Adam Walz <adam@adamwalz.net>
|
||||
Aditi Rajagopal <arajagopal@us.ibm.com>
|
||||
Aditya <aditya@netroy.in>
|
||||
Adolfo Ochagavía <aochagavia92@gmail.com>
|
||||
Adria Casas <adriacasas88@gmail.com>
|
||||
Adrian Moisey <adrian@changeover.za.net>
|
||||
Adrian Mouat <adrian.mouat@gmail.com>
|
||||
Adrian Oprea <adrian@codesi.nz>
|
||||
Adrien Folie <folie.adrien@gmail.com>
|
||||
|
@ -47,11 +41,9 @@ Alena Prokharchyk <alena@rancher.com>
|
|||
Alessandro Boch <aboch@docker.com>
|
||||
Alessio Biancalana <dottorblaster@gmail.com>
|
||||
Alex Chan <alex@alexwlchan.net>
|
||||
Alex Coventry <alx@empirical.com>
|
||||
Alex Crawford <alex.crawford@coreos.com>
|
||||
Alex Ellis <alexellis2@gmail.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alex Olshansky <i@creagenics.com>
|
||||
Alex Samorukov <samm@os2.kiev.ua>
|
||||
Alex Warhawk <ax.warhawk@gmail.com>
|
||||
Alexander Artemenko <svetlyak.40wt@gmail.com>
|
||||
|
@ -59,7 +51,7 @@ Alexander Boyd <alex@opengroove.org>
|
|||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexander Morozov <lk4d4@docker.com>
|
||||
Alexander Shopov <ash@kambanaria.org>
|
||||
Alexandre Beslic <alexandre.beslic@gmail.com>
|
||||
Alexandre Beslic <abronan@docker.com>
|
||||
Alexandre González <agonzalezro@gmail.com>
|
||||
Alexandru Sfirlogea <alexandru.sfirlogea@gmail.com>
|
||||
Alexey Guskov <lexag@mail.ru>
|
||||
|
@ -70,13 +62,11 @@ Ali Dehghani <ali.dehghani.g@gmail.com>
|
|||
Allen Madsen <blatyo@gmail.com>
|
||||
Allen Sun <allen.sun@daocloud.io>
|
||||
almoehi <almoehi@users.noreply.github.com>
|
||||
Alvaro Saurin <alvaro.saurin@gmail.com>
|
||||
Alvin Richards <alvin.richards@docker.com>
|
||||
amangoel <amangoel@gmail.com>
|
||||
Amen Belayneh <amenbelayneh@gmail.com>
|
||||
Amit Bakshi <ambakshi@gmail.com>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Amit Shukla <amit.shukla@docker.com>
|
||||
Amy Lindburg <amy.lindburg@docker.com>
|
||||
Anand Patil <anand.prabhakar.patil@gmail.com>
|
||||
AnandkumarPatel <anandkumarpatel@gmail.com>
|
||||
|
@ -90,7 +80,6 @@ Andrea Turli <andrea.turli@gmail.com>
|
|||
Andreas Köhler <andi5.py@gmx.net>
|
||||
Andreas Savvides <andreas@editd.com>
|
||||
Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrei Gherzan <andrei@resin.io>
|
||||
Andrew C. Bodine <acbodine@us.ibm.com>
|
||||
Andrew Clay Shafer <andrewcshafer@gmail.com>
|
||||
Andrew Duckworth <grillopress@gmail.com>
|
||||
|
@ -102,7 +91,6 @@ Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
|||
Andrew Macpherson <hopscotch23@gmail.com>
|
||||
Andrew Martin <sublimino@gmail.com>
|
||||
Andrew Munsell <andrew@wizardapps.net>
|
||||
Andrew Po <absourd.noise@gmail.com>
|
||||
Andrew Weiss <andrew.weiss@outlook.com>
|
||||
Andrew Williams <williams.andrew@gmail.com>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
|
@ -119,7 +107,6 @@ Andy Smith <github@anarkystic.com>
|
|||
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
||||
Anes Hasicic <anes.hasicic@gmail.com>
|
||||
Anil Belur <askb23@gmail.com>
|
||||
Anil Madhavapeddy <anil@recoil.org>
|
||||
Ankush Agarwal <ankushagarwal11@gmail.com>
|
||||
Anonmily <michelle@michelleliu.io>
|
||||
Anthon van der Neut <anthon@mnt.org>
|
||||
|
@ -131,13 +118,11 @@ Anton Nikitin <anton.k.nikitin@gmail.com>
|
|||
Anton Polonskiy <anton.polonskiy@gmail.com>
|
||||
Anton Tiurin <noxiouz@yandex.ru>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com>
|
||||
Antonis Kalipetis <akalipetis@gmail.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
Anuj Bahuguna <anujbahuguna.dev@gmail.com>
|
||||
Anusha Ragunathan <anusha.ragunathan@docker.com>
|
||||
apocas <petermdias@gmail.com>
|
||||
ArikaChen <eaglesora@gmail.com>
|
||||
Arnaud Lefebvre <a.lefebvre@outlook.fr>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||
Arthur Barr <arthur.barr@uk.ibm.com>
|
||||
Arthur Gautier <baloo@gandi.net>
|
||||
|
@ -147,7 +132,6 @@ Asbjørn Enge <asbjorn@hanafjedle.net>
|
|||
averagehuman <averagehuman@users.noreply.github.com>
|
||||
Avi Das <andas222@gmail.com>
|
||||
Avi Miller <avi.miller@oracle.com>
|
||||
Avi Vaid <avaid1996@gmail.com>
|
||||
ayoshitake <airandfingers@gmail.com>
|
||||
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||
|
@ -169,7 +153,6 @@ Bernerd Schaefer <bj.schaefer@gmail.com>
|
|||
Bert Goethals <bert@bertg.be>
|
||||
Bharath Thiruveedula <bharath_ves@hotmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Bilal Amarni <bilal.amarni@gmail.com>
|
||||
Bill W <SydOps@users.noreply.github.com>
|
||||
bin liu <liubin0329@users.noreply.github.com>
|
||||
Blake Geno <blakegeno@gmail.com>
|
||||
|
@ -218,9 +201,7 @@ Cameron Boehmer <cameron.boehmer@gmail.com>
|
|||
Cameron Spear <cameronspear@gmail.com>
|
||||
Campbell Allen <campbell.allen@gmail.com>
|
||||
Candid Dauth <cdauth@cdauth.eu>
|
||||
Cao Weiwei <cao.weiwei30@zte.com.cn>
|
||||
Carl Henrik Lunde <chlunde@ping.uio.no>
|
||||
Carl Loa Odin <carlodin@gmail.com>
|
||||
Carl X. Su <bcbcarl@gmail.com>
|
||||
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Carlos Sanchez <carlos@apache.org>
|
||||
|
@ -238,7 +219,6 @@ Charles Law <claw@conduce.com>
|
|||
Charles Lindsay <chaz@chazomatic.us>
|
||||
Charles Merriam <charles.merriam@gmail.com>
|
||||
Charles Sarrazin <charles@sarraz.in>
|
||||
Charles Smith <charles.smith@docker.com>
|
||||
Charlie Lewis <charliel@lab41.org>
|
||||
Chase Bolt <chase.bolt@gmail.com>
|
||||
ChaYoung You <yousbe@gmail.com>
|
||||
|
@ -290,7 +270,6 @@ Colm Hally <colmhally@gmail.com>
|
|||
companycy <companycy@gmail.com>
|
||||
Cory Forsyth <cory.forsyth@gmail.com>
|
||||
cressie176 <github@stephen-cresswell.net>
|
||||
CrimsonGlory <CrimsonGlory@users.noreply.github.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
cristiano balducci <cristiano.balducci@gmail.com>
|
||||
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
||||
|
@ -299,14 +278,12 @@ Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
|||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
Dafydd Crosby <dtcrsby@gmail.com>
|
||||
dalanlan <dalanlan925@gmail.com>
|
||||
Damian Smyth <damian@dsau.co>
|
||||
Damien Nadé <github@livna.org>
|
||||
Damien Nozay <damien.nozay@gmail.com>
|
||||
Damjan Georgievski <gdamjan@gmail.com>
|
||||
Dan Anolik <dan@anolik.net>
|
||||
Dan Buch <d.buch@modcloth.com>
|
||||
Dan Cotora <dan@bluevision.ro>
|
||||
Dan Feldman <danf@jfrog.com>
|
||||
Dan Griffin <dgriffin@peer1.com>
|
||||
Dan Hirsch <thequux@upstandinghackers.com>
|
||||
Dan Keder <dan.keder@gmail.com>
|
||||
|
@ -329,7 +306,6 @@ Daniel Nordberg <dnordberg@gmail.com>
|
|||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel S <dan.streby@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel X Moore <yahivin@gmail.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
Daniel Zhang <jmzwcn@gmail.com>
|
||||
Daniel, Dao Quang Minh <dqminh@cloudflare.com>
|
||||
|
@ -338,9 +314,8 @@ Danny Yates <danny@codeaholics.org>
|
|||
Darren Coxall <darren@darrencoxall.com>
|
||||
Darren Shepherd <darren.s.shepherd@gmail.com>
|
||||
Darren Stahl <darst@microsoft.com>
|
||||
Davanum Srinivas <davanum@gmail.com>
|
||||
Dave Barboza <dbarboza@datto.com>
|
||||
Dave Henderson <dhenderson@gmail.com>
|
||||
Dave Henderson <Dave.Henderson@ca.ibm.com>
|
||||
Dave MacDonald <mindlapse@gmail.com>
|
||||
Dave Tucker <dt@docker.com>
|
||||
David Anderson <dave@natulte.net>
|
||||
|
@ -349,12 +324,9 @@ David Corking <dmc-source@dcorking.com>
|
|||
David Cramer <davcrame@cisco.com>
|
||||
David Currie <david_currie@uk.ibm.com>
|
||||
David Davis <daviddavis@redhat.com>
|
||||
David Dooling <dooling@gmail.com>
|
||||
David Gageot <david@gageot.net>
|
||||
David Gebler <davidgebler@gmail.com>
|
||||
David Lawrence <david.lawrence@docker.com>
|
||||
David Lechner <david@lechnology.com>
|
||||
David M. Karr <davidmichaelkarr@gmail.com>
|
||||
David Mackey <tdmackey@booleanhaiku.com>
|
||||
David Mat <david@davidmat.com>
|
||||
David Mcanulty <github@hellspark.com>
|
||||
|
@ -363,12 +335,10 @@ David R. Jenni <david.r.jenni@gmail.com>
|
|||
David Röthlisberger <david@rothlis.net>
|
||||
David Sheets <sheets@alum.mit.edu>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
David Trott <github@davidtrott.com>
|
||||
David Xia <dxia@spotify.com>
|
||||
David Young <yangboh@cn.ibm.com>
|
||||
Davide Ceretti <davide.ceretti@hogarthww.com>
|
||||
Dawn Chen <dawnchen@google.com>
|
||||
dbdd <wangtong2712@gmail.com>
|
||||
dcylabs <dcylabs@gmail.com>
|
||||
decadent <decadent@users.noreply.github.com>
|
||||
deed02392 <georgehafiz@gmail.com>
|
||||
|
@ -387,10 +357,8 @@ devmeyster <arthurfbi@yahoo.com>
|
|||
Devvyn Murphy <devvyn@devvyn.com>
|
||||
Dharmit Shah <shahdharmit@gmail.com>
|
||||
Dieter Reuter <dieter.reuter@me.com>
|
||||
Dillon Dixon <dillondixon@gmail.com>
|
||||
Dima Stopel <dima@twistlock.com>
|
||||
Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
|
||||
Dimitris Rozakis <dimrozakis@gmail.com>
|
||||
Dimitry Andric <d.andric@activevideo.com>
|
||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||
Diogo Monica <diogo@docker.com>
|
||||
|
@ -398,10 +366,8 @@ DiuDiugirl <sophia.wang@pku.edu.cn>
|
|||
Djibril Koné <kone.djibril@gmail.com>
|
||||
dkumor <daniel@dkumor.com>
|
||||
Dmitri Logvinenko <dmitri.logvinenko@gmail.com>
|
||||
Dmitri Shuralyov <shurcooL@gmail.com>
|
||||
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
||||
Dmitry Gusev <dmitry.gusev@gmail.com>
|
||||
Dmitry Smirnov <onlyjob@member.fsf.org>
|
||||
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
||||
Dmitry Vorobev <dimahabr@gmail.com>
|
||||
Dolph Mathews <dolph.mathews@gmail.com>
|
||||
|
@ -413,20 +379,17 @@ Don Spaulding <donspauldingii@gmail.com>
|
|||
Donald Huang <don.hcd@gmail.com>
|
||||
Dong Chen <dongluo.chen@docker.com>
|
||||
Donovan Jones <git@gamma.net.nz>
|
||||
Doron Podoleanu <doronp@il.ibm.com>
|
||||
Doug Davis <dug@us.ibm.com>
|
||||
Doug MacEachern <dougm@vmware.com>
|
||||
Doug Tangren <d.tangren@gmail.com>
|
||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||
dragon788 <dragon788@users.noreply.github.com>
|
||||
Dražen Lučanin <kermit666@gmail.com>
|
||||
Drew Erny <drew.erny@docker.com>
|
||||
Dustin Sallings <dustin@spy.net>
|
||||
Ed Costello <epc@epcostello.com>
|
||||
Edmund Wagner <edmund-wagner@web.de>
|
||||
Eiichi Tsukata <devel@etsukata.com>
|
||||
Eike Herzbach <eike@herzbach.net>
|
||||
Eivin Giske Skaaren <eivinsn@axis.com>
|
||||
Eivind Uggedal <eivind@uggedal.com>
|
||||
Elan Ruusamäe <glen@delfi.ee>
|
||||
Elias Probst <mail@eliasprobst.eu>
|
||||
|
@ -439,7 +402,6 @@ Emily Rose <emily@contactvibe.com>
|
|||
Emir Ozer <emirozer@yandex.com>
|
||||
Enguerran <engcolson@gmail.com>
|
||||
Eohyung Lee <liquidnuker@gmail.com>
|
||||
Eric Barch <barch@tomesoftware.com>
|
||||
Eric Hanchrow <ehanchrow@ine.com>
|
||||
Eric Lee <thenorthsecedes@gmail.com>
|
||||
Eric Myhre <hash@exultant.us>
|
||||
|
@ -468,14 +430,12 @@ Evan Hazlett <ejhazlett@gmail.com>
|
|||
Evan Krall <krall@yelp.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
Everett Toews <everett.toews@rackspace.com>
|
||||
Evgeny Vereshchagin <evvers@ya.ru>
|
||||
Ewa Czechowska <ewa@ai-traders.com>
|
||||
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Fabiano Rosas <farosas@br.ibm.com>
|
||||
Fabio Falci <fabiofalci@gmail.com>
|
||||
Fabio Rapposelli <fabio@vmware.com>
|
||||
Fabio Rehm <fgrehm@gmail.com>
|
||||
Fabrizio Regini <freegenie@gmail.com>
|
||||
Fabrizio Soppelsa <fsoppelsa@mirantis.com>
|
||||
|
@ -488,12 +448,10 @@ Federico Gimenez <fgimenez@coit.es>
|
|||
Felix Geisendörfer <felix@debuggable.com>
|
||||
Felix Hupfeld <quofelix@users.noreply.github.com>
|
||||
Felix Rabe <felix@rabe.io>
|
||||
Felix Ruess <felix.ruess@roboception.de>
|
||||
Felix Schindler <fschindler@weluse.de>
|
||||
Ferenc Szabo <pragmaticfrank@gmail.com>
|
||||
Fernando <fermayo@gmail.com>
|
||||
Fero Volar <alian@alian.info>
|
||||
Ferran Rodenas <frodenas@gmail.com>
|
||||
Filipe Brandenburger <filbranden@google.com>
|
||||
Filipe Oliveira <contato@fmoliveira.com.br>
|
||||
fl0yd <fl0yd@me.com>
|
||||
|
@ -504,10 +462,7 @@ Florian Klein <florian.klein@free.fr>
|
|||
Florian Maier <marsmensch@users.noreply.github.com>
|
||||
Florian Weingarten <flo@hackvalue.de>
|
||||
Florin Asavoaie <florin.asavoaie@gmail.com>
|
||||
fonglh <fonglh@gmail.com>
|
||||
fortinux <fortinux@users.noreply.github.com>
|
||||
Francesc Campoy <campoy@google.com>
|
||||
Francis Chuang <francis.chuang@boostport.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Frank Groeneveld <frank@ivaldi.nl>
|
||||
|
@ -519,7 +474,6 @@ Frederick F. Kautz IV <fkautz@redhat.com>
|
|||
Frederik Loeffert <frederik@zitrusmedia.de>
|
||||
Frederik Nordahl Jul Sabroe <frederikns@gmail.com>
|
||||
Freek Kalter <freek@kalteronline.org>
|
||||
frosforever <frosforever@users.noreply.github.com>
|
||||
fy2462 <fy2462@gmail.com>
|
||||
Félix Baylac-Jacqué <baylac.felix@gmail.com>
|
||||
Félix Cantournet <felix.cantournet@cloudwatt.com>
|
||||
|
@ -575,7 +529,7 @@ Hao Zhang <21521210@zju.edu.cn>
|
|||
Harald Albers <github@albersweb.de>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Harold Cooper <hrldcpr@gmail.com>
|
||||
Harry Zhang <harryz@hyper.sh>
|
||||
Harry Zhang <harryzhang@zju.edu.cn>
|
||||
He Simei <hesimei@zju.edu.cn>
|
||||
heartlock <21521209@zju.edu.cn>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
|
@ -647,7 +601,6 @@ Jan Toebes <jan@toebes.info>
|
|||
Jan-Gerd Tenberge <janten@gmail.com>
|
||||
Jan-Jaap Driessen <janjaapdriessen@gmail.com>
|
||||
Jana Radhakrishnan <mrjana@docker.com>
|
||||
Jannick Fahlbusch <git@jf-projects.de>
|
||||
Januar Wayong <januar@gmail.com>
|
||||
Jared Biel <jared.biel@bolderthinking.com>
|
||||
Jared Hocutt <jaredh@netapp.com>
|
||||
|
@ -680,7 +633,6 @@ Jeff Lindsay <progrium@gmail.com>
|
|||
Jeff Mickey <j@codemac.net>
|
||||
Jeff Minard <jeff@creditkarma.com>
|
||||
Jeff Nickoloff <jeff.nickoloff@gmail.com>
|
||||
Jeff Silberman <jsilberm@gmail.com>
|
||||
Jeff Welch <whatthejeff@gmail.com>
|
||||
Jeffrey Bolle <jeffreybolle@gmail.com>
|
||||
Jeffrey Morgan <jmorganca@gmail.com>
|
||||
|
@ -693,11 +645,10 @@ Jeremy Unruh <jeremybunruh@gmail.com>
|
|||
Jeroen Jacobs <github@jeroenj.be>
|
||||
Jesse Dearing <jesse.dearing@gmail.com>
|
||||
Jesse Dubay <jesse@thefortytwo.net>
|
||||
Jessica Frazelle <jessfraz@google.com>
|
||||
Jessica Frazelle <jess@mesosphere.com>
|
||||
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||
jgeiger <jgeiger@gmail.com>
|
||||
Jhon Honce <jhonce@redhat.com>
|
||||
Ji.Zhilong <zhilongji@gmail.com>
|
||||
Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
|
||||
jianbosun <wonderflow.sun@gmail.com>
|
||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||
|
@ -711,7 +662,6 @@ Jiri Popelka <jpopelka@redhat.com>
|
|||
Jiří Župka <jzupka@redhat.com>
|
||||
jjy <jiangjinyang@outlook.com>
|
||||
jmzwcn <jmzwcn@gmail.com>
|
||||
Joao Fernandes <joao.fernandes@docker.com>
|
||||
Joe Beda <joe.github@bedafamily.com>
|
||||
Joe Doliner <jdoliner@pachyderm.io>
|
||||
Joe Ferguson <joe@infosiftr.com>
|
||||
|
@ -727,7 +677,6 @@ Joey Gibson <joey@joeygibson.com>
|
|||
Joffrey F <joffrey@docker.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Johan Rydberg <johan.rydberg@gmail.com>
|
||||
Johanan Lieberman <johanan.lieberman@gmail.com>
|
||||
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||
John Costa <john.costa@gmail.com>
|
||||
John Feminella <jxf@jxf.me>
|
||||
|
@ -739,7 +688,6 @@ John Starks <jostarks@microsoft.com>
|
|||
John Tims <john.k.tims@gmail.com>
|
||||
John Warwick <jwarwick@gmail.com>
|
||||
John Willis <john.willis@docker.com>
|
||||
johnharris85 <john@johnharris.io>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
||||
|
@ -747,25 +695,21 @@ Jonathan Boulle <jonathanboulle@gmail.com>
|
|||
Jonathan Camp <jonathan@irondojo.com>
|
||||
Jonathan Dowland <jon+github@alcopop.org>
|
||||
Jonathan Lebon <jlebon@redhat.com>
|
||||
Jonathan Lomas <jonathan@floatinglomas.ca>
|
||||
Jonathan McCrohan <jmccrohan@gmail.com>
|
||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
||||
Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Jonathan Stoppani <jonathan.stoppani@divio.com>
|
||||
Joost Cassee <joost@cassee.net>
|
||||
Jordan <jjn2009@users.noreply.github.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Jordan Sissel <jls@semicomplete.com>
|
||||
Jordan Williams <jordan@jwillikers.com>
|
||||
Jose Diaz-Gonzalez <josegonzalez@users.noreply.github.com>
|
||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||
Joseph Hager <ajhager@gmail.com>
|
||||
Joseph Kern <jkern@semafour.net>
|
||||
Josh <jokajak@gmail.com>
|
||||
Josh Bodah <jb3689@yahoo.com>
|
||||
Josh Chorlton <jchorlton@gmail.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Josh Horwitz <horwitz@addthis.com>
|
||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||
Josiah Kiehl <jkiehl@riotgames.com>
|
||||
José Tomás Albornoz <jojo@eljojo.net>
|
||||
|
@ -786,16 +730,14 @@ Justin Force <justin.force@gmail.com>
|
|||
Justin Plock <jplock@users.noreply.github.com>
|
||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||
Justin Terry <juterry@microsoft.com>
|
||||
Justyn Temme <justyntemme@gmail.com>
|
||||
Jyrki Puttonen <jyrkiput@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Jörg Thalheim <joerg@higgsboson.tk>
|
||||
Kai Blin <kai@samba.org>
|
||||
Kai Qiang Wu(Kennan) <wkq5325@gmail.com>
|
||||
Kai Qiang Wu(Kennan) <wkqwu@cn.ibm.com>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
kamjar gerami <kami.gerami@gmail.com>
|
||||
Kanstantsin Shautsou <kanstantsin.sha@gmail.com>
|
||||
Kara Alexandra <kalexandra@us.ibm.com>
|
||||
Karan Lyons <karan@karanlyons.com>
|
||||
Kareem Khazem <karkhaz@karkhaz.com>
|
||||
kargakis <kargakis@users.noreply.github.com>
|
||||
|
@ -807,22 +749,17 @@ Katrina Owen <katrina.owen@gmail.com>
|
|||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
||||
kayrus <kay.diam@gmail.com>
|
||||
Ke Xu <leonhartx.k@gmail.com>
|
||||
Keith Hudgins <greenman@greenman.org>
|
||||
Keli Hu <dev@keli.hu>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Ken Herner <kherner@progress.com>
|
||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
|
||||
Kenjiro Nakayama <nakayamakenjiro@gmail.com>
|
||||
Kent Johnson <kentoj@gmail.com>
|
||||
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
||||
Kevin Burke <kev@inburke.com>
|
||||
Kevin Clark <kevin.clark@gmail.com>
|
||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||
Kevin Jing Qiu <kevin@idempotent.ca>
|
||||
Kevin Menard <kevin@nirvdrum.com>
|
||||
Kevin P. Kucharczyk <kevinkucharczyk@gmail.com>
|
||||
Kevin Richardson <kevin@kevinrichardson.co>
|
||||
Kevin Shi <kshi@andrew.cmu.edu>
|
||||
Kevin Wallace <kevin@pentabarf.net>
|
||||
Kevin Yap <me@kevinyap.ca>
|
||||
|
@ -834,23 +771,19 @@ Kim Eik <kim@heldig.org>
|
|||
Kimbro Staken <kstaken@kstaken.com>
|
||||
Kir Kolyshkin <kir@openvz.org>
|
||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||
Kirill Kolyshkin <kolyshkin@users.noreply.github.com>
|
||||
Kirill SIbirev <l0kix2@gmail.com>
|
||||
knappe <tyler.knappe@gmail.com>
|
||||
Kohei Tsuruta <coheyxyz@gmail.com>
|
||||
Koichi Shiraishi <k@zchee.io>
|
||||
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
||||
Konstantin L <sw.double@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
Krasimir Georgiev <support@vip-consult.co.uk>
|
||||
Kris-Mikael Krister <krismikael@protonmail.com>
|
||||
Kristian Haugene <kristian.haugene@capgemini.com>
|
||||
Kristina Zabunova <triara.xiii@gmail.com>
|
||||
krrg <krrgithub@gmail.com>
|
||||
Kun Zhang <zkazure@gmail.com>
|
||||
Kunal Kushwaha <kunal.kushwaha@gmail.com>
|
||||
Kyle Conroy <kyle.j.conroy@gmail.com>
|
||||
Kyle Linden <linden.kyle@gmail.com>
|
||||
kyu <leehk1227@gmail.com>
|
||||
Lachlan Coote <lcoote@vmware.com>
|
||||
Lai Jiangshan <jiangshanlai@gmail.com>
|
||||
|
@ -867,7 +800,6 @@ Laszlo Meszaros <lacienator@gmail.com>
|
|||
Laurent Erignoux <lerignoux@gmail.com>
|
||||
Laurie Voss <github@seldo.com>
|
||||
Leandro Siqueira <leandro.siqueira@gmail.com>
|
||||
Lee Chao <932819864@qq.com>
|
||||
Lee, Meng-Han <sunrisedm4@gmail.com>
|
||||
leeplay <hyeongkyu.lee@navercorp.com>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
|
@ -878,7 +810,6 @@ Levi Blackstone <levi.blackstone@rackspace.com>
|
|||
Levi Gross <levi@levigross.com>
|
||||
Lewis Marshall <lewis@lmars.net>
|
||||
Lewis Peckover <lew+github@lew.io>
|
||||
Liam Macgillavry <liam@kumina.nl>
|
||||
Liana Lo <liana.lixia@gmail.com>
|
||||
Liang Mingqiang <mqliang.zju@gmail.com>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
|
@ -891,7 +822,6 @@ Liran Tal <liran.tal@gmail.com>
|
|||
Liron Levin <liron@twistlock.com>
|
||||
Liu Bo <bo.li.liu@oracle.com>
|
||||
Liu Hua <sdu.liu@huawei.com>
|
||||
lixiaobing10051267 <li.xiaobing1@zte.com.cn>
|
||||
LIZAO LI <lzlarryli@gmail.com>
|
||||
Lloyd Dewolf <foolswisdom@gmail.com>
|
||||
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
||||
|
@ -903,8 +833,6 @@ Luca Marturana <lucamarturana@gmail.com>
|
|||
Luca Orlandi <luca.orlandi@gmail.com>
|
||||
Luca-Bogdan Grigorescu <Luca-Bogdan Grigorescu>
|
||||
Lucas Chan <lucas-github@lucaschan.com>
|
||||
Lucas Chi <lucas@teacherspayteachers.com>
|
||||
Luciano Mores <leslau@gmail.com>
|
||||
Luis Martínez de Bartolomé Izquierdo <lmartinez@biicode.com>
|
||||
Lukas Waslowski <cr7pt0gr4ph7@gmail.com>
|
||||
lukaspustina <lukas.pustina@centerdevice.com>
|
||||
|
@ -923,7 +851,6 @@ Malte Janduda <mail@janduda.net>
|
|||
manchoz <giampaolo@trampolineup.com>
|
||||
Manfred Touron <m@42.am>
|
||||
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
||||
Mansi Nahar <mmn4185@rit.edu>
|
||||
mansinahar <mansinahar@users.noreply.github.com>
|
||||
Manuel Meurer <manuel@krautcomputing.com>
|
||||
Manuel Woelker <github@manuel.woelker.org>
|
||||
|
@ -966,10 +893,8 @@ Matt Apperson <me@mattapperson.com>
|
|||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matt Bentley <matt.bentley@docker.com>
|
||||
Matt Haggard <haggardii@gmail.com>
|
||||
Matt Hoyle <matt@deployable.co>
|
||||
Matt McCormick <matt.mccormick@kitware.com>
|
||||
Matt Moore <mattmoor@google.com>
|
||||
Matt Richardson <matt@redgumtech.com.au>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matthew Heon <mheon@redhat.com>
|
||||
Matthew Mayer <matthewkmayer@gmail.com>
|
||||
|
@ -981,10 +906,9 @@ Matthias Rampke <mr@soundcloud.com>
|
|||
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||
mattymo <raytrac3r@gmail.com>
|
||||
mattyw <mattyw@me.com>
|
||||
Mauricio Garavaglia <mauricio@medallia.com>
|
||||
Mauricio Garavaglia <mauriciogaravaglia@gmail.com>
|
||||
mauriyouth <mauriyouth@gmail.com>
|
||||
Max Shytikov <mshytikov@gmail.com>
|
||||
Maxim Fedchyshyn <sevmax@gmail.com>
|
||||
Maxim Ivanov <ivanov.maxim@gmail.com>
|
||||
Maxim Kulkin <mkulkin@mirantis.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
|
@ -993,10 +917,8 @@ Meaglith Ma <genedna@gmail.com>
|
|||
meejah <meejah@meejah.ca>
|
||||
Megan Kostick <mkostick@us.ibm.com>
|
||||
Mehul Kar <mehul.kar@gmail.com>
|
||||
Mei ChunTao <mei.chuntao@zte.com.cn>
|
||||
Mengdi Gao <usrgdd@gmail.com>
|
||||
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
||||
mgniu <mgniu@dataman-inc.com>
|
||||
Micah Zoltu <micah@newrelic.com>
|
||||
Michael A. Smith <michael@smith-li.com>
|
||||
Michael Bridgen <mikeb@squaremobius.net>
|
||||
|
@ -1022,11 +944,9 @@ Michal Fojtik <mfojtik@redhat.com>
|
|||
Michal Gebauer <mishak@mishak.net>
|
||||
Michal Jemala <michal.jemala@gmail.com>
|
||||
Michal Minar <miminar@redhat.com>
|
||||
Michal Wieczorek <wieczorek-michal@wp.pl>
|
||||
Michaël Pailloncy <mpapo.dev@gmail.com>
|
||||
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||
Michiel@unhosted <michiel@unhosted.org>
|
||||
Mickaël FORTUNATO <morsi.morsicus@gmail.com>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Miguel Morales <mimoralea@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com>
|
||||
|
@ -1037,7 +957,7 @@ Mike Danese <mikedanese@google.com>
|
|||
Mike Dillon <mike@embody.org>
|
||||
Mike Dougherty <mike.dougherty@docker.com>
|
||||
Mike Gaffney <mike@uberu.com>
|
||||
Mike Goelzer <mike.goelzer@docker.com>
|
||||
Mike Goelzer <mgoelzer@docker.com>
|
||||
Mike Leone <mleone896@gmail.com>
|
||||
Mike MacCana <mike.maccana@gmail.com>
|
||||
Mike Naberezny <mike@naberezny.com>
|
||||
|
@ -1047,7 +967,6 @@ Mikhail Sobolev <mss@mawhrin.net>
|
|||
Miloslav Trmač <mitr@redhat.com>
|
||||
mingqing <limingqing@cyou-inc.com>
|
||||
Mingzhen Feng <fmzhen@zju.edu.cn>
|
||||
Misty Stanley-Jones <misty@docker.com>
|
||||
Mitch Capper <mitch.capper@gmail.com>
|
||||
mlarcher <github@ringabell.org>
|
||||
Mohammad Banikazemi <mb@us.ibm.com>
|
||||
|
@ -1084,20 +1003,15 @@ Nathan LeClaire <nathan.leclaire@docker.com>
|
|||
Nathan McCauley <nathan.mccauley@docker.com>
|
||||
Nathan Williams <nathan@teamtreehouse.com>
|
||||
Neal McBurnett <neal@mcburnett.org>
|
||||
Neil Peterson <neilpeterson@outlook.com>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
Neyazul Haque <nuhaque@gmail.com>
|
||||
Nghia Tran <nghia@google.com>
|
||||
Niall O'Higgins <niallo@unworkable.org>
|
||||
Nicholas E. Rabenau <nerab@gmx.at>
|
||||
nick <nicholasjamesrusso@gmail.com>
|
||||
Nick DeCoursin <n.decoursin@foodpanda.com>
|
||||
Nick Irvine <nfirvine@nfirvine.com>
|
||||
Nick Parker <nikaios@gmail.com>
|
||||
Nick Payne <nick@kurai.co.uk>
|
||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||
Nick Stinemates <nick@stinemates.org>
|
||||
Nicola Kabar <nicolaka@gmail.com>
|
||||
Nicolas Borboën <ponsfrilus@users.noreply.github.com>
|
||||
Nicolas De loof <nicolas.deloof@gmail.com>
|
||||
Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
||||
|
@ -1122,14 +1036,11 @@ odk- <github@odkurzacz.org>
|
|||
Oguz Bilgic <fisyonet@gmail.com>
|
||||
Oh Jinkyun <tintypemolly@gmail.com>
|
||||
Ohad Schneider <ohadschn@users.noreply.github.com>
|
||||
ohmystack <jun.jiang02@ele.me>
|
||||
Ole Reifschneider <mail@ole-reifschneider.de>
|
||||
Oliver Neal <ItsVeryWindy@users.noreply.github.com>
|
||||
Olivier Gambier <dmp42@users.noreply.github.com>
|
||||
Olle Jonsson <olle.jonsson@gmail.com>
|
||||
Oriol Francès <oriolfa@gmail.com>
|
||||
orkaa <orkica@gmail.com>
|
||||
Oskar Niburski <oskarniburski@gmail.com>
|
||||
Otto Kekäläinen <otto@seravo.fi>
|
||||
oyld <oyld0210@163.com>
|
||||
ozlerhakan <hakan.ozler@kodcu.com>
|
||||
|
@ -1139,7 +1050,6 @@ panticz <mail@konczalski.de>
|
|||
Paolo G. Giarrusso <p.giarrusso@gmail.com>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Pascal Hartig <phartig@rdrei.net>
|
||||
Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||
Patrick Devine <patrick.devine@docker.com>
|
||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||
Patrick Stapleton <github@gdi2290.com>
|
||||
|
@ -1149,7 +1059,6 @@ paul <paul@inkling.com>
|
|||
Paul Annesley <paul@annesley.cc>
|
||||
Paul Bellamy <paul.a.bellamy@gmail.com>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Furtado <pfurtado@hubspot.com>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Paul Jimenez <pj@place.org>
|
||||
Paul Lietar <paul@lietar.net>
|
||||
|
@ -1157,19 +1066,16 @@ Paul Liljenberg <liljenberg.paul@gmail.com>
|
|||
Paul Morie <pmorie@gmail.com>
|
||||
Paul Nasrat <pnasrat@gmail.com>
|
||||
Paul Weaver <pauweave@cisco.com>
|
||||
Paulo Ribeiro <paigr.io@gmail.com>
|
||||
Pavel Lobashov <ShockwaveNN@gmail.com>
|
||||
Pavel Pospisil <pospispa@gmail.com>
|
||||
Pavel Sutyrin <pavel.sutyrin@gmail.com>
|
||||
Pavel Tikhomirov <ptikhomirov@parallels.com>
|
||||
Pavlos Ratis <dastergon@gentoo.org>
|
||||
Pavol Vargovcik <pallly.vargovcik@gmail.com>
|
||||
Peeyush Gupta <gpeeyush@linux.vnet.ibm.com>
|
||||
Peggy Li <peggyli.224@gmail.com>
|
||||
Pei Su <sillyousu@gmail.com>
|
||||
Penghan Wang <ph.wang@daocloud.io>
|
||||
perhapszzy@sina.com <perhapszzy@sina.com>
|
||||
pestophagous <pestophagous@users.noreply.github.com>
|
||||
Peter Bourgon <peter@bourgon.org>
|
||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||
Peter Choi <reikani@Peters-MacBook-Pro.local>
|
||||
|
@ -1181,7 +1087,6 @@ Peter Malmgren <ptmalmgren@gmail.com>
|
|||
Peter Salvatore <peter@psftw.com>
|
||||
Peter Volpe <petervo@redhat.com>
|
||||
Peter Waller <p@pwaller.net>
|
||||
Petr Švihlík <svihlik.petr@gmail.com>
|
||||
Phil <underscorephil@gmail.com>
|
||||
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
|
@ -1193,7 +1098,6 @@ pidster <pid@pidster.com>
|
|||
Piergiuliano Bossi <pgbossi@gmail.com>
|
||||
Pierre <py@poujade.org>
|
||||
Pierre Carrier <pierre@meteor.com>
|
||||
Pierre Dal-Pra <dalpra.pierre@gmail.com>
|
||||
Pierre Wacrenier <pierre.wacrenier@gmail.com>
|
||||
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
|
@ -1219,11 +1123,9 @@ Raghavendra K T <raghavendra.kt@linux.vnet.ibm.com>
|
|||
Raghuram Devarakonda <draghuram@gmail.com>
|
||||
Rajat Pandit <rp@rajatpandit.com>
|
||||
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
||||
Ralf Sippl <ralf.sippl@gmail.com>
|
||||
Ralle <spam@rasmusa.net>
|
||||
Ralph Bean <rbean@redhat.com>
|
||||
Ramkumar Ramachandra <artagnon@gmail.com>
|
||||
Ramon Brooker <rbrooker@aetherealmind.com>
|
||||
Ramon van Alteren <ramon@vanalteren.nl>
|
||||
Ray Tsang <saturnism@users.noreply.github.com>
|
||||
ReadmeCritic <frankensteinbot@gmail.com>
|
||||
|
@ -1234,12 +1136,10 @@ Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
|||
resouer <resouer@163.com>
|
||||
rgstephens <greg@udon.org>
|
||||
Rhys Hiltner <rhys@twitch.tv>
|
||||
Rich Moyse <rich@moyse.us>
|
||||
Rich Seymour <rseymour@gmail.com>
|
||||
Richard <richard.scothern@gmail.com>
|
||||
Richard Burnison <rburnison@ebay.com>
|
||||
Richard Harvey <richard@squarecows.com>
|
||||
Richard Mathie <richard.mathie@amey.co.uk>
|
||||
Richard Metzler <richard@paadee.com>
|
||||
Richard Scothern <richard.scothern@gmail.com>
|
||||
Richo Healey <richo@psych0tik.net>
|
||||
|
@ -1256,7 +1156,6 @@ Robert Bachmann <rb@robertbachmann.at>
|
|||
Robert Bittle <guywithnose@gmail.com>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Robert Stern <lexandro2000@gmail.com>
|
||||
Robert Terhaar <robbyt@users.noreply.github.com>
|
||||
Robert Wallis <smilingrob@gmail.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
||||
Robin Naundorf <r.naundorf@fh-muenster.de>
|
||||
|
@ -1275,10 +1174,8 @@ Roland Moriz <rmoriz@users.noreply.github.com>
|
|||
Roma Sokolov <sokolov.r.v@gmail.com>
|
||||
Roman Strashkin <roman.strashkin@gmail.com>
|
||||
Ron Smits <ron.smits@gmail.com>
|
||||
Ron Williams <ron.a.williams@gmail.com>
|
||||
root <docker-dummy@example.com>
|
||||
root <root@localhost>
|
||||
root <root@lxdebmas.marist.edu>
|
||||
root <root@ubuntu-14.04-amd64-vbox>
|
||||
root <root@webm215.cluster016.ha.ovh.net>
|
||||
Rory Hunter <roryhunter2@gmail.com>
|
||||
|
@ -1289,7 +1186,6 @@ Rozhnov Alexandr <nox73@ya.ru>
|
|||
rsmoorthy <rsmoorthy@users.noreply.github.com>
|
||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||
Rui Lopes <rgl@ruilopes.com>
|
||||
Runshen Zhu <runshen.zhu@gmail.com>
|
||||
Ryan Anderson <anderson.ryanc@gmail.com>
|
||||
Ryan Aslett <github@mixologic.com>
|
||||
Ryan Belgrave <rmb1993@gmail.com>
|
||||
|
@ -1309,7 +1205,6 @@ Sabin Basyal <sabin.basyal@gmail.com>
|
|||
Sachin Joshi <sachin_jayant_joshi@hotmail.com>
|
||||
Sagar Hani <sagarhani33@gmail.com>
|
||||
Sainath Grandhi <sainath.grandhi@intel.com>
|
||||
sakeven <jc5930@sina.cn>
|
||||
Sally O'Malley <somalley@redhat.com>
|
||||
Sam Abed <sam.abed@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
|
@ -1331,7 +1226,6 @@ sapphiredev <se.imas.kr@gmail.com>
|
|||
Satnam Singh <satnam@raintown.org>
|
||||
satoru <satorulogic@gmail.com>
|
||||
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
||||
Satoshi Tagomori <tagomoris@gmail.com>
|
||||
scaleoutsean <scaleoutsean@users.noreply.github.com>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Scott Collier <emailscottcollier@gmail.com>
|
||||
|
@ -1352,25 +1246,19 @@ Seongyeol Lim <seongyeol37@gmail.com>
|
|||
Serge Hallyn <serge.hallyn@ubuntu.com>
|
||||
Sergey Alekseev <sergey.alekseev.minsk@gmail.com>
|
||||
Sergey Evstifeev <sergey.evstifeev@gmail.com>
|
||||
Serhat Gülçiçek <serhat25@gmail.com>
|
||||
Sevki Hasirci <s@sevki.org>
|
||||
Shane Canon <scanon@lbl.gov>
|
||||
Shane da Silva <shane@dasilva.io>
|
||||
shaunol <shaunol@gmail.com>
|
||||
Shawn Landden <shawn@churchofgit.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
shawnhe <shawnhe@shawnhedeMacBook-Pro.local>
|
||||
Shekhar Gulati <shekhargulati84@gmail.com>
|
||||
Sheng Yang <sheng@yasker.org>
|
||||
Shengbo Song <thomassong@tencent.com>
|
||||
Shev Yan <yandong_8212@163.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Shijiang Wei <mountkin@gmail.com>
|
||||
Shishir Mahajan <shishir.mahajan@redhat.com>
|
||||
Shoubhik Bose <sbose78@gmail.com>
|
||||
Shourya Sarcar <shourya.sarcar@gmail.com>
|
||||
shuai-z <zs.broccoli@gmail.com>
|
||||
Shukui Yang <yangshukui@huawei.com>
|
||||
Shuwei Hao <haosw@cn.ibm.com>
|
||||
Sian Lerk Lau <kiawin@gmail.com>
|
||||
sidharthamani <sid@rancher.com>
|
||||
|
@ -1381,7 +1269,6 @@ Simon Leinen <simon.leinen@gmail.com>
|
|||
Simon Taranto <simon.taranto@gmail.com>
|
||||
Sindhu S <sindhus@live.in>
|
||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||
skaasten <shaunk@gmail.com>
|
||||
Solganik Alexander <solganik@gmail.com>
|
||||
Solomon Hykes <solomon@docker.com>
|
||||
Song Gao <song@gao.io>
|
||||
|
@ -1402,13 +1289,11 @@ Stefan Staudenmeyer <doerte@instana.com>
|
|||
Stefan Weil <sw@weilnetz.de>
|
||||
Stephen Crosby <stevecrozz@gmail.com>
|
||||
Stephen Day <stephen.day@docker.com>
|
||||
Stephen Drake <stephen@xenolith.net>
|
||||
Stephen Rust <srust@blockbridge.com>
|
||||
Steve Durrheimer <s.durrheimer@gmail.com>
|
||||
Steve Francia <steve.francia@gmail.com>
|
||||
Steve Koch <stevekochscience@gmail.com>
|
||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||
Steven Erenst <stevenerenst@gmail.com>
|
||||
Steven Iveson <sjiveson@outlook.com>
|
||||
Steven Merrill <steven.merrill@gmail.com>
|
||||
Steven Richards <steven@axiomzen.co>
|
||||
|
@ -1423,7 +1308,6 @@ Sylvain Bellemare <sylvain@ascribe.io>
|
|||
Sébastien <sebastien@yoozio.com>
|
||||
Sébastien Luttringer <seblu@seblu.net>
|
||||
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
||||
Tadej Janež <tadej.j@nez.si>
|
||||
TAGOMORI Satoshi <tagomoris@gmail.com>
|
||||
tang0th <tang0th@gmx.com>
|
||||
Tangi COLIN <tangicolin@gmail.com>
|
||||
|
@ -1456,9 +1340,7 @@ Thomas Swift <tgs242@gmail.com>
|
|||
Thomas Tanaka <thomas.tanaka@oracle.com>
|
||||
Thomas Texier <sharkone@en-mousse.org>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tianyi Wang <capkurmagati@gmail.com>
|
||||
Tibor Vass <teabee89@gmail.com>
|
||||
Tiffany Jernigan <tiffany.f.j@gmail.com>
|
||||
Tiffany Low <tiffany@box.com>
|
||||
Tim Bosse <taim@bosboot.org>
|
||||
Tim Dettrick <t.dettrick@uq.edu.au>
|
||||
|
@ -1470,7 +1352,6 @@ Tim Terhorst <mynamewastaken+git@gmail.com>
|
|||
Tim Wang <timwangdev@gmail.com>
|
||||
Tim Waugh <twaugh@redhat.com>
|
||||
Tim Wraight <tim.wraight@tangentlabs.co.uk>
|
||||
timfeirg <kkcocogogo@gmail.com>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
tjwebb123 <tjwebb123@users.noreply.github.com>
|
||||
tobe <tobegit3hub@gmail.com>
|
||||
|
@ -1478,7 +1359,6 @@ Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
|||
Tobias Bradtke <webwurst@gmail.com>
|
||||
Tobias Gesellchen <tobias@gesellix.de>
|
||||
Tobias Klauser <tklauser@distanz.ch>
|
||||
Tobias Munk <schmunk@usrbin.de>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schwab <tobias.schwab@dynport.de>
|
||||
Todd Crane <todd@toddcrane.com>
|
||||
|
@ -1523,7 +1403,6 @@ vagrant <vagrant@ubuntu-14.04-amd64-vbox>
|
|||
Vaidas Jablonskis <jablonskis@gmail.com>
|
||||
Veres Lajos <vlajos@gmail.com>
|
||||
vgeta <gopikannan.venugopalsamy@gmail.com>
|
||||
Victor Algaze <valgaze@gmail.com>
|
||||
Victor Coisne <victor.coisne@dotcloud.com>
|
||||
Victor Costan <costan@gmail.com>
|
||||
Victor I. Wood <viw@t2am.com>
|
||||
|
@ -1552,7 +1431,6 @@ Vivek Dasgupta <vdasgupt@redhat.com>
|
|||
Vivek Goyal <vgoyal@redhat.com>
|
||||
Vladimir Bulyga <xx@ccxx.cc>
|
||||
Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Vladimir Pouzanov <farcaller@google.com>
|
||||
Vladimir Rutsky <altsysrq@gmail.com>
|
||||
Vladimir Varankin <nek.narqo+git@gmail.com>
|
||||
VladimirAus <v_roudakov@yahoo.com>
|
||||
|
@ -1570,7 +1448,6 @@ weiyan <weiyan3@huawei.com>
|
|||
Weiyang Zhu <cnresonant@gmail.com>
|
||||
Wen Cheng Ma <wenchma@cn.ibm.com>
|
||||
Wendel Fleming <wfleming@usc.edu>
|
||||
Wenkai Yin <yinw@vmware.com>
|
||||
Wenxuan Zhao <viz@linux.com>
|
||||
Wenyu You <21551128@zju.edu.cn>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
|
@ -1587,9 +1464,7 @@ WiseTrem <shepelyov.g@gmail.com>
|
|||
wlan0 <sidharthamn@gmail.com>
|
||||
Wolfgang Powisch <powo@powo.priv.at>
|
||||
wonderflow <wonderflow.sun@gmail.com>
|
||||
Wonjun Kim <wonjun.kim@navercorp.com>
|
||||
xamyzhao <x.amy.zhao@gmail.com>
|
||||
Xianlu Bird <xianlubird@gmail.com>
|
||||
XiaoBing Jiang <s7v7nislands@gmail.com>
|
||||
Xiaoxu Chen <chenxiaoxu14@otcaix.iscas.ac.cn>
|
||||
xiekeyang <xiekeyang@huawei.com>
|
||||
|
@ -1602,7 +1477,6 @@ YAMADA Tsuyoshi <tyamada@minimum2scp.org>
|
|||
Yan Feng <yanfeng2@huawei.com>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
yangshukui <yangshukui@huawei.com>
|
||||
Yanqiang Miao <miao.yanqiang@zte.com.cn>
|
||||
Yasunori Mahata <nori@mahata.net>
|
||||
Yestin Sun <sunyi0804@gmail.com>
|
||||
Yi EungJun <eungjun.yi@navercorp.com>
|
||||
|
@ -1617,8 +1491,6 @@ Youcef YEKHLEF <yyekhlef@gmail.com>
|
|||
Yuan Sun <sunyuan3@huawei.com>
|
||||
yuchangchun <yuchangchun1@huawei.com>
|
||||
yuchengxia <yuchengxia@huawei.com>
|
||||
yuexiao-wang <wang.yuexiao@zte.com.cn>
|
||||
YuPengZTE <yu.peng36@zte.com.cn>
|
||||
Yurii Rashkovskii <yrashk@gmail.com>
|
||||
yuzou <zouyu7@huawei.com>
|
||||
Zac Dover <zdover@redhat.com>
|
||||
|
@ -1633,7 +1505,6 @@ Zhang Kun <zkazure@gmail.com>
|
|||
Zhang Wei <zhangwei555@huawei.com>
|
||||
Zhang Wentao <zhangwentao234@huawei.com>
|
||||
Zhenan Ye <21551168@zju.edu.cn>
|
||||
zhouhao <zhouhao@cn.fujitsu.com>
|
||||
Zhu Guihua <zhugh.fnst@cn.fujitsu.com>
|
||||
Zhuoyun Wei <wzyboy@wzyboy.org>
|
||||
Zilin Du <zilin.du@gmail.com>
|
0
vendor/github.com/docker/docker/NOTICE → vendor/github.com/containers/storage/NOTICE
generated
vendored
0
vendor/github.com/docker/docker/NOTICE → vendor/github.com/containers/storage/NOTICE
generated
vendored
38
vendor/github.com/containers/storage/pkg/fileutils/exists_freebsd.go
generated
vendored
Normal file
38
vendor/github.com/containers/storage/pkg/fileutils/exists_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Exists checks whether a file or directory exists at the given path.
|
||||
// If the path is a symlink, the symlink is followed.
|
||||
func Exists(path string) error {
|
||||
// It uses unix.Faccessat which is a faster operation compared to os.Stat for
|
||||
// simply checking the existence of a file.
|
||||
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, 0)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lexists checks whether a file or directory exists at the given path.
|
||||
// If the path is a symlink, the symlink itself is checked.
|
||||
func Lexists(path string) error {
|
||||
// FreeBSD before 15.0 does not support the AT_SYMLINK_NOFOLLOW flag for
|
||||
// faccessat. In this case, the call to faccessat will return EINVAL and
|
||||
// we fall back to using Lstat.
|
||||
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EINVAL) {
|
||||
_, err = os.Lstat(path)
|
||||
return err
|
||||
}
|
||||
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
//go:build !windows && !freebsd
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Exists checks whether a file or directory exists at the given path.
|
||||
// If the path is a symlink, the symlink is followed.
|
||||
func Exists(path string) error {
|
||||
// It uses unix.Faccessat which is a faster operation compared to os.Stat for
|
||||
// simply checking the existence of a file.
|
||||
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, unix.AT_EACCESS)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lexists checks whether a file or directory exists at the given path.
|
||||
// If the path is a symlink, the symlink itself is checked.
|
||||
func Lexists(path string) error {
|
||||
// It uses unix.Faccessat which is a faster operation compared to os.Stat for
|
||||
// simply checking the existence of a file.
|
||||
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW|unix.AT_EACCESS)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
18
vendor/github.com/containers/storage/pkg/fileutils/exists_windows.go
generated
vendored
Normal file
18
vendor/github.com/containers/storage/pkg/fileutils/exists_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Exists checks whether a file or directory exists at the given path.
|
||||
func Exists(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
return err
|
||||
}
|
||||
|
||||
// Lexists checks whether a file or directory exists at the given path, without
|
||||
// resolving symlinks
|
||||
func Lexists(path string) error {
|
||||
_, err := os.Lstat(path)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PatternMatcher allows checking paths against a list of patterns
|
||||
type PatternMatcher struct {
|
||||
patterns []*Pattern
|
||||
exclusions bool
|
||||
}
|
||||
|
||||
// NewPatternMatcher creates a new matcher object for specific patterns that can
|
||||
// be used later to match against patterns against paths
|
||||
func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
|
||||
pm := &PatternMatcher{
|
||||
patterns: make([]*Pattern, 0, len(patterns)),
|
||||
}
|
||||
for _, p := range patterns {
|
||||
// Eliminate leading and trailing whitespace.
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
p = filepath.Clean(p)
|
||||
newp := &Pattern{}
|
||||
if p[0] == '!' {
|
||||
if len(p) == 1 {
|
||||
return nil, errors.New("illegal exclusion pattern: \"!\"")
|
||||
}
|
||||
newp.exclusion = true
|
||||
p = strings.TrimPrefix(filepath.Clean(p[1:]), "/")
|
||||
pm.exclusions = true
|
||||
}
|
||||
// Do some syntax checking on the pattern.
|
||||
// filepath's Match() has some really weird rules that are inconsistent
|
||||
// so instead of trying to dup their logic, just call Match() for its
|
||||
// error state and if there is an error in the pattern return it.
|
||||
// If this becomes an issue we can remove this since its really only
|
||||
// needed in the error (syntax) case - which isn't really critical.
|
||||
if _, err := filepath.Match(p, "."); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newp.cleanedPattern = p
|
||||
newp.dirs = strings.Split(p, string(os.PathSeparator))
|
||||
pm.patterns = append(pm.patterns, newp)
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
// Deprecated: Please use the `MatchesResult` method instead.
|
||||
// Matches matches path against all the patterns. Matches is not safe to be
|
||||
// called concurrently
|
||||
func (pm *PatternMatcher) Matches(file string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
negative := false
|
||||
|
||||
if pattern.exclusion {
|
||||
negative = true
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !negative
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
logrus.Debugf("Skipping excluded path: %s", file)
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
type MatchResult struct {
|
||||
isMatched bool
|
||||
matches, excludes uint
|
||||
}
|
||||
|
||||
// Excludes returns true if the overall result is matched
|
||||
func (m *MatchResult) IsMatched() bool {
|
||||
return m.isMatched
|
||||
}
|
||||
|
||||
// Excludes returns the amount of matches of an MatchResult
|
||||
func (m *MatchResult) Matches() uint {
|
||||
return m.matches
|
||||
}
|
||||
|
||||
// Excludes returns the amount of excludes of an MatchResult
|
||||
func (m *MatchResult) Excludes() uint {
|
||||
return m.excludes
|
||||
}
|
||||
|
||||
// MatchesResult verifies the provided filepath against all patterns.
|
||||
// It returns the `*MatchResult` result for the patterns on success, otherwise
|
||||
// an error. This method is not safe to be called concurrently.
|
||||
func (pm *PatternMatcher) MatchesResult(file string) (res *MatchResult, err error) {
|
||||
file = filepath.FromSlash(file)
|
||||
res = &MatchResult{false, 0, 0}
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
negative := false
|
||||
|
||||
if pattern.exclusion {
|
||||
negative = true
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if match {
|
||||
res.isMatched = !negative
|
||||
if negative {
|
||||
res.excludes++
|
||||
} else {
|
||||
res.matches++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res.matches > 0 {
|
||||
logrus.Debugf("Skipping excluded path: %s", file)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// IsMatch verifies the provided filepath against all patterns and returns true
|
||||
// if it matches. A match is valid if the last match is a positive one.
|
||||
// It returns an error on failure and is not safe to be called concurrently.
|
||||
func (pm *PatternMatcher) IsMatch(file string) (matched bool, err error) {
|
||||
res, err := pm.MatchesResult(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return res.isMatched, nil
|
||||
}
|
||||
|
||||
// Exclusions returns true if any of the patterns define exclusions
|
||||
func (pm *PatternMatcher) Exclusions() bool {
|
||||
return pm.exclusions
|
||||
}
|
||||
|
||||
// Patterns returns array of active patterns
|
||||
func (pm *PatternMatcher) Patterns() []*Pattern {
|
||||
return pm.patterns
|
||||
}
|
||||
|
||||
// Pattern defines a single regexp used to filter file paths.
|
||||
type Pattern struct {
|
||||
cleanedPattern string
|
||||
dirs []string
|
||||
regexp *regexp.Regexp
|
||||
exclusion bool
|
||||
}
|
||||
|
||||
func (p *Pattern) String() string {
|
||||
return p.cleanedPattern
|
||||
}
|
||||
|
||||
// Exclusion returns true if this pattern defines exclusion
|
||||
func (p *Pattern) Exclusion() bool {
|
||||
return p.exclusion
|
||||
}
|
||||
|
||||
func (p *Pattern) match(path string) (bool, error) {
|
||||
if p.regexp == nil {
|
||||
if err := p.compile(); err != nil {
|
||||
return false, filepath.ErrBadPattern
|
||||
}
|
||||
}
|
||||
|
||||
b := p.regexp.MatchString(path)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *Pattern) compile() error {
|
||||
regStr := "^"
|
||||
pattern := p.cleanedPattern
|
||||
// Go through the pattern and convert it to a regexp.
|
||||
// We use a scanner so we can support utf-8 chars.
|
||||
var scan scanner.Scanner
|
||||
scan.Init(strings.NewReader(pattern))
|
||||
|
||||
sl := string(os.PathSeparator)
|
||||
escSL := sl
|
||||
const bs = `\`
|
||||
if sl == bs {
|
||||
escSL += bs
|
||||
}
|
||||
|
||||
for scan.Peek() != scanner.EOF {
|
||||
ch := scan.Next()
|
||||
|
||||
if ch == '*' {
|
||||
if scan.Peek() == '*' {
|
||||
// is some flavor of "**"
|
||||
scan.Next()
|
||||
|
||||
// Treat **/ as ** so eat the "/"
|
||||
if string(scan.Peek()) == sl {
|
||||
scan.Next()
|
||||
}
|
||||
|
||||
if scan.Peek() == scanner.EOF {
|
||||
// is "**EOF" - to align with .gitignore just accept all
|
||||
regStr += ".*"
|
||||
} else {
|
||||
// is "**"
|
||||
// Note that this allows for any # of /'s (even 0) because
|
||||
// the .* will eat everything, even /'s
|
||||
regStr += "(.*" + escSL + ")?"
|
||||
}
|
||||
} else {
|
||||
// is "*" so map it to anything but "/"
|
||||
regStr += "[^" + escSL + "]*"
|
||||
}
|
||||
} else if ch == '?' {
|
||||
// "?" is any char except "/"
|
||||
regStr += "[^" + escSL + "]"
|
||||
} else if ch == '.' || ch == '$' {
|
||||
// Escape some regexp special chars that have no meaning
|
||||
// in golang's filepath.Match
|
||||
regStr += bs + string(ch)
|
||||
} else if ch == '\\' {
|
||||
// escape next char.
|
||||
if sl == bs {
|
||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||
// and then just continue because filepath.Match on
|
||||
// Windows doesn't allow escaping at all
|
||||
regStr += escSL
|
||||
continue
|
||||
}
|
||||
if scan.Peek() != scanner.EOF {
|
||||
regStr += bs + string(scan.Next())
|
||||
} else {
|
||||
return filepath.ErrBadPattern
|
||||
}
|
||||
} else {
|
||||
regStr += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
regStr += "(" + escSL + ".*)?$"
|
||||
|
||||
re, err := regexp.Compile(regStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.regexp = re
|
||||
return nil
|
||||
}
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
func Matches(file string, patterns []string) (bool, error) {
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return pm.IsMatch(file)
|
||||
}
|
||||
|
||||
// CopyFile copies from src to dst until either EOF is reached
|
||||
// on src or an error occurs. It verifies src exists and removes
|
||||
// the dst if it exists.
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
cleanSrc := filepath.Clean(src)
|
||||
cleanDst := filepath.Clean(dst)
|
||||
if cleanSrc == cleanDst {
|
||||
return 0, nil
|
||||
}
|
||||
sf, err := os.Open(cleanSrc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer sf.Close()
|
||||
if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
|
||||
return 0, err
|
||||
}
|
||||
df, err := os.Create(cleanDst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
return io.Copy(df, sf)
|
||||
}
|
||||
|
||||
// ReadSymlinkedDirectory returns the target directory of a symlink.
|
||||
// The target of the symbolic link may not be a file.
|
||||
func ReadSymlinkedDirectory(path string) (string, error) {
|
||||
var realPath string
|
||||
var err error
|
||||
if realPath, err = filepath.Abs(path); err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for %s: %w", path, err)
|
||||
}
|
||||
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to canonicalise path for %s: %w", path, err)
|
||||
}
|
||||
realPathInfo, err := os.Stat(realPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to stat target '%s' of '%s': %w", realPath, path, err)
|
||||
}
|
||||
if !realPathInfo.Mode().IsDir() {
|
||||
return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
|
||||
}
|
||||
return realPath, nil
|
||||
}
|
||||
|
||||
// ReadSymlinkedPath returns the target directory of a symlink.
|
||||
// The target of the symbolic link can be a file and a directory.
|
||||
func ReadSymlinkedPath(path string) (realPath string, err error) {
|
||||
if realPath, err = filepath.Abs(path); err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err)
|
||||
}
|
||||
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err)
|
||||
}
|
||||
if err := Exists(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to stat target %q of %q: %w", realPath, path, err)
|
||||
}
|
||||
return realPath, nil
|
||||
}
|
||||
|
||||
// CreateIfNotExists creates a file or a directory only if it does not already exist.
|
||||
func CreateIfNotExists(path string, isDir bool) error {
|
||||
if err := Exists(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if isDir {
|
||||
return os.MkdirAll(path, 0o755)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_CREATE, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
27
vendor/github.com/containers/storage/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
27
vendor/github.com/containers/storage/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetTotalUsedFds returns the number of used File Descriptors by
|
||||
// executing `lsof -p PID`
|
||||
func GetTotalUsedFds() int {
|
||||
pid := os.Getpid()
|
||||
|
||||
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid))
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
outputStr := strings.TrimSpace(string(output))
|
||||
|
||||
fds := strings.Split(outputStr, "\n")
|
||||
|
||||
return len(fds) - 1
|
||||
}
|
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package fileutils
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors.
|
||||
// On Solaris these limits are per process and not systemwide
|
||||
func GetTotalUsedFds() int {
|
||||
return -1
|
||||
}
|
21
vendor/github.com/containers/storage/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
21
vendor/github.com/containers/storage/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
//go:build linux || freebsd
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors by
|
||||
// reading it via /proc filesystem.
|
||||
func GetTotalUsedFds() int {
|
||||
if fds, err := os.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
|
||||
logrus.Errorf("%v", err)
|
||||
} else {
|
||||
return len(fds)
|
||||
}
|
||||
return -1
|
||||
}
|
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_windows.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package fileutils
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors. Not supported
|
||||
// on Windows.
|
||||
func GetTotalUsedFds() int {
|
||||
return -1
|
||||
}
|
20
vendor/github.com/containers/storage/pkg/fileutils/reflink_linux.go
generated
vendored
Normal file
20
vendor/github.com/containers/storage/pkg/fileutils/reflink_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// ReflinkOrCopy attempts to reflink the source to the destination fd.
|
||||
// If reflinking fails or is unsupported, it falls back to io.Copy().
|
||||
func ReflinkOrCopy(src, dst *os.File) error {
|
||||
err := unix.IoctlFileClone(int(dst.Fd()), int(src.Fd()))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
15
vendor/github.com/containers/storage/pkg/fileutils/reflink_unsupported.go
generated
vendored
Normal file
15
vendor/github.com/containers/storage/pkg/fileutils/reflink_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
//go:build !linux
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ReflinkOrCopy attempts to reflink the source to the destination fd.
|
||||
// If reflinking fails or is unsupported, it falls back to io.Copy().
|
||||
func ReflinkOrCopy(src, dst *os.File) error {
|
||||
_, err := io.Copy(dst, src)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,620 @@
|
|||
package idtools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// IDMap contains a single entry for user namespace range remapping. An array
|
||||
// of IDMap entries represents the structure that will be provided to the Linux
|
||||
// kernel for creating a user namespace.
|
||||
type IDMap struct {
|
||||
ContainerID int `json:"container_id"`
|
||||
HostID int `json:"host_id"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type subIDRange struct {
|
||||
Start int
|
||||
Length int
|
||||
}
|
||||
|
||||
type ranges []subIDRange
|
||||
|
||||
func (e ranges) Len() int { return len(e) }
|
||||
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
|
||||
|
||||
const (
|
||||
subuidFileName string = "/etc/subuid"
|
||||
subgidFileName string = "/etc/subgid"
|
||||
ContainersOverrideXattr = "user.containers.override_stat"
|
||||
)
|
||||
|
||||
// MkdirAllAs creates a directory (include any along the path) and then modifies
|
||||
// ownership to the requested uid/gid. If the directory already exists, this
|
||||
// function will still change ownership to the requested uid/gid pair.
|
||||
// Deprecated: Use MkdirAllAndChown
|
||||
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||
return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
|
||||
}
|
||||
|
||||
// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
|
||||
// If the directory already exists, this function still changes ownership
|
||||
// Deprecated: Use MkdirAndChown with a IDPair
|
||||
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||
return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
|
||||
}
|
||||
|
||||
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
|
||||
// ownership to the requested uid/gid. If the directory already exists, this
|
||||
// function will still change ownership to the requested uid/gid pair.
|
||||
func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error {
|
||||
return mkdirAs(path, mode, ids.UID, ids.GID, true, true)
|
||||
}
|
||||
|
||||
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
|
||||
// If the directory already exists, this function still changes ownership
|
||||
func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error {
|
||||
return mkdirAs(path, mode, ids.UID, ids.GID, false, true)
|
||||
}
|
||||
|
||||
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
|
||||
// ownership ONLY of newly created directories to the requested uid/gid. If the
|
||||
// directories along the path exist, no change of ownership will be performed
|
||||
func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error {
|
||||
return mkdirAs(path, mode, ids.UID, ids.GID, true, false)
|
||||
}
|
||||
|
||||
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
|
||||
// If the maps are empty, then the root uid/gid will default to "real" 0/0
|
||||
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
|
||||
var uid, gid int
|
||||
var err error
|
||||
if len(uidMap) == 1 && uidMap[0].Size == 1 {
|
||||
uid = uidMap[0].HostID
|
||||
} else {
|
||||
uid, err = RawToHost(0, uidMap)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
}
|
||||
if len(gidMap) == 1 && gidMap[0].Size == 1 {
|
||||
gid = gidMap[0].HostID
|
||||
} else {
|
||||
gid, err = RawToHost(0, gidMap)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
// RawToContainer takes an id mapping, and uses it to translate a host ID to
|
||||
// the remapped ID. If no map is provided, then the translation assumes a
|
||||
// 1-to-1 mapping and returns the passed in id.
|
||||
//
|
||||
// If you wish to map a (uid,gid) combination you should use the corresponding
|
||||
// IDMappings methods, which ensure that you are mapping the correct ID against
|
||||
// the correct mapping.
|
||||
func RawToContainer(hostID int, idMap []IDMap) (int, error) {
|
||||
if idMap == nil {
|
||||
return hostID, nil
|
||||
}
|
||||
for _, m := range idMap {
|
||||
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
|
||||
contID := m.ContainerID + (hostID - m.HostID)
|
||||
return contID, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("host ID %d cannot be mapped to a container ID", hostID)
|
||||
}
|
||||
|
||||
// RawToHost takes an id mapping and a remapped ID, and translates the ID to
|
||||
// the mapped host ID. If no map is provided, then the translation assumes a
|
||||
// 1-to-1 mapping and returns the passed in id.
|
||||
//
|
||||
// If you wish to map a (uid,gid) combination you should use the corresponding
|
||||
// IDMappings methods, which ensure that you are mapping the correct ID against
|
||||
// the correct mapping.
|
||||
func RawToHost(contID int, idMap []IDMap) (int, error) {
|
||||
if idMap == nil {
|
||||
return contID, nil
|
||||
}
|
||||
for _, m := range idMap {
|
||||
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
|
||||
hostID := m.HostID + (contID - m.ContainerID)
|
||||
return hostID, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID)
|
||||
}
|
||||
|
||||
// IDPair is a UID and GID pair
|
||||
type IDPair struct {
|
||||
UID int
|
||||
GID int
|
||||
}
|
||||
|
||||
// IDMappings contains a mappings of UIDs and GIDs
|
||||
type IDMappings struct {
|
||||
uids []IDMap
|
||||
gids []IDMap
|
||||
}
|
||||
|
||||
// NewIDMappings takes a requested user and group name and
|
||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||
// proper uid and gid remapping ranges for that user/group pair
|
||||
func NewIDMappings(username, groupname string) (*IDMappings, error) {
|
||||
subuidRanges, err := readSubuid(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subgidRanges, err := readSubgid(groupname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(subuidRanges) == 0 {
|
||||
return nil, fmt.Errorf("no subuid ranges found for user %q in %s", username, subuidFileName)
|
||||
}
|
||||
if len(subgidRanges) == 0 {
|
||||
return nil, fmt.Errorf("no subgid ranges found for group %q in %s", groupname, subgidFileName)
|
||||
}
|
||||
|
||||
return &IDMappings{
|
||||
uids: createIDMap(subuidRanges),
|
||||
gids: createIDMap(subgidRanges),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewIDMappingsFromMaps creates a new mapping from two slices
|
||||
// Deprecated: this is a temporary shim while transitioning to IDMapping
|
||||
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings {
|
||||
return &IDMappings{uids: uids, gids: gids}
|
||||
}
|
||||
|
||||
// RootPair returns a uid and gid pair for the root user. The error is ignored
|
||||
// because a root user always exists, and the defaults are correct when the uid
|
||||
// and gid maps are empty.
|
||||
func (i *IDMappings) RootPair() IDPair {
|
||||
uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
|
||||
return IDPair{UID: uid, GID: gid}
|
||||
}
|
||||
|
||||
// ToHost returns the host UID and GID for the container uid, gid.
|
||||
func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) {
|
||||
var err error
|
||||
var target IDPair
|
||||
|
||||
target.UID, err = RawToHost(pair.UID, i.uids)
|
||||
if err != nil {
|
||||
return target, err
|
||||
}
|
||||
|
||||
target.GID, err = RawToHost(pair.GID, i.gids)
|
||||
return target, err
|
||||
}
|
||||
|
||||
var (
|
||||
overflowUIDOnce sync.Once
|
||||
overflowGIDOnce sync.Once
|
||||
overflowUID int
|
||||
overflowGID int
|
||||
)
|
||||
|
||||
// getOverflowUID returns the UID mapped to the overflow user
|
||||
func getOverflowUID() int {
|
||||
overflowUIDOnce.Do(func() {
|
||||
// 65534 is the value on older kernels where /proc/sys/kernel/overflowuid is not present
|
||||
overflowUID = 65534
|
||||
if content, err := os.ReadFile("/proc/sys/kernel/overflowuid"); err == nil {
|
||||
if tmp, err := strconv.Atoi(string(content)); err == nil {
|
||||
overflowUID = tmp
|
||||
}
|
||||
}
|
||||
})
|
||||
return overflowUID
|
||||
}
|
||||
|
||||
// getOverflowGID returns the GID mapped to the overflow user
|
||||
func getOverflowGID() int {
|
||||
overflowGIDOnce.Do(func() {
|
||||
// 65534 is the value on older kernels where /proc/sys/kernel/overflowgid is not present
|
||||
overflowGID = 65534
|
||||
if content, err := os.ReadFile("/proc/sys/kernel/overflowgid"); err == nil {
|
||||
if tmp, err := strconv.Atoi(string(content)); err == nil {
|
||||
overflowGID = tmp
|
||||
}
|
||||
}
|
||||
})
|
||||
return overflowGID
|
||||
}
|
||||
|
||||
// ToHost returns the host UID and GID for the container uid, gid.
|
||||
// Remapping is only performed if the ids aren't already the remapped root ids
|
||||
// If the mapping is not possible because the target ID is not mapped into
|
||||
// the namespace, then the overflow ID is used.
|
||||
func (i *IDMappings) ToHostOverflow(pair IDPair) (IDPair, error) {
|
||||
var err error
|
||||
target := i.RootPair()
|
||||
|
||||
if pair.UID != target.UID {
|
||||
target.UID, err = RawToHost(pair.UID, i.uids)
|
||||
if err != nil {
|
||||
target.UID = getOverflowUID()
|
||||
logrus.Debugf("Failed to map UID %v to the target mapping, using the overflow ID %v", pair.UID, target.UID)
|
||||
}
|
||||
}
|
||||
|
||||
if pair.GID != target.GID {
|
||||
target.GID, err = RawToHost(pair.GID, i.gids)
|
||||
if err != nil {
|
||||
target.GID = getOverflowGID()
|
||||
logrus.Debugf("Failed to map GID %v to the target mapping, using the overflow ID %v", pair.GID, target.GID)
|
||||
}
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// ToContainer returns the container UID and GID for the host uid and gid
|
||||
func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) {
|
||||
uid, err := RawToContainer(pair.UID, i.uids)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
gid, err := RawToContainer(pair.GID, i.gids)
|
||||
return uid, gid, err
|
||||
}
|
||||
|
||||
// Empty returns true if there are no id mappings
|
||||
func (i *IDMappings) Empty() bool {
|
||||
return len(i.uids) == 0 && len(i.gids) == 0
|
||||
}
|
||||
|
||||
// UIDs return the UID mapping
|
||||
// TODO: remove this once everything has been refactored to use pairs
|
||||
func (i *IDMappings) UIDs() []IDMap {
|
||||
return i.uids
|
||||
}
|
||||
|
||||
// GIDs return the UID mapping
|
||||
// TODO: remove this once everything has been refactored to use pairs
|
||||
func (i *IDMappings) GIDs() []IDMap {
|
||||
return i.gids
|
||||
}
|
||||
|
||||
func createIDMap(subidRanges ranges) []IDMap {
|
||||
idMap := []IDMap{}
|
||||
|
||||
// sort the ranges by lowest ID first
|
||||
sort.Sort(subidRanges)
|
||||
containerID := 0
|
||||
for _, idrange := range subidRanges {
|
||||
idMap = append(idMap, IDMap{
|
||||
ContainerID: containerID,
|
||||
HostID: idrange.Start,
|
||||
Size: idrange.Length,
|
||||
})
|
||||
containerID = containerID + idrange.Length
|
||||
}
|
||||
return idMap
|
||||
}
|
||||
|
||||
// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
|
||||
// and return all found ranges for a specified username. If the special value
|
||||
// "ALL" is supplied for username, then all ranges in the file will be returned
|
||||
func parseSubidFile(path, username string) (ranges, error) {
|
||||
var (
|
||||
rangeList ranges
|
||||
uidstr string
|
||||
)
|
||||
if u, err := user.Lookup(username); err == nil {
|
||||
uidstr = u.Uid
|
||||
}
|
||||
|
||||
subidFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return rangeList, err
|
||||
}
|
||||
defer subidFile.Close()
|
||||
|
||||
s := bufio.NewScanner(subidFile)
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return rangeList, err
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(s.Text())
|
||||
if text == "" || strings.HasPrefix(text, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(text, ":")
|
||||
if len(parts) != 3 {
|
||||
return rangeList, fmt.Errorf("cannot parse subuid/gid information: Format not correct for %s file", path)
|
||||
}
|
||||
if parts[0] == username || username == "ALL" || (parts[0] == uidstr && parts[0] != "") {
|
||||
startid, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return rangeList, fmt.Errorf("string to int conversion failed during subuid/gid parsing of %s: %w", path, err)
|
||||
}
|
||||
length, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return rangeList, fmt.Errorf("string to int conversion failed during subuid/gid parsing of %s: %w", path, err)
|
||||
}
|
||||
rangeList = append(rangeList, subIDRange{startid, length})
|
||||
}
|
||||
}
|
||||
return rangeList, nil
|
||||
}
|
||||
|
||||
func checkChownErr(err error, name string, uid, gid int) error {
|
||||
var e *os.PathError
|
||||
if errors.As(err, &e) && e.Err == syscall.EINVAL {
|
||||
return fmt.Errorf(`potentially insufficient UIDs or GIDs available in user namespace (requested %d:%d for %s): Check /etc/subuid and /etc/subgid if configured locally and run "podman system migrate": %w`, uid, gid, name, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Stat contains file states that can be overridden with ContainersOverrideXattr.
|
||||
type Stat struct {
|
||||
IDs IDPair
|
||||
Mode os.FileMode
|
||||
Major int
|
||||
Minor int
|
||||
}
|
||||
|
||||
// FormatContainersOverrideXattr will format the given uid, gid, and mode into a string
|
||||
// that can be used as the value for the ContainersOverrideXattr xattr.
|
||||
func FormatContainersOverrideXattr(uid, gid, mode int) string {
|
||||
return FormatContainersOverrideXattrDevice(uid, gid, fs.FileMode(mode), 0, 0)
|
||||
}
|
||||
|
||||
// FormatContainersOverrideXattrDevice will format the given uid, gid, and mode into a string
|
||||
// that can be used as the value for the ContainersOverrideXattr xattr. For devices, it also
|
||||
// needs the major and minor numbers.
|
||||
func FormatContainersOverrideXattrDevice(uid, gid int, mode fs.FileMode, major, minor int) string {
|
||||
typ := ""
|
||||
switch mode & os.ModeType {
|
||||
case os.ModeDir:
|
||||
typ = "dir"
|
||||
case os.ModeSymlink:
|
||||
typ = "symlink"
|
||||
case os.ModeNamedPipe:
|
||||
typ = "pipe"
|
||||
case os.ModeSocket:
|
||||
typ = "socket"
|
||||
case os.ModeDevice:
|
||||
typ = fmt.Sprintf("block-%d-%d", major, minor)
|
||||
case os.ModeDevice | os.ModeCharDevice:
|
||||
typ = fmt.Sprintf("char-%d-%d", major, minor)
|
||||
default:
|
||||
typ = "file"
|
||||
}
|
||||
unixMode := mode & os.ModePerm
|
||||
if mode&os.ModeSetuid != 0 {
|
||||
unixMode |= 0o4000
|
||||
}
|
||||
if mode&os.ModeSetgid != 0 {
|
||||
unixMode |= 0o2000
|
||||
}
|
||||
if mode&os.ModeSticky != 0 {
|
||||
unixMode |= 0o1000
|
||||
}
|
||||
return fmt.Sprintf("%d:%d:%04o:%s", uid, gid, unixMode, typ)
|
||||
}
|
||||
|
||||
// GetContainersOverrideXattr will get and decode ContainersOverrideXattr.
|
||||
func GetContainersOverrideXattr(path string) (Stat, error) {
|
||||
xstat, err := system.Lgetxattr(path, ContainersOverrideXattr)
|
||||
if err != nil {
|
||||
return Stat{}, err
|
||||
}
|
||||
return parseOverrideXattr(xstat) // This will fail if (xstat, err) == (nil, nil), i.e. the xattr does not exist.
|
||||
}
|
||||
|
||||
func parseOverrideXattr(xstat []byte) (Stat, error) {
|
||||
var stat Stat
|
||||
attrs := strings.Split(string(xstat), ":")
|
||||
if len(attrs) < 3 {
|
||||
return stat, fmt.Errorf("the number of parts in %s is less than 3",
|
||||
ContainersOverrideXattr)
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(attrs[0], 10, 32)
|
||||
if err != nil {
|
||||
return stat, fmt.Errorf("failed to parse UID: %w", err)
|
||||
}
|
||||
stat.IDs.UID = int(value)
|
||||
|
||||
value, err = strconv.ParseUint(attrs[1], 10, 32)
|
||||
if err != nil {
|
||||
return stat, fmt.Errorf("failed to parse GID: %w", err)
|
||||
}
|
||||
stat.IDs.GID = int(value)
|
||||
|
||||
value, err = strconv.ParseUint(attrs[2], 8, 32)
|
||||
if err != nil {
|
||||
return stat, fmt.Errorf("failed to parse mode: %w", err)
|
||||
}
|
||||
stat.Mode = os.FileMode(value) & os.ModePerm
|
||||
if value&0o1000 != 0 {
|
||||
stat.Mode |= os.ModeSticky
|
||||
}
|
||||
if value&0o2000 != 0 {
|
||||
stat.Mode |= os.ModeSetgid
|
||||
}
|
||||
if value&0o4000 != 0 {
|
||||
stat.Mode |= os.ModeSetuid
|
||||
}
|
||||
|
||||
if len(attrs) > 3 {
|
||||
typ := attrs[3]
|
||||
if strings.HasPrefix(typ, "file") {
|
||||
} else if strings.HasPrefix(typ, "dir") {
|
||||
stat.Mode |= os.ModeDir
|
||||
} else if strings.HasPrefix(typ, "symlink") {
|
||||
stat.Mode |= os.ModeSymlink
|
||||
} else if strings.HasPrefix(typ, "pipe") {
|
||||
stat.Mode |= os.ModeNamedPipe
|
||||
} else if strings.HasPrefix(typ, "socket") {
|
||||
stat.Mode |= os.ModeSocket
|
||||
} else if strings.HasPrefix(typ, "block") {
|
||||
stat.Mode |= os.ModeDevice
|
||||
stat.Major, stat.Minor, err = parseDevice(typ)
|
||||
if err != nil {
|
||||
return stat, err
|
||||
}
|
||||
} else if strings.HasPrefix(typ, "char") {
|
||||
stat.Mode |= os.ModeDevice | os.ModeCharDevice
|
||||
stat.Major, stat.Minor, err = parseDevice(typ)
|
||||
if err != nil {
|
||||
return stat, err
|
||||
}
|
||||
} else {
|
||||
return stat, fmt.Errorf("invalid file type %s", typ)
|
||||
}
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func parseDevice(typ string) (int, int, error) {
|
||||
parts := strings.Split(typ, "-")
|
||||
// If there are more than 3 parts, just ignore them to be forward compatible
|
||||
if len(parts) < 3 {
|
||||
return 0, 0, fmt.Errorf("invalid device type %s", typ)
|
||||
}
|
||||
if parts[0] != "block" && parts[0] != "char" {
|
||||
return 0, 0, fmt.Errorf("invalid device type %s", typ)
|
||||
}
|
||||
major, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to parse major number: %w", err)
|
||||
}
|
||||
minor, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to parse minor number: %w", err)
|
||||
}
|
||||
return major, minor, nil
|
||||
}
|
||||
|
||||
// SetContainersOverrideXattr will encode and set ContainersOverrideXattr.
|
||||
func SetContainersOverrideXattr(path string, stat Stat) error {
|
||||
value := FormatContainersOverrideXattrDevice(stat.IDs.UID, stat.IDs.GID, stat.Mode, stat.Major, stat.Minor)
|
||||
return system.Lsetxattr(path, ContainersOverrideXattr, []byte(value), 0)
|
||||
}
|
||||
|
||||
func SafeChown(name string, uid, gid int) error {
|
||||
if runtime.GOOS == "darwin" {
|
||||
stat := Stat{
|
||||
Mode: os.FileMode(0o0700),
|
||||
}
|
||||
xstat, err := system.Lgetxattr(name, ContainersOverrideXattr)
|
||||
if err == nil && xstat != nil {
|
||||
stat, err = parseOverrideXattr(xstat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
st, err := os.Stat(name) // Ideally we would share this with system.Stat below, but then we would need to convert Mode.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stat.Mode = st.Mode()
|
||||
}
|
||||
stat.IDs = IDPair{UID: uid, GID: gid}
|
||||
if err = SetContainersOverrideXattr(name, stat); err != nil {
|
||||
return err
|
||||
}
|
||||
uid = os.Getuid()
|
||||
gid = os.Getgid()
|
||||
}
|
||||
if stat, statErr := system.Stat(name); statErr == nil {
|
||||
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return checkChownErr(os.Chown(name, uid, gid), name, uid, gid)
|
||||
}
|
||||
|
||||
func SafeLchown(name string, uid, gid int) error {
|
||||
if runtime.GOOS == "darwin" {
|
||||
stat := Stat{
|
||||
Mode: os.FileMode(0o0700),
|
||||
}
|
||||
xstat, err := system.Lgetxattr(name, ContainersOverrideXattr)
|
||||
if err == nil && xstat != nil {
|
||||
stat, err = parseOverrideXattr(xstat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
st, err := os.Lstat(name) // Ideally we would share this with system.Stat below, but then we would need to convert Mode.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stat.Mode = st.Mode()
|
||||
}
|
||||
stat.IDs = IDPair{UID: uid, GID: gid}
|
||||
if err = SetContainersOverrideXattr(name, stat); err != nil {
|
||||
return err
|
||||
}
|
||||
uid = os.Getuid()
|
||||
gid = os.Getgid()
|
||||
}
|
||||
if stat, statErr := system.Lstat(name); statErr == nil {
|
||||
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return checkChownErr(os.Lchown(name, uid, gid), name, uid, gid)
|
||||
}
|
||||
|
||||
type sortByHostID []IDMap
|
||||
|
||||
func (e sortByHostID) Len() int { return len(e) }
|
||||
func (e sortByHostID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
func (e sortByHostID) Less(i, j int) bool { return e[i].HostID < e[j].HostID }
|
||||
|
||||
type sortByContainerID []IDMap
|
||||
|
||||
func (e sortByContainerID) Len() int { return len(e) }
|
||||
func (e sortByContainerID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
func (e sortByContainerID) Less(i, j int) bool { return e[i].ContainerID < e[j].ContainerID }
|
||||
|
||||
// IsContiguous checks if the specified mapping is contiguous and doesn't
|
||||
// have any hole.
|
||||
func IsContiguous(mappings []IDMap) bool {
|
||||
if len(mappings) < 2 {
|
||||
return true
|
||||
}
|
||||
|
||||
var mh sortByHostID = mappings[:]
|
||||
sort.Sort(mh)
|
||||
for i := 1; i < len(mh); i++ {
|
||||
if mh[i].HostID != mh[i-1].HostID+mh[i-1].Size {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var mc sortByContainerID = mappings[:]
|
||||
sort.Sort(mc)
|
||||
for i := 1; i < len(mc); i++ {
|
||||
if mc[i].ContainerID != mc[i-1].ContainerID+mc[i-1].Size {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
91
vendor/github.com/containers/storage/pkg/idtools/idtools_supported.go
generated
vendored
Normal file
91
vendor/github.com/containers/storage/pkg/idtools/idtools_supported.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
//go:build linux && cgo && libsubid
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/user"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -l subid
|
||||
#include <shadow/subid.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct subid_range get_range(struct subid_range *ranges, int i)
|
||||
{
|
||||
return ranges[i];
|
||||
}
|
||||
|
||||
#if !defined(SUBID_ABI_MAJOR) || (SUBID_ABI_MAJOR < 4)
|
||||
# define subid_init libsubid_init
|
||||
# define subid_get_uid_ranges get_subuid_ranges
|
||||
# define subid_get_gid_ranges get_subgid_ranges
|
||||
#endif
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var onceInit sync.Once
|
||||
|
||||
func readSubid(username string, isUser bool) (ranges, error) {
|
||||
var ret ranges
|
||||
uidstr := ""
|
||||
|
||||
if username == "ALL" {
|
||||
return nil, errors.New("username ALL not supported")
|
||||
}
|
||||
|
||||
if u, err := user.Lookup(username); err == nil {
|
||||
uidstr = u.Uid
|
||||
}
|
||||
|
||||
onceInit.Do(func() {
|
||||
C.subid_init(C.CString("storage"), C.stderr)
|
||||
})
|
||||
|
||||
cUsername := C.CString(username)
|
||||
defer C.free(unsafe.Pointer(cUsername))
|
||||
|
||||
cuidstr := C.CString(uidstr)
|
||||
defer C.free(unsafe.Pointer(cuidstr))
|
||||
|
||||
var nRanges C.int
|
||||
var cRanges *C.struct_subid_range
|
||||
if isUser {
|
||||
nRanges = C.subid_get_uid_ranges(cUsername, &cRanges)
|
||||
if nRanges <= 0 {
|
||||
nRanges = C.subid_get_uid_ranges(cuidstr, &cRanges)
|
||||
}
|
||||
} else {
|
||||
nRanges = C.subid_get_gid_ranges(cUsername, &cRanges)
|
||||
if nRanges <= 0 {
|
||||
nRanges = C.subid_get_gid_ranges(cuidstr, &cRanges)
|
||||
}
|
||||
}
|
||||
if nRanges < 0 {
|
||||
return nil, errors.New("cannot read subids")
|
||||
}
|
||||
defer C.free(unsafe.Pointer(cRanges))
|
||||
|
||||
for i := 0; i < int(nRanges); i++ {
|
||||
r := C.get_range(cRanges, C.int(i))
|
||||
newRange := subIDRange{
|
||||
Start: int(r.start),
|
||||
Length: int(r.count),
|
||||
}
|
||||
ret = append(ret, newRange)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func readSubuid(username string) (ranges, error) {
|
||||
return readSubid(username, true)
|
||||
}
|
||||
|
||||
func readSubgid(username string) (ranges, error) {
|
||||
return readSubid(username, false)
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
//go:build !windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/fileutils"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
var (
|
||||
entOnce sync.Once
|
||||
getentCmd string
|
||||
)
|
||||
|
||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||
// make an array containing the original path asked for, plus (for mkAll == true)
|
||||
// all path components leading up to the complete path that don't exist before we MkdirAll
|
||||
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
||||
// chown the full directory path if it exists
|
||||
var paths []string
|
||||
st, err := os.Stat(path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
paths = []string{path}
|
||||
} else if err == nil {
|
||||
if !st.IsDir() {
|
||||
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||
}
|
||||
if chownExisting {
|
||||
// short-circuit--we were called with an existing directory and chown was requested
|
||||
return SafeChown(path, ownerUID, ownerGID)
|
||||
}
|
||||
// nothing to do; directory exists and chown was NOT requested
|
||||
return nil
|
||||
}
|
||||
|
||||
if mkAll {
|
||||
// walk back to "/" looking for directories which do not exist
|
||||
// and add them to the paths array for chown after creation
|
||||
dirPath := path
|
||||
if !filepath.IsAbs(dirPath) {
|
||||
return fmt.Errorf("path: %s should be absolute", dirPath)
|
||||
}
|
||||
for {
|
||||
dirPath = filepath.Dir(dirPath)
|
||||
if dirPath == "/" {
|
||||
break
|
||||
}
|
||||
if err := fileutils.Exists(dirPath); err != nil && os.IsNotExist(err) {
|
||||
paths = append(paths, dirPath)
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// even if it existed, we will chown the requested path + any subpaths that
|
||||
// didn't exist when we called MkdirAll
|
||||
for _, pathComponent := range paths {
|
||||
if err := SafeChown(pathComponent, ownerUID, ownerGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||
// if that uid, gid pair has access (execute bit) to the directory
|
||||
func CanAccess(path string, pair IDPair) bool {
|
||||
statInfo, err := system.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fileMode := os.FileMode(statInfo.Mode())
|
||||
permBits := fileMode.Perm()
|
||||
return accessible(statInfo.UID() == uint32(pair.UID),
|
||||
statInfo.GID() == uint32(pair.GID), permBits)
|
||||
}
|
||||
|
||||
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
|
||||
if isOwner && (perms&0o100 == 0o100) {
|
||||
return true
|
||||
}
|
||||
if isGroup && (perms&0o010 == 0o010) {
|
||||
return true
|
||||
}
|
||||
if perms&0o001 == 0o001 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUser(username string) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUser(username)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUID(uid int) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUid(uid)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
|
||||
}
|
||||
|
||||
func getentUser(args string) (user.User, error) {
|
||||
reader, err := callGetent(args)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
users, err := user.ParsePasswd(reader)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
|
||||
}
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGroup(groupname string) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGroup(groupname)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
|
||||
}
|
||||
|
||||
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGID(gid int) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGid(gid)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(fmt.Sprintf("%s %d", "group", gid))
|
||||
}
|
||||
|
||||
func getentGroup(args string) (user.Group, error) {
|
||||
reader, err := callGetent(args)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
groups, err := user.ParseGroup(reader)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
|
||||
}
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
func callGetent(args string) (io.Reader, error) {
|
||||
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
||||
// if no `getent` command on host, can't do anything else
|
||||
if getentCmd == "" {
|
||||
return nil, fmt.Errorf("")
|
||||
}
|
||||
out, err := execCmd(getentCmd, args)
|
||||
if err != nil {
|
||||
exitCode, errC := system.GetExitCode(err)
|
||||
if errC != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch exitCode {
|
||||
case 1:
|
||||
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||
case 2:
|
||||
terms := strings.Split(args, " ")
|
||||
return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
|
||||
case 3:
|
||||
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return bytes.NewReader(out), nil
|
||||
}
|
11
vendor/github.com/containers/storage/pkg/idtools/idtools_unsupported.go
generated
vendored
Normal file
11
vendor/github.com/containers/storage/pkg/idtools/idtools_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
//go:build !linux || !libsubid || !cgo
|
||||
|
||||
package idtools
|
||||
|
||||
func readSubuid(username string) (ranges, error) {
|
||||
return parseSubidFile(subuidFileName, username)
|
||||
}
|
||||
|
||||
func readSubgid(username string) (ranges, error) {
|
||||
return parseSubidFile(subgidFileName, username)
|
||||
}
|
23
vendor/github.com/containers/storage/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
23
vendor/github.com/containers/storage/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
//go:build windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Platforms such as Windows do not support the UID/GID concept. So make this
|
||||
// just a wrapper around system.MkdirAll.
|
||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||
if err := os.MkdirAll(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||
// if that uid, gid pair has access (execute bit) to the directory
|
||||
// Windows does not require/support this function, so always return true
|
||||
func CanAccess(path string, pair IDPair) bool {
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/bits"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseTriple(spec []string) (container, host, size uint32, err error) {
|
||||
cid, err := strconv.ParseUint(spec[0], 10, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("parsing id map value %q: %w", spec[0], err)
|
||||
}
|
||||
hid, err := strconv.ParseUint(spec[1], 10, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("parsing id map value %q: %w", spec[1], err)
|
||||
}
|
||||
sz, err := strconv.ParseUint(spec[2], 10, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("parsing id map value %q: %w", spec[2], err)
|
||||
}
|
||||
return uint32(cid), uint32(hid), uint32(sz), nil
|
||||
}
|
||||
|
||||
// ParseIDMap parses idmap triples from string.
|
||||
func ParseIDMap(mapSpec []string, mapSetting string) (idmap []IDMap, err error) {
|
||||
stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"uint32:uint32:uint32\"]: %q", mapSetting, mapSpec)
|
||||
for _, idMapSpec := range mapSpec {
|
||||
if idMapSpec == "" {
|
||||
continue
|
||||
}
|
||||
idSpec := strings.Split(idMapSpec, ":")
|
||||
if len(idSpec)%3 != 0 {
|
||||
return nil, stdErr
|
||||
}
|
||||
for i := range idSpec {
|
||||
if i%3 != 0 {
|
||||
continue
|
||||
}
|
||||
cid, hid, size, err := parseTriple(idSpec[i : i+3])
|
||||
if err != nil {
|
||||
return nil, stdErr
|
||||
}
|
||||
// Avoid possible integer overflow on 32bit builds
|
||||
if bits.UintSize == 32 && (cid > math.MaxInt32 || hid > math.MaxInt32 || size > math.MaxInt32) {
|
||||
return nil, stdErr
|
||||
}
|
||||
mapping := IDMap{
|
||||
ContainerID: int(cid),
|
||||
HostID: int(hid),
|
||||
Size: int(size),
|
||||
}
|
||||
idmap = append(idmap, mapping)
|
||||
}
|
||||
}
|
||||
return idmap, nil
|
||||
}
|
164
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
164
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/storage/pkg/regexp"
|
||||
)
|
||||
|
||||
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
||||
// Linux distribution commands:
|
||||
// adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
|
||||
// useradd -r -s /bin/false <username>
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
userCommand string
|
||||
|
||||
cmdTemplates = map[string]string{
|
||||
"adduser": "--system --shell /bin/false --no-create-home --disabled-login --disabled-password --group %s",
|
||||
"useradd": "-r -s /bin/false %s",
|
||||
"usermod": "-%s %d-%d %s",
|
||||
}
|
||||
|
||||
idOutRegexp = regexp.Delayed(`uid=([0-9]+).*gid=([0-9]+)`)
|
||||
// default length for a UID/GID subordinate range
|
||||
defaultRangeLen = 65536
|
||||
defaultRangeStart = 100000
|
||||
userMod = "usermod"
|
||||
)
|
||||
|
||||
// AddNamespaceRangesUser takes a username and uses the standard system
|
||||
// utility to create a system user/group pair used to hold the
|
||||
// /etc/sub{uid,gid} ranges which will be used for user namespace
|
||||
// mapping ranges in containers.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
if err := addUser(name); err != nil {
|
||||
return -1, -1, fmt.Errorf("adding user %q: %w", name, err)
|
||||
}
|
||||
|
||||
// Query the system for the created uid and gid pair
|
||||
out, err := execCmd("id", name)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("trying to find uid/gid for new user %q: %w", name, err)
|
||||
}
|
||||
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||
if len(matches) != 3 {
|
||||
return -1, -1, fmt.Errorf("can't find uid, gid from `id` output: %q", string(out))
|
||||
}
|
||||
uid, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("can't convert found uid (%s) to int: %w", matches[1], err)
|
||||
}
|
||||
gid, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("can't convert found gid (%s) to int: %w", matches[2], err)
|
||||
}
|
||||
|
||||
// Now we need to create the subuid/subgid ranges for our new user/group (system users
|
||||
// do not get auto-created ranges in subuid/subgid)
|
||||
|
||||
if err := createSubordinateRanges(name); err != nil {
|
||||
return -1, -1, fmt.Errorf("couldn't create subordinate ID ranges: %w", err)
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func addUser(userName string) error {
|
||||
once.Do(func() {
|
||||
// set up which commands are used for adding users/groups dependent on distro
|
||||
if _, err := resolveBinary("adduser"); err == nil {
|
||||
userCommand = "adduser"
|
||||
} else if _, err := resolveBinary("useradd"); err == nil {
|
||||
userCommand = "useradd"
|
||||
}
|
||||
})
|
||||
if userCommand == "" {
|
||||
return fmt.Errorf("cannot add user; no useradd/adduser binary found")
|
||||
}
|
||||
args := fmt.Sprintf(cmdTemplates[userCommand], userName)
|
||||
out, err := execCmd(userCommand, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add user with error: %w; output: %q", err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSubordinateRanges(name string) error {
|
||||
// first, we should verify that ranges weren't automatically created
|
||||
// by the distro tooling
|
||||
ranges, err := readSubuid(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while looking for subuid ranges for user %q: %w", name, err)
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
// no UID ranges; let's create one
|
||||
startID, err := findNextUIDRange()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't find available subuid range: %w", err)
|
||||
}
|
||||
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "v", startID, startID+defaultRangeLen-1, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add subuid range to user: %q; output: %s, err: %w", name, out, err)
|
||||
}
|
||||
}
|
||||
|
||||
ranges, err = readSubgid(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while looking for subgid ranges for user %q: %w", name, err)
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
// no GID ranges; let's create one
|
||||
startID, err := findNextGIDRange()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't find available subgid range: %w", err)
|
||||
}
|
||||
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "w", startID, startID+defaultRangeLen-1, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add subgid range to user: %q; output: %s, err: %w", name, out, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findNextUIDRange() (int, error) {
|
||||
ranges, err := readSubuid("ALL")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("couldn't parse all ranges in /etc/subuid file: %w", err)
|
||||
}
|
||||
sort.Sort(ranges)
|
||||
return findNextRangeStart(ranges)
|
||||
}
|
||||
|
||||
func findNextGIDRange() (int, error) {
|
||||
ranges, err := readSubgid("ALL")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("couldn't parse all ranges in /etc/subgid file: %w", err)
|
||||
}
|
||||
sort.Sort(ranges)
|
||||
return findNextRangeStart(ranges)
|
||||
}
|
||||
|
||||
func findNextRangeStart(rangeList ranges) (int, error) {
|
||||
startID := defaultRangeStart
|
||||
for _, arange := range rangeList {
|
||||
if wouldOverlap(arange, startID) {
|
||||
startID = arange.Start + arange.Length
|
||||
}
|
||||
}
|
||||
return startID, nil
|
||||
}
|
||||
|
||||
func wouldOverlap(arange subIDRange, ID int) bool {
|
||||
low := ID
|
||||
high := ID + defaultRangeLen
|
||||
if (low >= arange.Start && low <= arange.Start+arange.Length) ||
|
||||
(high <= arange.Start+arange.Length && high >= arange.Start) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
12
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
//go:build !linux
|
||||
|
||||
package idtools
|
||||
|
||||
import "fmt"
|
||||
|
||||
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
||||
// and calls the appropriate helper function to add the group and then
|
||||
// the user to the group in /etc/group and /etc/passwd respectively.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
return -1, -1, fmt.Errorf("no support for adding users or groups on this OS")
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//go:build !windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func resolveBinary(binname string) (string, error) {
|
||||
binaryPath, err := exec.LookPath(binname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// only return no error if the final resolved binary basename
|
||||
// matches what was searched for
|
||||
if filepath.Base(resolvedPath) == binname {
|
||||
return resolvedPath, nil
|
||||
}
|
||||
return "", fmt.Errorf("binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||
}
|
||||
|
||||
func execCmd(cmd, args string) ([]byte, error) {
|
||||
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
||||
return execCmd.CombinedOutput()
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var flags = map[string]struct {
|
||||
clear bool
|
||||
flag int
|
||||
}{
|
||||
"defaults": {false, 0},
|
||||
"ro": {false, RDONLY},
|
||||
"rw": {true, RDONLY},
|
||||
"suid": {true, NOSUID},
|
||||
"nosuid": {false, NOSUID},
|
||||
"dev": {true, NODEV},
|
||||
"nodev": {false, NODEV},
|
||||
"exec": {true, NOEXEC},
|
||||
"noexec": {false, NOEXEC},
|
||||
"sync": {false, SYNCHRONOUS},
|
||||
"async": {true, SYNCHRONOUS},
|
||||
"dirsync": {false, DIRSYNC},
|
||||
"remount": {false, REMOUNT},
|
||||
"mand": {false, MANDLOCK},
|
||||
"nomand": {true, MANDLOCK},
|
||||
"atime": {true, NOATIME},
|
||||
"noatime": {false, NOATIME},
|
||||
"diratime": {true, NODIRATIME},
|
||||
"nodiratime": {false, NODIRATIME},
|
||||
"bind": {false, BIND},
|
||||
"rbind": {false, RBIND},
|
||||
"unbindable": {false, UNBINDABLE},
|
||||
"runbindable": {false, RUNBINDABLE},
|
||||
"private": {false, PRIVATE},
|
||||
"rprivate": {false, RPRIVATE},
|
||||
"shared": {false, SHARED},
|
||||
"rshared": {false, RSHARED},
|
||||
"slave": {false, SLAVE},
|
||||
"rslave": {false, RSLAVE},
|
||||
"relatime": {false, RELATIME},
|
||||
"norelatime": {true, RELATIME},
|
||||
"strictatime": {false, STRICTATIME},
|
||||
"nostrictatime": {true, STRICTATIME},
|
||||
}
|
||||
|
||||
var validFlags = map[string]bool{
|
||||
"": true,
|
||||
"size": true,
|
||||
"mode": true,
|
||||
"uid": true,
|
||||
"gid": true,
|
||||
"nr_inodes": true,
|
||||
"nr_blocks": true,
|
||||
"mpol": true,
|
||||
}
|
||||
|
||||
var propagationFlags = map[string]bool{
|
||||
"bind": true,
|
||||
"rbind": true,
|
||||
"unbindable": true,
|
||||
"runbindable": true,
|
||||
"private": true,
|
||||
"rprivate": true,
|
||||
"shared": true,
|
||||
"rshared": true,
|
||||
"slave": true,
|
||||
"rslave": true,
|
||||
}
|
||||
|
||||
// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
|
||||
func MergeTmpfsOptions(options []string) ([]string, error) {
|
||||
// We use collisions maps to remove duplicates.
|
||||
// For flag, the key is the flag value (the key for propagation flag is -1)
|
||||
// For data=value, the key is the data
|
||||
flagCollisions := map[int]bool{}
|
||||
dataCollisions := map[string]bool{}
|
||||
|
||||
var newOptions []string
|
||||
// We process in reverse order
|
||||
for i := len(options) - 1; i >= 0; i-- {
|
||||
option := options[i]
|
||||
if option == "defaults" {
|
||||
continue
|
||||
}
|
||||
if f, ok := flags[option]; ok && f.flag != 0 {
|
||||
// There is only one propagation mode
|
||||
key := f.flag
|
||||
if propagationFlags[option] {
|
||||
key = -1
|
||||
}
|
||||
// Check to see if there is collision for flag
|
||||
if !flagCollisions[key] {
|
||||
// We prepend the option and add to collision map
|
||||
newOptions = append([]string{option}, newOptions...)
|
||||
flagCollisions[key] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
opt, _, ok := strings.Cut(option, "=")
|
||||
if !ok || !validFlags[opt] {
|
||||
return nil, fmt.Errorf("invalid tmpfs option %q", opt)
|
||||
}
|
||||
if !dataCollisions[opt] {
|
||||
// We prepend the option and add to collision map
|
||||
newOptions = append([]string{option}, newOptions...)
|
||||
dataCollisions[opt] = true
|
||||
}
|
||||
}
|
||||
|
||||
return newOptions, nil
|
||||
}
|
||||
|
||||
// ParseOptions parses fstab type mount options into mount() flags
|
||||
// and device specific data
|
||||
func ParseOptions(options string) (int, string) {
|
||||
var (
|
||||
flag int
|
||||
data []string
|
||||
)
|
||||
|
||||
for _, o := range strings.Split(options, ",") {
|
||||
// If the option does not exist in the flags table or the flag
|
||||
// is not supported on the platform,
|
||||
// then it is a data value for a specific fs type
|
||||
if f, exists := flags[o]; exists && f.flag != 0 {
|
||||
if f.clear {
|
||||
flag &= ^f.flag
|
||||
} else {
|
||||
flag |= f.flag
|
||||
}
|
||||
} else {
|
||||
data = append(data, o)
|
||||
}
|
||||
}
|
||||
return flag, strings.Join(data, ",")
|
||||
}
|
||||
|
||||
// ParseTmpfsOptions parse fstab type mount options into flags and data
|
||||
func ParseTmpfsOptions(options string) (int, string, error) {
|
||||
flags, data := ParseOptions(options)
|
||||
for _, o := range strings.Split(data, ",") {
|
||||
opt, _, _ := strings.Cut(o, "=")
|
||||
if !validFlags[opt] {
|
||||
return 0, "", fmt.Errorf("invalid tmpfs option %q", opt)
|
||||
}
|
||||
}
|
||||
return flags, data, nil
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// RDONLY will mount the file system read-only.
|
||||
RDONLY = unix.MNT_RDONLY
|
||||
|
||||
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
|
||||
// take effect.
|
||||
NOSUID = unix.MNT_NOSUID
|
||||
|
||||
// NOEXEC will not allow execution of any binaries on the mounted file system.
|
||||
NOEXEC = unix.MNT_NOEXEC
|
||||
|
||||
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
|
||||
SYNCHRONOUS = unix.MNT_SYNCHRONOUS
|
||||
|
||||
// REMOUNT will attempt to remount an already-mounted file system. This is
|
||||
// commonly used to change the mount flags for a file system, especially to
|
||||
// make a readonly file system writeable. It does not change device or mount
|
||||
// point.
|
||||
REMOUNT = unix.MNT_UPDATE
|
||||
|
||||
// NOATIME will not update the file access time when reading from a file.
|
||||
NOATIME = unix.MNT_NOATIME
|
||||
|
||||
mntDetach = unix.MNT_FORCE
|
||||
|
||||
NODIRATIME = 0
|
||||
NODEV = 0
|
||||
DIRSYNC = 0
|
||||
MANDLOCK = 0
|
||||
BIND = 0
|
||||
RBIND = 0
|
||||
UNBINDABLE = 0
|
||||
RUNBINDABLE = 0
|
||||
PRIVATE = 0
|
||||
RPRIVATE = 0
|
||||
SLAVE = 0
|
||||
RSLAVE = 0
|
||||
SHARED = 0
|
||||
RSHARED = 0
|
||||
RELATIME = 0
|
||||
STRICTATIME = 0
|
||||
)
|
|
@ -0,0 +1,87 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// RDONLY will mount the file system read-only.
|
||||
RDONLY = unix.MS_RDONLY
|
||||
|
||||
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
|
||||
// take effect.
|
||||
NOSUID = unix.MS_NOSUID
|
||||
|
||||
// NODEV will not interpret character or block special devices on the file
|
||||
// system.
|
||||
NODEV = unix.MS_NODEV
|
||||
|
||||
// NOEXEC will not allow execution of any binaries on the mounted file system.
|
||||
NOEXEC = unix.MS_NOEXEC
|
||||
|
||||
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
|
||||
SYNCHRONOUS = unix.MS_SYNCHRONOUS
|
||||
|
||||
// DIRSYNC will force all directory updates within the file system to be done
|
||||
// synchronously. This affects the following system calls: create, link,
|
||||
// unlink, symlink, mkdir, rmdir, mknod and rename.
|
||||
DIRSYNC = unix.MS_DIRSYNC
|
||||
|
||||
// REMOUNT will attempt to remount an already-mounted file system. This is
|
||||
// commonly used to change the mount flags for a file system, especially to
|
||||
// make a readonly file system writeable. It does not change device or mount
|
||||
// point.
|
||||
REMOUNT = unix.MS_REMOUNT
|
||||
|
||||
// MANDLOCK will force mandatory locks on a filesystem.
|
||||
MANDLOCK = unix.MS_MANDLOCK
|
||||
|
||||
// NOATIME will not update the file access time when reading from a file.
|
||||
NOATIME = unix.MS_NOATIME
|
||||
|
||||
// NODIRATIME will not update the directory access time.
|
||||
NODIRATIME = unix.MS_NODIRATIME
|
||||
|
||||
// BIND remounts a subtree somewhere else.
|
||||
BIND = unix.MS_BIND
|
||||
|
||||
// RBIND remounts a subtree and all possible submounts somewhere else.
|
||||
RBIND = unix.MS_BIND | unix.MS_REC
|
||||
|
||||
// UNBINDABLE creates a mount which cannot be cloned through a bind operation.
|
||||
UNBINDABLE = unix.MS_UNBINDABLE
|
||||
|
||||
// RUNBINDABLE marks the entire mount tree as UNBINDABLE.
|
||||
RUNBINDABLE = unix.MS_UNBINDABLE | unix.MS_REC
|
||||
|
||||
// PRIVATE creates a mount which carries no propagation abilities.
|
||||
PRIVATE = unix.MS_PRIVATE
|
||||
|
||||
// RPRIVATE marks the entire mount tree as PRIVATE.
|
||||
RPRIVATE = unix.MS_PRIVATE | unix.MS_REC
|
||||
|
||||
// SLAVE creates a mount which receives propagation from its master, but not
|
||||
// vice versa.
|
||||
SLAVE = unix.MS_SLAVE
|
||||
|
||||
// RSLAVE marks the entire mount tree as SLAVE.
|
||||
RSLAVE = unix.MS_SLAVE | unix.MS_REC
|
||||
|
||||
// SHARED creates a mount which provides the ability to create mirrors of
|
||||
// that mount such that mounts and unmounts within any of the mirrors
|
||||
// propagate to the other mirrors.
|
||||
SHARED = unix.MS_SHARED
|
||||
|
||||
// RSHARED marks the entire mount tree as SHARED.
|
||||
RSHARED = unix.MS_SHARED | unix.MS_REC
|
||||
|
||||
// RELATIME updates inode access times relative to modify or change time.
|
||||
RELATIME = unix.MS_RELATIME
|
||||
|
||||
// STRICTATIME allows to explicitly request full atime updates. This makes
|
||||
// it possible for the kernel to default to relatime or noatime but still
|
||||
// allow userspace to override it.
|
||||
STRICTATIME = unix.MS_STRICTATIME
|
||||
|
||||
mntDetach = unix.MNT_DETACH
|
||||
)
|
31
vendor/github.com/containers/storage/pkg/mount/flags_unsupported.go
generated
vendored
Normal file
31
vendor/github.com/containers/storage/pkg/mount/flags_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
//go:build !linux && !freebsd
|
||||
|
||||
package mount
|
||||
|
||||
// These flags are unsupported.
|
||||
const (
|
||||
BIND = 0
|
||||
DIRSYNC = 0
|
||||
MANDLOCK = 0
|
||||
NOATIME = 0
|
||||
NODEV = 0
|
||||
NODIRATIME = 0
|
||||
NOEXEC = 0
|
||||
NOSUID = 0
|
||||
UNBINDABLE = 0
|
||||
RUNBINDABLE = 0
|
||||
PRIVATE = 0
|
||||
RPRIVATE = 0
|
||||
SHARED = 0
|
||||
RSHARED = 0
|
||||
SLAVE = 0
|
||||
RSLAVE = 0
|
||||
RBIND = 0
|
||||
RELATIME = 0
|
||||
RELATIVE = 0
|
||||
REMOUNT = 0
|
||||
STRICTATIME = 0
|
||||
SYNCHRONOUS = 0
|
||||
RDONLY = 0
|
||||
mntDetach = 0
|
||||
)
|
|
@ -0,0 +1,110 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// mountError holds an error from a mount or unmount operation
|
||||
type mountError struct {
|
||||
op string
|
||||
source, target string
|
||||
flags uintptr
|
||||
data string
|
||||
err error
|
||||
}
|
||||
|
||||
// Error returns a string representation of mountError
|
||||
func (e *mountError) Error() string {
|
||||
out := e.op + " "
|
||||
|
||||
if e.source != "" {
|
||||
out += e.source + ":" + e.target
|
||||
} else {
|
||||
out += e.target
|
||||
}
|
||||
|
||||
if e.flags != uintptr(0) {
|
||||
out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
|
||||
}
|
||||
if e.data != "" {
|
||||
out += ", data: " + e.data
|
||||
}
|
||||
|
||||
out += ": " + e.err.Error()
|
||||
return out
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error
|
||||
func (e *mountError) Cause() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying cause of the error
|
||||
func (e *mountError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// Mount will mount filesystem according to the specified configuration, on the
|
||||
// condition that the target path is *not* already mounted. Options must be
|
||||
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
|
||||
// flags.go for supported option flags.
|
||||
func Mount(device, target, mType, options string) error {
|
||||
flag, data := ParseOptions(options)
|
||||
if flag&REMOUNT != REMOUNT {
|
||||
if mounted, err := Mounted(target); err != nil || mounted {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return mount(device, target, mType, uintptr(flag), data)
|
||||
}
|
||||
|
||||
// ForceMount will mount a filesystem according to the specified configuration,
|
||||
// *regardless* if the target path is not already mounted. Options must be
|
||||
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
|
||||
// flags.go for supported option flags.
|
||||
func ForceMount(device, target, mType, options string) error {
|
||||
flag, data := ParseOptions(options)
|
||||
return mount(device, target, mType, uintptr(flag), data)
|
||||
}
|
||||
|
||||
// Unmount lazily unmounts a filesystem on supported platforms, otherwise
|
||||
// does a normal unmount.
|
||||
func Unmount(target string) error {
|
||||
return unmount(target, mntDetach)
|
||||
}
|
||||
|
||||
// RecursiveUnmount unmounts the target and all mounts underneath, starting with
|
||||
// the deepest mount first.
|
||||
func RecursiveUnmount(target string) error {
|
||||
mounts, err := GetMounts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the deepest mount be first
|
||||
sort.Slice(mounts, func(i, j int) bool {
|
||||
return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
|
||||
})
|
||||
|
||||
for i, m := range mounts {
|
||||
if !strings.HasPrefix(m.Mountpoint, target) {
|
||||
continue
|
||||
}
|
||||
if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 {
|
||||
return err
|
||||
// Ignore errors for submounts and continue trying to unmount others
|
||||
// The final unmount should fail if there are any submounts remaining
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceUnmount lazily unmounts a filesystem on supported platforms,
|
||||
// otherwise does a normal unmount.
|
||||
//
|
||||
// Deprecated: please use Unmount instead, it is identical.
|
||||
func ForceUnmount(target string) error {
|
||||
return unmount(target, mntDetach)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
//go:build freebsd && cgo
|
||||
|
||||
package mount
|
||||
|
||||
/*
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/_iovec.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func allocateIOVecs(options []string) []C.struct_iovec {
|
||||
out := make([]C.struct_iovec, len(options))
|
||||
for i, option := range options {
|
||||
out[i].iov_base = unsafe.Pointer(C.CString(option))
|
||||
out[i].iov_len = C.size_t(len(option) + 1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mount(device, target, mType string, flag uintptr, data string) error {
|
||||
isNullFS := false
|
||||
|
||||
options := []string{"fspath", target}
|
||||
|
||||
if data != "" {
|
||||
xs := strings.Split(data, ",")
|
||||
for _, x := range xs {
|
||||
if x == "bind" {
|
||||
isNullFS = true
|
||||
continue
|
||||
}
|
||||
name, val, _ := strings.Cut(x, "=")
|
||||
options = append(options, name)
|
||||
options = append(options, val)
|
||||
}
|
||||
}
|
||||
|
||||
if isNullFS {
|
||||
options = append(options, "fstype", "nullfs", "target", device)
|
||||
} else {
|
||||
options = append(options, "fstype", mType, "from", device)
|
||||
}
|
||||
rawOptions := allocateIOVecs(options)
|
||||
for _, rawOption := range rawOptions {
|
||||
defer C.free(rawOption.iov_base)
|
||||
}
|
||||
|
||||
if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
|
||||
reason := C.GoString(C.strerror(*C.__error()))
|
||||
return fmt.Errorf("failed to call nmount: %s", reason)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// ptypes is the set propagation types.
|
||||
ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE
|
||||
|
||||
// pflags is the full set valid flags for a change propagation call.
|
||||
pflags = ptypes | unix.MS_REC | unix.MS_SILENT
|
||||
|
||||
// broflags is the combination of bind and read only
|
||||
broflags = unix.MS_BIND | unix.MS_RDONLY
|
||||
|
||||
none = "none"
|
||||
)
|
||||
|
||||
// isremount returns true if either device name or flags identify a remount request, false otherwise.
|
||||
func isremount(device string, flags uintptr) bool {
|
||||
switch {
|
||||
// We treat device "" and "none" as a remount request to provide compatibility with
|
||||
// requests that don't explicitly set MS_REMOUNT such as those manipulating bind mounts.
|
||||
case flags&unix.MS_REMOUNT != 0, device == "", device == none:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func mount(device, target, mType string, flags uintptr, data string) error {
|
||||
oflags := flags &^ ptypes
|
||||
if !isremount(device, flags) || data != "" {
|
||||
// Initial call applying all non-propagation flags for mount
|
||||
// or remount with changed data
|
||||
if err := unix.Mount(device, target, mType, oflags, data); err != nil {
|
||||
return &mountError{
|
||||
op: "mount",
|
||||
source: device,
|
||||
target: target,
|
||||
flags: oflags,
|
||||
data: data,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flags&ptypes != 0 {
|
||||
// Change the propagation type.
|
||||
if err := unix.Mount("", target, "", flags&pflags, ""); err != nil {
|
||||
return &mountError{
|
||||
op: "remount",
|
||||
target: target,
|
||||
flags: flags & pflags,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oflags&broflags == broflags {
|
||||
// Remount the bind to apply read only.
|
||||
if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil {
|
||||
return &mountError{
|
||||
op: "remount-ro",
|
||||
target: target,
|
||||
flags: oflags | unix.MS_REMOUNT,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
7
vendor/github.com/containers/storage/pkg/mount/mounter_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/mount/mounter_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build !linux && !(freebsd && cgo)
|
||||
|
||||
package mount
|
||||
|
||||
func mount(device, target, mType string, flag uintptr, data string) error {
|
||||
panic("Not implemented")
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"github.com/moby/sys/mountinfo"
|
||||
)
|
||||
|
||||
type Info = mountinfo.Info
|
||||
|
||||
var Mounted = mountinfo.Mounted
|
||||
|
||||
func GetMounts() ([]*Info, error) {
|
||||
return mountinfo.GetMounts(nil)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/moby/sys/mountinfo"
|
||||
)
|
||||
|
||||
func PidMountInfo(pid int) ([]*Info, error) {
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return mountinfo.GetMountsFromReader(f, nil)
|
||||
}
|
64
vendor/github.com/containers/storage/pkg/mount/sharedsubtree_linux.go
generated
vendored
Normal file
64
vendor/github.com/containers/storage/pkg/mount/sharedsubtree_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package mount
|
||||
|
||||
// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeShared(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, SHARED)
|
||||
}
|
||||
|
||||
// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeRShared(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, RSHARED)
|
||||
}
|
||||
|
||||
// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakePrivate(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, PRIVATE)
|
||||
}
|
||||
|
||||
// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
|
||||
// enabled. See the supported options in flags.go for further reference.
|
||||
func MakeRPrivate(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, RPRIVATE)
|
||||
}
|
||||
|
||||
// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeSlave(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, SLAVE)
|
||||
}
|
||||
|
||||
// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeRSlave(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, RSLAVE)
|
||||
}
|
||||
|
||||
// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
|
||||
// enabled. See the supported options in flags.go for further reference.
|
||||
func MakeUnbindable(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, UNBINDABLE)
|
||||
}
|
||||
|
||||
// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
|
||||
// option enabled. See the supported options in flags.go for further reference.
|
||||
func MakeRUnbindable(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, RUNBINDABLE)
|
||||
}
|
||||
|
||||
func ensureMountedAs(mnt string, flags int) error {
|
||||
mounted, err := Mounted(mnt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !mounted {
|
||||
if err := mount(mnt, mnt, "none", uintptr(BIND), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return mount("", mnt, "none", uintptr(flags), "")
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//go:build !windows
|
||||
|
||||
package mount
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func unmount(target string, flags int) error {
|
||||
var err error
|
||||
for range 50 {
|
||||
err = unix.Unmount(target, flags)
|
||||
switch err {
|
||||
case unix.EBUSY:
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
continue
|
||||
case unix.EINVAL, nil:
|
||||
// Ignore "not mounted" error here. Note the same error
|
||||
// can be returned if flags are invalid, so this code
|
||||
// assumes that the flags value is always correct.
|
||||
return nil
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return &mountError{
|
||||
op: "umount",
|
||||
target: target,
|
||||
flags: uintptr(flags),
|
||||
err: err,
|
||||
}
|
||||
}
|
7
vendor/github.com/containers/storage/pkg/mount/unmount_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/mount/unmount_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build windows
|
||||
|
||||
package mount
|
||||
|
||||
func unmount(target string, flag int) error {
|
||||
panic("Not implemented")
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
# reexec
|
||||
|
||||
The `reexec` package facilitates the busybox style reexec of the docker binary that we require because
|
||||
of the forking limitations of using Go. Handlers can be registered with a name and the argv 0 of
|
||||
the exec of the binary will be used to find and execute custom init paths.
|
37
vendor/github.com/containers/storage/pkg/reexec/command_freebsd.go
generated
vendored
Normal file
37
vendor/github.com/containers/storage/pkg/reexec/command_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
//go:build freebsd
|
||||
|
||||
package reexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Self returns the path to the current process's binary.
|
||||
// Uses sysctl.
|
||||
func Self() string {
|
||||
path, err := unix.SysctlArgs("kern.proc.pathname", -1)
|
||||
if err == nil {
|
||||
return path
|
||||
}
|
||||
return os.Args[0]
|
||||
}
|
||||
|
||||
// Command returns *exec.Cmd which has Path as current binary.
|
||||
// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
|
||||
// be set to "/usr/bin/docker".
|
||||
func Command(args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CommandContext returns *exec.Cmd which has Path as current binary.
|
||||
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//go:build linux
|
||||
|
||||
package reexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Self returns the path to the current process's binary.
|
||||
// Returns "/proc/self/exe".
|
||||
func Self() string {
|
||||
return "/proc/self/exe"
|
||||
}
|
||||
|
||||
// Command returns *exec.Cmd which has Path as current binary.
|
||||
// This will use the in-memory version (/proc/self/exe) of the current binary,
|
||||
// it is thus safe to delete or replace the on-disk binary (os.Args[0]).
|
||||
func Command(args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
cmd := exec.Command(Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CommandContext returns *exec.Cmd which has Path as current binary.
|
||||
// This will use the in-memory version (/proc/self/exe) of the current binary,
|
||||
// it is thus safe to delete or replace the on-disk binary (os.Args[0]).
|
||||
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
cmd := exec.CommandContext(ctx, Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//go:build solaris || darwin
|
||||
|
||||
package reexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Self returns the path to the current process's binary.
|
||||
// Uses os.Args[0].
|
||||
func Self() string {
|
||||
return naiveSelf()
|
||||
}
|
||||
|
||||
// Command returns *exec.Cmd which has Path as current binary.
|
||||
// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
|
||||
// be set to "/usr/bin/docker".
|
||||
func Command(args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
cmd := exec.Command(Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CommandContext returns *exec.Cmd which has Path as current binary.
|
||||
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
cmd := exec.CommandContext(ctx, Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
20
vendor/github.com/containers/storage/pkg/reexec/command_unsupported.go
generated
vendored
Normal file
20
vendor/github.com/containers/storage/pkg/reexec/command_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
//go:build !linux && !windows && !freebsd && !solaris && !darwin
|
||||
|
||||
package reexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Command is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
|
||||
func Command(args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandContext is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
|
||||
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
return nil
|
||||
}
|
34
vendor/github.com/containers/storage/pkg/reexec/command_windows.go
generated
vendored
Normal file
34
vendor/github.com/containers/storage/pkg/reexec/command_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
//go:build windows
|
||||
|
||||
package reexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Self returns the path to the current process's binary.
|
||||
// Uses os.Args[0].
|
||||
func Self() string {
|
||||
return naiveSelf()
|
||||
}
|
||||
|
||||
// Command returns *exec.Cmd which has Path as current binary.
|
||||
// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
|
||||
// be set to "C:\docker.exe".
|
||||
func Command(args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
cmd := exec.Command(Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Command returns *exec.Cmd which has Path as current binary.
|
||||
// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
|
||||
// be set to "C:\docker.exe".
|
||||
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||
panicIfNotInitialized()
|
||||
cmd := exec.CommandContext(ctx, Self())
|
||||
cmd.Args = args
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package reexec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
registeredInitializers = make(map[string]func())
|
||||
initWasCalled = false
|
||||
)
|
||||
|
||||
// Register adds an initialization func under the specified name
|
||||
func Register(name string, initializer func()) {
|
||||
if _, exists := registeredInitializers[name]; exists {
|
||||
panic(fmt.Sprintf("reexec func already registered under name %q", name))
|
||||
}
|
||||
|
||||
registeredInitializers[name] = initializer
|
||||
}
|
||||
|
||||
// Init is called as the first part of the exec process and returns true if an
|
||||
// initialization function was called.
|
||||
func Init() bool {
|
||||
initializer, exists := registeredInitializers[os.Args[0]]
|
||||
initWasCalled = true
|
||||
if exists {
|
||||
initializer()
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func panicIfNotInitialized() {
|
||||
if !initWasCalled {
|
||||
// The reexec package is used to run subroutines in
|
||||
// subprocesses which would otherwise have unacceptable side
|
||||
// effects on the main thread. If you found this error, then
|
||||
// your program uses a package which needs to do this. In
|
||||
// order for that to work, main() should start with this
|
||||
// boilerplate, or an equivalent:
|
||||
// if reexec.Init() {
|
||||
// return
|
||||
// }
|
||||
panic("a library subroutine needed to run a subprocess, but reexec.Init() was not called in main()")
|
||||
}
|
||||
}
|
||||
|
||||
func naiveSelf() string {
|
||||
name := os.Args[0]
|
||||
if filepath.Base(name) == name {
|
||||
if lp, err := exec.LookPath(name); err == nil {
|
||||
return lp
|
||||
}
|
||||
}
|
||||
// handle conversion of relative paths to absolute
|
||||
if absName, err := filepath.Abs(name); err == nil {
|
||||
return absName
|
||||
}
|
||||
// if we couldn't get absolute name, return original
|
||||
// (NOTE: Go only errors on Abs() if os.Getwd fails)
|
||||
return name
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
package regexp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Regexp is a wrapper struct used for wrapping MustCompile regex expressions
|
||||
// used as global variables. Using this structure helps speed the startup time
|
||||
// of apps that want to use global regex variables. This library initializes them on
|
||||
// first use as opposed to the start of the executable.
|
||||
type Regexp struct {
|
||||
*regexpStruct
|
||||
}
|
||||
|
||||
type regexpStruct struct {
|
||||
_ noCopy
|
||||
once sync.Once
|
||||
regexp *regexp.Regexp
|
||||
val string
|
||||
}
|
||||
|
||||
func Delayed(val string) Regexp {
|
||||
re := ®expStruct{
|
||||
val: val,
|
||||
}
|
||||
if precompile {
|
||||
re.regexp = regexp.MustCompile(re.val)
|
||||
}
|
||||
return Regexp{re}
|
||||
}
|
||||
|
||||
func (re *regexpStruct) compile() {
|
||||
if precompile {
|
||||
return
|
||||
}
|
||||
re.once.Do(func() {
|
||||
re.regexp = regexp.MustCompile(re.val)
|
||||
})
|
||||
}
|
||||
|
||||
func (re *regexpStruct) Expand(dst []byte, template []byte, src []byte, match []int) []byte {
|
||||
re.compile()
|
||||
return re.regexp.Expand(dst, template, src, match)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ExpandString(dst []byte, template string, src string, match []int) []byte {
|
||||
re.compile()
|
||||
return re.regexp.ExpandString(dst, template, src, match)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) Find(b []byte) []byte {
|
||||
re.compile()
|
||||
return re.regexp.Find(b)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAll(b []byte, n int) [][]byte {
|
||||
re.compile()
|
||||
return re.regexp.FindAll(b, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllIndex(b []byte, n int) [][]int {
|
||||
re.compile()
|
||||
return re.regexp.FindAllIndex(b, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllString(s string, n int) []string {
|
||||
re.compile()
|
||||
return re.regexp.FindAllString(s, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllStringIndex(s string, n int) [][]int {
|
||||
re.compile()
|
||||
return re.regexp.FindAllStringIndex(s, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllStringSubmatch(s string, n int) [][]string {
|
||||
re.compile()
|
||||
return re.regexp.FindAllStringSubmatch(s, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllStringSubmatchIndex(s string, n int) [][]int {
|
||||
re.compile()
|
||||
return re.regexp.FindAllStringSubmatchIndex(s, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllSubmatch(b []byte, n int) [][][]byte {
|
||||
re.compile()
|
||||
return re.regexp.FindAllSubmatch(b, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindAllSubmatchIndex(b []byte, n int) [][]int {
|
||||
re.compile()
|
||||
return re.regexp.FindAllSubmatchIndex(b, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindIndex(b []byte) (loc []int) {
|
||||
re.compile()
|
||||
return re.regexp.FindIndex(b)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindReaderIndex(r io.RuneReader) (loc []int) {
|
||||
re.compile()
|
||||
return re.regexp.FindReaderIndex(r)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindReaderSubmatchIndex(r io.RuneReader) []int {
|
||||
re.compile()
|
||||
return re.regexp.FindReaderSubmatchIndex(r)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindString(s string) string {
|
||||
re.compile()
|
||||
return re.regexp.FindString(s)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindStringIndex(s string) (loc []int) {
|
||||
re.compile()
|
||||
return re.regexp.FindStringIndex(s)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindStringSubmatch(s string) []string {
|
||||
re.compile()
|
||||
return re.regexp.FindStringSubmatch(s)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindStringSubmatchIndex(s string) []int {
|
||||
re.compile()
|
||||
return re.regexp.FindStringSubmatchIndex(s)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindSubmatch(b []byte) [][]byte {
|
||||
re.compile()
|
||||
return re.regexp.FindSubmatch(b)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) FindSubmatchIndex(b []byte) []int {
|
||||
re.compile()
|
||||
return re.regexp.FindSubmatchIndex(b)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) LiteralPrefix() (prefix string, complete bool) {
|
||||
re.compile()
|
||||
return re.regexp.LiteralPrefix()
|
||||
}
|
||||
|
||||
func (re *regexpStruct) Longest() {
|
||||
re.compile()
|
||||
re.regexp.Longest()
|
||||
}
|
||||
|
||||
func (re *regexpStruct) Match(b []byte) bool {
|
||||
re.compile()
|
||||
return re.regexp.Match(b)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) MatchReader(r io.RuneReader) bool {
|
||||
re.compile()
|
||||
return re.regexp.MatchReader(r)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) MatchString(s string) bool {
|
||||
re.compile()
|
||||
return re.regexp.MatchString(s)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) NumSubexp() int {
|
||||
re.compile()
|
||||
return re.regexp.NumSubexp()
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ReplaceAll(src, repl []byte) []byte {
|
||||
re.compile()
|
||||
return re.regexp.ReplaceAll(src, repl)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte {
|
||||
re.compile()
|
||||
return re.regexp.ReplaceAllFunc(src, repl)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ReplaceAllLiteral(src, repl []byte) []byte {
|
||||
re.compile()
|
||||
return re.regexp.ReplaceAllLiteral(src, repl)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ReplaceAllLiteralString(src, repl string) string {
|
||||
re.compile()
|
||||
return re.regexp.ReplaceAllLiteralString(src, repl)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ReplaceAllString(src, repl string) string {
|
||||
re.compile()
|
||||
return re.regexp.ReplaceAllString(src, repl)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) ReplaceAllStringFunc(src string, repl func(string) string) string {
|
||||
re.compile()
|
||||
return re.regexp.ReplaceAllStringFunc(src, repl)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) Split(s string, n int) []string {
|
||||
re.compile()
|
||||
return re.regexp.Split(s, n)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) String() string {
|
||||
re.compile()
|
||||
return re.regexp.String()
|
||||
}
|
||||
|
||||
func (re *regexpStruct) SubexpIndex(name string) int {
|
||||
re.compile()
|
||||
return re.regexp.SubexpIndex(name)
|
||||
}
|
||||
|
||||
func (re *regexpStruct) SubexpNames() []string {
|
||||
re.compile()
|
||||
return re.regexp.SubexpNames()
|
||||
}
|
||||
|
||||
// noCopy may be added to structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
//
|
||||
// Note that it must not be embedded, due to the Lock and Unlock methods.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
5
vendor/github.com/containers/storage/pkg/regexp/regexp_dontprecompile.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/pkg/regexp/regexp_dontprecompile.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
//go:build !regexp_precompile
|
||||
|
||||
package regexp
|
||||
|
||||
const precompile = false
|
5
vendor/github.com/containers/storage/pkg/regexp/regexp_precompile.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/pkg/regexp/regexp_precompile.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
//go:build regexp_precompile
|
||||
|
||||
package regexp
|
||||
|
||||
const precompile = true
|
|
@ -0,0 +1,17 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Chmod(name string, mode os.FileMode) error {
|
||||
err := os.Chmod(name, mode)
|
||||
|
||||
for err != nil && errors.Is(err, syscall.EINTR) {
|
||||
err = os.Chmod(name, mode)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Chtimes changes the access time and modified time of a file at the given path
|
||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
unixMinTime := time.Unix(0, 0)
|
||||
unixMaxTime := maxTime
|
||||
|
||||
// If the modified time is prior to the Unix Epoch, or after the
|
||||
// end of Unix Time, os.Chtimes has undefined behavior
|
||||
// default to Unix Epoch in this case, just in case
|
||||
|
||||
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
|
||||
atime = unixMinTime
|
||||
}
|
||||
|
||||
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
|
||||
mtime = unixMinTime
|
||||
}
|
||||
|
||||
if err := os.Chtimes(name, atime, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Take platform specific action for setting create time.
|
||||
if err := setCTime(name, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
//go:build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// setCTime will set the create time on a file. On Unix, the create
|
||||
// time is updated as a side effect of setting the modified time, so
|
||||
// no action is required.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
return nil
|
||||
}
|
28
vendor/github.com/containers/storage/pkg/system/chtimes_windows.go
generated
vendored
Normal file
28
vendor/github.com/containers/storage/pkg/system/chtimes_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
//go:build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// setCTime will set the create time on a file. On Windows, this requires
|
||||
// calling SetFileTime and explicitly including the create time.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
ctimespec := windows.NsecToTimespec(ctime.UnixNano())
|
||||
pathp, e := windows.UTF16PtrFromString(path)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
h, e := windows.CreateFile(pathp,
|
||||
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
|
||||
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer windows.Close(h)
|
||||
c := windows.NsecToFiletime(windows.TimespecToNsec(ctimespec))
|
||||
return windows.SetFileTime(h, &c, nil, nil)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotSupportedPlatform means the platform is not supported.
|
||||
var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
|
@ -0,0 +1,33 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// GetExitCode returns the ExitStatus of the specified error if its type is
|
||||
// exec.ExitError, returns 0 and an error otherwise.
|
||||
func GetExitCode(err error) (int, error) {
|
||||
exitCode := 0
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
return procExit.ExitStatus(), nil
|
||||
}
|
||||
}
|
||||
return exitCode, fmt.Errorf("failed to get exit code")
|
||||
}
|
||||
|
||||
// ProcessExitCode process the specified error and returns the exit status code
|
||||
// if the error was of type exec.ExitError, returns nothing otherwise.
|
||||
func ProcessExitCode(err error) (exitCode int) {
|
||||
if err != nil {
|
||||
var exiterr error
|
||||
if exitCode, exiterr = GetExitCode(err); exiterr != nil {
|
||||
// TODO: Fix this so we check the error's text.
|
||||
// we've failed to retrieve exit code, so we set it to 127
|
||||
exitCode = 127
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
93
vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go
generated
vendored
Normal file
93
vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
//go:build freebsd
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
EXTATTR_NAMESPACE_EMPTY = unix.EXTATTR_NAMESPACE_EMPTY
|
||||
EXTATTR_NAMESPACE_USER = unix.EXTATTR_NAMESPACE_USER
|
||||
EXTATTR_NAMESPACE_SYSTEM = unix.EXTATTR_NAMESPACE_SYSTEM
|
||||
)
|
||||
|
||||
// ExtattrGetLink retrieves the value of the extended attribute identified by attrname
|
||||
// in the given namespace and associated with the given path in the file system.
|
||||
// If the path is a symbolic link, the extended attribute is retrieved from the link itself.
|
||||
// Returns a []byte slice if the extattr is set and nil otherwise.
|
||||
func ExtattrGetLink(path string, attrnamespace int, attrname string) ([]byte, error) {
|
||||
size, errno := unix.ExtattrGetLink(path, attrnamespace, attrname,
|
||||
uintptr(unsafe.Pointer(nil)), 0)
|
||||
if errno != nil {
|
||||
if errno == unix.ENOATTR {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, &os.PathError{Op: "extattr_get_link", Path: path, Err: errno}
|
||||
}
|
||||
if size == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
dest := make([]byte, size)
|
||||
size, errno = unix.ExtattrGetLink(path, attrnamespace, attrname,
|
||||
uintptr(unsafe.Pointer(&dest[0])), size)
|
||||
if errno != nil {
|
||||
return nil, &os.PathError{Op: "extattr_get_link", Path: path, Err: errno}
|
||||
}
|
||||
|
||||
return dest[:size], nil
|
||||
}
|
||||
|
||||
// ExtattrSetLink sets the value of extended attribute identified by attrname
|
||||
// in the given namespace and associated with the given path in the file system.
|
||||
// If the path is a symbolic link, the extended attribute is set on the link itself.
|
||||
func ExtattrSetLink(path string, attrnamespace int, attrname string, data []byte) error {
|
||||
if len(data) == 0 {
|
||||
data = []byte{} // ensure non-nil for empty data
|
||||
}
|
||||
if _, errno := unix.ExtattrSetLink(path, attrnamespace, attrname,
|
||||
uintptr(unsafe.Pointer(&data[0])), len(data)); errno != nil {
|
||||
return &os.PathError{Op: "extattr_set_link", Path: path, Err: errno}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtattrListLink lists extended attributes associated with the given path
|
||||
// in the specified namespace. If the path is a symbolic link, the attributes
|
||||
// are listed from the link itself.
|
||||
func ExtattrListLink(path string, attrnamespace int) ([]string, error) {
|
||||
size, errno := unix.ExtattrListLink(path, attrnamespace,
|
||||
uintptr(unsafe.Pointer(nil)), 0)
|
||||
if errno != nil {
|
||||
return nil, &os.PathError{Op: "extattr_list_link", Path: path, Err: errno}
|
||||
}
|
||||
if size == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
dest := make([]byte, size)
|
||||
size, errno = unix.ExtattrListLink(path, attrnamespace,
|
||||
uintptr(unsafe.Pointer(&dest[0])), size)
|
||||
if errno != nil {
|
||||
return nil, &os.PathError{Op: "extattr_list_link", Path: path, Err: errno}
|
||||
}
|
||||
|
||||
var attrs []string
|
||||
for i := 0; i < size; {
|
||||
// Each attribute is preceded by a single byte length
|
||||
length := int(dest[i])
|
||||
i++
|
||||
if i+length > size {
|
||||
break
|
||||
}
|
||||
attrs = append(attrs, string(dest[i:i+length]))
|
||||
i += length
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
24
vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go
generated
vendored
Normal file
24
vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
//go:build !freebsd
|
||||
|
||||
package system
|
||||
|
||||
const (
|
||||
EXTATTR_NAMESPACE_EMPTY = 0
|
||||
EXTATTR_NAMESPACE_USER = 0
|
||||
EXTATTR_NAMESPACE_SYSTEM = 0
|
||||
)
|
||||
|
||||
// ExtattrGetLink is not supported on platforms other than FreeBSD.
|
||||
func ExtattrGetLink(path string, attrnamespace int, attrname string) ([]byte, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
// ExtattrSetLink is not supported on platforms other than FreeBSD.
|
||||
func ExtattrSetLink(path string, attrnamespace int, attrname string, data []byte) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
// ExtattrListLink is not supported on platforms other than FreeBSD.
|
||||
func ExtattrListLink(path string, attrnamespace int) ([]string, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// maxTime is used by chtimes.
|
||||
var maxTime time.Time
|
||||
|
||||
func init() {
|
||||
// chtimes initialization
|
||||
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
||||
// This is a 64 bit timespec
|
||||
// os.Chtimes limits time to the following
|
||||
maxTime = time.Unix(0, 1<<63-1)
|
||||
} else {
|
||||
// This is a 32 bit timespec
|
||||
maxTime = time.Unix(1<<31-1, 0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package system
|
||||
|
||||
import "os"
|
||||
|
||||
// LCOWSupported determines if Linux Containers on Windows are supported.
|
||||
// Note: This feature is in development (06/17) and enabled through an
|
||||
// environment variable. At a future time, it will be enabled based
|
||||
// on build number. @jhowardmsft
|
||||
var lcowSupported = false
|
||||
|
||||
func init() {
|
||||
// LCOW initialization
|
||||
if os.Getenv("LCOW_SUPPORTED") != "" {
|
||||
lcowSupported = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
//go:build freebsd
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Flag values from <sys/stat.h>
|
||||
const (
|
||||
/*
|
||||
* Definitions of flags stored in file flags word.
|
||||
*
|
||||
* Super-user and owner changeable flags.
|
||||
*/
|
||||
UF_SETTABLE uint32 = 0x0000ffff /* mask of owner changeable flags */
|
||||
UF_NODUMP uint32 = 0x00000001 /* do not dump file */
|
||||
UF_IMMUTABLE uint32 = 0x00000002 /* file may not be changed */
|
||||
UF_APPEND uint32 = 0x00000004 /* writes to file may only append */
|
||||
UF_OPAQUE uint32 = 0x00000008 /* directory is opaque wrt. union */
|
||||
UF_NOUNLINK uint32 = 0x00000010 /* file may not be removed or renamed */
|
||||
|
||||
UF_SYSTEM uint32 = 0x00000080 /* Windows system file bit */
|
||||
UF_SPARSE uint32 = 0x00000100 /* sparse file */
|
||||
UF_OFFLINE uint32 = 0x00000200 /* file is offline */
|
||||
UF_REPARSE uint32 = 0x00000400 /* Windows reparse point file bit */
|
||||
UF_ARCHIVE uint32 = 0x00000800 /* file needs to be archived */
|
||||
UF_READONLY uint32 = 0x00001000 /* Windows readonly file bit */
|
||||
/* This is the same as the MacOS X definition of UF_HIDDEN. */
|
||||
UF_HIDDEN uint32 = 0x00008000 /* file is hidden */
|
||||
|
||||
/*
|
||||
* Super-user changeable flags.
|
||||
*/
|
||||
SF_SETTABLE uint32 = 0xffff0000 /* mask of superuser changeable flags */
|
||||
SF_ARCHIVED uint32 = 0x00010000 /* file is archived */
|
||||
SF_IMMUTABLE uint32 = 0x00020000 /* file may not be changed */
|
||||
SF_APPEND uint32 = 0x00040000 /* writes to file may only append */
|
||||
SF_NOUNLINK uint32 = 0x00100000 /* file may not be removed or renamed */
|
||||
SF_SNAPSHOT uint32 = 0x00200000 /* snapshot inode */
|
||||
)
|
||||
|
||||
func Lchflags(path string, flags uint32) error {
|
||||
p, err := unix.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, e1 := unix.Syscall(unix.SYS_LCHFLAGS, uintptr(unsafe.Pointer(p)), uintptr(flags), 0)
|
||||
if e1 != 0 {
|
||||
return e1
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Lchown(name string, uid, gid int) error {
|
||||
err := syscall.Lchown(name, uid, gid)
|
||||
|
||||
for err == syscall.EINTR {
|
||||
err = syscall.Lchown(name, uid, gid)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "lchown", Path: name, Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
//go:build !windows
|
||||
|
||||
package system
|
||||
|
||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||
func LCOWSupported() bool {
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package system
|
||||
|
||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||
func LCOWSupported() bool {
|
||||
return lcowSupported
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//go:build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Lstat takes a path to a file and returns
|
||||
// a system.StatT type pertaining to that file.
|
||||
//
|
||||
// Throws an error if the file does not exist
|
||||
func Lstat(path string) (*StatT, error) {
|
||||
s := &syscall.Stat_t{}
|
||||
if err := syscall.Lstat(path, s); err != nil {
|
||||
return nil, &os.PathError{Op: "Lstat", Path: path, Err: err}
|
||||
}
|
||||
return fromStatT(s)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package system
|
||||
|
||||
import "os"
|
||||
|
||||
// Lstat calls os.Lstat to get a fileinfo interface back.
|
||||
// This is then copied into our own locally defined structure.
|
||||
func Lstat(path string) (*StatT, error) {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromStatT(&fi)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package system
|
||||
|
||||
// MemInfo contains memory statistics of the host system.
|
||||
type MemInfo struct {
|
||||
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
||||
// kernel binary code).
|
||||
MemTotal int64
|
||||
|
||||
// Amount of free memory.
|
||||
MemFree int64
|
||||
|
||||
// Total amount of swap space available.
|
||||
SwapTotal int64
|
||||
|
||||
// Amount of swap space that is currently unused.
|
||||
SwapFree int64
|
||||
}
|
85
vendor/github.com/containers/storage/pkg/system/meminfo_freebsd.go
generated
vendored
Normal file
85
vendor/github.com/containers/storage/pkg/system/meminfo_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
//go:build freebsd && cgo
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// #include <unistd.h>
|
||||
// #include <sys/vmmeter.h>
|
||||
// #include <sys/sysctl.h>
|
||||
// #include <vm/vm_param.h>
|
||||
import "C"
|
||||
|
||||
func getMemInfo() (int64, int64, error) {
|
||||
data, err := unix.SysctlRaw("vm.vmtotal")
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("can't get kernel info: %w", err)
|
||||
}
|
||||
if len(data) != C.sizeof_struct_vmtotal {
|
||||
return -1, -1, fmt.Errorf("unexpected vmtotal size %d", len(data))
|
||||
}
|
||||
|
||||
total := (*C.struct_vmtotal)(unsafe.Pointer(&data[0]))
|
||||
|
||||
pagesize := int64(C.sysconf(C._SC_PAGESIZE))
|
||||
npages := int64(C.sysconf(C._SC_PHYS_PAGES))
|
||||
return pagesize * npages, pagesize * int64(total.t_free), nil
|
||||
}
|
||||
|
||||
func getSwapInfo() (int64, int64, error) {
|
||||
var (
|
||||
total int64 = 0
|
||||
used int64 = 0
|
||||
)
|
||||
swapCount, err := unix.SysctlUint32("vm.nswapdev")
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("reading vm.nswapdev: %w", err)
|
||||
}
|
||||
for i := 0; i < int(swapCount); i++ {
|
||||
data, err := unix.SysctlRaw("vm.swap_info", i)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("reading vm.swap_info.%d: %w", i, err)
|
||||
}
|
||||
if len(data) != C.sizeof_struct_xswdev {
|
||||
return -1, -1, fmt.Errorf("unexpected swap_info size %d", len(data))
|
||||
}
|
||||
xsw := (*C.struct_xswdev)(unsafe.Pointer(&data[0]))
|
||||
total += int64(xsw.xsw_nblks)
|
||||
used += int64(xsw.xsw_used)
|
||||
}
|
||||
pagesize := int64(C.sysconf(C._SC_PAGESIZE))
|
||||
return pagesize * total, pagesize * (total - used), nil
|
||||
}
|
||||
|
||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||
//
|
||||
// MemInfo type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
MemTotal, MemFree, err := getMemInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting memory totals %w", err)
|
||||
}
|
||||
SwapTotal, SwapFree, err := getSwapInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting swap totals %w", err)
|
||||
}
|
||||
|
||||
if MemTotal < 0 || MemFree < 0 || SwapTotal < 0 || SwapFree < 0 {
|
||||
return nil, errors.New("getting system memory info")
|
||||
}
|
||||
|
||||
meminfo := &MemInfo{}
|
||||
// Total memory is total physical memory less than memory locked by kernel
|
||||
meminfo.MemTotal = MemTotal
|
||||
meminfo.MemFree = MemFree
|
||||
meminfo.SwapTotal = SwapTotal
|
||||
meminfo.SwapFree = SwapFree
|
||||
|
||||
return meminfo, nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||
// MemInfo type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
file, err := os.Open("/proc/meminfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return parseMemInfo(file)
|
||||
}
|
||||
|
||||
// parseMemInfo parses the /proc/meminfo file into
|
||||
// a MemInfo object given an io.Reader to the file.
|
||||
// Throws error if there are problems reading from the file
|
||||
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
|
||||
meminfo := &MemInfo{}
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
// Expected format: ["MemTotal:", "1234", "kB"]
|
||||
parts := strings.Fields(scanner.Text())
|
||||
|
||||
// Sanity checks: Skip malformed entries.
|
||||
if len(parts) < 3 || parts[2] != "kB" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to bytes.
|
||||
size, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
bytes := int64(size) * units.KiB
|
||||
|
||||
switch parts[0] {
|
||||
case "MemTotal:":
|
||||
meminfo.MemTotal = bytes
|
||||
case "MemFree:":
|
||||
meminfo.MemFree = bytes
|
||||
case "SwapTotal:":
|
||||
meminfo.SwapTotal = bytes
|
||||
case "SwapFree:":
|
||||
meminfo.SwapFree = bytes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle errors that may have occurred during the reading of the file.
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return meminfo, nil
|
||||
}
|
129
vendor/github.com/containers/storage/pkg/system/meminfo_solaris.go
generated
vendored
Normal file
129
vendor/github.com/containers/storage/pkg/system/meminfo_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
//go:build solaris && cgo
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #cgo CFLAGS: -std=c99
|
||||
// #cgo LDFLAGS: -lkstat
|
||||
// #include <unistd.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <stdio.h>
|
||||
// #include <kstat.h>
|
||||
// #include <sys/swap.h>
|
||||
// #include <sys/param.h>
|
||||
// struct swaptable *allocSwaptable(int num) {
|
||||
// struct swaptable *st;
|
||||
// struct swapent *swapent;
|
||||
// st = (struct swaptable *)malloc(num * sizeof(swapent_t) + sizeof (int));
|
||||
// swapent = st->swt_ent;
|
||||
// for (int i = 0; i < num; i++,swapent++) {
|
||||
// swapent->ste_path = (char *)malloc(MAXPATHLEN * sizeof (char));
|
||||
// }
|
||||
// st->swt_n = num;
|
||||
// return st;
|
||||
//}
|
||||
// void freeSwaptable (struct swaptable *st) {
|
||||
// struct swapent *swapent = st->swt_ent;
|
||||
// for (int i = 0; i < st->swt_n; i++,swapent++) {
|
||||
// free(swapent->ste_path);
|
||||
// }
|
||||
// free(st);
|
||||
// }
|
||||
// swapent_t getSwapEnt(swapent_t *ent, int i) {
|
||||
// return ent[i];
|
||||
// }
|
||||
// int64_t getPpKernel() {
|
||||
// int64_t pp_kernel = 0;
|
||||
// kstat_ctl_t *ksc;
|
||||
// kstat_t *ks;
|
||||
// kstat_named_t *knp;
|
||||
// kid_t kid;
|
||||
//
|
||||
// if ((ksc = kstat_open()) == NULL) {
|
||||
// return -1;
|
||||
// }
|
||||
// if ((ks = kstat_lookup(ksc, "unix", 0, "system_pages")) == NULL) {
|
||||
// return -1;
|
||||
// }
|
||||
// if (((kid = kstat_read(ksc, ks, NULL)) == -1) ||
|
||||
// ((knp = kstat_data_lookup(ks, "pp_kernel")) == NULL)) {
|
||||
// return -1;
|
||||
// }
|
||||
// switch (knp->data_type) {
|
||||
// case KSTAT_DATA_UINT64:
|
||||
// pp_kernel = knp->value.ui64;
|
||||
// break;
|
||||
// case KSTAT_DATA_UINT32:
|
||||
// pp_kernel = knp->value.ui32;
|
||||
// break;
|
||||
// }
|
||||
// pp_kernel *= sysconf(_SC_PAGESIZE);
|
||||
// return (pp_kernel > 0 ? pp_kernel : -1);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
// Get the system memory info using sysconf same as prtconf
|
||||
func getTotalMem() int64 {
|
||||
pagesize := C.sysconf(C._SC_PAGESIZE)
|
||||
npages := C.sysconf(C._SC_PHYS_PAGES)
|
||||
return int64(pagesize * npages)
|
||||
}
|
||||
|
||||
func getFreeMem() int64 {
|
||||
pagesize := C.sysconf(C._SC_PAGESIZE)
|
||||
npages := C.sysconf(C._SC_AVPHYS_PAGES)
|
||||
return int64(pagesize * npages)
|
||||
}
|
||||
|
||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||
//
|
||||
// MemInfo type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
ppKernel := C.getPpKernel()
|
||||
MemTotal := getTotalMem()
|
||||
MemFree := getFreeMem()
|
||||
SwapTotal, SwapFree, err := getSysSwap()
|
||||
|
||||
if ppKernel < 0 || MemTotal < 0 || MemFree < 0 || SwapTotal < 0 ||
|
||||
SwapFree < 0 {
|
||||
return nil, fmt.Errorf("getting system memory info %w", err)
|
||||
}
|
||||
|
||||
meminfo := &MemInfo{}
|
||||
// Total memory is total physical memory less than memory locked by kernel
|
||||
meminfo.MemTotal = MemTotal - int64(ppKernel)
|
||||
meminfo.MemFree = MemFree
|
||||
meminfo.SwapTotal = SwapTotal
|
||||
meminfo.SwapFree = SwapFree
|
||||
|
||||
return meminfo, nil
|
||||
}
|
||||
|
||||
func getSysSwap() (int64, int64, error) {
|
||||
var tSwap int64
|
||||
var fSwap int64
|
||||
var diskblksPerPage int64
|
||||
num, err := C.swapctl(C.SC_GETNSWP, nil)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
st := C.allocSwaptable(num)
|
||||
_, err = C.swapctl(C.SC_LIST, unsafe.Pointer(st))
|
||||
if err != nil {
|
||||
C.freeSwaptable(st)
|
||||
return -1, -1, err
|
||||
}
|
||||
|
||||
diskblksPerPage = int64(C.sysconf(C._SC_PAGESIZE) >> C.DEV_BSHIFT)
|
||||
for i := 0; i < int(num); i++ {
|
||||
swapent := C.getSwapEnt(&st.swt_ent[0], C.int(i))
|
||||
tSwap += int64(swapent.ste_pages) * diskblksPerPage
|
||||
fSwap += int64(swapent.ste_free) * diskblksPerPage
|
||||
}
|
||||
C.freeSwaptable(st)
|
||||
return tSwap, fSwap, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue