Compare commits
No commits in common. "main" and "v1.2.1" have entirely different histories.
71
.cirrus.yml
71
.cirrus.yml
|
@ -11,8 +11,8 @@ env:
|
||||||
GOCACHE: "${HOME}/.cache/go-build"
|
GOCACHE: "${HOME}/.cache/go-build"
|
||||||
|
|
||||||
# VM Image built in containers/automation_images
|
# VM Image built in containers/automation_images
|
||||||
IMAGE_SUFFIX: "c20250324t111922z-f41f40d13"
|
_BUILT_IMAGE_SUFFIX: "c6524344056676352"
|
||||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
|
FEDORA_CACHE_IMAGE_NAME: "fedora-${_BUILT_IMAGE_SUFFIX}"
|
||||||
|
|
||||||
# Must be defined true when testing w/in containers
|
# Must be defined true when testing w/in containers
|
||||||
CONTAINER: "false"
|
CONTAINER: "false"
|
||||||
|
@ -21,11 +21,21 @@ env:
|
||||||
gcp_credentials: ENCRYPTED[2ba9cffb563741f8538eab6d4a8b2d4684c0de23693a8ade80aced34596669a87a6c01e45ce45fad7f7db1d995e8c777]
|
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
|
# Update metadata on VM images referenced by this repository state
|
||||||
meta_task:
|
meta_task:
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: "quay.io/libpod/imgts:latest"
|
image: "quay.io/libpod/imgts:master" # maintained in libpod repo
|
||||||
cpu: 1
|
cpu: 1
|
||||||
memory: 1
|
memory: 1
|
||||||
|
|
||||||
|
@ -48,27 +58,49 @@ meta_task:
|
||||||
vendor_task:
|
vendor_task:
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: golang:1.23
|
image: golang:1.14
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make vendor
|
- 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
|
# run unit and integration tests
|
||||||
build_and_test_task:
|
test_task:
|
||||||
|
|
||||||
gce_instance:
|
gce_instance:
|
||||||
image_project: "${IMAGE_PROJECT}"
|
matrix:
|
||||||
zone: "us-central1-c" # Required by Cirrus for the time being
|
image_name: $FEDORA_CACHE_IMAGE_NAME
|
||||||
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
|
# Avoid downloading this stuff every time
|
||||||
gocache_cache:
|
gocache_cache:
|
||||||
|
@ -93,17 +125,10 @@ build_and_test_task:
|
||||||
script:
|
script:
|
||||||
- $SCRIPT_BASE/setup.sh
|
- $SCRIPT_BASE/setup.sh
|
||||||
- make binary
|
- make binary
|
||||||
- make install.tools
|
|
||||||
- make validate
|
|
||||||
- make docs
|
|
||||||
- sudo make PREFIX=/usr install
|
- sudo make PREFIX=/usr install
|
||||||
- make test-unit
|
- make test-unit
|
||||||
- sudo make test-integration
|
- sudo make test-integration
|
||||||
|
|
||||||
# The hook runs in the background, errors will be in the journal.
|
|
||||||
always:
|
|
||||||
journal_script: journalctl -b
|
|
||||||
|
|
||||||
binaries_artifacts:
|
binaries_artifacts:
|
||||||
path: "bin/*"
|
path: "bin/*"
|
||||||
type: "application/octet-stream"
|
type: "application/octet-stream"
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
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 Community Code of Conduct
|
||||||
|
|
||||||
The oci-seccomp-bpf-hook project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md).
|
The oci-seccomp-bpf-hook project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/master/CODE-OF-CONDUCT.md).
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -6,7 +6,6 @@ GO_BUILD=$(GO) build
|
||||||
ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true)
|
ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true)
|
||||||
GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor
|
GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor
|
||||||
endif
|
endif
|
||||||
BUILDDIR ?= .
|
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
|
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
|
||||||
|
@ -47,11 +46,11 @@ docs:
|
||||||
|
|
||||||
.PHONY: binary
|
.PHONY: binary
|
||||||
binary:
|
binary:
|
||||||
$(GO_BUILD) -mod=vendor -o $(BUILDDIR)/bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
$(GO_BUILD) -mod=vendor -o bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
||||||
|
|
||||||
.PHONY: validate
|
.PHONY: validate
|
||||||
validate:
|
validate:
|
||||||
./build/golangci-lint run
|
$(GOBIN)/golangci-lint run
|
||||||
|
|
||||||
.PHONY: vendor
|
.PHONY: vendor
|
||||||
vendor:
|
vendor:
|
||||||
|
@ -74,9 +73,10 @@ test-unit:
|
||||||
.PHONY: install.tools
|
.PHONY: install.tools
|
||||||
install.tools: .install.golangci-lint .install.md2man
|
install.tools: .install.golangci-lint .install.md2man
|
||||||
|
|
||||||
.install.golangci-lint: VERSION=v1.60.3
|
|
||||||
.install.golangci-lint:
|
.install.golangci-lint:
|
||||||
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/$(VERSION)/install.sh | sh -s -- -b ./build $(VERSION)
|
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
|
||||||
|
|
||||||
.install.md2man:
|
.install.md2man:
|
||||||
if [ -z "$(shell type -P go-md2man)" ]; then \
|
if [ -z "$(shell type -P go-md2man)" ]; then \
|
||||||
|
@ -95,7 +95,7 @@ install.docs:
|
||||||
install-nobuild: install.docs-nobuild
|
install-nobuild: install.docs-nobuild
|
||||||
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_BIN_DIR)
|
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_BIN_DIR)
|
||||||
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_DIR)
|
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_DIR)
|
||||||
install $(SELINUXOPT) -m 755 $(BUILDDIR)/bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
install $(SELINUXOPT) -m 755 bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
||||||
install $(SELINUXOPT) -m 644 oci-seccomp-bpf-hook.json $(DESTDIR)$(HOOK_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
|
sed -i 's|HOOK_BIN_DIR|$(HOOK_BIN_DIR)|g' $(DESTDIR)$(HOOK_DIR)/oci-seccomp-bpf-hook.json
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
[](https://cirrus-ci.com/github/containers/oci-seccomp-bpf-hook/main)
|
[](https://cirrus-ci.com/github/containers/oci-seccomp-bpf-hook/master)
|
||||||
|
|
||||||
# oci-seccomp-bpf-hook
|
# oci-seccomp-bpf-hook
|
||||||
|
|
||||||
|
@ -23,5 +23,3 @@ 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.
|
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.
|
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
|
## 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/main/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/master/SECURITY.md) for the Containers Projects.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM fedora:latest
|
FROM fedora:31
|
||||||
|
|
||||||
ENV GOPATH=/var/tmp/go
|
ENV GOPATH=/var/tmp/go
|
||||||
ENV GOSRC=$GOPATH/src/github.com/containers/oci-seccomp-bpf-hook
|
ENV GOSRC=$GOPATH/src/github.com/containers/oci-seccomp-bpf-hook
|
||||||
|
|
|
@ -1,50 +1,61 @@
|
||||||
|
#!/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)/../../)}"
|
GOSRC="${GOSRC:-$(realpath $(dirname $0)/../../)}"
|
||||||
SCRIPT_BASE="${SCRIPT_BASE:-./contrib/cirrus}"
|
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
|
# Set 'true' when operating inside a container
|
||||||
CONTAINER="${CONTAINER:-false}"
|
CONTAINER="${CONTAINER:-false}"
|
||||||
|
|
||||||
if type -P go &> /dev/null; then
|
if type -P go &> /dev/null; then
|
||||||
eval "$(go env)"
|
set -a && eval "$(go env)" && set +a
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# END Global export of all variables
|
die() {
|
||||||
set +a
|
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
|
||||||
|
}
|
||||||
|
|
||||||
bad_os_id_ver() {
|
bad_os_id_ver() {
|
||||||
echo "Unknown/Unsupported distro. $OS_RELEASE_ID and/or version $OS_RELEASE_VER for $(basename $0)"
|
echo "Unknown/Unsupported distro. $OS_RELEASE_ID and/or version $OS_RELEASE_VER for $(basename $0)"
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/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,11 @@ set -e
|
||||||
|
|
||||||
source $(dirname $0)/lib.sh
|
source $(dirname $0)/lib.sh
|
||||||
|
|
||||||
req_env_vars OS_RELEASE_ID GOSRC
|
|
||||||
|
|
||||||
cd $GOSRC
|
cd $GOSRC
|
||||||
|
|
||||||
|
# Only Output on Error wrapper
|
||||||
|
install_ooe
|
||||||
|
|
||||||
CRITICAL_PKGS=()
|
CRITICAL_PKGS=()
|
||||||
INSTALL_PACKAGES=()
|
INSTALL_PACKAGES=()
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ case "$OS_RELEASE_ID" in
|
||||||
python
|
python
|
||||||
python3-dateutil
|
python3-dateutil
|
||||||
python3-psutil
|
python3-psutil
|
||||||
python3-toml
|
python3-pytoml
|
||||||
selinux-policy-devel
|
selinux-policy-devel
|
||||||
unzip
|
unzip
|
||||||
vim
|
vim
|
||||||
|
@ -100,6 +101,14 @@ case "$OS_RELEASE_ID" in
|
||||||
xz
|
xz
|
||||||
zip
|
zip
|
||||||
)
|
)
|
||||||
|
# Some small differences between 30 and 31
|
||||||
|
case "$OS_RELEASE_VER" in
|
||||||
|
33)
|
||||||
|
INSTALL_PACKAGES+=(crun)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
bad_os_id_ver ;;
|
||||||
|
esac
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
bad_os_id_ver
|
bad_os_id_ver
|
||||||
|
@ -111,6 +120,12 @@ if [[ "${#INSTALL_PACKAGES[@]}" -gt "0" ]]; then
|
||||||
ooe.sh $INSTALL_COMMAND ${INSTALL_PACKAGES[@]}
|
ooe.sh $INSTALL_COMMAND ${INSTALL_PACKAGES[@]}
|
||||||
fi
|
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
|
# Some variables change after package install
|
||||||
source $(dirname $0)/lib.sh
|
source $(dirname $0)/lib.sh
|
||||||
|
|
||||||
|
@ -120,7 +135,3 @@ ooe.sh make install.tools
|
||||||
echo "Names and versions of critical packages"
|
echo "Names and versions of critical packages"
|
||||||
NOT_INSTALLED_RE='(package .+ is not installed)|(no packages found matching .+)'
|
NOT_INSTALLED_RE='(package .+ is not installed)|(no packages found matching .+)'
|
||||||
$LIST_COMMAND ${CRITICAL_PKGS[@]} | sed -r -e "s/$NOT_INSTALLED_RE/ > > \0/" | sort
|
$LIST_COMMAND ${CRITICAL_PKGS[@]} | sed -r -e "s/$NOT_INSTALLED_RE/ > > \0/" | sort
|
||||||
|
|
||||||
show_env_vars
|
|
||||||
|
|
||||||
setenforce 0
|
|
||||||
|
|
|
@ -4,16 +4,14 @@ set -e
|
||||||
|
|
||||||
source $(dirname $0)/lib.sh
|
source $(dirname $0)/lib.sh
|
||||||
|
|
||||||
req_env_vars GOSRC
|
|
||||||
|
|
||||||
BIN=bin/oci-seccomp-bpf-hook
|
BIN=bin/oci-seccomp-bpf-hook
|
||||||
|
|
||||||
cd $GOSRC
|
cd $GOSRC
|
||||||
|
|
||||||
# This should have been populated by cache, but if not just build again
|
# This should have been populated by cache, but if not just build again
|
||||||
if [[ ! -x "$BIN" ]]; then
|
if [[ ! -x "$BIN" ]]; then
|
||||||
warn "$BIN not found, expecting to find it cached as 'gosrc' from build_task"
|
echo "Warning: $BIN not found, expecting to find it cached as 'gosrc' from build_task"
|
||||||
msg "Re-building binaries"
|
echo "Re-building binaries"
|
||||||
make
|
make
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -22,8 +20,8 @@ fi
|
||||||
|
|
||||||
ls -la bin
|
ls -la bin
|
||||||
|
|
||||||
msg "Installing oci-seccomp-bpf-hook"
|
echo "Installing oci-seccomp-bpf-hook"
|
||||||
make install PREFIX=/usr
|
make install PREFIX=/usr
|
||||||
|
|
||||||
msg "Executing integration tests"
|
echo "Executing integration tests"
|
||||||
make test-integration BATS_OPTS=--tap
|
make test-integration BATS_OPTS=--tap
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
source $(dirname $0)/lib.sh
|
set -a && eval "$(go env)" && set +a
|
||||||
|
|
||||||
req_env_vars GOSRC
|
|
||||||
|
|
||||||
cd $GOSRC
|
|
||||||
|
|
||||||
make validate
|
make validate
|
||||||
|
|
|
@ -4,8 +4,6 @@ set -e
|
||||||
|
|
||||||
source $(dirname $0)/lib.sh
|
source $(dirname $0)/lib.sh
|
||||||
|
|
||||||
req_env_vars GOSRC
|
|
||||||
|
|
||||||
cd $GOSRC
|
cd $GOSRC
|
||||||
|
|
||||||
make vendor
|
make vendor
|
||||||
|
|
42
ebpf.go
42
ebpf.go
|
@ -18,6 +18,7 @@ type event struct {
|
||||||
// Complete documentation is available at
|
// Complete documentation is available at
|
||||||
// https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
|
// https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
|
||||||
const source string = `
|
const source string = `
|
||||||
|
#include <linux/bpf.h>
|
||||||
#include <linux/nsproxy.h>
|
#include <linux/nsproxy.h>
|
||||||
#include <linux/pid_namespace.h>
|
#include <linux/pid_namespace.h>
|
||||||
#include <linux/ns_common.h>
|
#include <linux/ns_common.h>
|
||||||
|
@ -37,8 +38,6 @@ struct mnt_namespace {
|
||||||
// of the processes inside the container.
|
// of the processes inside the container.
|
||||||
BPF_HASH(parent_namespace, u64, unsigned int);
|
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
|
// Opens a custom BPF table to push data to user space via perf ring buffer
|
||||||
BPF_PERF_OUTPUT(events);
|
BPF_PERF_OUTPUT(events);
|
||||||
|
|
||||||
|
@ -69,10 +68,9 @@ int enter_trace(struct tracepoint__raw_syscalls__sys_enter* args)
|
||||||
struct task_struct *task;
|
struct task_struct *task;
|
||||||
struct nsproxy *nsproxy;
|
struct nsproxy *nsproxy;
|
||||||
struct mnt_namespace *mnt_ns;
|
struct mnt_namespace *mnt_ns;
|
||||||
int id = (int)args->id;
|
|
||||||
|
|
||||||
data.pid = bpf_get_current_pid_tgid();
|
data.pid = bpf_get_current_pid_tgid();
|
||||||
data.id = id;
|
data.id = (int)args->id;
|
||||||
bpf_get_current_comm(&data.comm, sizeof(data.comm));
|
bpf_get_current_comm(&data.comm, sizeof(data.comm));
|
||||||
|
|
||||||
task = (struct task_struct *)bpf_get_current_task();
|
task = (struct task_struct *)bpf_get_current_task();
|
||||||
|
@ -86,46 +84,12 @@ int enter_trace(struct tracepoint__raw_syscalls__sys_enter* args)
|
||||||
}
|
}
|
||||||
unsigned int* parent_inum = parent_namespace.lookup_or_init(&key, &zero);
|
unsigned int* parent_inum = parent_namespace.lookup_or_init(&key, &zero);
|
||||||
|
|
||||||
if (parent_inum != NULL && *parent_inum != inum) {
|
if (*parent_inum != inum) {
|
||||||
return 0;
|
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;
|
data.stopTracing = false;
|
||||||
events.perf_submit(args, &data, sizeof(data));
|
events.perf_submit(args, &data, sizeof(data));
|
||||||
|
|
||||||
record_and_exit:
|
|
||||||
seen++;
|
|
||||||
seen_syscalls.update(&id, &seen);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
32
go.mod
32
go.mod
|
@ -1,28 +1,14 @@
|
||||||
module github.com/containers/oci-seccomp-bpf-hook
|
module github.com/containers/oci-seccomp-bpf-hook
|
||||||
|
|
||||||
go 1.23.0
|
go 1.14
|
||||||
|
|
||||||
toolchain go1.23.8
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containers/common v0.62.3
|
github.com/iovisor/gobpf v0.0.0-20210109143822-fb892541d416
|
||||||
github.com/containers/storage v1.58.0
|
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6
|
||||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/opencontainers/runtime-spec v1.2.1
|
github.com/seccomp/containers-golang v0.6.0
|
||||||
github.com/seccomp/libseccomp-golang v0.10.0
|
github.com/seccomp/libseccomp-golang v0.9.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.4.0
|
||||||
)
|
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
|
||||||
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
105
go.sum
105
go.sum
|
@ -1,55 +1,60 @@
|
||||||
github.com/containers/common v0.62.3 h1:aOGryqXfW6aKBbHbqOveH7zB+ihavUN03X/2pUSvWFI=
|
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||||
github.com/containers/common v0.62.3/go.mod h1:3R8kDox2prC9uj/a2hmXj/YjZz5sBEUNrcDiw51S0Lo=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
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.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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4 h1:WpizD4VUT5V+VcaQSvW5BlvFpQYrd2974H9KbiGa5/0=
|
github.com/iovisor/gobpf v0.0.0-20210109143822-fb892541d416 h1:KIP9lHDm4Y+rNr6ONL+5fC5lX+79FgRlF6Xmi9VasVk=
|
||||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4/go.mod h1:WSY9Jj5RhdgC3ci1QaacvbFdQ8cbrEjrpiZbLHLt2s4=
|
github.com/iovisor/gobpf v0.0.0-20210109143822-fb892541d416/go.mod h1:+5U5qu5UOu8YJ5oHVLvWKH7/Dr5QNHU7mZ2RfPEeXg8=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/opencontainers/runtime-spec v1.0.3-0.20200710190001-3e4195d92445/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6 h1:NhsM2gc769rVWDqJvapK37r+7+CBXI8xHhnfnt8uQsg=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY=
|
||||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/seccomp/containers-golang v0.6.0 h1:VWPMMIDr8pAtNjCX0WvLEEK9EQi5lAm4HtJbDtAtFvQ=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/seccomp/containers-golang v0.6.0/go.mod h1:Dd9mONHvW4YdbSzdm23yf2CFw0iqvqLhO0mEFvPIvm4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||||
github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY=
|
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||||
github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM=
|
||||||
|
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
|
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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
|
||||||
|
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
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,64 +1,217 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
set -e
|
||||||
|
|
||||||
SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}")
|
RED="\e[1;36;41m"
|
||||||
SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH")
|
YEL="\e[1;33;44m"
|
||||||
REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../")
|
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"
|
||||||
|
|
||||||
# Help detect if we were called by get_ci_vm container
|
# Shared tmp directory between container and us
|
||||||
GET_CI_VM="${GET_CI_VM:-0}"
|
TMPDIR=$(mktemp -d --tmpdir $(basename $0)_tmpdir_XXXXXX)
|
||||||
in_get_ci_vm() {
|
|
||||||
if ((GET_CI_VM==0)); then
|
SECCOMPHOOKROOT=$(realpath "$(dirname $0)/../")
|
||||||
echo "Error: $1 is not intended for use in this context"
|
# else: Assume $PWD is the root of the oci-seccomp-bpf-hook repository
|
||||||
exit 2
|
[[ "$SECCOMPHOOKROOT" != "/" ]] || SECCOMPHOOKROOT=$PWD
|
||||||
|
|
||||||
|
# Command shortcuts save some typing (assumes $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
|
||||||
|
"$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# get_ci_vm APIv1 container entrypoint calls into this script
|
cleanup() {
|
||||||
# to obtain required repo. specific configuration options.
|
RET=$?
|
||||||
if [[ "$1" == "--config" ]]; then
|
set +e
|
||||||
in_get_ci_vm "$1"
|
wait
|
||||||
cat <<EOF
|
|
||||||
DESTDIR="/var/tmp/go/src/github.com/containers/oci-seccomp-bpf-hook"
|
# set GCLOUD_DEBUG to leave tmpdir behind for postmortem
|
||||||
UPSTREAM_REPO="https://github.com/containers/oci-seccomp-bpf-hook.git"
|
test -z "$GCLOUD_DEBUG" && rm -rf $TMPDIR
|
||||||
GCLOUD_PROJECT="oci-seccomp-bpf-hook"
|
|
||||||
GCLOUD_IMGPROJECT="libpod-218412"
|
# Not always called from an exit handler, but should always exit when called
|
||||||
GCLOUD_CFG="oci-seccomp-bpf-hook"
|
exit $RET
|
||||||
GCLOUD_ZONE="${GCLOUD_ZONE:-us-central1-c}"
|
}
|
||||||
GCLOUD_CPUS="2"
|
trap cleanup EXIT
|
||||||
GCLOUD_MEMORY="4Gb"
|
|
||||||
GCLOUD_DISK="200"
|
delvm() {
|
||||||
EOF
|
echo -e "\n"
|
||||||
elif [[ "$1" == "--setup" ]]; then
|
echo -e "\n${YEL}Offering to Delete $VMNAME ${RED}(Might take a minute or two)${NOR}"
|
||||||
in_get_ci_vm "$1"
|
echo -e "\n${YEL}Note: It's safe to answer N, then re-run script again later.${NOR}"
|
||||||
# get_ci_vm container entrypoint calls us with this option on the
|
showrun $CLEANUP_CMD # prompts for Yes/No
|
||||||
# Cirrus-CI environment instance, to perform repo.-specific setup.
|
cleanup
|
||||||
cd $REPO_DIRPATH
|
}
|
||||||
echo "+ Loading ./contrib/cirrus/lib.sh" > /dev/stderr
|
|
||||||
source ./contrib/cirrus/lib.sh
|
image_hints() {
|
||||||
echo "+ Running environment setup" > /dev/stderr
|
_BIS=$(egrep -m 1 '_BUILT_IMAGE_SUFFIX:[[:space:]+"[[:print:]]+"' \
|
||||||
./contrib/cirrus/setup.sh
|
"$SECCOMPHOOKROOT/.cirrus.yml" | cut -d: -f 2 | tr -d '"[:blank:]')
|
||||||
else
|
egrep '[[:space:]]+[[:alnum:]].+_CACHE_IMAGE_NAME:[[:space:]+"[[:print:]]+"' \
|
||||||
# Create and access VM for specified Cirrus-CI task
|
"$SECCOMPHOOKROOT/.cirrus.yml" | cut -d: -f 2 | tr -d '"[:blank:]' | \
|
||||||
mkdir -p $HOME/.config/gcloud/ssh
|
sed -r -e "s/\\\$[{]_BUILT_IMAGE_SUFFIX[}]/$_BIS/" | sort -u
|
||||||
podman run -it --rm \
|
}
|
||||||
--tz=local \
|
|
||||||
-e NAME="$USER" \
|
show_usage() {
|
||||||
-e SRCDIR=/src \
|
echo -e "\n${RED}ERROR: $1${NOR}"
|
||||||
-e GCLOUD_ZONE="$GCLOUD_ZONE" \
|
echo -e "${YEL}Usage: $(basename $0) <image_name>${NOR}"
|
||||||
-e DEBUG="${DEBUG:-0}" \
|
echo ""
|
||||||
-v $REPO_DIRPATH:/src:O \
|
if [[ -r ".cirrus.yml" ]]
|
||||||
-v $HOME/.config/gcloud:/root/.config/gcloud:z \
|
then
|
||||||
-v $HOME/.config/gcloud/ssh:/root/.ssh:z \
|
echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}"
|
||||||
quay.io/libpod/get_ci_vm:latest "$@"
|
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"
|
||||||
fi
|
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}Transferring 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,9 +5,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -21,10 +21,10 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "github.com/containers/common/pkg/seccomp"
|
|
||||||
"github.com/containers/storage/pkg/unshare"
|
|
||||||
"github.com/iovisor/gobpf/bcc"
|
"github.com/iovisor/gobpf/bcc"
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
types "github.com/seccomp/containers-golang"
|
||||||
seccomp "github.com/seccomp/libseccomp-golang"
|
seccomp "github.com/seccomp/libseccomp-golang"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
|
@ -57,11 +57,6 @@ func main() {
|
||||||
logrus.AddHook(hook)
|
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")
|
runBPF := flag.Int("r", 0, "Trace the specified PID")
|
||||||
outputFile := flag.String("o", "", "Path of the output file")
|
outputFile := flag.String("o", "", "Path of the output file")
|
||||||
inputFile := flag.String("i", "", "Path of the input file")
|
inputFile := flag.String("i", "", "Path of the input file")
|
||||||
|
@ -129,7 +124,7 @@ func detachAndTrace() error {
|
||||||
|
|
||||||
// Sanity check the PID.
|
// Sanity check the PID.
|
||||||
if s.Pid <= 0 {
|
if s.Pid <= 0 {
|
||||||
return fmt.Errorf("invalid PID %d (must be greater than 0)", s.Pid)
|
return errors.Errorf("invalid PID %d (must be greater than 0)", s.Pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the State's annotation.
|
// Parse the State's annotation.
|
||||||
|
@ -164,12 +159,12 @@ func detachAndTrace() error {
|
||||||
|
|
||||||
executable, err := os.Executable()
|
executable, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot determine executable: %v", err)
|
return errors.Wrap(err, "cannot determine executable")
|
||||||
}
|
}
|
||||||
|
|
||||||
process, err := os.StartProcess(executable, []string{"oci-seccomp-bpf-hook", "-r", strconv.Itoa(s.Pid), "-o", outputFile, "-i", inputFile}, attr)
|
process, err := os.StartProcess(executable, []string{"oci-seccomp-bpf-hook", "-r", strconv.Itoa(s.Pid), "-o", outputFile, "-i", inputFile}, attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot re-execute: %v", err)
|
return errors.Wrap(err, "cannot re-execute")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := process.Release(); err != nil {
|
if err := process.Release(); err != nil {
|
||||||
|
@ -188,7 +183,7 @@ func detachAndTrace() error {
|
||||||
case syscall.SIGUSR2:
|
case syscall.SIGUSR2:
|
||||||
return errors.New("error while tracing")
|
return errors.New("error while tracing")
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected signal %v", s)
|
return errors.Errorf("unexpected signal %v", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The timeout kicked in. Kill the child and return the sad news.
|
// The timeout kicked in. Kill the child and return the sad news.
|
||||||
|
@ -196,7 +191,7 @@ func detachAndTrace() error {
|
||||||
if err := process.Kill(); err != nil {
|
if err := process.Kill(); err != nil {
|
||||||
logrus.Errorf("error killing child process: %v", err)
|
logrus.Errorf("error killing child process: %v", err)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("BPF program didn't compile and attach within %d seconds", BPFTimeout)
|
return errors.Errorf("BPF program didn't compile and attach within %d seconds", BPFTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -209,7 +204,7 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
||||||
ppid := os.Getppid()
|
ppid := os.Getppid()
|
||||||
parentProcess, err := os.FindProcess(ppid)
|
parentProcess, err := os.FindProcess(ppid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot find parent process %d: %v", ppid, err)
|
return errors.Wrapf(err, "cannot find parent process %d", ppid)
|
||||||
}
|
}
|
||||||
logrus.Infof("Running floating process PID to attach: %d", pid)
|
logrus.Infof("Running floating process PID to attach: %d", pid)
|
||||||
|
|
||||||
|
@ -228,30 +223,37 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
||||||
m := bcc.NewModule(src, []string{})
|
m := bcc.NewModule(src, []string{})
|
||||||
defer m.Close()
|
defer m.Close()
|
||||||
|
|
||||||
table := bcc.NewTable(m.TableId("events"), m)
|
|
||||||
channel := make(chan []byte)
|
|
||||||
perfMap, err := bcc.InitPerfMap(table, channel, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error initializing perf map: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Info("Loading enter tracepoint")
|
logrus.Info("Loading enter tracepoint")
|
||||||
enterTrace, err := m.LoadTracepoint("enter_trace")
|
enterTrace, err := m.LoadTracepoint("enter_trace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error loading tracepoint: %v", err)
|
return errors.Wrap(err, "error loading tracepoint")
|
||||||
}
|
}
|
||||||
logrus.Info("Loading exit tracepoint")
|
logrus.Info("Loading exit tracepoint")
|
||||||
checkExit, err := m.LoadTracepoint("check_exit")
|
checkExit, err := m.LoadTracepoint("check_exit")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error loading tracepoint: %v", err)
|
return errors.Wrap(err, "error loading tracepoint")
|
||||||
}
|
}
|
||||||
logrus.Info("Loaded tracepoints")
|
logrus.Info("Loaded tracepoints")
|
||||||
|
|
||||||
if err := m.AttachTracepoint("raw_syscalls:sys_enter", enterTrace); err != nil {
|
if err := m.AttachTracepoint("raw_syscalls:sys_enter", enterTrace); err != nil {
|
||||||
return fmt.Errorf("error attaching to tracepoint: %v", err)
|
return errors.Wrap(err, "error attaching to tracepoint")
|
||||||
}
|
}
|
||||||
if err := m.AttachTracepoint("sched:sched_process_exit", checkExit); err != nil {
|
if err := m.AttachTracepoint("sched:sched_process_exit", checkExit); err != nil {
|
||||||
return fmt.Errorf("error attaching to tracepoint: %v", err)
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the wait group used to wait for the tracing to be finished.
|
// Initialize the wait group used to wait for the tracing to be finished.
|
||||||
|
@ -288,26 +290,28 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
||||||
logrus.Info("PerfMap Start")
|
logrus.Info("PerfMap Start")
|
||||||
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
|
// Waiting for the goroutine which is reading the perf buffer to be done
|
||||||
// The goroutine will exit when the container exits
|
// The goroutine will exit when the container exits
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
logrus.Info("BPF program has finished")
|
logrus.Info("BPF program has finished")
|
||||||
|
|
||||||
// Post-process the recorded events and extract the syscall names.
|
// Post-process the recorded events and extract the syscall names.
|
||||||
|
recordSyscalls := false
|
||||||
for _, e := range events {
|
for _, e := range events {
|
||||||
name, err := syscallIDtoName(e.ID)
|
name, err := syscallIDtoName(e.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error getting the name for syscall ID %d", e.ID)
|
logrus.Errorf("error getting the name for syscall ID %d", e.ID)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
syscalls[name]++
|
// 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]++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Info("PerfMap Stop")
|
logrus.Info("PerfMap Stop")
|
||||||
|
@ -315,7 +319,7 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
||||||
|
|
||||||
logrus.Infof("Writing seccomp profile to %q", profilePath)
|
logrus.Infof("Writing seccomp profile to %q", profilePath)
|
||||||
if err := generateProfile(syscalls, profilePath, inputFile); err != nil {
|
if err := generateProfile(syscalls, profilePath, inputFile); err != nil {
|
||||||
return fmt.Errorf("error generating final seccomp profile: %v", err)
|
return errors.Wrap(err, "error generating final seccomp profile")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -327,13 +331,13 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
||||||
inputProfile := types.Seccomp{}
|
inputProfile := types.Seccomp{}
|
||||||
|
|
||||||
if inputFile != "" {
|
if inputFile != "" {
|
||||||
input, err := os.ReadFile(inputFile)
|
input, err := ioutil.ReadFile(inputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading input file: %v", err)
|
return errors.Wrap(err, "error reading input file")
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(input, &inputProfile)
|
err = json.Unmarshal(input, &inputProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing input file: %v", err)
|
return errors.Wrap(err, "error parsing input file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,7 +355,7 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
||||||
outputProfile.DefaultAction = types.ActErrno
|
outputProfile.DefaultAction = types.ActErrno
|
||||||
|
|
||||||
if err := appendArchIfNotAlreadyIncluded(runtime.GOARCH, &outputProfile); err != nil {
|
if err := appendArchIfNotAlreadyIncluded(runtime.GOARCH, &outputProfile); err != nil {
|
||||||
return fmt.Errorf("appending architecture to output profile: %v", err)
|
return errors.Wrap(err, "appending architecture to output profile")
|
||||||
}
|
}
|
||||||
|
|
||||||
outputProfile.Syscalls = append(outputProfile.Syscalls, &types.Syscall{
|
outputProfile.Syscalls = append(outputProfile.Syscalls, &types.Syscall{
|
||||||
|
@ -362,10 +366,10 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
||||||
|
|
||||||
sJSON, err := json.Marshal(outputProfile)
|
sJSON, err := json.Marshal(outputProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error writing seccomp profile: %v", err)
|
return errors.Wrap(err, "error writing seccomp profile")
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(profilePath, sJSON, 0644); err != nil {
|
if err := ioutil.WriteFile(profilePath, sJSON, 0644); err != nil {
|
||||||
return fmt.Errorf("error writing seccomp profile: %v", err)
|
return errors.Wrap(err, "error writing seccomp profile")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -375,7 +379,7 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
||||||
func parseAnnotation(annotation string) (outputFile string, inputFile string, err error) {
|
func parseAnnotation(annotation string) (outputFile string, inputFile string, err error) {
|
||||||
annotationSplit := strings.Split(annotation, ";")
|
annotationSplit := strings.Split(annotation, ";")
|
||||||
if len(annotationSplit) > 2 {
|
if len(annotationSplit) > 2 {
|
||||||
return "", "", fmt.Errorf("%v: more than one semi-colon: %q", errInvalidAnnotation, annotation)
|
return "", "", errors.Wrapf(errInvalidAnnotation, "more than one semi-colon: %q", annotation)
|
||||||
}
|
}
|
||||||
for _, path := range annotationSplit {
|
for _, path := range annotationSplit {
|
||||||
switch {
|
switch {
|
||||||
|
@ -383,33 +387,33 @@ func parseAnnotation(annotation string) (outputFile string, inputFile string, er
|
||||||
case strings.HasPrefix(path, "if:"):
|
case strings.HasPrefix(path, "if:"):
|
||||||
inputFile = strings.TrimSpace(strings.TrimPrefix(path, InputPrefix))
|
inputFile = strings.TrimSpace(strings.TrimPrefix(path, InputPrefix))
|
||||||
if !filepath.IsAbs(inputFile) {
|
if !filepath.IsAbs(inputFile) {
|
||||||
return "", "", fmt.Errorf("%v: input file path must be absolute: %q", errInvalidAnnotation, inputFile)
|
return "", "", errors.Wrapf(errInvalidAnnotation, "input file path must be absolute: %q", inputFile)
|
||||||
}
|
}
|
||||||
inputProfile := types.Seccomp{}
|
inputProfile := types.Seccomp{}
|
||||||
input, err := os.ReadFile(inputFile)
|
input, err := ioutil.ReadFile(inputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("%v: error reading input file: %q", errInvalidAnnotation, inputFile)
|
return "", "", errors.Wrapf(errInvalidAnnotation, "error reading input file: %q", inputFile)
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(input, &inputProfile)
|
err = json.Unmarshal(input, &inputProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("%v: error parsing input file: %q", errInvalidAnnotation, inputFile)
|
return "", "", errors.Wrapf(errInvalidAnnotation, "error parsing input file: %q", inputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output profile
|
// Output profile
|
||||||
case strings.HasPrefix(path, "of:"):
|
case strings.HasPrefix(path, "of:"):
|
||||||
outputFile = strings.TrimSpace(strings.TrimPrefix(path, OutputPrefix))
|
outputFile = strings.TrimSpace(strings.TrimPrefix(path, OutputPrefix))
|
||||||
if !filepath.IsAbs(outputFile) {
|
if !filepath.IsAbs(outputFile) {
|
||||||
return "", "", fmt.Errorf("%v: output file path must be absolute: %q", errInvalidAnnotation, outputFile)
|
return "", "", errors.Wrapf(errInvalidAnnotation, "output file path must be absolute: %q", outputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsupported default
|
// Unsupported default
|
||||||
default:
|
default:
|
||||||
return "", "", fmt.Errorf("%v: must start %q or %q prefix", errInvalidAnnotation, InputPrefix, OutputPrefix)
|
return "", "", errors.Wrapf(errInvalidAnnotation, "must start %q or %q prefix", InputPrefix, OutputPrefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if outputFile == "" {
|
if outputFile == "" {
|
||||||
return "", "", fmt.Errorf("%v: providing output file is mandatory", errInvalidAnnotation)
|
return "", "", errors.Wrap(errInvalidAnnotation, "providing output file is mandatory")
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputFile, inputFile, nil
|
return outputFile, inputFile, nil
|
||||||
|
@ -438,7 +442,7 @@ func syscallInProfile(profile *types.Seccomp, syscall string) bool {
|
||||||
func appendArchIfNotAlreadyIncluded(goArch string, profile *types.Seccomp) error {
|
func appendArchIfNotAlreadyIncluded(goArch string, profile *types.Seccomp) error {
|
||||||
targetArch, err := types.GoArchToSeccompArch(goArch)
|
targetArch, err := types.GoArchToSeccompArch(goArch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("determine target architecture: %v", err)
|
return errors.Wrap(err, "determine target architecture")
|
||||||
}
|
}
|
||||||
for _, arch := range profile.Architectures {
|
for _, arch := range profile.Architectures {
|
||||||
if arch == targetArch {
|
if arch == targetArch {
|
||||||
|
|
|
@ -2,11 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
types "github.com/containers/common/pkg/seccomp"
|
types "github.com/seccomp/containers-golang"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ func TestParseAnnotation(t *testing.T) {
|
||||||
testProfile := types.Seccomp{}
|
testProfile := types.Seccomp{}
|
||||||
testProfile.DefaultAction = types.ActErrno
|
testProfile.DefaultAction = types.ActErrno
|
||||||
|
|
||||||
tmpFile, err := os.CreateTemp(os.TempDir(), "input-*.json")
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "input-*.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot create temporary file")
|
t.Fatalf("cannot create temporary file")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,6 @@ load helpers
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "Version check" {
|
@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
|
local version
|
||||||
version=$(cat ./VERSION)
|
version=$(cat ./VERSION)
|
||||||
run ./bin/oci-seccomp-bpf-hook --version
|
run ./bin/oci-seccomp-bpf-hook --version
|
||||||
|
@ -85,7 +78,7 @@ load helpers
|
||||||
echo "Podman output: ${lines[*]}"
|
echo "Podman output: ${lines[*]}"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
run podman run --net=host --security-opt seccomp=${tmpFile} ${ALPINE} ping -c3 $PINGABLE_HOST
|
run podman run --net=host --security-opt seccomp=${tmpFile} ${ALPINE} ping -c3 google.com
|
||||||
echo "Podman output: ${lines[*]}"
|
echo "Podman output: ${lines[*]}"
|
||||||
[ "$status" -ne 0 ]
|
[ "$status" -ne 0 ]
|
||||||
}
|
}
|
||||||
|
@ -93,7 +86,7 @@ load helpers
|
||||||
@test "Extend existing seccomp profile" {
|
@test "Extend existing seccomp profile" {
|
||||||
local tmpFile1
|
local tmpFile1
|
||||||
local tmpFile2
|
local tmpFile2
|
||||||
local size
|
local size
|
||||||
|
|
||||||
tmpFile1=$(mktemp)
|
tmpFile1=$(mktemp)
|
||||||
tmpFile2=$(mktemp)
|
tmpFile2=$(mktemp)
|
||||||
|
@ -109,15 +102,15 @@ load helpers
|
||||||
echo "Size of the first generated file: ${size}"
|
echo "Size of the first generated file: ${size}"
|
||||||
[ "${size}" -gt 0 ]
|
[ "${size}" -gt 0 ]
|
||||||
|
|
||||||
run podman run --net=host --security-opt seccomp=${tmpFile1} ${ALPINE} ping -c3 $PINGABLE_HOST
|
run podman run --net=host --security-opt seccomp=${tmpFile1} ${ALPINE} ping -c3 google.com
|
||||||
echo "Podman output: ${lines[*]}"
|
echo "Podman output: ${lines[*]}"
|
||||||
[ "$status" -ne 0 ]
|
[ "$status" -ne 0 ]
|
||||||
|
|
||||||
run podman run --net=host --annotation io.containers.trace-syscall="if:${tmpFile1};of:${tmpFile2}" ${ALPINE} ping -c3 $PINGABLE_HOST
|
run podman run --net=host --annotation io.containers.trace-syscall="if:${tmpFile1};of:${tmpFile2}" ${ALPINE} ping -c3 google.com
|
||||||
echo "Podman output: ${lines[*]}"
|
echo "Podman output: ${lines[*]}"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
sleep 2 # sleep two seconds to let the hook finish writing the file
|
sleep 2 # sleep two seconds to let the hook finish writing the file
|
||||||
|
|
||||||
size=$(du -b ${tmpFile2} | awk '{ print $1 }')
|
size=$(du -b ${tmpFile2} | awk '{ print $1 }')
|
||||||
echo "Size of the second generated file: ${size}"
|
echo "Size of the second generated file: ${size}"
|
||||||
[ "${size}" -gt 0 ]
|
[ "${size}" -gt 0 ]
|
||||||
|
@ -126,14 +119,14 @@ load helpers
|
||||||
echo "Podman output: ${lines[*]}"
|
echo "Podman output: ${lines[*]}"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
run podman run --net=host --security-opt seccomp=${tmpFile2} ${ALPINE} ping -c3 $PINGABLE_HOST
|
run podman run --net=host --security-opt seccomp=${tmpFile2} ${ALPINE} ping -c3 google.com
|
||||||
echo "Podman output: ${lines[*]}"
|
echo "Podman output: ${lines[*]}"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "Syscall blocked in input profile remains blocked in output profile" {
|
@test "Syscall blocked in input profile remains blocked in output profile" {
|
||||||
local tmpFile
|
local tmpFile
|
||||||
local size
|
local size
|
||||||
|
|
||||||
tmpFile=$(mktemp)
|
tmpFile=$(mktemp)
|
||||||
echo "Temporary file : ${tmpFile}"
|
echo "Temporary file : ${tmpFile}"
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
ALPINE="quay.io/libpod/alpine:latest"
|
ALPINE="docker.io/library/alpine:latest"
|
||||||
BLOCK_MKDIR=$(realpath $(dirname ${BASH_SOURCE[0]})/fixtures/block-mkdir.json)
|
BLOCK_MKDIR=$(realpath ./test/fixtures/block-mkdir.json)
|
||||||
|
|
||||||
# Hostname that should be ping'able from any environment in which we run tests
|
|
||||||
PINGABLE_HOST=github.com
|
|
||||||
|
|
|
@ -1,197 +0,0 @@
|
||||||
// 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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
//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),
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
//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,28 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,191 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
https://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
Copyright 2013-2016 Docker, Inc.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,19 +0,0 @@
|
||||||
Docker
|
|
||||||
Copyright 2012-2016 Docker, Inc.
|
|
||||||
|
|
||||||
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
|
||||||
|
|
||||||
This product contains software (https://github.com/kr/pty) developed
|
|
||||||
by Keith Rarick, licensed under the MIT License.
|
|
||||||
|
|
||||||
The following is courtesy of our legal counsel:
|
|
||||||
|
|
||||||
|
|
||||||
Use and transfer of Docker may be subject to certain restrictions by the
|
|
||||||
United States and other governments.
|
|
||||||
It is your responsibility to ensure that your use and/or transfer does not
|
|
||||||
violate applicable laws.
|
|
||||||
|
|
||||||
For more information, please see https://www.bis.doc.gov
|
|
||||||
|
|
||||||
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
|
|
@ -1,38 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,371 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package fileutils
|
|
||||||
|
|
||||||
// GetTotalUsedFds Returns the number of used File Descriptors. Not supported
|
|
||||||
// on Windows.
|
|
||||||
func GetTotalUsedFds() int {
|
|
||||||
return -1
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,620 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
|
@ -1,214 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
//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")
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
//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()
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
|
@ -1,87 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
|
@ -1,31 +0,0 @@
|
||||||
//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
|
|
||||||
)
|
|
|
@ -1,110 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
//go:build !linux && !(freebsd && cgo)
|
|
||||||
|
|
||||||
package mount
|
|
||||||
|
|
||||||
func mount(device, target, mType string, flag uintptr, data string) error {
|
|
||||||
panic("Not implemented")
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package mount
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/moby/sys/mountinfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Info = mountinfo.Info
|
|
||||||
|
|
||||||
var Mounted = mountinfo.Mounted
|
|
||||||
|
|
||||||
func GetMounts() ([]*Info, error) {
|
|
||||||
return mountinfo.GetMounts(nil)
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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), "")
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package mount
|
|
||||||
|
|
||||||
func unmount(target string, flag int) error {
|
|
||||||
panic("Not implemented")
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
# 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.
|
|
|
@ -1,37 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,234 +0,0 @@
|
||||||
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() {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
//go:build !regexp_precompile
|
|
||||||
|
|
||||||
package regexp
|
|
||||||
|
|
||||||
const precompile = false
|
|
|
@ -1,5 +0,0 @@
|
||||||
//go:build regexp_precompile
|
|
||||||
|
|
||||||
package regexp
|
|
||||||
|
|
||||||
const precompile = true
|
|
|
@ -1,17 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNotSupportedPlatform means the platform is not supported.
|
|
||||||
var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
|
|
@ -1,33 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
|
||||||
func LCOWSupported() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
|
||||||
func LCOWSupported() bool {
|
|
||||||
return lcowSupported
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
//go:build !linux && !windows && !solaris && !(freebsd && cgo)
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// ReadMemInfo is not supported on platforms other than linux and windows.
|
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
|
||||||
return nil, ErrNotSupportedPlatform
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
|
|
||||||
procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx
|
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366770(v=vs.85).aspx
|
|
||||||
type memorystatusex struct {
|
|
||||||
dwLength uint32
|
|
||||||
dwMemoryLoad uint32
|
|
||||||
ullTotalPhys uint64
|
|
||||||
ullAvailPhys uint64
|
|
||||||
ullTotalPageFile uint64
|
|
||||||
ullAvailPageFile uint64
|
|
||||||
ullTotalVirtual uint64
|
|
||||||
ullAvailVirtual uint64
|
|
||||||
ullAvailExtendedVirtual uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
|
||||||
//
|
|
||||||
// MemInfo type.
|
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
|
||||||
msi := &memorystatusex{
|
|
||||||
dwLength: 64,
|
|
||||||
}
|
|
||||||
r1, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msi)))
|
|
||||||
if r1 == 0 {
|
|
||||||
return &MemInfo{}, nil
|
|
||||||
}
|
|
||||||
return &MemInfo{
|
|
||||||
MemTotal: int64(msi.ullTotalPhys),
|
|
||||||
MemFree: int64(msi.ullAvailPhys),
|
|
||||||
SwapTotal: int64(msi.ullTotalPageFile),
|
|
||||||
SwapFree: int64(msi.ullAvailPageFile),
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
//go:build !windows && !freebsd
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mknod creates a filesystem node (file, device special file or named pipe) named path
|
|
||||||
// with attributes specified by mode and dev.
|
|
||||||
func Mknod(path string, mode uint32, dev uint32) error {
|
|
||||||
return unix.Mknod(path, mode, int(dev))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdev is used to build the value of linux devices (in /dev/) which specifies major
|
|
||||||
// and minor number of the newly created device special file.
|
|
||||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
|
||||||
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
|
||||||
// then the top 12 bits of the minor.
|
|
||||||
func Mkdev(major int64, minor int64) uint32 {
|
|
||||||
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
//go:build freebsd
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mknod creates a filesystem node (file, device special file or named pipe) named path
|
|
||||||
// with attributes specified by mode and dev.
|
|
||||||
func Mknod(path string, mode uint32, dev uint64) error {
|
|
||||||
return unix.Mknod(path, mode, dev)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdev is used to build the value of linux devices (in /dev/) which specifies major
|
|
||||||
// and minor number of the newly created device special file.
|
|
||||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
|
||||||
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
|
||||||
// then the top 12 bits of the minor.
|
|
||||||
func Mkdev(major int64, minor int64) uint64 {
|
|
||||||
return uint64(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// Mknod is not implemented on Windows.
|
|
||||||
func Mknod(path string, mode uint32, dev int) error {
|
|
||||||
return ErrNotSupportedPlatform
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdev is not implemented on Windows.
|
|
||||||
func Mkdev(major int64, minor int64) uint32 {
|
|
||||||
panic("Mkdev not implemented on Windows.")
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import "runtime"
|
|
||||||
|
|
||||||
const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
|
|
||||||
// DefaultPathEnv is unix style list of directories to search for
|
|
||||||
// executables. Each directory is separated from the next by a colon
|
|
||||||
// ':' character .
|
|
||||||
func DefaultPathEnv(platform string) string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if platform != runtime.GOOS && LCOWSupported() {
|
|
||||||
return defaultUnixPathEnv
|
|
||||||
}
|
|
||||||
// Deliberately empty on Windows containers on Windows as the default path will be set by
|
|
||||||
// the container. Docker has no context of what the default path should be.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return defaultUnixPathEnv
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
|
||||||
// is the system drive. This is a no-op on Linux.
|
|
||||||
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
|
||||||
// This is used, for example, when validating a user provided path in docker cp.
|
|
||||||
// If a drive letter is supplied, it must be the system drive. The drive letter
|
|
||||||
// is always removed. Also, it translates it to OS semantics (IOW / to \). We
|
|
||||||
// need the path in this syntax so that it can ultimately be concatenated with
|
|
||||||
// a Windows long-path which doesn't support drive-letters. Examples:
|
|
||||||
// C: --> Fail
|
|
||||||
// C:\ --> \
|
|
||||||
// a --> a
|
|
||||||
// /a --> \a
|
|
||||||
// d:\ --> Fail
|
|
||||||
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
|
||||||
if len(path) == 2 && string(path[1]) == ":" {
|
|
||||||
return "", fmt.Errorf("relative path not specified in %q", path)
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(path) || len(path) < 2 {
|
|
||||||
return filepath.FromSlash(path), nil
|
|
||||||
}
|
|
||||||
if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") {
|
|
||||||
return "", fmt.Errorf("specified path is not on the system drive (C:)")
|
|
||||||
}
|
|
||||||
return filepath.FromSlash(path[2:]), nil
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue