Compare commits
151 Commits
Author | SHA1 | Date |
---|---|---|
|
7c6167725b | |
|
b63cf81bb5 | |
|
e86b33e7c5 | |
|
71fa86684d | |
|
9d8d33bd67 | |
|
bfdd693beb | |
|
2fe6ea2d9f | |
|
4590fdba26 | |
|
a028edf9cb | |
|
22a4f30f48 | |
|
34b21b6dc3 | |
|
63fefe0f20 | |
|
591153cb8f | |
|
8a24390d18 | |
|
97f2983630 | |
|
e2a50af30e | |
|
1a717caef7 | |
|
d363e25d2a | |
|
bdb2268d7b | |
|
c5e4904edc | |
|
630a09f34e | |
|
51eaa377ce | |
|
b8a02cc18f | |
|
6c8ecb124e | |
|
6a2d670f51 | |
|
e9fdef4dbf | |
|
76a55c7ad9 | |
|
e6400f5b96 | |
|
bad4003415 | |
|
a26fed5314 | |
|
1a9b6396ef | |
|
b03cedfb33 | |
|
1326c0606a | |
|
898f7b190e | |
|
3b4191616c | |
|
ff7d010baf | |
|
7af78de4f7 | |
|
00e2d7245d | |
|
f72ed843c0 | |
|
0bc7da7ab1 | |
|
51015d1966 | |
|
f9b9e536f8 | |
|
2f5237395e | |
|
b897a7d464 | |
|
7a6c9d3a3d | |
|
86b2c1c2cc | |
|
266bdfe05f | |
|
1ec8ce61a5 | |
|
ba91441159 | |
|
5553f90b1c | |
|
681179f1bd | |
|
b486e8181e | |
|
cd907412d5 | |
|
969537eb7a | |
|
ec31ff1e86 | |
|
fe7a74ceed | |
|
01ca720a78 | |
|
57e5d60a3f | |
|
062cbb9697 | |
|
12a6b5a7ef | |
|
d7918b036c | |
|
8b59acfd3d | |
|
3082eed8f1 | |
|
a4c0f838cd | |
|
fff84e808d | |
|
5ed5002d6c | |
|
ad9b137be0 | |
|
1c78fe1b15 | |
|
a7bafcb864 | |
|
8cdee7b11e | |
|
e8f359faac | |
|
c6dd74b3e7 | |
|
86c18727bb | |
|
162cf0d113 | |
|
c23a2764ac | |
|
3cbd5e812a | |
|
e8062f8abe | |
|
a12ae46058 | |
|
a868c45bf8 | |
|
32c22ee33f | |
|
560bae97b8 | |
|
8c709c228c | |
|
d560060775 | |
|
21d936bbef | |
|
fb98b83d20 | |
|
61c7d317ea | |
|
55a5929566 | |
|
e98e224fab | |
|
56d0907d7d | |
|
9e495e0a80 | |
|
9745218df4 | |
|
16395c5ed7 | |
|
8a9af6861b | |
|
8c5ed87d79 | |
|
5d4d04f251 | |
|
ce7d972ba4 | |
|
8c4b4dcc6b | |
|
32e2606d27 | |
|
38f6ab6090 | |
|
5e3793b640 | |
|
57823f9c42 | |
|
4cc0634fc1 | |
|
1c8eaf3085 | |
|
d525ee9695 | |
|
37fbd3020f | |
|
cf1b3ce101 | |
|
3b5d827ce8 | |
|
967fea3e8e | |
|
39ea9e139f | |
|
6fdae39069 | |
|
28d462ed74 | |
|
4e983ea172 | |
|
89503c8f6b | |
|
ab5d270372 | |
|
c5eecd3be7 | |
|
d131bebcca | |
|
c3a431d834 | |
|
3718eaafa4 | |
|
b09d71ad89 | |
|
9ca3f9f7b5 | |
|
3280db1a60 | |
|
f1cc6af344 | |
|
00e4b496e1 | |
|
28f1b41688 | |
|
82113f6b3a | |
|
b9068bfb01 | |
|
3f6c6065a8 | |
|
7021239842 | |
|
bed5713b5a | |
|
19d707a515 | |
|
9ee6f0a8ba | |
|
bc40b01a56 | |
|
8cc1f7aedd | |
|
bd33a9cdce | |
|
e6accd4cfe | |
|
544c991c77 | |
|
1003459cbc | |
|
36a96f5d5a | |
|
8013aee634 | |
|
a742ba49c8 | |
|
cc4650c778 | |
|
872fc48dce | |
|
c9476b811f | |
|
d6a8b8c7bf | |
|
b7030ae105 | |
|
2258f0a2ce | |
|
ee91033618 | |
|
2543f4f6e5 | |
|
245cc45ad4 | |
|
934aee1fc5 | |
|
3fee319eaf |
32
.cirrus.yml
32
.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
|
||||||
_BUILT_IMAGE_SUFFIX: "c5878804328480768"
|
IMAGE_SUFFIX: "c20250324t111922z-f41f40d13"
|
||||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${_BUILT_IMAGE_SUFFIX}"
|
FEDORA_CACHE_IMAGE_NAME: "fedora-${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,16 +21,6 @@ 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:
|
||||||
|
|
||||||
|
@ -58,7 +48,7 @@ meta_task:
|
||||||
vendor_task:
|
vendor_task:
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: golang:1.18
|
image: golang:1.23
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make vendor
|
- make vendor
|
||||||
|
@ -68,7 +58,17 @@ vendor_task:
|
||||||
build_and_test_task:
|
build_and_test_task:
|
||||||
|
|
||||||
gce_instance:
|
gce_instance:
|
||||||
image_name: $FEDORA_CACHE_IMAGE_NAME
|
image_project: "${IMAGE_PROJECT}"
|
||||||
|
zone: "us-central1-c" # Required by Cirrus for the time being
|
||||||
|
cpu: 2
|
||||||
|
memory: "4Gb"
|
||||||
|
disk: 200 # Required for performance reasons
|
||||||
|
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
- name: "Test on Fedora"
|
||||||
|
gce_instance:
|
||||||
|
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||||
|
|
||||||
# Avoid downloading this stuff every time
|
# Avoid downloading this stuff every time
|
||||||
gocache_cache:
|
gocache_cache:
|
||||||
|
@ -100,6 +100,10 @@ build_and_test_task:
|
||||||
- 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"
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
Renovate is a service similar to GitHub Dependabot, but with
|
||||||
|
(fantastically) more configuration options. So many options
|
||||||
|
in fact, if you're new I recommend glossing over this cheat-sheet
|
||||||
|
prior to the official documentation:
|
||||||
|
|
||||||
|
https://www.augmentedmind.de/2021/07/25/renovate-bot-cheat-sheet
|
||||||
|
|
||||||
|
Configuration Update/Change Procedure:
|
||||||
|
1. Make changes
|
||||||
|
2. Manually validate changes (from repo-root):
|
||||||
|
|
||||||
|
podman run -it \
|
||||||
|
-v ./.github/renovate.json5:/usr/src/app/renovate.json5:z \
|
||||||
|
docker.io/renovate/renovate:latest \
|
||||||
|
renovate-config-validator
|
||||||
|
3. Commit.
|
||||||
|
|
||||||
|
Configuration Reference:
|
||||||
|
https://docs.renovatebot.com/configuration-options/
|
||||||
|
|
||||||
|
Monitoring Dashboard:
|
||||||
|
https://app.renovatebot.com/dashboard#github/containers
|
||||||
|
|
||||||
|
Note: The Renovate bot will create/manage it's business on
|
||||||
|
branches named 'renovate/*'. Otherwise, and by
|
||||||
|
default, the only the copy of this file that matters
|
||||||
|
is the one on the `main` branch. No other branches
|
||||||
|
will be monitored or touched in any way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
|
||||||
|
/*************************************************
|
||||||
|
****** Global/general configuration options *****
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
// Re-use predefined sets of configuration options to DRY
|
||||||
|
"extends": [
|
||||||
|
// https://github.com/containers/automation/blob/main/renovate/defaults.json5
|
||||||
|
"github>containers/automation//renovate/defaults.json5"
|
||||||
|
],
|
||||||
|
|
||||||
|
// Permit automatic rebasing when base-branch changes by more than
|
||||||
|
// one commit.
|
||||||
|
"rebaseWhen": "behind-base-branch",
|
||||||
|
|
||||||
|
/*************************************************
|
||||||
|
*** Repository-specific configuration options ***
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
"assignees": ["vrothberg", "rhatdan"],
|
||||||
|
}
|
7
Makefile
7
Makefile
|
@ -6,6 +6,7 @@ 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)
|
||||||
|
@ -46,7 +47,7 @@ docs:
|
||||||
|
|
||||||
.PHONY: binary
|
.PHONY: binary
|
||||||
binary:
|
binary:
|
||||||
$(GO_BUILD) -mod=vendor -o bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
$(GO_BUILD) -mod=vendor -o $(BUILDDIR)/bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
||||||
|
|
||||||
.PHONY: validate
|
.PHONY: validate
|
||||||
validate:
|
validate:
|
||||||
|
@ -73,7 +74,7 @@ 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.45.2
|
.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)
|
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/$(VERSION)/install.sh | sh -s -- -b ./build $(VERSION)
|
||||||
|
|
||||||
|
@ -94,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 bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
install $(SELINUXOPT) -m 755 $(BUILDDIR)/bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
||||||
install $(SELINUXOPT) -m 644 oci-seccomp-bpf-hook.json $(DESTDIR)$(HOOK_DIR)
|
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 @@
|
||||||
FROM fedora:31
|
FROM fedora:latest
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -36,12 +36,6 @@ 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}"
|
||||||
|
|
||||||
|
|
8
ebpf.go
8
ebpf.go
|
@ -18,7 +18,6 @@ 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>
|
||||||
|
@ -112,6 +111,13 @@ int enter_trace(struct tracepoint__raw_syscalls__sys_enter* args)
|
||||||
// The syscall was already notified.
|
// The syscall was already notified.
|
||||||
if (seen > 0)
|
if (seen > 0)
|
||||||
return 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;
|
||||||
|
|
30
go.mod
30
go.mod
|
@ -1,12 +1,28 @@
|
||||||
module github.com/containers/oci-seccomp-bpf-hook
|
module github.com/containers/oci-seccomp-bpf-hook
|
||||||
|
|
||||||
go 1.14
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.8
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containers/common v0.48.0
|
github.com/containers/common v0.62.3
|
||||||
github.com/iovisor/gobpf v0.2.0
|
github.com/containers/storage v1.58.0
|
||||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
|
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4
|
||||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921
|
github.com/opencontainers/runtime-spec v1.2.1
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/seccomp/libseccomp-golang v0.10.0
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/moby/sys/capability v0.4.0 // indirect
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||||
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -23,6 +22,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "github.com/containers/common/pkg/seccomp"
|
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"
|
||||||
seccomp "github.com/seccomp/libseccomp-golang"
|
seccomp "github.com/seccomp/libseccomp-golang"
|
||||||
|
@ -57,6 +57,11 @@ 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")
|
||||||
|
@ -223,6 +228,13 @@ 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 {
|
||||||
|
@ -242,13 +254,6 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
||||||
return fmt.Errorf("error attaching to tracepoint: %v", err)
|
return fmt.Errorf("error attaching to tracepoint: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
var events []event
|
var events []event
|
||||||
|
@ -322,7 +327,7 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
||||||
inputProfile := types.Seccomp{}
|
inputProfile := types.Seccomp{}
|
||||||
|
|
||||||
if inputFile != "" {
|
if inputFile != "" {
|
||||||
input, err := ioutil.ReadFile(inputFile)
|
input, err := os.ReadFile(inputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading input file: %v", err)
|
return fmt.Errorf("error reading input file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -359,7 +364,7 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error writing seccomp profile: %v", err)
|
return fmt.Errorf("error writing seccomp profile: %v", err)
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(profilePath, sJSON, 0644); err != nil {
|
if err := os.WriteFile(profilePath, sJSON, 0644); err != nil {
|
||||||
return fmt.Errorf("error writing seccomp profile: %v", err)
|
return fmt.Errorf("error writing seccomp profile: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -381,7 +386,7 @@ func parseAnnotation(annotation string) (outputFile string, inputFile string, er
|
||||||
return "", "", fmt.Errorf("%v: input file path must be absolute: %q", errInvalidAnnotation, inputFile)
|
return "", "", fmt.Errorf("%v: input file path must be absolute: %q", errInvalidAnnotation, inputFile)
|
||||||
}
|
}
|
||||||
inputProfile := types.Seccomp{}
|
inputProfile := types.Seccomp{}
|
||||||
input, err := ioutil.ReadFile(inputFile)
|
input, err := os.ReadFile(inputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("%v: error reading input file: %q", errInvalidAnnotation, inputFile)
|
return "", "", fmt.Errorf("%v: error reading input file: %q", errInvalidAnnotation, inputFile)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -15,7 +14,7 @@ func TestParseAnnotation(t *testing.T) {
|
||||||
testProfile := types.Seccomp{}
|
testProfile := types.Seccomp{}
|
||||||
testProfile.DefaultAction = types.ActErrno
|
testProfile.DefaultAction = types.ActErrno
|
||||||
|
|
||||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "input-*.json")
|
tmpFile, err := os.CreateTemp(os.TempDir(), "input-*.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot create temporary file")
|
t.Fatalf("cannot create temporary file")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -71,11 +70,12 @@ var (
|
||||||
// https://github.com/opencontainers/runtime-spec/pull/1064
|
// https://github.com/opencontainers/runtime-spec/pull/1064
|
||||||
// specs.ActKillProcess ActKillProcess,
|
// specs.ActKillProcess ActKillProcess,
|
||||||
// specs.ActKillThread ActKillThread,
|
// specs.ActKillThread ActKillThread,
|
||||||
specs.ActErrno: ActErrno,
|
specs.ActErrno: ActErrno,
|
||||||
specs.ActTrap: ActTrap,
|
specs.ActTrap: ActTrap,
|
||||||
specs.ActAllow: ActAllow,
|
specs.ActAllow: ActAllow,
|
||||||
specs.ActTrace: ActTrace,
|
specs.ActTrace: ActTrace,
|
||||||
specs.ActLog: ActLog,
|
specs.ActLog: ActLog,
|
||||||
|
specs.ActNotify: ActNotify,
|
||||||
}
|
}
|
||||||
specOperatorToSeccompOperatorMap = map[specs.LinuxSeccompOperator]Operator{
|
specOperatorToSeccompOperatorMap = map[specs.LinuxSeccompOperator]Operator{
|
||||||
specs.OpNotEqual: OpNotEqual,
|
specs.OpNotEqual: OpNotEqual,
|
||||||
|
@ -107,7 +107,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
||||||
for _, arch := range spec.Architectures {
|
for _, arch := range spec.Architectures {
|
||||||
newArch, err := specArchToSeccompArch(arch)
|
newArch, err := specArchToSeccompArch(arch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "convert spec arch")
|
return nil, fmt.Errorf("convert spec arch: %w", err)
|
||||||
}
|
}
|
||||||
res.Architectures = append(res.Architectures, newArch)
|
res.Architectures = append(res.Architectures, newArch)
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
||||||
// Convert default action
|
// Convert default action
|
||||||
newDefaultAction, err := specActionToSeccompAction(spec.DefaultAction)
|
newDefaultAction, err := specActionToSeccompAction(spec.DefaultAction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "convert default action")
|
return nil, fmt.Errorf("convert default action: %w", err)
|
||||||
}
|
}
|
||||||
res.DefaultAction = newDefaultAction
|
res.DefaultAction = newDefaultAction
|
||||||
res.DefaultErrnoRet = spec.DefaultErrnoRet
|
res.DefaultErrnoRet = spec.DefaultErrnoRet
|
||||||
|
@ -124,7 +124,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
||||||
for _, call := range spec.Syscalls {
|
for _, call := range spec.Syscalls {
|
||||||
newAction, err := specActionToSeccompAction(call.Action)
|
newAction, err := specActionToSeccompAction(call.Action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "convert action")
|
return nil, fmt.Errorf("convert action: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range call.Names {
|
for _, name := range call.Names {
|
||||||
|
@ -139,7 +139,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
||||||
for _, arg := range call.Args {
|
for _, arg := range call.Args {
|
||||||
newOp, err := specOperatorToSeccompOperator(arg.Op)
|
newOp, err := specOperatorToSeccompOperator(arg.Op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "convert operator")
|
return nil, fmt.Errorf("convert operator: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newArg := Arg{
|
newArg := Arg{
|
||||||
|
@ -163,7 +163,7 @@ func specArchToLibseccompArch(arch specs.Arch) (string, error) {
|
||||||
if res, ok := specArchToLibseccompArchMap[arch]; ok {
|
if res, ok := specArchToLibseccompArchMap[arch]; ok {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
return "", errors.Errorf(
|
return "", fmt.Errorf(
|
||||||
"architecture %q is not valid for libseccomp", arch,
|
"architecture %q is not valid for libseccomp", arch,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ func specArchToSeccompArch(arch specs.Arch) (Arch, error) {
|
||||||
if res, ok := specArchToSeccompArchMap[arch]; ok {
|
if res, ok := specArchToSeccompArchMap[arch]; ok {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
return "", errors.Errorf("architecture %q is not valid", arch)
|
return "", fmt.Errorf("architecture %q is not valid", arch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// specActionToSeccompAction converts a spec action into a seccomp one.
|
// specActionToSeccompAction converts a spec action into a seccomp one.
|
||||||
|
@ -181,7 +181,7 @@ func specActionToSeccompAction(action specs.LinuxSeccompAction) (Action, error)
|
||||||
if res, ok := specActionToSeccompActionMap[action]; ok {
|
if res, ok := specActionToSeccompActionMap[action]; ok {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
return "", errors.Errorf(
|
return "", fmt.Errorf(
|
||||||
"spec action %q is not valid internal action", action,
|
"spec action %q is not valid internal action", action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ func specOperatorToSeccompOperator(operator specs.LinuxSeccompOperator) (Operato
|
||||||
if op, ok := specOperatorToSeccompOperatorMap[operator]; ok {
|
if op, ok := specOperatorToSeccompOperatorMap[operator]; ok {
|
||||||
return op, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
return "", errors.Errorf(
|
return "", fmt.Errorf(
|
||||||
"spec operator %q is not a valid internal operator", operator,
|
"spec operator %q is not a valid internal operator", operator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,16 @@ func DefaultProfile() *Seccomp {
|
||||||
{
|
{
|
||||||
Names: []string{
|
Names: []string{
|
||||||
"bdflush",
|
"bdflush",
|
||||||
|
"cachestat",
|
||||||
|
"futex_requeue",
|
||||||
|
"futex_wait",
|
||||||
|
"futex_waitv",
|
||||||
|
"futex_wake",
|
||||||
"io_pgetevents",
|
"io_pgetevents",
|
||||||
|
"io_pgetevents_time64",
|
||||||
"kexec_file_load",
|
"kexec_file_load",
|
||||||
"kexec_load",
|
"kexec_load",
|
||||||
|
"map_shadow_stack",
|
||||||
"migrate_pages",
|
"migrate_pages",
|
||||||
"move_pages",
|
"move_pages",
|
||||||
"nfsservctl",
|
"nfsservctl",
|
||||||
|
@ -68,9 +75,9 @@ func DefaultProfile() *Seccomp {
|
||||||
"pciconfig_write",
|
"pciconfig_write",
|
||||||
"sgetmask",
|
"sgetmask",
|
||||||
"ssetmask",
|
"ssetmask",
|
||||||
"swapcontext",
|
|
||||||
"swapoff",
|
"swapoff",
|
||||||
"swapon",
|
"swapon",
|
||||||
|
"syscall",
|
||||||
"sysfs",
|
"sysfs",
|
||||||
"uselib",
|
"uselib",
|
||||||
"userfaultfd",
|
"userfaultfd",
|
||||||
|
@ -142,6 +149,7 @@ func DefaultProfile() *Seccomp {
|
||||||
"fchdir",
|
"fchdir",
|
||||||
"fchmod",
|
"fchmod",
|
||||||
"fchmodat",
|
"fchmodat",
|
||||||
|
"fchmodat2",
|
||||||
"fchown",
|
"fchown",
|
||||||
"fchown32",
|
"fchown32",
|
||||||
"fchownat",
|
"fchownat",
|
||||||
|
@ -221,6 +229,9 @@ func DefaultProfile() *Seccomp {
|
||||||
"ipc",
|
"ipc",
|
||||||
"keyctl",
|
"keyctl",
|
||||||
"kill",
|
"kill",
|
||||||
|
"landlock_add_rule",
|
||||||
|
"landlock_create_ruleset",
|
||||||
|
"landlock_restrict_self",
|
||||||
"lchown",
|
"lchown",
|
||||||
"lchown32",
|
"lchown32",
|
||||||
"lgetxattr",
|
"lgetxattr",
|
||||||
|
@ -306,7 +317,6 @@ func DefaultProfile() *Seccomp {
|
||||||
"pwritev2",
|
"pwritev2",
|
||||||
"read",
|
"read",
|
||||||
"readahead",
|
"readahead",
|
||||||
"readdir",
|
|
||||||
"readlink",
|
"readlink",
|
||||||
"readlinkat",
|
"readlinkat",
|
||||||
"readv",
|
"readv",
|
||||||
|
@ -394,15 +404,12 @@ func DefaultProfile() *Seccomp {
|
||||||
"shmdt",
|
"shmdt",
|
||||||
"shmget",
|
"shmget",
|
||||||
"shutdown",
|
"shutdown",
|
||||||
"sigaction",
|
|
||||||
"sigaltstack",
|
"sigaltstack",
|
||||||
"signal",
|
"signal",
|
||||||
"signalfd",
|
"signalfd",
|
||||||
"signalfd4",
|
"signalfd4",
|
||||||
"sigpending",
|
|
||||||
"sigprocmask",
|
"sigprocmask",
|
||||||
"sigreturn",
|
"sigreturn",
|
||||||
"sigsuspend",
|
|
||||||
"socketcall",
|
"socketcall",
|
||||||
"socketpair",
|
"socketpair",
|
||||||
"splice",
|
"splice",
|
||||||
|
@ -416,7 +423,6 @@ func DefaultProfile() *Seccomp {
|
||||||
"sync",
|
"sync",
|
||||||
"sync_file_range",
|
"sync_file_range",
|
||||||
"syncfs",
|
"syncfs",
|
||||||
"syscall",
|
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"syslog",
|
"syslog",
|
||||||
"tee",
|
"tee",
|
||||||
|
@ -429,7 +435,6 @@ func DefaultProfile() *Seccomp {
|
||||||
"timer_gettime64",
|
"timer_gettime64",
|
||||||
"timer_settime",
|
"timer_settime",
|
||||||
"timer_settime64",
|
"timer_settime64",
|
||||||
"timerfd",
|
|
||||||
"timerfd_create",
|
"timerfd_create",
|
||||||
"timerfd_gettime",
|
"timerfd_gettime",
|
||||||
"timerfd_gettime64",
|
"timerfd_gettime64",
|
||||||
|
@ -519,6 +524,7 @@ func DefaultProfile() *Seccomp {
|
||||||
{
|
{
|
||||||
Names: []string{
|
Names: []string{
|
||||||
"sync_file_range2",
|
"sync_file_range2",
|
||||||
|
"swapcontext",
|
||||||
},
|
},
|
||||||
Action: ActAllow,
|
Action: ActAllow,
|
||||||
Args: []*Arg{},
|
Args: []*Arg{},
|
||||||
|
@ -573,6 +579,16 @@ func DefaultProfile() *Seccomp {
|
||||||
Arches: []string{"s390", "s390x"},
|
Arches: []string{"s390", "s390x"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Names: []string{
|
||||||
|
"riscv_flush_icache",
|
||||||
|
},
|
||||||
|
Action: ActAllow,
|
||||||
|
Args: []*Arg{},
|
||||||
|
Includes: Filter{
|
||||||
|
Arches: []string{"riscv64"},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Names: []string{
|
Names: []string{
|
||||||
"open_by_handle_at",
|
"open_by_handle_at",
|
||||||
|
@ -600,8 +616,8 @@ func DefaultProfile() *Seccomp {
|
||||||
"bpf",
|
"bpf",
|
||||||
"fanotify_init",
|
"fanotify_init",
|
||||||
"lookup_dcookie",
|
"lookup_dcookie",
|
||||||
"perf_event_open",
|
|
||||||
"quotactl",
|
"quotactl",
|
||||||
|
"quotactl_fd",
|
||||||
"setdomainname",
|
"setdomainname",
|
||||||
"sethostname",
|
"sethostname",
|
||||||
"setns",
|
"setns",
|
||||||
|
@ -614,11 +630,11 @@ func DefaultProfile() *Seccomp {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Names: []string{
|
Names: []string{
|
||||||
"bpf",
|
|
||||||
"fanotify_init",
|
"fanotify_init",
|
||||||
"lookup_dcookie",
|
"lookup_dcookie",
|
||||||
"perf_event_open",
|
"perf_event_open",
|
||||||
"quotactl",
|
"quotactl",
|
||||||
|
"quotactl_fd",
|
||||||
"setdomainname",
|
"setdomainname",
|
||||||
"sethostname",
|
"sethostname",
|
||||||
"setns",
|
"setns",
|
||||||
|
@ -881,6 +897,50 @@ func DefaultProfile() *Seccomp {
|
||||||
Caps: []string{"CAP_AUDIT_WRITE"},
|
Caps: []string{"CAP_AUDIT_WRITE"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Names: []string{
|
||||||
|
"bpf",
|
||||||
|
},
|
||||||
|
Action: ActErrno,
|
||||||
|
Errno: "EPERM",
|
||||||
|
ErrnoRet: &eperm,
|
||||||
|
Args: []*Arg{},
|
||||||
|
Excludes: Filter{
|
||||||
|
Caps: []string{"CAP_SYS_ADMIN", "CAP_BPF"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Names: []string{
|
||||||
|
"bpf",
|
||||||
|
},
|
||||||
|
Action: ActAllow,
|
||||||
|
Args: []*Arg{},
|
||||||
|
Includes: Filter{
|
||||||
|
Caps: []string{"CAP_BPF"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Names: []string{
|
||||||
|
"perf_event_open",
|
||||||
|
},
|
||||||
|
Action: ActErrno,
|
||||||
|
Errno: "EPERM",
|
||||||
|
ErrnoRet: &eperm,
|
||||||
|
Args: []*Arg{},
|
||||||
|
Excludes: Filter{
|
||||||
|
Caps: []string{"CAP_SYS_ADMIN", "CAP_BPF"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Names: []string{
|
||||||
|
"perf_event_open",
|
||||||
|
},
|
||||||
|
Action: ActAllow,
|
||||||
|
Args: []*Arg{},
|
||||||
|
Includes: Filter{
|
||||||
|
Caps: []string{"CAP_PERFMON"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Seccomp{
|
return &Seccomp{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build linux && seccomp
|
//go:build linux && seccomp
|
||||||
// +build linux,seccomp
|
|
||||||
|
|
||||||
package seccomp
|
package seccomp
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build seccomp
|
//go:build seccomp
|
||||||
// +build seccomp
|
|
||||||
|
|
||||||
// NOTE: this package has originally been copied from
|
// NOTE: this package has originally been copied from
|
||||||
// github.com/opencontainers/runc and modified to work for other use cases
|
// github.com/opencontainers/runc and modified to work for other use cases
|
||||||
|
@ -7,8 +6,10 @@
|
||||||
package seccomp
|
package seccomp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
|
||||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
@ -39,39 +40,39 @@ func BuildFilter(spec *specs.LinuxSeccomp) (*libseccomp.ScmpFilter, error) {
|
||||||
|
|
||||||
profile, err := specToSeccomp(spec)
|
profile, err := specToSeccomp(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "convert spec to seccomp profile")
|
return nil, fmt.Errorf("convert spec to seccomp profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultAction, err := toAction(profile.DefaultAction, profile.DefaultErrnoRet)
|
defaultAction, err := toAction(profile.DefaultAction, profile.DefaultErrnoRet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "convert default action %s", profile.DefaultAction)
|
return nil, fmt.Errorf("convert default action %s: %w", profile.DefaultAction, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filter, err := libseccomp.NewFilter(defaultAction)
|
filter, err := libseccomp.NewFilter(defaultAction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "create filter for default action %s", defaultAction)
|
return nil, fmt.Errorf("create filter for default action %s: %w", defaultAction, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add extra architectures
|
// Add extra architectures
|
||||||
for _, arch := range spec.Architectures {
|
for _, arch := range spec.Architectures {
|
||||||
libseccompArch, err := specArchToLibseccompArch(arch)
|
libseccompArch, err := specArchToLibseccompArch(arch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "convert spec arch")
|
return nil, fmt.Errorf("convert spec arch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
scmpArch, err := libseccomp.GetArchFromString(libseccompArch)
|
scmpArch, err := libseccomp.GetArchFromString(libseccompArch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "validate Seccomp architecture %s", arch)
|
return nil, fmt.Errorf("validate Seccomp architecture %s: %w", arch, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := filter.AddArch(scmpArch); err != nil {
|
if err := filter.AddArch(scmpArch); err != nil {
|
||||||
return nil, errors.Wrap(err, "add architecture to seccomp filter")
|
return nil, fmt.Errorf("add architecture to seccomp filter: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unset no new privs bit
|
// Unset no new privs bit
|
||||||
if err := filter.SetNoNewPrivsBit(false); err != nil {
|
if err := filter.SetNoNewPrivsBit(false); err != nil {
|
||||||
return nil, errors.Wrap(err, "set no new privileges flag")
|
return nil, fmt.Errorf("set no new privileges flag: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a rule for each syscall
|
// Add a rule for each syscall
|
||||||
|
@ -81,7 +82,7 @@ func BuildFilter(spec *specs.LinuxSeccomp) (*libseccomp.ScmpFilter, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = matchSyscall(filter, call); err != nil {
|
if err = matchSyscall(filter, call); err != nil {
|
||||||
return nil, errors.Wrap(err, "filter matches syscall")
|
return nil, fmt.Errorf("filter matches syscall: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,13 +108,13 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
|
||||||
// Convert the call's action to the libseccomp equivalent
|
// Convert the call's action to the libseccomp equivalent
|
||||||
callAct, err := toAction(call.Action, call.ErrnoRet)
|
callAct, err := toAction(call.Action, call.ErrnoRet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "convert action %s", call.Action)
|
return fmt.Errorf("convert action %s: %w", call.Action, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unconditional match - just add the rule
|
// Unconditional match - just add the rule
|
||||||
if len(call.Args) == 0 {
|
if len(call.Args) == 0 {
|
||||||
if err = filter.AddRule(callNum, callAct); err != nil {
|
if err = filter.AddRule(callNum, callAct); err != nil {
|
||||||
return errors.Wrapf(err, "add seccomp filter rule for syscall %s", call.Name)
|
return fmt.Errorf("add seccomp filter rule for syscall %s: %w", call.Name, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Linux system calls can have at most 6 arguments
|
// Linux system calls can have at most 6 arguments
|
||||||
|
@ -127,10 +128,10 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
|
||||||
for _, cond := range call.Args {
|
for _, cond := range call.Args {
|
||||||
newCond, err := toCondition(cond)
|
newCond, err := toCondition(cond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "create seccomp syscall condition for syscall %s", call.Name)
|
return fmt.Errorf("create seccomp syscall condition for syscall %s: %w", call.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
argCounts[cond.Index] += 1
|
argCounts[cond.Index]++
|
||||||
|
|
||||||
conditions = append(conditions, newCond)
|
conditions = append(conditions, newCond)
|
||||||
}
|
}
|
||||||
|
@ -150,13 +151,13 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
|
||||||
condArr := []libseccomp.ScmpCondition{cond}
|
condArr := []libseccomp.ScmpCondition{cond}
|
||||||
|
|
||||||
if err = filter.AddRuleConditional(callNum, callAct, condArr); err != nil {
|
if err = filter.AddRuleConditional(callNum, callAct, condArr); err != nil {
|
||||||
return errors.Wrapf(err, "add seccomp rule for syscall %s", call.Name)
|
return fmt.Errorf("add seccomp rule for syscall %s: %w", call.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if err = filter.AddRuleConditional(callNum, callAct, conditions); err != nil {
|
} else if err = filter.AddRuleConditional(callNum, callAct, conditions); err != nil {
|
||||||
// No conditions share same argument
|
// No conditions share same argument
|
||||||
// Use new, proper behavior
|
// Use new, proper behavior
|
||||||
return errors.Wrapf(err, "add seccomp rule for syscall %s", call.Name)
|
return fmt.Errorf("add seccomp rule for syscall %s: %w", call.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +169,8 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
|
||||||
func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
|
func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
|
||||||
switch act {
|
switch act {
|
||||||
case ActKill:
|
case ActKill:
|
||||||
return libseccomp.ActKill, nil
|
// lint was not passing until this was changed from ActKill to ActKilThread.
|
||||||
|
return libseccomp.ActKillThread, nil
|
||||||
case ActKillProcess:
|
case ActKillProcess:
|
||||||
return libseccomp.ActKillProcess, nil
|
return libseccomp.ActKillProcess, nil
|
||||||
case ActErrno:
|
case ActErrno:
|
||||||
|
@ -188,7 +190,7 @@ func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
|
||||||
case ActLog:
|
case ActLog:
|
||||||
return libseccomp.ActLog, nil
|
return libseccomp.ActLog, nil
|
||||||
default:
|
default:
|
||||||
return libseccomp.ActInvalid, errors.Errorf("invalid action %s", act)
|
return libseccomp.ActInvalid, fmt.Errorf("invalid action %s", act)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,14 +203,14 @@ func toCondition(arg *Arg) (cond libseccomp.ScmpCondition, err error) {
|
||||||
|
|
||||||
op, err := toCompareOp(arg.Op)
|
op, err := toCompareOp(arg.Op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cond, errors.Wrap(err, "convert compare operator")
|
return cond, fmt.Errorf("convert compare operator: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
condition, err := libseccomp.MakeCondition(
|
condition, err := libseccomp.MakeCondition(
|
||||||
arg.Index, op, arg.Value, arg.ValueTwo,
|
arg.Index, op, arg.Value, arg.ValueTwo,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cond, errors.Wrap(err, "make condition")
|
return cond, fmt.Errorf("make condition: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return condition, nil
|
return condition, nil
|
||||||
|
@ -233,6 +235,6 @@ func toCompareOp(op Operator) (libseccomp.ScmpCompareOp, error) {
|
||||||
case OpMaskedEqual:
|
case OpMaskedEqual:
|
||||||
return libseccomp.CompareMaskedEqual, nil
|
return libseccomp.CompareMaskedEqual, nil
|
||||||
default:
|
default:
|
||||||
return libseccomp.CompareInvalid, errors.Errorf("invalid operator %s", op)
|
return libseccomp.CompareInvalid, fmt.Errorf("invalid operator %s", op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,9 +55,16 @@
|
||||||
{
|
{
|
||||||
"names": [
|
"names": [
|
||||||
"bdflush",
|
"bdflush",
|
||||||
|
"cachestat",
|
||||||
|
"futex_requeue",
|
||||||
|
"futex_wait",
|
||||||
|
"futex_waitv",
|
||||||
|
"futex_wake",
|
||||||
"io_pgetevents",
|
"io_pgetevents",
|
||||||
|
"io_pgetevents_time64",
|
||||||
"kexec_file_load",
|
"kexec_file_load",
|
||||||
"kexec_load",
|
"kexec_load",
|
||||||
|
"map_shadow_stack",
|
||||||
"migrate_pages",
|
"migrate_pages",
|
||||||
"move_pages",
|
"move_pages",
|
||||||
"nfsservctl",
|
"nfsservctl",
|
||||||
|
@ -72,9 +79,9 @@
|
||||||
"pciconfig_write",
|
"pciconfig_write",
|
||||||
"sgetmask",
|
"sgetmask",
|
||||||
"ssetmask",
|
"ssetmask",
|
||||||
"swapcontext",
|
|
||||||
"swapoff",
|
"swapoff",
|
||||||
"swapon",
|
"swapon",
|
||||||
|
"syscall",
|
||||||
"sysfs",
|
"sysfs",
|
||||||
"uselib",
|
"uselib",
|
||||||
"userfaultfd",
|
"userfaultfd",
|
||||||
|
@ -149,6 +156,7 @@
|
||||||
"fchdir",
|
"fchdir",
|
||||||
"fchmod",
|
"fchmod",
|
||||||
"fchmodat",
|
"fchmodat",
|
||||||
|
"fchmodat2",
|
||||||
"fchown",
|
"fchown",
|
||||||
"fchown32",
|
"fchown32",
|
||||||
"fchownat",
|
"fchownat",
|
||||||
|
@ -228,6 +236,9 @@
|
||||||
"ipc",
|
"ipc",
|
||||||
"keyctl",
|
"keyctl",
|
||||||
"kill",
|
"kill",
|
||||||
|
"landlock_add_rule",
|
||||||
|
"landlock_create_ruleset",
|
||||||
|
"landlock_restrict_self",
|
||||||
"lchown",
|
"lchown",
|
||||||
"lchown32",
|
"lchown32",
|
||||||
"lgetxattr",
|
"lgetxattr",
|
||||||
|
@ -313,7 +324,6 @@
|
||||||
"pwritev2",
|
"pwritev2",
|
||||||
"read",
|
"read",
|
||||||
"readahead",
|
"readahead",
|
||||||
"readdir",
|
|
||||||
"readlink",
|
"readlink",
|
||||||
"readlinkat",
|
"readlinkat",
|
||||||
"readv",
|
"readv",
|
||||||
|
@ -401,15 +411,12 @@
|
||||||
"shmdt",
|
"shmdt",
|
||||||
"shmget",
|
"shmget",
|
||||||
"shutdown",
|
"shutdown",
|
||||||
"sigaction",
|
|
||||||
"sigaltstack",
|
"sigaltstack",
|
||||||
"signal",
|
"signal",
|
||||||
"signalfd",
|
"signalfd",
|
||||||
"signalfd4",
|
"signalfd4",
|
||||||
"sigpending",
|
|
||||||
"sigprocmask",
|
"sigprocmask",
|
||||||
"sigreturn",
|
"sigreturn",
|
||||||
"sigsuspend",
|
|
||||||
"socketcall",
|
"socketcall",
|
||||||
"socketpair",
|
"socketpair",
|
||||||
"splice",
|
"splice",
|
||||||
|
@ -423,7 +430,6 @@
|
||||||
"sync",
|
"sync",
|
||||||
"sync_file_range",
|
"sync_file_range",
|
||||||
"syncfs",
|
"syncfs",
|
||||||
"syscall",
|
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"syslog",
|
"syslog",
|
||||||
"tee",
|
"tee",
|
||||||
|
@ -436,7 +442,6 @@
|
||||||
"timer_gettime64",
|
"timer_gettime64",
|
||||||
"timer_settime",
|
"timer_settime",
|
||||||
"timer_settime64",
|
"timer_settime64",
|
||||||
"timerfd",
|
|
||||||
"timerfd_create",
|
"timerfd_create",
|
||||||
"timerfd_gettime",
|
"timerfd_gettime",
|
||||||
"timerfd_gettime64",
|
"timerfd_gettime64",
|
||||||
|
@ -558,7 +563,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"names": [
|
"names": [
|
||||||
"sync_file_range2"
|
"sync_file_range2",
|
||||||
|
"swapcontext"
|
||||||
],
|
],
|
||||||
"action": "SCMP_ACT_ALLOW",
|
"action": "SCMP_ACT_ALLOW",
|
||||||
"args": [],
|
"args": [],
|
||||||
|
@ -638,6 +644,20 @@
|
||||||
},
|
},
|
||||||
"excludes": {}
|
"excludes": {}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"riscv_flush_icache"
|
||||||
|
],
|
||||||
|
"action": "SCMP_ACT_ALLOW",
|
||||||
|
"args": [],
|
||||||
|
"comment": "",
|
||||||
|
"includes": {
|
||||||
|
"arches": [
|
||||||
|
"riscv64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"excludes": {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"names": [
|
"names": [
|
||||||
"open_by_handle_at"
|
"open_by_handle_at"
|
||||||
|
@ -673,8 +693,8 @@
|
||||||
"bpf",
|
"bpf",
|
||||||
"fanotify_init",
|
"fanotify_init",
|
||||||
"lookup_dcookie",
|
"lookup_dcookie",
|
||||||
"perf_event_open",
|
|
||||||
"quotactl",
|
"quotactl",
|
||||||
|
"quotactl_fd",
|
||||||
"setdomainname",
|
"setdomainname",
|
||||||
"sethostname",
|
"sethostname",
|
||||||
"setns"
|
"setns"
|
||||||
|
@ -691,11 +711,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"names": [
|
"names": [
|
||||||
"bpf",
|
|
||||||
"fanotify_init",
|
"fanotify_init",
|
||||||
"lookup_dcookie",
|
"lookup_dcookie",
|
||||||
"perf_event_open",
|
"perf_event_open",
|
||||||
"quotactl",
|
"quotactl",
|
||||||
|
"quotactl_fd",
|
||||||
"setdomainname",
|
"setdomainname",
|
||||||
"sethostname",
|
"sethostname",
|
||||||
"setns"
|
"setns"
|
||||||
|
@ -1043,6 +1063,68 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"excludes": {}
|
"excludes": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"bpf"
|
||||||
|
],
|
||||||
|
"action": "SCMP_ACT_ERRNO",
|
||||||
|
"args": [],
|
||||||
|
"comment": "",
|
||||||
|
"includes": {},
|
||||||
|
"excludes": {
|
||||||
|
"caps": [
|
||||||
|
"CAP_SYS_ADMIN",
|
||||||
|
"CAP_BPF"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"errnoRet": 1,
|
||||||
|
"errno": "EPERM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"bpf"
|
||||||
|
],
|
||||||
|
"action": "SCMP_ACT_ALLOW",
|
||||||
|
"args": [],
|
||||||
|
"comment": "",
|
||||||
|
"includes": {
|
||||||
|
"caps": [
|
||||||
|
"CAP_BPF"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"excludes": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"perf_event_open"
|
||||||
|
],
|
||||||
|
"action": "SCMP_ACT_ERRNO",
|
||||||
|
"args": [],
|
||||||
|
"comment": "",
|
||||||
|
"includes": {},
|
||||||
|
"excludes": {
|
||||||
|
"caps": [
|
||||||
|
"CAP_SYS_ADMIN",
|
||||||
|
"CAP_BPF"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"errnoRet": 1,
|
||||||
|
"errno": "EPERM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"perf_event_open"
|
||||||
|
],
|
||||||
|
"action": "SCMP_ACT_ALLOW",
|
||||||
|
"args": [],
|
||||||
|
"comment": "",
|
||||||
|
"includes": {
|
||||||
|
"caps": [
|
||||||
|
"CAP_PERFMON"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"excludes": {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build seccomp
|
//go:build seccomp
|
||||||
// +build seccomp
|
|
||||||
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build !linux || !seccomp
|
//go:build !linux || !seccomp
|
||||||
// +build !linux !seccomp
|
|
||||||
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
@ -15,12 +14,12 @@ import (
|
||||||
|
|
||||||
var errNotSupported = errors.New("seccomp not enabled in this build")
|
var errNotSupported = errors.New("seccomp not enabled in this build")
|
||||||
|
|
||||||
// LoadProfile returns an error on unsuppored systems
|
// LoadProfile returns an error on unsupported systems
|
||||||
func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||||
return nil, errNotSupported
|
return nil, errNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultProfile returns an error on unsuppored systems
|
// GetDefaultProfile returns an error on unsupported systems
|
||||||
func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) {
|
||||||
return nil, errNotSupported
|
return nil, errNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build linux && seccomp
|
//go:build linux && seccomp
|
||||||
// +build linux,seccomp
|
|
||||||
|
|
||||||
package seccomp
|
package seccomp
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ const (
|
||||||
ActTrace Action = "SCMP_ACT_TRACE"
|
ActTrace Action = "SCMP_ACT_TRACE"
|
||||||
ActAllow Action = "SCMP_ACT_ALLOW"
|
ActAllow Action = "SCMP_ACT_ALLOW"
|
||||||
ActLog Action = "SCMP_ACT_LOG"
|
ActLog Action = "SCMP_ACT_LOG"
|
||||||
|
ActNotify Action = "SCMP_ACT_NOTIFY"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operator used to match syscall arguments in Seccomp
|
// Operator used to match syscall arguments in Seccomp
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
//go:build seccomp
|
//go:build seccomp
|
||||||
// +build seccomp
|
|
||||||
|
|
||||||
package seccomp
|
package seccomp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateProfile does a basic validation for the provided seccomp profile
|
// ValidateProfile does a basic validation for the provided seccomp profile
|
||||||
|
@ -14,16 +12,16 @@ import (
|
||||||
func ValidateProfile(content string) error {
|
func ValidateProfile(content string) error {
|
||||||
profile := &Seccomp{}
|
profile := &Seccomp{}
|
||||||
if err := json.Unmarshal([]byte(content), &profile); err != nil {
|
if err := json.Unmarshal([]byte(content), &profile); err != nil {
|
||||||
return errors.Wrap(err, "decoding seccomp profile")
|
return fmt.Errorf("decoding seccomp profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := setupSeccomp(profile, nil)
|
spec, err := setupSeccomp(profile, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "create seccomp spec")
|
return fmt.Errorf("create seccomp spec: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := BuildFilter(spec); err != nil {
|
if _, err := BuildFilter(spec); err != nil {
|
||||||
return errors.Wrap(err, "build seccomp filter")
|
return fmt.Errorf("build seccomp filter: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,19 @@
|
||||||
|
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.
|
38
vendor/github.com/containers/storage/pkg/fileutils/exists_freebsd.go
generated
vendored
Normal file
38
vendor/github.com/containers/storage/pkg/fileutils/exists_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exists checks whether a file or directory exists at the given path.
|
||||||
|
// If the path is a symlink, the symlink is followed.
|
||||||
|
func Exists(path string) error {
|
||||||
|
// It uses unix.Faccessat which is a faster operation compared to os.Stat for
|
||||||
|
// simply checking the existence of a file.
|
||||||
|
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, 0)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lexists checks whether a file or directory exists at the given path.
|
||||||
|
// If the path is a symlink, the symlink itself is checked.
|
||||||
|
func Lexists(path string) error {
|
||||||
|
// FreeBSD before 15.0 does not support the AT_SYMLINK_NOFOLLOW flag for
|
||||||
|
// faccessat. In this case, the call to faccessat will return EINVAL and
|
||||||
|
// we fall back to using Lstat.
|
||||||
|
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, syscall.EINVAL) {
|
||||||
|
_, err = os.Lstat(path)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build !windows && !freebsd
|
||||||
|
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exists checks whether a file or directory exists at the given path.
|
||||||
|
// If the path is a symlink, the symlink is followed.
|
||||||
|
func Exists(path string) error {
|
||||||
|
// It uses unix.Faccessat which is a faster operation compared to os.Stat for
|
||||||
|
// simply checking the existence of a file.
|
||||||
|
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, unix.AT_EACCESS)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lexists checks whether a file or directory exists at the given path.
|
||||||
|
// If the path is a symlink, the symlink itself is checked.
|
||||||
|
func Lexists(path string) error {
|
||||||
|
// It uses unix.Faccessat which is a faster operation compared to os.Stat for
|
||||||
|
// simply checking the existence of a file.
|
||||||
|
err := unix.Faccessat(unix.AT_FDCWD, path, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW|unix.AT_EACCESS)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "faccessat", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
18
vendor/github.com/containers/storage/pkg/fileutils/exists_windows.go
generated
vendored
Normal file
18
vendor/github.com/containers/storage/pkg/fileutils/exists_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exists checks whether a file or directory exists at the given path.
|
||||||
|
func Exists(path string) error {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lexists checks whether a file or directory exists at the given path, without
|
||||||
|
// resolving symlinks
|
||||||
|
func Lexists(path string) error {
|
||||||
|
_, err := os.Lstat(path)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,371 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PatternMatcher allows checking paths against a list of patterns
|
||||||
|
type PatternMatcher struct {
|
||||||
|
patterns []*Pattern
|
||||||
|
exclusions bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPatternMatcher creates a new matcher object for specific patterns that can
|
||||||
|
// be used later to match against patterns against paths
|
||||||
|
func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
|
||||||
|
pm := &PatternMatcher{
|
||||||
|
patterns: make([]*Pattern, 0, len(patterns)),
|
||||||
|
}
|
||||||
|
for _, p := range patterns {
|
||||||
|
// Eliminate leading and trailing whitespace.
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p = filepath.Clean(p)
|
||||||
|
newp := &Pattern{}
|
||||||
|
if p[0] == '!' {
|
||||||
|
if len(p) == 1 {
|
||||||
|
return nil, errors.New("illegal exclusion pattern: \"!\"")
|
||||||
|
}
|
||||||
|
newp.exclusion = true
|
||||||
|
p = strings.TrimPrefix(filepath.Clean(p[1:]), "/")
|
||||||
|
pm.exclusions = true
|
||||||
|
}
|
||||||
|
// Do some syntax checking on the pattern.
|
||||||
|
// filepath's Match() has some really weird rules that are inconsistent
|
||||||
|
// so instead of trying to dup their logic, just call Match() for its
|
||||||
|
// error state and if there is an error in the pattern return it.
|
||||||
|
// If this becomes an issue we can remove this since its really only
|
||||||
|
// needed in the error (syntax) case - which isn't really critical.
|
||||||
|
if _, err := filepath.Match(p, "."); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newp.cleanedPattern = p
|
||||||
|
newp.dirs = strings.Split(p, string(os.PathSeparator))
|
||||||
|
pm.patterns = append(pm.patterns, newp)
|
||||||
|
}
|
||||||
|
return pm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Please use the `MatchesResult` method instead.
|
||||||
|
// Matches matches path against all the patterns. Matches is not safe to be
|
||||||
|
// called concurrently
|
||||||
|
func (pm *PatternMatcher) Matches(file string) (bool, error) {
|
||||||
|
matched := false
|
||||||
|
file = filepath.FromSlash(file)
|
||||||
|
|
||||||
|
for _, pattern := range pm.patterns {
|
||||||
|
negative := false
|
||||||
|
|
||||||
|
if pattern.exclusion {
|
||||||
|
negative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
match, err := pattern.match(file)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
matched = !negative
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
logrus.Debugf("Skipping excluded path: %s", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatchResult struct {
|
||||||
|
isMatched bool
|
||||||
|
matches, excludes uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excludes returns true if the overall result is matched
|
||||||
|
func (m *MatchResult) IsMatched() bool {
|
||||||
|
return m.isMatched
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excludes returns the amount of matches of an MatchResult
|
||||||
|
func (m *MatchResult) Matches() uint {
|
||||||
|
return m.matches
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excludes returns the amount of excludes of an MatchResult
|
||||||
|
func (m *MatchResult) Excludes() uint {
|
||||||
|
return m.excludes
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchesResult verifies the provided filepath against all patterns.
|
||||||
|
// It returns the `*MatchResult` result for the patterns on success, otherwise
|
||||||
|
// an error. This method is not safe to be called concurrently.
|
||||||
|
func (pm *PatternMatcher) MatchesResult(file string) (res *MatchResult, err error) {
|
||||||
|
file = filepath.FromSlash(file)
|
||||||
|
res = &MatchResult{false, 0, 0}
|
||||||
|
|
||||||
|
for _, pattern := range pm.patterns {
|
||||||
|
negative := false
|
||||||
|
|
||||||
|
if pattern.exclusion {
|
||||||
|
negative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
match, err := pattern.match(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
res.isMatched = !negative
|
||||||
|
if negative {
|
||||||
|
res.excludes++
|
||||||
|
} else {
|
||||||
|
res.matches++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.matches > 0 {
|
||||||
|
logrus.Debugf("Skipping excluded path: %s", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMatch verifies the provided filepath against all patterns and returns true
|
||||||
|
// if it matches. A match is valid if the last match is a positive one.
|
||||||
|
// It returns an error on failure and is not safe to be called concurrently.
|
||||||
|
func (pm *PatternMatcher) IsMatch(file string) (matched bool, err error) {
|
||||||
|
res, err := pm.MatchesResult(file)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return res.isMatched, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclusions returns true if any of the patterns define exclusions
|
||||||
|
func (pm *PatternMatcher) Exclusions() bool {
|
||||||
|
return pm.exclusions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patterns returns array of active patterns
|
||||||
|
func (pm *PatternMatcher) Patterns() []*Pattern {
|
||||||
|
return pm.patterns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern defines a single regexp used to filter file paths.
|
||||||
|
type Pattern struct {
|
||||||
|
cleanedPattern string
|
||||||
|
dirs []string
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
exclusion bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pattern) String() string {
|
||||||
|
return p.cleanedPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclusion returns true if this pattern defines exclusion
|
||||||
|
func (p *Pattern) Exclusion() bool {
|
||||||
|
return p.exclusion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pattern) match(path string) (bool, error) {
|
||||||
|
if p.regexp == nil {
|
||||||
|
if err := p.compile(); err != nil {
|
||||||
|
return false, filepath.ErrBadPattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b := p.regexp.MatchString(path)
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pattern) compile() error {
|
||||||
|
regStr := "^"
|
||||||
|
pattern := p.cleanedPattern
|
||||||
|
// Go through the pattern and convert it to a regexp.
|
||||||
|
// We use a scanner so we can support utf-8 chars.
|
||||||
|
var scan scanner.Scanner
|
||||||
|
scan.Init(strings.NewReader(pattern))
|
||||||
|
|
||||||
|
sl := string(os.PathSeparator)
|
||||||
|
escSL := sl
|
||||||
|
const bs = `\`
|
||||||
|
if sl == bs {
|
||||||
|
escSL += bs
|
||||||
|
}
|
||||||
|
|
||||||
|
for scan.Peek() != scanner.EOF {
|
||||||
|
ch := scan.Next()
|
||||||
|
|
||||||
|
if ch == '*' {
|
||||||
|
if scan.Peek() == '*' {
|
||||||
|
// is some flavor of "**"
|
||||||
|
scan.Next()
|
||||||
|
|
||||||
|
// Treat **/ as ** so eat the "/"
|
||||||
|
if string(scan.Peek()) == sl {
|
||||||
|
scan.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if scan.Peek() == scanner.EOF {
|
||||||
|
// is "**EOF" - to align with .gitignore just accept all
|
||||||
|
regStr += ".*"
|
||||||
|
} else {
|
||||||
|
// is "**"
|
||||||
|
// Note that this allows for any # of /'s (even 0) because
|
||||||
|
// the .* will eat everything, even /'s
|
||||||
|
regStr += "(.*" + escSL + ")?"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// is "*" so map it to anything but "/"
|
||||||
|
regStr += "[^" + escSL + "]*"
|
||||||
|
}
|
||||||
|
} else if ch == '?' {
|
||||||
|
// "?" is any char except "/"
|
||||||
|
regStr += "[^" + escSL + "]"
|
||||||
|
} else if ch == '.' || ch == '$' {
|
||||||
|
// Escape some regexp special chars that have no meaning
|
||||||
|
// in golang's filepath.Match
|
||||||
|
regStr += bs + string(ch)
|
||||||
|
} else if ch == '\\' {
|
||||||
|
// escape next char.
|
||||||
|
if sl == bs {
|
||||||
|
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||||
|
// and then just continue because filepath.Match on
|
||||||
|
// Windows doesn't allow escaping at all
|
||||||
|
regStr += escSL
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if scan.Peek() != scanner.EOF {
|
||||||
|
regStr += bs + string(scan.Next())
|
||||||
|
} else {
|
||||||
|
return filepath.ErrBadPattern
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
regStr += string(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regStr += "(" + escSL + ".*)?$"
|
||||||
|
|
||||||
|
re, err := regexp.Compile(regStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.regexp = re
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches returns true if file matches any of the patterns
|
||||||
|
// and isn't excluded by any of the subsequent patterns.
|
||||||
|
func Matches(file string, patterns []string) (bool, error) {
|
||||||
|
pm, err := NewPatternMatcher(patterns)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
file = filepath.Clean(file)
|
||||||
|
|
||||||
|
if file == "." {
|
||||||
|
// Don't let them exclude everything, kind of silly.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return pm.IsMatch(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFile copies from src to dst until either EOF is reached
|
||||||
|
// on src or an error occurs. It verifies src exists and removes
|
||||||
|
// the dst if it exists.
|
||||||
|
func CopyFile(src, dst string) (int64, error) {
|
||||||
|
cleanSrc := filepath.Clean(src)
|
||||||
|
cleanDst := filepath.Clean(dst)
|
||||||
|
if cleanSrc == cleanDst {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
sf, err := os.Open(cleanSrc)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer sf.Close()
|
||||||
|
if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
df, err := os.Create(cleanDst)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer df.Close()
|
||||||
|
return io.Copy(df, sf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSymlinkedDirectory returns the target directory of a symlink.
|
||||||
|
// The target of the symbolic link may not be a file.
|
||||||
|
func ReadSymlinkedDirectory(path string) (string, error) {
|
||||||
|
var realPath string
|
||||||
|
var err error
|
||||||
|
if realPath, err = filepath.Abs(path); err != nil {
|
||||||
|
return "", fmt.Errorf("unable to get absolute path for %s: %w", path, err)
|
||||||
|
}
|
||||||
|
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to canonicalise path for %s: %w", path, err)
|
||||||
|
}
|
||||||
|
realPathInfo, err := os.Stat(realPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to stat target '%s' of '%s': %w", realPath, path, err)
|
||||||
|
}
|
||||||
|
if !realPathInfo.Mode().IsDir() {
|
||||||
|
return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
|
||||||
|
}
|
||||||
|
return realPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSymlinkedPath returns the target directory of a symlink.
|
||||||
|
// The target of the symbolic link can be a file and a directory.
|
||||||
|
func ReadSymlinkedPath(path string) (realPath string, err error) {
|
||||||
|
if realPath, err = filepath.Abs(path); err != nil {
|
||||||
|
return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err)
|
||||||
|
}
|
||||||
|
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err)
|
||||||
|
}
|
||||||
|
if err := Exists(realPath); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to stat target %q of %q: %w", realPath, path, err)
|
||||||
|
}
|
||||||
|
return realPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateIfNotExists creates a file or a directory only if it does not already exist.
|
||||||
|
func CreateIfNotExists(path string, isDir bool) error {
|
||||||
|
if err := Exists(path); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if isDir {
|
||||||
|
return os.MkdirAll(path, 0o755)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(path, os.O_CREATE, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
27
vendor/github.com/containers/storage/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
27
vendor/github.com/containers/storage/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTotalUsedFds returns the number of used File Descriptors by
|
||||||
|
// executing `lsof -p PID`
|
||||||
|
func GetTotalUsedFds() int {
|
||||||
|
pid := os.Getpid()
|
||||||
|
|
||||||
|
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid))
|
||||||
|
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStr := strings.TrimSpace(string(output))
|
||||||
|
|
||||||
|
fds := strings.Split(outputStr, "\n")
|
||||||
|
|
||||||
|
return len(fds) - 1
|
||||||
|
}
|
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
// GetTotalUsedFds Returns the number of used File Descriptors.
|
||||||
|
// On Solaris these limits are per process and not systemwide
|
||||||
|
func GetTotalUsedFds() int {
|
||||||
|
return -1
|
||||||
|
}
|
21
vendor/github.com/containers/storage/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
21
vendor/github.com/containers/storage/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//go:build linux || freebsd
|
||||||
|
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTotalUsedFds Returns the number of used File Descriptors by
|
||||||
|
// reading it via /proc filesystem.
|
||||||
|
func GetTotalUsedFds() int {
|
||||||
|
if fds, err := os.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
|
||||||
|
logrus.Errorf("%v", err)
|
||||||
|
} else {
|
||||||
|
return len(fds)
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_windows.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/fileutils/fileutils_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
// GetTotalUsedFds Returns the number of used File Descriptors. Not supported
|
||||||
|
// on Windows.
|
||||||
|
func GetTotalUsedFds() int {
|
||||||
|
return -1
|
||||||
|
}
|
20
vendor/github.com/containers/storage/pkg/fileutils/reflink_linux.go
generated
vendored
Normal file
20
vendor/github.com/containers/storage/pkg/fileutils/reflink_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReflinkOrCopy attempts to reflink the source to the destination fd.
|
||||||
|
// If reflinking fails or is unsupported, it falls back to io.Copy().
|
||||||
|
func ReflinkOrCopy(src, dst *os.File) error {
|
||||||
|
err := unix.IoctlFileClone(int(dst.Fd()), int(src.Fd()))
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(dst, src)
|
||||||
|
return err
|
||||||
|
}
|
15
vendor/github.com/containers/storage/pkg/fileutils/reflink_unsupported.go
generated
vendored
Normal file
15
vendor/github.com/containers/storage/pkg/fileutils/reflink_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReflinkOrCopy attempts to reflink the source to the destination fd.
|
||||||
|
// If reflinking fails or is unsupported, it falls back to io.Copy().
|
||||||
|
func ReflinkOrCopy(src, dst *os.File) error {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,620 @@
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/system"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IDMap contains a single entry for user namespace range remapping. An array
|
||||||
|
// of IDMap entries represents the structure that will be provided to the Linux
|
||||||
|
// kernel for creating a user namespace.
|
||||||
|
type IDMap struct {
|
||||||
|
ContainerID int `json:"container_id"`
|
||||||
|
HostID int `json:"host_id"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type subIDRange struct {
|
||||||
|
Start int
|
||||||
|
Length int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ranges []subIDRange
|
||||||
|
|
||||||
|
func (e ranges) Len() int { return len(e) }
|
||||||
|
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||||
|
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
|
||||||
|
|
||||||
|
const (
|
||||||
|
subuidFileName string = "/etc/subuid"
|
||||||
|
subgidFileName string = "/etc/subgid"
|
||||||
|
ContainersOverrideXattr = "user.containers.override_stat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MkdirAllAs creates a directory (include any along the path) and then modifies
|
||||||
|
// ownership to the requested uid/gid. If the directory already exists, this
|
||||||
|
// function will still change ownership to the requested uid/gid pair.
|
||||||
|
// Deprecated: Use MkdirAllAndChown
|
||||||
|
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||||
|
return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
|
||||||
|
// If the directory already exists, this function still changes ownership
|
||||||
|
// Deprecated: Use MkdirAndChown with a IDPair
|
||||||
|
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||||
|
return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
|
||||||
|
// ownership to the requested uid/gid. If the directory already exists, this
|
||||||
|
// function will still change ownership to the requested uid/gid pair.
|
||||||
|
func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error {
|
||||||
|
return mkdirAs(path, mode, ids.UID, ids.GID, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
|
||||||
|
// If the directory already exists, this function still changes ownership
|
||||||
|
func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error {
|
||||||
|
return mkdirAs(path, mode, ids.UID, ids.GID, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
|
||||||
|
// ownership ONLY of newly created directories to the requested uid/gid. If the
|
||||||
|
// directories along the path exist, no change of ownership will be performed
|
||||||
|
func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error {
|
||||||
|
return mkdirAs(path, mode, ids.UID, ids.GID, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
|
||||||
|
// If the maps are empty, then the root uid/gid will default to "real" 0/0
|
||||||
|
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
|
||||||
|
var uid, gid int
|
||||||
|
var err error
|
||||||
|
if len(uidMap) == 1 && uidMap[0].Size == 1 {
|
||||||
|
uid = uidMap[0].HostID
|
||||||
|
} else {
|
||||||
|
uid, err = RawToHost(0, uidMap)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(gidMap) == 1 && gidMap[0].Size == 1 {
|
||||||
|
gid = gidMap[0].HostID
|
||||||
|
} else {
|
||||||
|
gid, err = RawToHost(0, gidMap)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawToContainer takes an id mapping, and uses it to translate a host ID to
|
||||||
|
// the remapped ID. If no map is provided, then the translation assumes a
|
||||||
|
// 1-to-1 mapping and returns the passed in id.
|
||||||
|
//
|
||||||
|
// If you wish to map a (uid,gid) combination you should use the corresponding
|
||||||
|
// IDMappings methods, which ensure that you are mapping the correct ID against
|
||||||
|
// the correct mapping.
|
||||||
|
func RawToContainer(hostID int, idMap []IDMap) (int, error) {
|
||||||
|
if idMap == nil {
|
||||||
|
return hostID, nil
|
||||||
|
}
|
||||||
|
for _, m := range idMap {
|
||||||
|
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
|
||||||
|
contID := m.ContainerID + (hostID - m.HostID)
|
||||||
|
return contID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("host ID %d cannot be mapped to a container ID", hostID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawToHost takes an id mapping and a remapped ID, and translates the ID to
|
||||||
|
// the mapped host ID. If no map is provided, then the translation assumes a
|
||||||
|
// 1-to-1 mapping and returns the passed in id.
|
||||||
|
//
|
||||||
|
// If you wish to map a (uid,gid) combination you should use the corresponding
|
||||||
|
// IDMappings methods, which ensure that you are mapping the correct ID against
|
||||||
|
// the correct mapping.
|
||||||
|
func RawToHost(contID int, idMap []IDMap) (int, error) {
|
||||||
|
if idMap == nil {
|
||||||
|
return contID, nil
|
||||||
|
}
|
||||||
|
for _, m := range idMap {
|
||||||
|
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
|
||||||
|
hostID := m.HostID + (contID - m.ContainerID)
|
||||||
|
return hostID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDPair is a UID and GID pair
|
||||||
|
type IDPair struct {
|
||||||
|
UID int
|
||||||
|
GID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDMappings contains a mappings of UIDs and GIDs
|
||||||
|
type IDMappings struct {
|
||||||
|
uids []IDMap
|
||||||
|
gids []IDMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIDMappings takes a requested user and group name and
|
||||||
|
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||||
|
// proper uid and gid remapping ranges for that user/group pair
|
||||||
|
func NewIDMappings(username, groupname string) (*IDMappings, error) {
|
||||||
|
subuidRanges, err := readSubuid(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subgidRanges, err := readSubgid(groupname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(subuidRanges) == 0 {
|
||||||
|
return nil, fmt.Errorf("no subuid ranges found for user %q in %s", username, subuidFileName)
|
||||||
|
}
|
||||||
|
if len(subgidRanges) == 0 {
|
||||||
|
return nil, fmt.Errorf("no subgid ranges found for group %q in %s", groupname, subgidFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IDMappings{
|
||||||
|
uids: createIDMap(subuidRanges),
|
||||||
|
gids: createIDMap(subgidRanges),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIDMappingsFromMaps creates a new mapping from two slices
|
||||||
|
// Deprecated: this is a temporary shim while transitioning to IDMapping
|
||||||
|
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings {
|
||||||
|
return &IDMappings{uids: uids, gids: gids}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootPair returns a uid and gid pair for the root user. The error is ignored
|
||||||
|
// because a root user always exists, and the defaults are correct when the uid
|
||||||
|
// and gid maps are empty.
|
||||||
|
func (i *IDMappings) RootPair() IDPair {
|
||||||
|
uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
|
||||||
|
return IDPair{UID: uid, GID: gid}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHost returns the host UID and GID for the container uid, gid.
|
||||||
|
func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) {
|
||||||
|
var err error
|
||||||
|
var target IDPair
|
||||||
|
|
||||||
|
target.UID, err = RawToHost(pair.UID, i.uids)
|
||||||
|
if err != nil {
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
|
|
||||||
|
target.GID, err = RawToHost(pair.GID, i.gids)
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
overflowUIDOnce sync.Once
|
||||||
|
overflowGIDOnce sync.Once
|
||||||
|
overflowUID int
|
||||||
|
overflowGID int
|
||||||
|
)
|
||||||
|
|
||||||
|
// getOverflowUID returns the UID mapped to the overflow user
|
||||||
|
func getOverflowUID() int {
|
||||||
|
overflowUIDOnce.Do(func() {
|
||||||
|
// 65534 is the value on older kernels where /proc/sys/kernel/overflowuid is not present
|
||||||
|
overflowUID = 65534
|
||||||
|
if content, err := os.ReadFile("/proc/sys/kernel/overflowuid"); err == nil {
|
||||||
|
if tmp, err := strconv.Atoi(string(content)); err == nil {
|
||||||
|
overflowUID = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return overflowUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOverflowGID returns the GID mapped to the overflow user
|
||||||
|
func getOverflowGID() int {
|
||||||
|
overflowGIDOnce.Do(func() {
|
||||||
|
// 65534 is the value on older kernels where /proc/sys/kernel/overflowgid is not present
|
||||||
|
overflowGID = 65534
|
||||||
|
if content, err := os.ReadFile("/proc/sys/kernel/overflowgid"); err == nil {
|
||||||
|
if tmp, err := strconv.Atoi(string(content)); err == nil {
|
||||||
|
overflowGID = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return overflowGID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHost returns the host UID and GID for the container uid, gid.
|
||||||
|
// Remapping is only performed if the ids aren't already the remapped root ids
|
||||||
|
// If the mapping is not possible because the target ID is not mapped into
|
||||||
|
// the namespace, then the overflow ID is used.
|
||||||
|
func (i *IDMappings) ToHostOverflow(pair IDPair) (IDPair, error) {
|
||||||
|
var err error
|
||||||
|
target := i.RootPair()
|
||||||
|
|
||||||
|
if pair.UID != target.UID {
|
||||||
|
target.UID, err = RawToHost(pair.UID, i.uids)
|
||||||
|
if err != nil {
|
||||||
|
target.UID = getOverflowUID()
|
||||||
|
logrus.Debugf("Failed to map UID %v to the target mapping, using the overflow ID %v", pair.UID, target.UID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pair.GID != target.GID {
|
||||||
|
target.GID, err = RawToHost(pair.GID, i.gids)
|
||||||
|
if err != nil {
|
||||||
|
target.GID = getOverflowGID()
|
||||||
|
logrus.Debugf("Failed to map GID %v to the target mapping, using the overflow ID %v", pair.GID, target.GID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToContainer returns the container UID and GID for the host uid and gid
|
||||||
|
func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) {
|
||||||
|
uid, err := RawToContainer(pair.UID, i.uids)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
gid, err := RawToContainer(pair.GID, i.gids)
|
||||||
|
return uid, gid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns true if there are no id mappings
|
||||||
|
func (i *IDMappings) Empty() bool {
|
||||||
|
return len(i.uids) == 0 && len(i.gids) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIDs return the UID mapping
|
||||||
|
// TODO: remove this once everything has been refactored to use pairs
|
||||||
|
func (i *IDMappings) UIDs() []IDMap {
|
||||||
|
return i.uids
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIDs return the UID mapping
|
||||||
|
// TODO: remove this once everything has been refactored to use pairs
|
||||||
|
func (i *IDMappings) GIDs() []IDMap {
|
||||||
|
return i.gids
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIDMap(subidRanges ranges) []IDMap {
|
||||||
|
idMap := []IDMap{}
|
||||||
|
|
||||||
|
// sort the ranges by lowest ID first
|
||||||
|
sort.Sort(subidRanges)
|
||||||
|
containerID := 0
|
||||||
|
for _, idrange := range subidRanges {
|
||||||
|
idMap = append(idMap, IDMap{
|
||||||
|
ContainerID: containerID,
|
||||||
|
HostID: idrange.Start,
|
||||||
|
Size: idrange.Length,
|
||||||
|
})
|
||||||
|
containerID = containerID + idrange.Length
|
||||||
|
}
|
||||||
|
return idMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
|
||||||
|
// and return all found ranges for a specified username. If the special value
|
||||||
|
// "ALL" is supplied for username, then all ranges in the file will be returned
|
||||||
|
func parseSubidFile(path, username string) (ranges, error) {
|
||||||
|
var (
|
||||||
|
rangeList ranges
|
||||||
|
uidstr string
|
||||||
|
)
|
||||||
|
if u, err := user.Lookup(username); err == nil {
|
||||||
|
uidstr = u.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
subidFile, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return rangeList, err
|
||||||
|
}
|
||||||
|
defer subidFile.Close()
|
||||||
|
|
||||||
|
s := bufio.NewScanner(subidFile)
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return rangeList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.TrimSpace(s.Text())
|
||||||
|
if text == "" || strings.HasPrefix(text, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Split(text, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return rangeList, fmt.Errorf("cannot parse subuid/gid information: Format not correct for %s file", path)
|
||||||
|
}
|
||||||
|
if parts[0] == username || username == "ALL" || (parts[0] == uidstr && parts[0] != "") {
|
||||||
|
startid, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return rangeList, fmt.Errorf("string to int conversion failed during subuid/gid parsing of %s: %w", path, err)
|
||||||
|
}
|
||||||
|
length, err := strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return rangeList, fmt.Errorf("string to int conversion failed during subuid/gid parsing of %s: %w", path, err)
|
||||||
|
}
|
||||||
|
rangeList = append(rangeList, subIDRange{startid, length})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rangeList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkChownErr(err error, name string, uid, gid int) error {
|
||||||
|
var e *os.PathError
|
||||||
|
if errors.As(err, &e) && e.Err == syscall.EINVAL {
|
||||||
|
return fmt.Errorf(`potentially insufficient UIDs or GIDs available in user namespace (requested %d:%d for %s): Check /etc/subuid and /etc/subgid if configured locally and run "podman system migrate": %w`, uid, gid, name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat contains file states that can be overridden with ContainersOverrideXattr.
|
||||||
|
type Stat struct {
|
||||||
|
IDs IDPair
|
||||||
|
Mode os.FileMode
|
||||||
|
Major int
|
||||||
|
Minor int
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatContainersOverrideXattr will format the given uid, gid, and mode into a string
|
||||||
|
// that can be used as the value for the ContainersOverrideXattr xattr.
|
||||||
|
func FormatContainersOverrideXattr(uid, gid, mode int) string {
|
||||||
|
return FormatContainersOverrideXattrDevice(uid, gid, fs.FileMode(mode), 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatContainersOverrideXattrDevice will format the given uid, gid, and mode into a string
|
||||||
|
// that can be used as the value for the ContainersOverrideXattr xattr. For devices, it also
|
||||||
|
// needs the major and minor numbers.
|
||||||
|
func FormatContainersOverrideXattrDevice(uid, gid int, mode fs.FileMode, major, minor int) string {
|
||||||
|
typ := ""
|
||||||
|
switch mode & os.ModeType {
|
||||||
|
case os.ModeDir:
|
||||||
|
typ = "dir"
|
||||||
|
case os.ModeSymlink:
|
||||||
|
typ = "symlink"
|
||||||
|
case os.ModeNamedPipe:
|
||||||
|
typ = "pipe"
|
||||||
|
case os.ModeSocket:
|
||||||
|
typ = "socket"
|
||||||
|
case os.ModeDevice:
|
||||||
|
typ = fmt.Sprintf("block-%d-%d", major, minor)
|
||||||
|
case os.ModeDevice | os.ModeCharDevice:
|
||||||
|
typ = fmt.Sprintf("char-%d-%d", major, minor)
|
||||||
|
default:
|
||||||
|
typ = "file"
|
||||||
|
}
|
||||||
|
unixMode := mode & os.ModePerm
|
||||||
|
if mode&os.ModeSetuid != 0 {
|
||||||
|
unixMode |= 0o4000
|
||||||
|
}
|
||||||
|
if mode&os.ModeSetgid != 0 {
|
||||||
|
unixMode |= 0o2000
|
||||||
|
}
|
||||||
|
if mode&os.ModeSticky != 0 {
|
||||||
|
unixMode |= 0o1000
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d:%d:%04o:%s", uid, gid, unixMode, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContainersOverrideXattr will get and decode ContainersOverrideXattr.
|
||||||
|
func GetContainersOverrideXattr(path string) (Stat, error) {
|
||||||
|
xstat, err := system.Lgetxattr(path, ContainersOverrideXattr)
|
||||||
|
if err != nil {
|
||||||
|
return Stat{}, err
|
||||||
|
}
|
||||||
|
return parseOverrideXattr(xstat) // This will fail if (xstat, err) == (nil, nil), i.e. the xattr does not exist.
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOverrideXattr(xstat []byte) (Stat, error) {
|
||||||
|
var stat Stat
|
||||||
|
attrs := strings.Split(string(xstat), ":")
|
||||||
|
if len(attrs) < 3 {
|
||||||
|
return stat, fmt.Errorf("the number of parts in %s is less than 3",
|
||||||
|
ContainersOverrideXattr)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseUint(attrs[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return stat, fmt.Errorf("failed to parse UID: %w", err)
|
||||||
|
}
|
||||||
|
stat.IDs.UID = int(value)
|
||||||
|
|
||||||
|
value, err = strconv.ParseUint(attrs[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return stat, fmt.Errorf("failed to parse GID: %w", err)
|
||||||
|
}
|
||||||
|
stat.IDs.GID = int(value)
|
||||||
|
|
||||||
|
value, err = strconv.ParseUint(attrs[2], 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return stat, fmt.Errorf("failed to parse mode: %w", err)
|
||||||
|
}
|
||||||
|
stat.Mode = os.FileMode(value) & os.ModePerm
|
||||||
|
if value&0o1000 != 0 {
|
||||||
|
stat.Mode |= os.ModeSticky
|
||||||
|
}
|
||||||
|
if value&0o2000 != 0 {
|
||||||
|
stat.Mode |= os.ModeSetgid
|
||||||
|
}
|
||||||
|
if value&0o4000 != 0 {
|
||||||
|
stat.Mode |= os.ModeSetuid
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attrs) > 3 {
|
||||||
|
typ := attrs[3]
|
||||||
|
if strings.HasPrefix(typ, "file") {
|
||||||
|
} else if strings.HasPrefix(typ, "dir") {
|
||||||
|
stat.Mode |= os.ModeDir
|
||||||
|
} else if strings.HasPrefix(typ, "symlink") {
|
||||||
|
stat.Mode |= os.ModeSymlink
|
||||||
|
} else if strings.HasPrefix(typ, "pipe") {
|
||||||
|
stat.Mode |= os.ModeNamedPipe
|
||||||
|
} else if strings.HasPrefix(typ, "socket") {
|
||||||
|
stat.Mode |= os.ModeSocket
|
||||||
|
} else if strings.HasPrefix(typ, "block") {
|
||||||
|
stat.Mode |= os.ModeDevice
|
||||||
|
stat.Major, stat.Minor, err = parseDevice(typ)
|
||||||
|
if err != nil {
|
||||||
|
return stat, err
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(typ, "char") {
|
||||||
|
stat.Mode |= os.ModeDevice | os.ModeCharDevice
|
||||||
|
stat.Major, stat.Minor, err = parseDevice(typ)
|
||||||
|
if err != nil {
|
||||||
|
return stat, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return stat, fmt.Errorf("invalid file type %s", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDevice(typ string) (int, int, error) {
|
||||||
|
parts := strings.Split(typ, "-")
|
||||||
|
// If there are more than 3 parts, just ignore them to be forward compatible
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return 0, 0, fmt.Errorf("invalid device type %s", typ)
|
||||||
|
}
|
||||||
|
if parts[0] != "block" && parts[0] != "char" {
|
||||||
|
return 0, 0, fmt.Errorf("invalid device type %s", typ)
|
||||||
|
}
|
||||||
|
major, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to parse major number: %w", err)
|
||||||
|
}
|
||||||
|
minor, err := strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to parse minor number: %w", err)
|
||||||
|
}
|
||||||
|
return major, minor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContainersOverrideXattr will encode and set ContainersOverrideXattr.
|
||||||
|
func SetContainersOverrideXattr(path string, stat Stat) error {
|
||||||
|
value := FormatContainersOverrideXattrDevice(stat.IDs.UID, stat.IDs.GID, stat.Mode, stat.Major, stat.Minor)
|
||||||
|
return system.Lsetxattr(path, ContainersOverrideXattr, []byte(value), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SafeChown(name string, uid, gid int) error {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
stat := Stat{
|
||||||
|
Mode: os.FileMode(0o0700),
|
||||||
|
}
|
||||||
|
xstat, err := system.Lgetxattr(name, ContainersOverrideXattr)
|
||||||
|
if err == nil && xstat != nil {
|
||||||
|
stat, err = parseOverrideXattr(xstat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
st, err := os.Stat(name) // Ideally we would share this with system.Stat below, but then we would need to convert Mode.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stat.Mode = st.Mode()
|
||||||
|
}
|
||||||
|
stat.IDs = IDPair{UID: uid, GID: gid}
|
||||||
|
if err = SetContainersOverrideXattr(name, stat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uid = os.Getuid()
|
||||||
|
gid = os.Getgid()
|
||||||
|
}
|
||||||
|
if stat, statErr := system.Stat(name); statErr == nil {
|
||||||
|
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkChownErr(os.Chown(name, uid, gid), name, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SafeLchown(name string, uid, gid int) error {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
stat := Stat{
|
||||||
|
Mode: os.FileMode(0o0700),
|
||||||
|
}
|
||||||
|
xstat, err := system.Lgetxattr(name, ContainersOverrideXattr)
|
||||||
|
if err == nil && xstat != nil {
|
||||||
|
stat, err = parseOverrideXattr(xstat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
st, err := os.Lstat(name) // Ideally we would share this with system.Stat below, but then we would need to convert Mode.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stat.Mode = st.Mode()
|
||||||
|
}
|
||||||
|
stat.IDs = IDPair{UID: uid, GID: gid}
|
||||||
|
if err = SetContainersOverrideXattr(name, stat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uid = os.Getuid()
|
||||||
|
gid = os.Getgid()
|
||||||
|
}
|
||||||
|
if stat, statErr := system.Lstat(name); statErr == nil {
|
||||||
|
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkChownErr(os.Lchown(name, uid, gid), name, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortByHostID []IDMap
|
||||||
|
|
||||||
|
func (e sortByHostID) Len() int { return len(e) }
|
||||||
|
func (e sortByHostID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||||
|
func (e sortByHostID) Less(i, j int) bool { return e[i].HostID < e[j].HostID }
|
||||||
|
|
||||||
|
type sortByContainerID []IDMap
|
||||||
|
|
||||||
|
func (e sortByContainerID) Len() int { return len(e) }
|
||||||
|
func (e sortByContainerID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||||
|
func (e sortByContainerID) Less(i, j int) bool { return e[i].ContainerID < e[j].ContainerID }
|
||||||
|
|
||||||
|
// IsContiguous checks if the specified mapping is contiguous and doesn't
|
||||||
|
// have any hole.
|
||||||
|
func IsContiguous(mappings []IDMap) bool {
|
||||||
|
if len(mappings) < 2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var mh sortByHostID = mappings[:]
|
||||||
|
sort.Sort(mh)
|
||||||
|
for i := 1; i < len(mh); i++ {
|
||||||
|
if mh[i].HostID != mh[i-1].HostID+mh[i-1].Size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mc sortByContainerID = mappings[:]
|
||||||
|
sort.Sort(mc)
|
||||||
|
for i := 1; i < len(mc); i++ {
|
||||||
|
if mc[i].ContainerID != mc[i-1].ContainerID+mc[i-1].Size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
91
vendor/github.com/containers/storage/pkg/idtools/idtools_supported.go
generated
vendored
Normal file
91
vendor/github.com/containers/storage/pkg/idtools/idtools_supported.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
//go:build linux && cgo && libsubid
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/user"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -l subid
|
||||||
|
#include <shadow/subid.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct subid_range get_range(struct subid_range *ranges, int i)
|
||||||
|
{
|
||||||
|
return ranges[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(SUBID_ABI_MAJOR) || (SUBID_ABI_MAJOR < 4)
|
||||||
|
# define subid_init libsubid_init
|
||||||
|
# define subid_get_uid_ranges get_subuid_ranges
|
||||||
|
# define subid_get_gid_ranges get_subgid_ranges
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
var onceInit sync.Once
|
||||||
|
|
||||||
|
func readSubid(username string, isUser bool) (ranges, error) {
|
||||||
|
var ret ranges
|
||||||
|
uidstr := ""
|
||||||
|
|
||||||
|
if username == "ALL" {
|
||||||
|
return nil, errors.New("username ALL not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if u, err := user.Lookup(username); err == nil {
|
||||||
|
uidstr = u.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
onceInit.Do(func() {
|
||||||
|
C.subid_init(C.CString("storage"), C.stderr)
|
||||||
|
})
|
||||||
|
|
||||||
|
cUsername := C.CString(username)
|
||||||
|
defer C.free(unsafe.Pointer(cUsername))
|
||||||
|
|
||||||
|
cuidstr := C.CString(uidstr)
|
||||||
|
defer C.free(unsafe.Pointer(cuidstr))
|
||||||
|
|
||||||
|
var nRanges C.int
|
||||||
|
var cRanges *C.struct_subid_range
|
||||||
|
if isUser {
|
||||||
|
nRanges = C.subid_get_uid_ranges(cUsername, &cRanges)
|
||||||
|
if nRanges <= 0 {
|
||||||
|
nRanges = C.subid_get_uid_ranges(cuidstr, &cRanges)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nRanges = C.subid_get_gid_ranges(cUsername, &cRanges)
|
||||||
|
if nRanges <= 0 {
|
||||||
|
nRanges = C.subid_get_gid_ranges(cuidstr, &cRanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nRanges < 0 {
|
||||||
|
return nil, errors.New("cannot read subids")
|
||||||
|
}
|
||||||
|
defer C.free(unsafe.Pointer(cRanges))
|
||||||
|
|
||||||
|
for i := 0; i < int(nRanges); i++ {
|
||||||
|
r := C.get_range(cRanges, C.int(i))
|
||||||
|
newRange := subIDRange{
|
||||||
|
Start: int(r.start),
|
||||||
|
Length: int(r.count),
|
||||||
|
}
|
||||||
|
ret = append(ret, newRange)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSubuid(username string) (ranges, error) {
|
||||||
|
return readSubid(username, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSubgid(username string) (ranges, error) {
|
||||||
|
return readSubid(username, false)
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/fileutils"
|
||||||
|
"github.com/containers/storage/pkg/system"
|
||||||
|
"github.com/moby/sys/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
entOnce sync.Once
|
||||||
|
getentCmd string
|
||||||
|
)
|
||||||
|
|
||||||
|
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||||
|
// make an array containing the original path asked for, plus (for mkAll == true)
|
||||||
|
// all path components leading up to the complete path that don't exist before we MkdirAll
|
||||||
|
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
||||||
|
// chown the full directory path if it exists
|
||||||
|
var paths []string
|
||||||
|
st, err := os.Stat(path)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
paths = []string{path}
|
||||||
|
} else if err == nil {
|
||||||
|
if !st.IsDir() {
|
||||||
|
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||||
|
}
|
||||||
|
if chownExisting {
|
||||||
|
// short-circuit--we were called with an existing directory and chown was requested
|
||||||
|
return SafeChown(path, ownerUID, ownerGID)
|
||||||
|
}
|
||||||
|
// nothing to do; directory exists and chown was NOT requested
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if mkAll {
|
||||||
|
// walk back to "/" looking for directories which do not exist
|
||||||
|
// and add them to the paths array for chown after creation
|
||||||
|
dirPath := path
|
||||||
|
if !filepath.IsAbs(dirPath) {
|
||||||
|
return fmt.Errorf("path: %s should be absolute", dirPath)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
dirPath = filepath.Dir(dirPath)
|
||||||
|
if dirPath == "/" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := fileutils.Exists(dirPath); err != nil && os.IsNotExist(err) {
|
||||||
|
paths = append(paths, dirPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(path, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// even if it existed, we will chown the requested path + any subpaths that
|
||||||
|
// didn't exist when we called MkdirAll
|
||||||
|
for _, pathComponent := range paths {
|
||||||
|
if err := SafeChown(pathComponent, ownerUID, ownerGID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||||
|
// if that uid, gid pair has access (execute bit) to the directory
|
||||||
|
func CanAccess(path string, pair IDPair) bool {
|
||||||
|
statInfo, err := system.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fileMode := os.FileMode(statInfo.Mode())
|
||||||
|
permBits := fileMode.Perm()
|
||||||
|
return accessible(statInfo.UID() == uint32(pair.UID),
|
||||||
|
statInfo.GID() == uint32(pair.GID), permBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
|
||||||
|
if isOwner && (perms&0o100 == 0o100) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isGroup && (perms&0o010 == 0o010) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if perms&0o001 == 0o001 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupUser(username string) (user.User, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
usr, err := user.LookupUser(username)
|
||||||
|
if err == nil {
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||||
|
usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
|
||||||
|
if err != nil {
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupUID(uid int) (user.User, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
usr, err := user.LookupUid(uid)
|
||||||
|
if err == nil {
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||||
|
return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getentUser(args string) (user.User, error) {
|
||||||
|
reader, err := callGetent(args)
|
||||||
|
if err != nil {
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
users, err := user.ParsePasswd(reader)
|
||||||
|
if err != nil {
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
|
||||||
|
}
|
||||||
|
return users[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupGroup(groupname string) (user.Group, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
group, err := user.LookupGroup(groupname)
|
||||||
|
if err == nil {
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||||
|
return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||||
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
|
func LookupGID(gid int) (user.Group, error) {
|
||||||
|
// first try a local system files lookup using existing capabilities
|
||||||
|
group, err := user.LookupGid(gid)
|
||||||
|
if err == nil {
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||||
|
return getentGroup(fmt.Sprintf("%s %d", "group", gid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getentGroup(args string) (user.Group, error) {
|
||||||
|
reader, err := callGetent(args)
|
||||||
|
if err != nil {
|
||||||
|
return user.Group{}, err
|
||||||
|
}
|
||||||
|
groups, err := user.ParseGroup(reader)
|
||||||
|
if err != nil {
|
||||||
|
return user.Group{}, err
|
||||||
|
}
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
|
||||||
|
}
|
||||||
|
return groups[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func callGetent(args string) (io.Reader, error) {
|
||||||
|
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
||||||
|
// if no `getent` command on host, can't do anything else
|
||||||
|
if getentCmd == "" {
|
||||||
|
return nil, fmt.Errorf("")
|
||||||
|
}
|
||||||
|
out, err := execCmd(getentCmd, args)
|
||||||
|
if err != nil {
|
||||||
|
exitCode, errC := system.GetExitCode(err)
|
||||||
|
if errC != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch exitCode {
|
||||||
|
case 1:
|
||||||
|
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||||
|
case 2:
|
||||||
|
terms := strings.Split(args, " ")
|
||||||
|
return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
|
||||||
|
case 3:
|
||||||
|
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return bytes.NewReader(out), nil
|
||||||
|
}
|
11
vendor/github.com/containers/storage/pkg/idtools/idtools_unsupported.go
generated
vendored
Normal file
11
vendor/github.com/containers/storage/pkg/idtools/idtools_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build !linux || !libsubid || !cgo
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
func readSubuid(username string) (ranges, error) {
|
||||||
|
return parseSubidFile(subuidFileName, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSubgid(username string) (ranges, error) {
|
||||||
|
return parseSubidFile(subgidFileName, username)
|
||||||
|
}
|
23
vendor/github.com/containers/storage/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
23
vendor/github.com/containers/storage/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Platforms such as Windows do not support the UID/GID concept. So make this
|
||||||
|
// just a wrapper around system.MkdirAll.
|
||||||
|
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||||
|
if err := os.MkdirAll(path, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||||
|
// if that uid, gid pair has access (execute bit) to the directory
|
||||||
|
// Windows does not require/support this function, so always return true
|
||||||
|
func CanAccess(path string, pair IDPair) bool {
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/bits"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseTriple(spec []string) (container, host, size uint32, err error) {
|
||||||
|
cid, err := strconv.ParseUint(spec[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("parsing id map value %q: %w", spec[0], err)
|
||||||
|
}
|
||||||
|
hid, err := strconv.ParseUint(spec[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("parsing id map value %q: %w", spec[1], err)
|
||||||
|
}
|
||||||
|
sz, err := strconv.ParseUint(spec[2], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("parsing id map value %q: %w", spec[2], err)
|
||||||
|
}
|
||||||
|
return uint32(cid), uint32(hid), uint32(sz), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIDMap parses idmap triples from string.
|
||||||
|
func ParseIDMap(mapSpec []string, mapSetting string) (idmap []IDMap, err error) {
|
||||||
|
stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"uint32:uint32:uint32\"]: %q", mapSetting, mapSpec)
|
||||||
|
for _, idMapSpec := range mapSpec {
|
||||||
|
if idMapSpec == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idSpec := strings.Split(idMapSpec, ":")
|
||||||
|
if len(idSpec)%3 != 0 {
|
||||||
|
return nil, stdErr
|
||||||
|
}
|
||||||
|
for i := range idSpec {
|
||||||
|
if i%3 != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cid, hid, size, err := parseTriple(idSpec[i : i+3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, stdErr
|
||||||
|
}
|
||||||
|
// Avoid possible integer overflow on 32bit builds
|
||||||
|
if bits.UintSize == 32 && (cid > math.MaxInt32 || hid > math.MaxInt32 || size > math.MaxInt32) {
|
||||||
|
return nil, stdErr
|
||||||
|
}
|
||||||
|
mapping := IDMap{
|
||||||
|
ContainerID: int(cid),
|
||||||
|
HostID: int(hid),
|
||||||
|
Size: int(size),
|
||||||
|
}
|
||||||
|
idmap = append(idmap, mapping)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idmap, nil
|
||||||
|
}
|
164
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
164
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
||||||
|
// Linux distribution commands:
|
||||||
|
// adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
|
||||||
|
// useradd -r -s /bin/false <username>
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
userCommand string
|
||||||
|
|
||||||
|
cmdTemplates = map[string]string{
|
||||||
|
"adduser": "--system --shell /bin/false --no-create-home --disabled-login --disabled-password --group %s",
|
||||||
|
"useradd": "-r -s /bin/false %s",
|
||||||
|
"usermod": "-%s %d-%d %s",
|
||||||
|
}
|
||||||
|
|
||||||
|
idOutRegexp = regexp.Delayed(`uid=([0-9]+).*gid=([0-9]+)`)
|
||||||
|
// default length for a UID/GID subordinate range
|
||||||
|
defaultRangeLen = 65536
|
||||||
|
defaultRangeStart = 100000
|
||||||
|
userMod = "usermod"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddNamespaceRangesUser takes a username and uses the standard system
|
||||||
|
// utility to create a system user/group pair used to hold the
|
||||||
|
// /etc/sub{uid,gid} ranges which will be used for user namespace
|
||||||
|
// mapping ranges in containers.
|
||||||
|
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||||
|
if err := addUser(name); err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("adding user %q: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the system for the created uid and gid pair
|
||||||
|
out, err := execCmd("id", name)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("trying to find uid/gid for new user %q: %w", name, err)
|
||||||
|
}
|
||||||
|
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||||
|
if len(matches) != 3 {
|
||||||
|
return -1, -1, fmt.Errorf("can't find uid, gid from `id` output: %q", string(out))
|
||||||
|
}
|
||||||
|
uid, err := strconv.Atoi(matches[1])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("can't convert found uid (%s) to int: %w", matches[1], err)
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(matches[2])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("can't convert found gid (%s) to int: %w", matches[2], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we need to create the subuid/subgid ranges for our new user/group (system users
|
||||||
|
// do not get auto-created ranges in subuid/subgid)
|
||||||
|
|
||||||
|
if err := createSubordinateRanges(name); err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("couldn't create subordinate ID ranges: %w", err)
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUser(userName string) error {
|
||||||
|
once.Do(func() {
|
||||||
|
// set up which commands are used for adding users/groups dependent on distro
|
||||||
|
if _, err := resolveBinary("adduser"); err == nil {
|
||||||
|
userCommand = "adduser"
|
||||||
|
} else if _, err := resolveBinary("useradd"); err == nil {
|
||||||
|
userCommand = "useradd"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if userCommand == "" {
|
||||||
|
return fmt.Errorf("cannot add user; no useradd/adduser binary found")
|
||||||
|
}
|
||||||
|
args := fmt.Sprintf(cmdTemplates[userCommand], userName)
|
||||||
|
out, err := execCmd(userCommand, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add user with error: %w; output: %q", err, string(out))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSubordinateRanges(name string) error {
|
||||||
|
// first, we should verify that ranges weren't automatically created
|
||||||
|
// by the distro tooling
|
||||||
|
ranges, err := readSubuid(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("while looking for subuid ranges for user %q: %w", name, err)
|
||||||
|
}
|
||||||
|
if len(ranges) == 0 {
|
||||||
|
// no UID ranges; let's create one
|
||||||
|
startID, err := findNextUIDRange()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't find available subuid range: %w", err)
|
||||||
|
}
|
||||||
|
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "v", startID, startID+defaultRangeLen-1, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to add subuid range to user: %q; output: %s, err: %w", name, out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges, err = readSubgid(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("while looking for subgid ranges for user %q: %w", name, err)
|
||||||
|
}
|
||||||
|
if len(ranges) == 0 {
|
||||||
|
// no GID ranges; let's create one
|
||||||
|
startID, err := findNextGIDRange()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't find available subgid range: %w", err)
|
||||||
|
}
|
||||||
|
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "w", startID, startID+defaultRangeLen-1, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to add subgid range to user: %q; output: %s, err: %w", name, out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextUIDRange() (int, error) {
|
||||||
|
ranges, err := readSubuid("ALL")
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("couldn't parse all ranges in /etc/subuid file: %w", err)
|
||||||
|
}
|
||||||
|
sort.Sort(ranges)
|
||||||
|
return findNextRangeStart(ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextGIDRange() (int, error) {
|
||||||
|
ranges, err := readSubgid("ALL")
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("couldn't parse all ranges in /etc/subgid file: %w", err)
|
||||||
|
}
|
||||||
|
sort.Sort(ranges)
|
||||||
|
return findNextRangeStart(ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextRangeStart(rangeList ranges) (int, error) {
|
||||||
|
startID := defaultRangeStart
|
||||||
|
for _, arange := range rangeList {
|
||||||
|
if wouldOverlap(arange, startID) {
|
||||||
|
startID = arange.Start + arange.Length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return startID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wouldOverlap(arange subIDRange, ID int) bool {
|
||||||
|
low := ID
|
||||||
|
high := ID + defaultRangeLen
|
||||||
|
if (low >= arange.Start && low <= arange.Start+arange.Length) ||
|
||||||
|
(high <= arange.Start+arange.Length && high >= arange.Start) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
12
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/containers/storage/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
||||||
|
// and calls the appropriate helper function to add the group and then
|
||||||
|
// the user to the group in /etc/group and /etc/passwd respectively.
|
||||||
|
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||||
|
return -1, -1, fmt.Errorf("no support for adding users or groups on this OS")
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package idtools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveBinary(binname string) (string, error) {
|
||||||
|
binaryPath, err := exec.LookPath(binname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// only return no error if the final resolved binary basename
|
||||||
|
// matches what was searched for
|
||||||
|
if filepath.Base(resolvedPath) == binname {
|
||||||
|
return resolvedPath, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCmd(cmd, args string) ([]byte, error) {
|
||||||
|
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
||||||
|
return execCmd.CombinedOutput()
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flags = map[string]struct {
|
||||||
|
clear bool
|
||||||
|
flag int
|
||||||
|
}{
|
||||||
|
"defaults": {false, 0},
|
||||||
|
"ro": {false, RDONLY},
|
||||||
|
"rw": {true, RDONLY},
|
||||||
|
"suid": {true, NOSUID},
|
||||||
|
"nosuid": {false, NOSUID},
|
||||||
|
"dev": {true, NODEV},
|
||||||
|
"nodev": {false, NODEV},
|
||||||
|
"exec": {true, NOEXEC},
|
||||||
|
"noexec": {false, NOEXEC},
|
||||||
|
"sync": {false, SYNCHRONOUS},
|
||||||
|
"async": {true, SYNCHRONOUS},
|
||||||
|
"dirsync": {false, DIRSYNC},
|
||||||
|
"remount": {false, REMOUNT},
|
||||||
|
"mand": {false, MANDLOCK},
|
||||||
|
"nomand": {true, MANDLOCK},
|
||||||
|
"atime": {true, NOATIME},
|
||||||
|
"noatime": {false, NOATIME},
|
||||||
|
"diratime": {true, NODIRATIME},
|
||||||
|
"nodiratime": {false, NODIRATIME},
|
||||||
|
"bind": {false, BIND},
|
||||||
|
"rbind": {false, RBIND},
|
||||||
|
"unbindable": {false, UNBINDABLE},
|
||||||
|
"runbindable": {false, RUNBINDABLE},
|
||||||
|
"private": {false, PRIVATE},
|
||||||
|
"rprivate": {false, RPRIVATE},
|
||||||
|
"shared": {false, SHARED},
|
||||||
|
"rshared": {false, RSHARED},
|
||||||
|
"slave": {false, SLAVE},
|
||||||
|
"rslave": {false, RSLAVE},
|
||||||
|
"relatime": {false, RELATIME},
|
||||||
|
"norelatime": {true, RELATIME},
|
||||||
|
"strictatime": {false, STRICTATIME},
|
||||||
|
"nostrictatime": {true, STRICTATIME},
|
||||||
|
}
|
||||||
|
|
||||||
|
var validFlags = map[string]bool{
|
||||||
|
"": true,
|
||||||
|
"size": true,
|
||||||
|
"mode": true,
|
||||||
|
"uid": true,
|
||||||
|
"gid": true,
|
||||||
|
"nr_inodes": true,
|
||||||
|
"nr_blocks": true,
|
||||||
|
"mpol": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var propagationFlags = map[string]bool{
|
||||||
|
"bind": true,
|
||||||
|
"rbind": true,
|
||||||
|
"unbindable": true,
|
||||||
|
"runbindable": true,
|
||||||
|
"private": true,
|
||||||
|
"rprivate": true,
|
||||||
|
"shared": true,
|
||||||
|
"rshared": true,
|
||||||
|
"slave": true,
|
||||||
|
"rslave": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
|
||||||
|
func MergeTmpfsOptions(options []string) ([]string, error) {
|
||||||
|
// We use collisions maps to remove duplicates.
|
||||||
|
// For flag, the key is the flag value (the key for propagation flag is -1)
|
||||||
|
// For data=value, the key is the data
|
||||||
|
flagCollisions := map[int]bool{}
|
||||||
|
dataCollisions := map[string]bool{}
|
||||||
|
|
||||||
|
var newOptions []string
|
||||||
|
// We process in reverse order
|
||||||
|
for i := len(options) - 1; i >= 0; i-- {
|
||||||
|
option := options[i]
|
||||||
|
if option == "defaults" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f, ok := flags[option]; ok && f.flag != 0 {
|
||||||
|
// There is only one propagation mode
|
||||||
|
key := f.flag
|
||||||
|
if propagationFlags[option] {
|
||||||
|
key = -1
|
||||||
|
}
|
||||||
|
// Check to see if there is collision for flag
|
||||||
|
if !flagCollisions[key] {
|
||||||
|
// We prepend the option and add to collision map
|
||||||
|
newOptions = append([]string{option}, newOptions...)
|
||||||
|
flagCollisions[key] = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opt, _, ok := strings.Cut(option, "=")
|
||||||
|
if !ok || !validFlags[opt] {
|
||||||
|
return nil, fmt.Errorf("invalid tmpfs option %q", opt)
|
||||||
|
}
|
||||||
|
if !dataCollisions[opt] {
|
||||||
|
// We prepend the option and add to collision map
|
||||||
|
newOptions = append([]string{option}, newOptions...)
|
||||||
|
dataCollisions[opt] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseOptions parses fstab type mount options into mount() flags
|
||||||
|
// and device specific data
|
||||||
|
func ParseOptions(options string) (int, string) {
|
||||||
|
var (
|
||||||
|
flag int
|
||||||
|
data []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, o := range strings.Split(options, ",") {
|
||||||
|
// If the option does not exist in the flags table or the flag
|
||||||
|
// is not supported on the platform,
|
||||||
|
// then it is a data value for a specific fs type
|
||||||
|
if f, exists := flags[o]; exists && f.flag != 0 {
|
||||||
|
if f.clear {
|
||||||
|
flag &= ^f.flag
|
||||||
|
} else {
|
||||||
|
flag |= f.flag
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = append(data, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag, strings.Join(data, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTmpfsOptions parse fstab type mount options into flags and data
|
||||||
|
func ParseTmpfsOptions(options string) (int, string, error) {
|
||||||
|
flags, data := ParseOptions(options)
|
||||||
|
for _, o := range strings.Split(data, ",") {
|
||||||
|
opt, _, _ := strings.Cut(o, "=")
|
||||||
|
if !validFlags[opt] {
|
||||||
|
return 0, "", fmt.Errorf("invalid tmpfs option %q", opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flags, data, nil
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RDONLY will mount the file system read-only.
|
||||||
|
RDONLY = unix.MNT_RDONLY
|
||||||
|
|
||||||
|
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
|
||||||
|
// take effect.
|
||||||
|
NOSUID = unix.MNT_NOSUID
|
||||||
|
|
||||||
|
// NOEXEC will not allow execution of any binaries on the mounted file system.
|
||||||
|
NOEXEC = unix.MNT_NOEXEC
|
||||||
|
|
||||||
|
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
|
||||||
|
SYNCHRONOUS = unix.MNT_SYNCHRONOUS
|
||||||
|
|
||||||
|
// REMOUNT will attempt to remount an already-mounted file system. This is
|
||||||
|
// commonly used to change the mount flags for a file system, especially to
|
||||||
|
// make a readonly file system writeable. It does not change device or mount
|
||||||
|
// point.
|
||||||
|
REMOUNT = unix.MNT_UPDATE
|
||||||
|
|
||||||
|
// NOATIME will not update the file access time when reading from a file.
|
||||||
|
NOATIME = unix.MNT_NOATIME
|
||||||
|
|
||||||
|
mntDetach = unix.MNT_FORCE
|
||||||
|
|
||||||
|
NODIRATIME = 0
|
||||||
|
NODEV = 0
|
||||||
|
DIRSYNC = 0
|
||||||
|
MANDLOCK = 0
|
||||||
|
BIND = 0
|
||||||
|
RBIND = 0
|
||||||
|
UNBINDABLE = 0
|
||||||
|
RUNBINDABLE = 0
|
||||||
|
PRIVATE = 0
|
||||||
|
RPRIVATE = 0
|
||||||
|
SLAVE = 0
|
||||||
|
RSLAVE = 0
|
||||||
|
SHARED = 0
|
||||||
|
RSHARED = 0
|
||||||
|
RELATIME = 0
|
||||||
|
STRICTATIME = 0
|
||||||
|
)
|
|
@ -0,0 +1,87 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RDONLY will mount the file system read-only.
|
||||||
|
RDONLY = unix.MS_RDONLY
|
||||||
|
|
||||||
|
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
|
||||||
|
// take effect.
|
||||||
|
NOSUID = unix.MS_NOSUID
|
||||||
|
|
||||||
|
// NODEV will not interpret character or block special devices on the file
|
||||||
|
// system.
|
||||||
|
NODEV = unix.MS_NODEV
|
||||||
|
|
||||||
|
// NOEXEC will not allow execution of any binaries on the mounted file system.
|
||||||
|
NOEXEC = unix.MS_NOEXEC
|
||||||
|
|
||||||
|
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
|
||||||
|
SYNCHRONOUS = unix.MS_SYNCHRONOUS
|
||||||
|
|
||||||
|
// DIRSYNC will force all directory updates within the file system to be done
|
||||||
|
// synchronously. This affects the following system calls: create, link,
|
||||||
|
// unlink, symlink, mkdir, rmdir, mknod and rename.
|
||||||
|
DIRSYNC = unix.MS_DIRSYNC
|
||||||
|
|
||||||
|
// REMOUNT will attempt to remount an already-mounted file system. This is
|
||||||
|
// commonly used to change the mount flags for a file system, especially to
|
||||||
|
// make a readonly file system writeable. It does not change device or mount
|
||||||
|
// point.
|
||||||
|
REMOUNT = unix.MS_REMOUNT
|
||||||
|
|
||||||
|
// MANDLOCK will force mandatory locks on a filesystem.
|
||||||
|
MANDLOCK = unix.MS_MANDLOCK
|
||||||
|
|
||||||
|
// NOATIME will not update the file access time when reading from a file.
|
||||||
|
NOATIME = unix.MS_NOATIME
|
||||||
|
|
||||||
|
// NODIRATIME will not update the directory access time.
|
||||||
|
NODIRATIME = unix.MS_NODIRATIME
|
||||||
|
|
||||||
|
// BIND remounts a subtree somewhere else.
|
||||||
|
BIND = unix.MS_BIND
|
||||||
|
|
||||||
|
// RBIND remounts a subtree and all possible submounts somewhere else.
|
||||||
|
RBIND = unix.MS_BIND | unix.MS_REC
|
||||||
|
|
||||||
|
// UNBINDABLE creates a mount which cannot be cloned through a bind operation.
|
||||||
|
UNBINDABLE = unix.MS_UNBINDABLE
|
||||||
|
|
||||||
|
// RUNBINDABLE marks the entire mount tree as UNBINDABLE.
|
||||||
|
RUNBINDABLE = unix.MS_UNBINDABLE | unix.MS_REC
|
||||||
|
|
||||||
|
// PRIVATE creates a mount which carries no propagation abilities.
|
||||||
|
PRIVATE = unix.MS_PRIVATE
|
||||||
|
|
||||||
|
// RPRIVATE marks the entire mount tree as PRIVATE.
|
||||||
|
RPRIVATE = unix.MS_PRIVATE | unix.MS_REC
|
||||||
|
|
||||||
|
// SLAVE creates a mount which receives propagation from its master, but not
|
||||||
|
// vice versa.
|
||||||
|
SLAVE = unix.MS_SLAVE
|
||||||
|
|
||||||
|
// RSLAVE marks the entire mount tree as SLAVE.
|
||||||
|
RSLAVE = unix.MS_SLAVE | unix.MS_REC
|
||||||
|
|
||||||
|
// SHARED creates a mount which provides the ability to create mirrors of
|
||||||
|
// that mount such that mounts and unmounts within any of the mirrors
|
||||||
|
// propagate to the other mirrors.
|
||||||
|
SHARED = unix.MS_SHARED
|
||||||
|
|
||||||
|
// RSHARED marks the entire mount tree as SHARED.
|
||||||
|
RSHARED = unix.MS_SHARED | unix.MS_REC
|
||||||
|
|
||||||
|
// RELATIME updates inode access times relative to modify or change time.
|
||||||
|
RELATIME = unix.MS_RELATIME
|
||||||
|
|
||||||
|
// STRICTATIME allows to explicitly request full atime updates. This makes
|
||||||
|
// it possible for the kernel to default to relatime or noatime but still
|
||||||
|
// allow userspace to override it.
|
||||||
|
STRICTATIME = unix.MS_STRICTATIME
|
||||||
|
|
||||||
|
mntDetach = unix.MNT_DETACH
|
||||||
|
)
|
31
vendor/github.com/containers/storage/pkg/mount/flags_unsupported.go
generated
vendored
Normal file
31
vendor/github.com/containers/storage/pkg/mount/flags_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
//go:build !linux && !freebsd
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
// These flags are unsupported.
|
||||||
|
const (
|
||||||
|
BIND = 0
|
||||||
|
DIRSYNC = 0
|
||||||
|
MANDLOCK = 0
|
||||||
|
NOATIME = 0
|
||||||
|
NODEV = 0
|
||||||
|
NODIRATIME = 0
|
||||||
|
NOEXEC = 0
|
||||||
|
NOSUID = 0
|
||||||
|
UNBINDABLE = 0
|
||||||
|
RUNBINDABLE = 0
|
||||||
|
PRIVATE = 0
|
||||||
|
RPRIVATE = 0
|
||||||
|
SHARED = 0
|
||||||
|
RSHARED = 0
|
||||||
|
SLAVE = 0
|
||||||
|
RSLAVE = 0
|
||||||
|
RBIND = 0
|
||||||
|
RELATIME = 0
|
||||||
|
RELATIVE = 0
|
||||||
|
REMOUNT = 0
|
||||||
|
STRICTATIME = 0
|
||||||
|
SYNCHRONOUS = 0
|
||||||
|
RDONLY = 0
|
||||||
|
mntDetach = 0
|
||||||
|
)
|
|
@ -0,0 +1,110 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mountError holds an error from a mount or unmount operation
|
||||||
|
type mountError struct {
|
||||||
|
op string
|
||||||
|
source, target string
|
||||||
|
flags uintptr
|
||||||
|
data string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a string representation of mountError
|
||||||
|
func (e *mountError) Error() string {
|
||||||
|
out := e.op + " "
|
||||||
|
|
||||||
|
if e.source != "" {
|
||||||
|
out += e.source + ":" + e.target
|
||||||
|
} else {
|
||||||
|
out += e.target
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.flags != uintptr(0) {
|
||||||
|
out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
|
||||||
|
}
|
||||||
|
if e.data != "" {
|
||||||
|
out += ", data: " + e.data
|
||||||
|
}
|
||||||
|
|
||||||
|
out += ": " + e.err.Error()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the underlying cause of the error
|
||||||
|
func (e *mountError) Cause() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying cause of the error
|
||||||
|
func (e *mountError) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount will mount filesystem according to the specified configuration, on the
|
||||||
|
// condition that the target path is *not* already mounted. Options must be
|
||||||
|
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
|
||||||
|
// flags.go for supported option flags.
|
||||||
|
func Mount(device, target, mType, options string) error {
|
||||||
|
flag, data := ParseOptions(options)
|
||||||
|
if flag&REMOUNT != REMOUNT {
|
||||||
|
if mounted, err := Mounted(target); err != nil || mounted {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mount(device, target, mType, uintptr(flag), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceMount will mount a filesystem according to the specified configuration,
|
||||||
|
// *regardless* if the target path is not already mounted. Options must be
|
||||||
|
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
|
||||||
|
// flags.go for supported option flags.
|
||||||
|
func ForceMount(device, target, mType, options string) error {
|
||||||
|
flag, data := ParseOptions(options)
|
||||||
|
return mount(device, target, mType, uintptr(flag), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount lazily unmounts a filesystem on supported platforms, otherwise
|
||||||
|
// does a normal unmount.
|
||||||
|
func Unmount(target string) error {
|
||||||
|
return unmount(target, mntDetach)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecursiveUnmount unmounts the target and all mounts underneath, starting with
|
||||||
|
// the deepest mount first.
|
||||||
|
func RecursiveUnmount(target string) error {
|
||||||
|
mounts, err := GetMounts()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the deepest mount be first
|
||||||
|
sort.Slice(mounts, func(i, j int) bool {
|
||||||
|
return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, m := range mounts {
|
||||||
|
if !strings.HasPrefix(m.Mountpoint, target) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 {
|
||||||
|
return err
|
||||||
|
// Ignore errors for submounts and continue trying to unmount others
|
||||||
|
// The final unmount should fail if there are any submounts remaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceUnmount lazily unmounts a filesystem on supported platforms,
|
||||||
|
// otherwise does a normal unmount.
|
||||||
|
//
|
||||||
|
// Deprecated: please use Unmount instead, it is identical.
|
||||||
|
func ForceUnmount(target string) error {
|
||||||
|
return unmount(target, mntDetach)
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
//go:build freebsd && cgo
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/_iovec.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func allocateIOVecs(options []string) []C.struct_iovec {
|
||||||
|
out := make([]C.struct_iovec, len(options))
|
||||||
|
for i, option := range options {
|
||||||
|
out[i].iov_base = unsafe.Pointer(C.CString(option))
|
||||||
|
out[i].iov_len = C.size_t(len(option) + 1)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func mount(device, target, mType string, flag uintptr, data string) error {
|
||||||
|
isNullFS := false
|
||||||
|
|
||||||
|
options := []string{"fspath", target}
|
||||||
|
|
||||||
|
if data != "" {
|
||||||
|
xs := strings.Split(data, ",")
|
||||||
|
for _, x := range xs {
|
||||||
|
if x == "bind" {
|
||||||
|
isNullFS = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, val, _ := strings.Cut(x, "=")
|
||||||
|
options = append(options, name)
|
||||||
|
options = append(options, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNullFS {
|
||||||
|
options = append(options, "fstype", "nullfs", "target", device)
|
||||||
|
} else {
|
||||||
|
options = append(options, "fstype", mType, "from", device)
|
||||||
|
}
|
||||||
|
rawOptions := allocateIOVecs(options)
|
||||||
|
for _, rawOption := range rawOptions {
|
||||||
|
defer C.free(rawOption.iov_base)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
|
||||||
|
reason := C.GoString(C.strerror(*C.__error()))
|
||||||
|
return fmt.Errorf("failed to call nmount: %s", reason)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ptypes is the set propagation types.
|
||||||
|
ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE
|
||||||
|
|
||||||
|
// pflags is the full set valid flags for a change propagation call.
|
||||||
|
pflags = ptypes | unix.MS_REC | unix.MS_SILENT
|
||||||
|
|
||||||
|
// broflags is the combination of bind and read only
|
||||||
|
broflags = unix.MS_BIND | unix.MS_RDONLY
|
||||||
|
|
||||||
|
none = "none"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isremount returns true if either device name or flags identify a remount request, false otherwise.
|
||||||
|
func isremount(device string, flags uintptr) bool {
|
||||||
|
switch {
|
||||||
|
// We treat device "" and "none" as a remount request to provide compatibility with
|
||||||
|
// requests that don't explicitly set MS_REMOUNT such as those manipulating bind mounts.
|
||||||
|
case flags&unix.MS_REMOUNT != 0, device == "", device == none:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mount(device, target, mType string, flags uintptr, data string) error {
|
||||||
|
oflags := flags &^ ptypes
|
||||||
|
if !isremount(device, flags) || data != "" {
|
||||||
|
// Initial call applying all non-propagation flags for mount
|
||||||
|
// or remount with changed data
|
||||||
|
if err := unix.Mount(device, target, mType, oflags, data); err != nil {
|
||||||
|
return &mountError{
|
||||||
|
op: "mount",
|
||||||
|
source: device,
|
||||||
|
target: target,
|
||||||
|
flags: oflags,
|
||||||
|
data: data,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags&ptypes != 0 {
|
||||||
|
// Change the propagation type.
|
||||||
|
if err := unix.Mount("", target, "", flags&pflags, ""); err != nil {
|
||||||
|
return &mountError{
|
||||||
|
op: "remount",
|
||||||
|
target: target,
|
||||||
|
flags: flags & pflags,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oflags&broflags == broflags {
|
||||||
|
// Remount the bind to apply read only.
|
||||||
|
if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil {
|
||||||
|
return &mountError{
|
||||||
|
op: "remount-ro",
|
||||||
|
target: target,
|
||||||
|
flags: oflags | unix.MS_REMOUNT,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
7
vendor/github.com/containers/storage/pkg/mount/mounter_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/mount/mounter_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build !linux && !(freebsd && cgo)
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
func mount(device, target, mType string, flag uintptr, data string) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/moby/sys/mountinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Info = mountinfo.Info
|
||||||
|
|
||||||
|
var Mounted = mountinfo.Mounted
|
||||||
|
|
||||||
|
func GetMounts() ([]*Info, error) {
|
||||||
|
return mountinfo.GetMounts(nil)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/moby/sys/mountinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PidMountInfo(pid int) ([]*Info, error) {
|
||||||
|
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return mountinfo.GetMountsFromReader(f, nil)
|
||||||
|
}
|
64
vendor/github.com/containers/storage/pkg/mount/sharedsubtree_linux.go
generated
vendored
Normal file
64
vendor/github.com/containers/storage/pkg/mount/sharedsubtree_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
|
||||||
|
// See the supported options in flags.go for further reference.
|
||||||
|
func MakeShared(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, SHARED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
|
||||||
|
// See the supported options in flags.go for further reference.
|
||||||
|
func MakeRShared(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, RSHARED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
|
||||||
|
// See the supported options in flags.go for further reference.
|
||||||
|
func MakePrivate(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, PRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
|
||||||
|
// enabled. See the supported options in flags.go for further reference.
|
||||||
|
func MakeRPrivate(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, RPRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
|
||||||
|
// See the supported options in flags.go for further reference.
|
||||||
|
func MakeSlave(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, SLAVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
|
||||||
|
// See the supported options in flags.go for further reference.
|
||||||
|
func MakeRSlave(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, RSLAVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
|
||||||
|
// enabled. See the supported options in flags.go for further reference.
|
||||||
|
func MakeUnbindable(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, UNBINDABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
|
||||||
|
// option enabled. See the supported options in flags.go for further reference.
|
||||||
|
func MakeRUnbindable(mountPoint string) error {
|
||||||
|
return ensureMountedAs(mountPoint, RUNBINDABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureMountedAs(mnt string, flags int) error {
|
||||||
|
mounted, err := Mounted(mnt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mounted {
|
||||||
|
if err := mount(mnt, mnt, "none", uintptr(BIND), ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mount("", mnt, "none", uintptr(flags), "")
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unmount(target string, flags int) error {
|
||||||
|
var err error
|
||||||
|
for range 50 {
|
||||||
|
err = unix.Unmount(target, flags)
|
||||||
|
switch err {
|
||||||
|
case unix.EBUSY:
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
case unix.EINVAL, nil:
|
||||||
|
// Ignore "not mounted" error here. Note the same error
|
||||||
|
// can be returned if flags are invalid, so this code
|
||||||
|
// assumes that the flags value is always correct.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mountError{
|
||||||
|
op: "umount",
|
||||||
|
target: target,
|
||||||
|
flags: uintptr(flags),
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
7
vendor/github.com/containers/storage/pkg/mount/unmount_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/pkg/mount/unmount_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
func unmount(target string, flag int) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
# reexec
|
||||||
|
|
||||||
|
The `reexec` package facilitates the busybox style reexec of the docker binary that we require because
|
||||||
|
of the forking limitations of using Go. Handlers can be registered with a name and the argv 0 of
|
||||||
|
the exec of the binary will be used to find and execute custom init paths.
|
37
vendor/github.com/containers/storage/pkg/reexec/command_freebsd.go
generated
vendored
Normal file
37
vendor/github.com/containers/storage/pkg/reexec/command_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Uses sysctl.
|
||||||
|
func Self() string {
|
||||||
|
path, err := unix.SysctlArgs("kern.proc.pathname", -1)
|
||||||
|
if err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return os.Args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
|
||||||
|
// be set to "/usr/bin/docker".
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandContext returns *exec.Cmd which has Path as current binary.
|
||||||
|
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||||
|
cmd := exec.CommandContext(ctx, Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Returns "/proc/self/exe".
|
||||||
|
func Self() string {
|
||||||
|
return "/proc/self/exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// This will use the in-memory version (/proc/self/exe) of the current binary,
|
||||||
|
// it is thus safe to delete or replace the on-disk binary (os.Args[0]).
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
cmd := exec.Command(Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandContext returns *exec.Cmd which has Path as current binary.
|
||||||
|
// This will use the in-memory version (/proc/self/exe) of the current binary,
|
||||||
|
// it is thus safe to delete or replace the on-disk binary (os.Args[0]).
|
||||||
|
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
cmd := exec.CommandContext(ctx, Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
//go:build solaris || darwin
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Uses os.Args[0].
|
||||||
|
func Self() string {
|
||||||
|
return naiveSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
|
||||||
|
// be set to "/usr/bin/docker".
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
cmd := exec.Command(Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandContext returns *exec.Cmd which has Path as current binary.
|
||||||
|
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
cmd := exec.CommandContext(ctx, Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
20
vendor/github.com/containers/storage/pkg/reexec/command_unsupported.go
generated
vendored
Normal file
20
vendor/github.com/containers/storage/pkg/reexec/command_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
//go:build !linux && !windows && !freebsd && !solaris && !darwin
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandContext is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
|
||||||
|
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
return nil
|
||||||
|
}
|
34
vendor/github.com/containers/storage/pkg/reexec/command_windows.go
generated
vendored
Normal file
34
vendor/github.com/containers/storage/pkg/reexec/command_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Uses os.Args[0].
|
||||||
|
func Self() string {
|
||||||
|
return naiveSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
|
||||||
|
// be set to "C:\docker.exe".
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
cmd := exec.Command(Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
|
||||||
|
// be set to "C:\docker.exe".
|
||||||
|
func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
|
||||||
|
panicIfNotInitialized()
|
||||||
|
cmd := exec.CommandContext(ctx, Self())
|
||||||
|
cmd.Args = args
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
registeredInitializers = make(map[string]func())
|
||||||
|
initWasCalled = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register adds an initialization func under the specified name
|
||||||
|
func Register(name string, initializer func()) {
|
||||||
|
if _, exists := registeredInitializers[name]; exists {
|
||||||
|
panic(fmt.Sprintf("reexec func already registered under name %q", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
registeredInitializers[name] = initializer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init is called as the first part of the exec process and returns true if an
|
||||||
|
// initialization function was called.
|
||||||
|
func Init() bool {
|
||||||
|
initializer, exists := registeredInitializers[os.Args[0]]
|
||||||
|
initWasCalled = true
|
||||||
|
if exists {
|
||||||
|
initializer()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func panicIfNotInitialized() {
|
||||||
|
if !initWasCalled {
|
||||||
|
// The reexec package is used to run subroutines in
|
||||||
|
// subprocesses which would otherwise have unacceptable side
|
||||||
|
// effects on the main thread. If you found this error, then
|
||||||
|
// your program uses a package which needs to do this. In
|
||||||
|
// order for that to work, main() should start with this
|
||||||
|
// boilerplate, or an equivalent:
|
||||||
|
// if reexec.Init() {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
panic("a library subroutine needed to run a subprocess, but reexec.Init() was not called in main()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func naiveSelf() string {
|
||||||
|
name := os.Args[0]
|
||||||
|
if filepath.Base(name) == name {
|
||||||
|
if lp, err := exec.LookPath(name); err == nil {
|
||||||
|
return lp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// handle conversion of relative paths to absolute
|
||||||
|
if absName, err := filepath.Abs(name); err == nil {
|
||||||
|
return absName
|
||||||
|
}
|
||||||
|
// if we couldn't get absolute name, return original
|
||||||
|
// (NOTE: Go only errors on Abs() if os.Getwd fails)
|
||||||
|
return name
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
package regexp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regexp is a wrapper struct used for wrapping MustCompile regex expressions
|
||||||
|
// used as global variables. Using this structure helps speed the startup time
|
||||||
|
// of apps that want to use global regex variables. This library initializes them on
|
||||||
|
// first use as opposed to the start of the executable.
|
||||||
|
type Regexp struct {
|
||||||
|
*regexpStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
type regexpStruct struct {
|
||||||
|
_ noCopy
|
||||||
|
once sync.Once
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delayed(val string) Regexp {
|
||||||
|
re := ®expStruct{
|
||||||
|
val: val,
|
||||||
|
}
|
||||||
|
if precompile {
|
||||||
|
re.regexp = regexp.MustCompile(re.val)
|
||||||
|
}
|
||||||
|
return Regexp{re}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) compile() {
|
||||||
|
if precompile {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
re.once.Do(func() {
|
||||||
|
re.regexp = regexp.MustCompile(re.val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) Expand(dst []byte, template []byte, src []byte, match []int) []byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.Expand(dst, template, src, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ExpandString(dst []byte, template string, src string, match []int) []byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ExpandString(dst, template, src, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) Find(b []byte) []byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.Find(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAll(b []byte, n int) [][]byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAll(b, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllIndex(b []byte, n int) [][]int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllIndex(b, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllString(s string, n int) []string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllString(s, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllStringIndex(s string, n int) [][]int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllStringIndex(s, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllStringSubmatch(s string, n int) [][]string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllStringSubmatch(s, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllStringSubmatchIndex(s string, n int) [][]int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllStringSubmatchIndex(s, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllSubmatch(b []byte, n int) [][][]byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllSubmatch(b, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindAllSubmatchIndex(b []byte, n int) [][]int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindAllSubmatchIndex(b, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindIndex(b []byte) (loc []int) {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindIndex(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindReaderIndex(r io.RuneReader) (loc []int) {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindReaderIndex(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindReaderSubmatchIndex(r io.RuneReader) []int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindReaderSubmatchIndex(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindString(s string) string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindStringIndex(s string) (loc []int) {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindStringIndex(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindStringSubmatch(s string) []string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindStringSubmatch(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindStringSubmatchIndex(s string) []int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindStringSubmatchIndex(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindSubmatch(b []byte) [][]byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindSubmatch(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) FindSubmatchIndex(b []byte) []int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.FindSubmatchIndex(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) LiteralPrefix() (prefix string, complete bool) {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.LiteralPrefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) Longest() {
|
||||||
|
re.compile()
|
||||||
|
re.regexp.Longest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) Match(b []byte) bool {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.Match(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) MatchReader(r io.RuneReader) bool {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.MatchReader(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) MatchString(s string) bool {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) NumSubexp() int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.NumSubexp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ReplaceAll(src, repl []byte) []byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ReplaceAll(src, repl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ReplaceAllFunc(src, repl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ReplaceAllLiteral(src, repl []byte) []byte {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ReplaceAllLiteral(src, repl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ReplaceAllLiteralString(src, repl string) string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ReplaceAllLiteralString(src, repl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ReplaceAllString(src, repl string) string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ReplaceAllString(src, repl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) ReplaceAllStringFunc(src string, repl func(string) string) string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.ReplaceAllStringFunc(src, repl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) Split(s string, n int) []string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.Split(s, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) String() string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) SubexpIndex(name string) int {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.SubexpIndex(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *regexpStruct) SubexpNames() []string {
|
||||||
|
re.compile()
|
||||||
|
return re.regexp.SubexpNames()
|
||||||
|
}
|
||||||
|
|
||||||
|
// noCopy may be added to structs which must not be copied
|
||||||
|
// after the first use.
|
||||||
|
//
|
||||||
|
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||||
|
// for details.
|
||||||
|
//
|
||||||
|
// Note that it must not be embedded, due to the Lock and Unlock methods.
|
||||||
|
type noCopy struct{}
|
||||||
|
|
||||||
|
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||||
|
func (*noCopy) Lock() {}
|
||||||
|
func (*noCopy) Unlock() {}
|
5
vendor/github.com/containers/storage/pkg/regexp/regexp_dontprecompile.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/pkg/regexp/regexp_dontprecompile.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build !regexp_precompile
|
||||||
|
|
||||||
|
package regexp
|
||||||
|
|
||||||
|
const precompile = false
|
5
vendor/github.com/containers/storage/pkg/regexp/regexp_precompile.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/pkg/regexp/regexp_precompile.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build regexp_precompile
|
||||||
|
|
||||||
|
package regexp
|
||||||
|
|
||||||
|
const precompile = true
|
|
@ -0,0 +1,17 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Chmod(name string, mode os.FileMode) error {
|
||||||
|
err := os.Chmod(name, mode)
|
||||||
|
|
||||||
|
for err != nil && errors.Is(err, syscall.EINTR) {
|
||||||
|
err = os.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Chtimes changes the access time and modified time of a file at the given path
|
||||||
|
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
unixMinTime := time.Unix(0, 0)
|
||||||
|
unixMaxTime := maxTime
|
||||||
|
|
||||||
|
// If the modified time is prior to the Unix Epoch, or after the
|
||||||
|
// end of Unix Time, os.Chtimes has undefined behavior
|
||||||
|
// default to Unix Epoch in this case, just in case
|
||||||
|
|
||||||
|
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
|
||||||
|
atime = unixMinTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
|
||||||
|
mtime = unixMinTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chtimes(name, atime, mtime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take platform specific action for setting create time.
|
||||||
|
if err := setCTime(name, mtime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setCTime will set the create time on a file. On Unix, the create
|
||||||
|
// time is updated as a side effect of setting the modified time, so
|
||||||
|
// no action is required.
|
||||||
|
func setCTime(path string, ctime time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
28
vendor/github.com/containers/storage/pkg/system/chtimes_windows.go
generated
vendored
Normal file
28
vendor/github.com/containers/storage/pkg/system/chtimes_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setCTime will set the create time on a file. On Windows, this requires
|
||||||
|
// calling SetFileTime and explicitly including the create time.
|
||||||
|
func setCTime(path string, ctime time.Time) error {
|
||||||
|
ctimespec := windows.NsecToTimespec(ctime.UnixNano())
|
||||||
|
pathp, e := windows.UTF16PtrFromString(path)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
h, e := windows.CreateFile(pathp,
|
||||||
|
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
|
||||||
|
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
defer windows.Close(h)
|
||||||
|
c := windows.NsecToFiletime(windows.TimespecToNsec(ctimespec))
|
||||||
|
return windows.SetFileTime(h, &c, nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotSupportedPlatform means the platform is not supported.
|
||||||
|
var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
|
@ -0,0 +1,33 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetExitCode returns the ExitStatus of the specified error if its type is
|
||||||
|
// exec.ExitError, returns 0 and an error otherwise.
|
||||||
|
func GetExitCode(err error) (int, error) {
|
||||||
|
exitCode := 0
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
return procExit.ExitStatus(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exitCode, fmt.Errorf("failed to get exit code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessExitCode process the specified error and returns the exit status code
|
||||||
|
// if the error was of type exec.ExitError, returns nothing otherwise.
|
||||||
|
func ProcessExitCode(err error) (exitCode int) {
|
||||||
|
if err != nil {
|
||||||
|
var exiterr error
|
||||||
|
if exitCode, exiterr = GetExitCode(err); exiterr != nil {
|
||||||
|
// TODO: Fix this so we check the error's text.
|
||||||
|
// we've failed to retrieve exit code, so we set it to 127
|
||||||
|
exitCode = 127
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
93
vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go
generated
vendored
Normal file
93
vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EXTATTR_NAMESPACE_EMPTY = unix.EXTATTR_NAMESPACE_EMPTY
|
||||||
|
EXTATTR_NAMESPACE_USER = unix.EXTATTR_NAMESPACE_USER
|
||||||
|
EXTATTR_NAMESPACE_SYSTEM = unix.EXTATTR_NAMESPACE_SYSTEM
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtattrGetLink retrieves the value of the extended attribute identified by attrname
|
||||||
|
// in the given namespace and associated with the given path in the file system.
|
||||||
|
// If the path is a symbolic link, the extended attribute is retrieved from the link itself.
|
||||||
|
// Returns a []byte slice if the extattr is set and nil otherwise.
|
||||||
|
func ExtattrGetLink(path string, attrnamespace int, attrname string) ([]byte, error) {
|
||||||
|
size, errno := unix.ExtattrGetLink(path, attrnamespace, attrname,
|
||||||
|
uintptr(unsafe.Pointer(nil)), 0)
|
||||||
|
if errno != nil {
|
||||||
|
if errno == unix.ENOATTR {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, &os.PathError{Op: "extattr_get_link", Path: path, Err: errno}
|
||||||
|
}
|
||||||
|
if size == 0 {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := make([]byte, size)
|
||||||
|
size, errno = unix.ExtattrGetLink(path, attrnamespace, attrname,
|
||||||
|
uintptr(unsafe.Pointer(&dest[0])), size)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, &os.PathError{Op: "extattr_get_link", Path: path, Err: errno}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtattrSetLink sets the value of extended attribute identified by attrname
|
||||||
|
// in the given namespace and associated with the given path in the file system.
|
||||||
|
// If the path is a symbolic link, the extended attribute is set on the link itself.
|
||||||
|
func ExtattrSetLink(path string, attrnamespace int, attrname string, data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
data = []byte{} // ensure non-nil for empty data
|
||||||
|
}
|
||||||
|
if _, errno := unix.ExtattrSetLink(path, attrnamespace, attrname,
|
||||||
|
uintptr(unsafe.Pointer(&data[0])), len(data)); errno != nil {
|
||||||
|
return &os.PathError{Op: "extattr_set_link", Path: path, Err: errno}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtattrListLink lists extended attributes associated with the given path
|
||||||
|
// in the specified namespace. If the path is a symbolic link, the attributes
|
||||||
|
// are listed from the link itself.
|
||||||
|
func ExtattrListLink(path string, attrnamespace int) ([]string, error) {
|
||||||
|
size, errno := unix.ExtattrListLink(path, attrnamespace,
|
||||||
|
uintptr(unsafe.Pointer(nil)), 0)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, &os.PathError{Op: "extattr_list_link", Path: path, Err: errno}
|
||||||
|
}
|
||||||
|
if size == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := make([]byte, size)
|
||||||
|
size, errno = unix.ExtattrListLink(path, attrnamespace,
|
||||||
|
uintptr(unsafe.Pointer(&dest[0])), size)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, &os.PathError{Op: "extattr_list_link", Path: path, Err: errno}
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrs []string
|
||||||
|
for i := 0; i < size; {
|
||||||
|
// Each attribute is preceded by a single byte length
|
||||||
|
length := int(dest[i])
|
||||||
|
i++
|
||||||
|
if i+length > size {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
attrs = append(attrs, string(dest[i:i+length]))
|
||||||
|
i += length
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs, nil
|
||||||
|
}
|
24
vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go
generated
vendored
Normal file
24
vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//go:build !freebsd
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
const (
|
||||||
|
EXTATTR_NAMESPACE_EMPTY = 0
|
||||||
|
EXTATTR_NAMESPACE_USER = 0
|
||||||
|
EXTATTR_NAMESPACE_SYSTEM = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtattrGetLink is not supported on platforms other than FreeBSD.
|
||||||
|
func ExtattrGetLink(path string, attrnamespace int, attrname string) ([]byte, error) {
|
||||||
|
return nil, ErrNotSupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtattrSetLink is not supported on platforms other than FreeBSD.
|
||||||
|
func ExtattrSetLink(path string, attrnamespace int, attrname string, data []byte) error {
|
||||||
|
return ErrNotSupportedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtattrListLink is not supported on platforms other than FreeBSD.
|
||||||
|
func ExtattrListLink(path string, attrnamespace int) ([]string, error) {
|
||||||
|
return nil, ErrNotSupportedPlatform
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxTime is used by chtimes.
|
||||||
|
var maxTime time.Time
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// chtimes initialization
|
||||||
|
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
||||||
|
// This is a 64 bit timespec
|
||||||
|
// os.Chtimes limits time to the following
|
||||||
|
maxTime = time.Unix(0, 1<<63-1)
|
||||||
|
} else {
|
||||||
|
// This is a 32 bit timespec
|
||||||
|
maxTime = time.Unix(1<<31-1, 0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// LCOWSupported determines if Linux Containers on Windows are supported.
|
||||||
|
// Note: This feature is in development (06/17) and enabled through an
|
||||||
|
// environment variable. At a future time, it will be enabled based
|
||||||
|
// on build number. @jhowardmsft
|
||||||
|
var lcowSupported = false
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// LCOW initialization
|
||||||
|
if os.Getenv("LCOW_SUPPORTED") != "" {
|
||||||
|
lcowSupported = true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flag values from <sys/stat.h>
|
||||||
|
const (
|
||||||
|
/*
|
||||||
|
* Definitions of flags stored in file flags word.
|
||||||
|
*
|
||||||
|
* Super-user and owner changeable flags.
|
||||||
|
*/
|
||||||
|
UF_SETTABLE uint32 = 0x0000ffff /* mask of owner changeable flags */
|
||||||
|
UF_NODUMP uint32 = 0x00000001 /* do not dump file */
|
||||||
|
UF_IMMUTABLE uint32 = 0x00000002 /* file may not be changed */
|
||||||
|
UF_APPEND uint32 = 0x00000004 /* writes to file may only append */
|
||||||
|
UF_OPAQUE uint32 = 0x00000008 /* directory is opaque wrt. union */
|
||||||
|
UF_NOUNLINK uint32 = 0x00000010 /* file may not be removed or renamed */
|
||||||
|
|
||||||
|
UF_SYSTEM uint32 = 0x00000080 /* Windows system file bit */
|
||||||
|
UF_SPARSE uint32 = 0x00000100 /* sparse file */
|
||||||
|
UF_OFFLINE uint32 = 0x00000200 /* file is offline */
|
||||||
|
UF_REPARSE uint32 = 0x00000400 /* Windows reparse point file bit */
|
||||||
|
UF_ARCHIVE uint32 = 0x00000800 /* file needs to be archived */
|
||||||
|
UF_READONLY uint32 = 0x00001000 /* Windows readonly file bit */
|
||||||
|
/* This is the same as the MacOS X definition of UF_HIDDEN. */
|
||||||
|
UF_HIDDEN uint32 = 0x00008000 /* file is hidden */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Super-user changeable flags.
|
||||||
|
*/
|
||||||
|
SF_SETTABLE uint32 = 0xffff0000 /* mask of superuser changeable flags */
|
||||||
|
SF_ARCHIVED uint32 = 0x00010000 /* file is archived */
|
||||||
|
SF_IMMUTABLE uint32 = 0x00020000 /* file may not be changed */
|
||||||
|
SF_APPEND uint32 = 0x00040000 /* writes to file may only append */
|
||||||
|
SF_NOUNLINK uint32 = 0x00100000 /* file may not be removed or renamed */
|
||||||
|
SF_SNAPSHOT uint32 = 0x00200000 /* snapshot inode */
|
||||||
|
)
|
||||||
|
|
||||||
|
func Lchflags(path string, flags uint32) error {
|
||||||
|
p, err := unix.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, e1 := unix.Syscall(unix.SYS_LCHFLAGS, uintptr(unsafe.Pointer(p)), uintptr(flags), 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
return e1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Lchown(name string, uid, gid int) error {
|
||||||
|
err := syscall.Lchown(name, uid, gid)
|
||||||
|
|
||||||
|
for err == syscall.EINTR {
|
||||||
|
err = syscall.Lchown(name, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "lchown", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||||
|
func LCOWSupported() bool {
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||||
|
func LCOWSupported() bool {
|
||||||
|
return lcowSupported
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lstat takes a path to a file and returns
|
||||||
|
// a system.StatT type pertaining to that file.
|
||||||
|
//
|
||||||
|
// Throws an error if the file does not exist
|
||||||
|
func Lstat(path string) (*StatT, error) {
|
||||||
|
s := &syscall.Stat_t{}
|
||||||
|
if err := syscall.Lstat(path, s); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "Lstat", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return fromStatT(s)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// Lstat calls os.Lstat to get a fileinfo interface back.
|
||||||
|
// This is then copied into our own locally defined structure.
|
||||||
|
func Lstat(path string) (*StatT, error) {
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromStatT(&fi)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
// MemInfo contains memory statistics of the host system.
|
||||||
|
type MemInfo struct {
|
||||||
|
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
||||||
|
// kernel binary code).
|
||||||
|
MemTotal int64
|
||||||
|
|
||||||
|
// Amount of free memory.
|
||||||
|
MemFree int64
|
||||||
|
|
||||||
|
// Total amount of swap space available.
|
||||||
|
SwapTotal int64
|
||||||
|
|
||||||
|
// Amount of swap space that is currently unused.
|
||||||
|
SwapFree int64
|
||||||
|
}
|
85
vendor/github.com/containers/storage/pkg/system/meminfo_freebsd.go
generated
vendored
Normal file
85
vendor/github.com/containers/storage/pkg/system/meminfo_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
//go:build freebsd && cgo
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #include <unistd.h>
|
||||||
|
// #include <sys/vmmeter.h>
|
||||||
|
// #include <sys/sysctl.h>
|
||||||
|
// #include <vm/vm_param.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func getMemInfo() (int64, int64, error) {
|
||||||
|
data, err := unix.SysctlRaw("vm.vmtotal")
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("can't get kernel info: %w", err)
|
||||||
|
}
|
||||||
|
if len(data) != C.sizeof_struct_vmtotal {
|
||||||
|
return -1, -1, fmt.Errorf("unexpected vmtotal size %d", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
total := (*C.struct_vmtotal)(unsafe.Pointer(&data[0]))
|
||||||
|
|
||||||
|
pagesize := int64(C.sysconf(C._SC_PAGESIZE))
|
||||||
|
npages := int64(C.sysconf(C._SC_PHYS_PAGES))
|
||||||
|
return pagesize * npages, pagesize * int64(total.t_free), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSwapInfo() (int64, int64, error) {
|
||||||
|
var (
|
||||||
|
total int64 = 0
|
||||||
|
used int64 = 0
|
||||||
|
)
|
||||||
|
swapCount, err := unix.SysctlUint32("vm.nswapdev")
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("reading vm.nswapdev: %w", err)
|
||||||
|
}
|
||||||
|
for i := 0; i < int(swapCount); i++ {
|
||||||
|
data, err := unix.SysctlRaw("vm.swap_info", i)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("reading vm.swap_info.%d: %w", i, err)
|
||||||
|
}
|
||||||
|
if len(data) != C.sizeof_struct_xswdev {
|
||||||
|
return -1, -1, fmt.Errorf("unexpected swap_info size %d", len(data))
|
||||||
|
}
|
||||||
|
xsw := (*C.struct_xswdev)(unsafe.Pointer(&data[0]))
|
||||||
|
total += int64(xsw.xsw_nblks)
|
||||||
|
used += int64(xsw.xsw_used)
|
||||||
|
}
|
||||||
|
pagesize := int64(C.sysconf(C._SC_PAGESIZE))
|
||||||
|
return pagesize * total, pagesize * (total - used), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||||
|
//
|
||||||
|
// MemInfo type.
|
||||||
|
func ReadMemInfo() (*MemInfo, error) {
|
||||||
|
MemTotal, MemFree, err := getMemInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting memory totals %w", err)
|
||||||
|
}
|
||||||
|
SwapTotal, SwapFree, err := getSwapInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting swap totals %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if MemTotal < 0 || MemFree < 0 || SwapTotal < 0 || SwapFree < 0 {
|
||||||
|
return nil, errors.New("getting system memory info")
|
||||||
|
}
|
||||||
|
|
||||||
|
meminfo := &MemInfo{}
|
||||||
|
// Total memory is total physical memory less than memory locked by kernel
|
||||||
|
meminfo.MemTotal = MemTotal
|
||||||
|
meminfo.MemFree = MemFree
|
||||||
|
meminfo.SwapTotal = SwapTotal
|
||||||
|
meminfo.SwapFree = SwapFree
|
||||||
|
|
||||||
|
return meminfo, nil
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||||
|
// MemInfo type.
|
||||||
|
func ReadMemInfo() (*MemInfo, error) {
|
||||||
|
file, err := os.Open("/proc/meminfo")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return parseMemInfo(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMemInfo parses the /proc/meminfo file into
|
||||||
|
// a MemInfo object given an io.Reader to the file.
|
||||||
|
// Throws error if there are problems reading from the file
|
||||||
|
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
|
||||||
|
meminfo := &MemInfo{}
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
// Expected format: ["MemTotal:", "1234", "kB"]
|
||||||
|
parts := strings.Fields(scanner.Text())
|
||||||
|
|
||||||
|
// Sanity checks: Skip malformed entries.
|
||||||
|
if len(parts) < 3 || parts[2] != "kB" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to bytes.
|
||||||
|
size, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bytes := int64(size) * units.KiB
|
||||||
|
|
||||||
|
switch parts[0] {
|
||||||
|
case "MemTotal:":
|
||||||
|
meminfo.MemTotal = bytes
|
||||||
|
case "MemFree:":
|
||||||
|
meminfo.MemFree = bytes
|
||||||
|
case "SwapTotal:":
|
||||||
|
meminfo.SwapTotal = bytes
|
||||||
|
case "SwapFree:":
|
||||||
|
meminfo.SwapFree = bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors that may have occurred during the reading of the file.
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return meminfo, nil
|
||||||
|
}
|
129
vendor/github.com/containers/storage/pkg/system/meminfo_solaris.go
generated
vendored
Normal file
129
vendor/github.com/containers/storage/pkg/system/meminfo_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
//go:build solaris && cgo
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #cgo CFLAGS: -std=c99
|
||||||
|
// #cgo LDFLAGS: -lkstat
|
||||||
|
// #include <unistd.h>
|
||||||
|
// #include <stdlib.h>
|
||||||
|
// #include <stdio.h>
|
||||||
|
// #include <kstat.h>
|
||||||
|
// #include <sys/swap.h>
|
||||||
|
// #include <sys/param.h>
|
||||||
|
// struct swaptable *allocSwaptable(int num) {
|
||||||
|
// struct swaptable *st;
|
||||||
|
// struct swapent *swapent;
|
||||||
|
// st = (struct swaptable *)malloc(num * sizeof(swapent_t) + sizeof (int));
|
||||||
|
// swapent = st->swt_ent;
|
||||||
|
// for (int i = 0; i < num; i++,swapent++) {
|
||||||
|
// swapent->ste_path = (char *)malloc(MAXPATHLEN * sizeof (char));
|
||||||
|
// }
|
||||||
|
// st->swt_n = num;
|
||||||
|
// return st;
|
||||||
|
//}
|
||||||
|
// void freeSwaptable (struct swaptable *st) {
|
||||||
|
// struct swapent *swapent = st->swt_ent;
|
||||||
|
// for (int i = 0; i < st->swt_n; i++,swapent++) {
|
||||||
|
// free(swapent->ste_path);
|
||||||
|
// }
|
||||||
|
// free(st);
|
||||||
|
// }
|
||||||
|
// swapent_t getSwapEnt(swapent_t *ent, int i) {
|
||||||
|
// return ent[i];
|
||||||
|
// }
|
||||||
|
// int64_t getPpKernel() {
|
||||||
|
// int64_t pp_kernel = 0;
|
||||||
|
// kstat_ctl_t *ksc;
|
||||||
|
// kstat_t *ks;
|
||||||
|
// kstat_named_t *knp;
|
||||||
|
// kid_t kid;
|
||||||
|
//
|
||||||
|
// if ((ksc = kstat_open()) == NULL) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// if ((ks = kstat_lookup(ksc, "unix", 0, "system_pages")) == NULL) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// if (((kid = kstat_read(ksc, ks, NULL)) == -1) ||
|
||||||
|
// ((knp = kstat_data_lookup(ks, "pp_kernel")) == NULL)) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// switch (knp->data_type) {
|
||||||
|
// case KSTAT_DATA_UINT64:
|
||||||
|
// pp_kernel = knp->value.ui64;
|
||||||
|
// break;
|
||||||
|
// case KSTAT_DATA_UINT32:
|
||||||
|
// pp_kernel = knp->value.ui32;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// pp_kernel *= sysconf(_SC_PAGESIZE);
|
||||||
|
// return (pp_kernel > 0 ? pp_kernel : -1);
|
||||||
|
// }
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// Get the system memory info using sysconf same as prtconf
|
||||||
|
func getTotalMem() int64 {
|
||||||
|
pagesize := C.sysconf(C._SC_PAGESIZE)
|
||||||
|
npages := C.sysconf(C._SC_PHYS_PAGES)
|
||||||
|
return int64(pagesize * npages)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFreeMem() int64 {
|
||||||
|
pagesize := C.sysconf(C._SC_PAGESIZE)
|
||||||
|
npages := C.sysconf(C._SC_AVPHYS_PAGES)
|
||||||
|
return int64(pagesize * npages)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||||
|
//
|
||||||
|
// MemInfo type.
|
||||||
|
func ReadMemInfo() (*MemInfo, error) {
|
||||||
|
ppKernel := C.getPpKernel()
|
||||||
|
MemTotal := getTotalMem()
|
||||||
|
MemFree := getFreeMem()
|
||||||
|
SwapTotal, SwapFree, err := getSysSwap()
|
||||||
|
|
||||||
|
if ppKernel < 0 || MemTotal < 0 || MemFree < 0 || SwapTotal < 0 ||
|
||||||
|
SwapFree < 0 {
|
||||||
|
return nil, fmt.Errorf("getting system memory info %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
meminfo := &MemInfo{}
|
||||||
|
// Total memory is total physical memory less than memory locked by kernel
|
||||||
|
meminfo.MemTotal = MemTotal - int64(ppKernel)
|
||||||
|
meminfo.MemFree = MemFree
|
||||||
|
meminfo.SwapTotal = SwapTotal
|
||||||
|
meminfo.SwapFree = SwapFree
|
||||||
|
|
||||||
|
return meminfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSysSwap() (int64, int64, error) {
|
||||||
|
var tSwap int64
|
||||||
|
var fSwap int64
|
||||||
|
var diskblksPerPage int64
|
||||||
|
num, err := C.swapctl(C.SC_GETNSWP, nil)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
st := C.allocSwaptable(num)
|
||||||
|
_, err = C.swapctl(C.SC_LIST, unsafe.Pointer(st))
|
||||||
|
if err != nil {
|
||||||
|
C.freeSwaptable(st)
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
diskblksPerPage = int64(C.sysconf(C._SC_PAGESIZE) >> C.DEV_BSHIFT)
|
||||||
|
for i := 0; i < int(num); i++ {
|
||||||
|
swapent := C.getSwapEnt(&st.swt_ent[0], C.int(i))
|
||||||
|
tSwap += int64(swapent.ste_pages) * diskblksPerPage
|
||||||
|
fSwap += int64(swapent.ste_free) * diskblksPerPage
|
||||||
|
}
|
||||||
|
C.freeSwaptable(st)
|
||||||
|
return tSwap, fSwap, nil
|
||||||
|
}
|
8
vendor/github.com/containers/storage/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//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
|
||||||
|
}
|
46
vendor/github.com/containers/storage/pkg/system/meminfo_windows.go
generated
vendored
Normal file
46
vendor/github.com/containers/storage/pkg/system/meminfo_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
//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))
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
//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))
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//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.")
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
//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
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
//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
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//go:build linux || freebsd || solaris || darwin
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsProcessAlive returns true if process with a given pid is running.
|
||||||
|
func IsProcessAlive(pid int) bool {
|
||||||
|
err := unix.Kill(pid, syscall.Signal(0))
|
||||||
|
if err == nil || err == unix.EPERM {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// KillProcess force-stops a process.
|
||||||
|
func KillProcess(pid int) {
|
||||||
|
_ = unix.Kill(pid, unix.SIGKILL)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/mount"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnsureRemoveAll wraps `os.RemoveAll` to check for specific errors that can
|
||||||
|
// often be remedied.
|
||||||
|
// Only use `EnsureRemoveAll` if you really want to make every effort to remove
|
||||||
|
// a directory.
|
||||||
|
//
|
||||||
|
// Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there
|
||||||
|
// can be a race between reading directory entries and then actually attempting
|
||||||
|
// to remove everything in the directory.
|
||||||
|
// These types of errors do not need to be returned since it's ok for the dir to
|
||||||
|
// be gone we can just retry the remove operation.
|
||||||
|
//
|
||||||
|
// This should not return a `os.ErrNotExist` kind of error under any circumstances
|
||||||
|
func EnsureRemoveAll(dir string) error {
|
||||||
|
notExistErr := make(map[string]bool)
|
||||||
|
|
||||||
|
// track retries
|
||||||
|
exitOnErr := make(map[string]int)
|
||||||
|
maxRetry := 1000
|
||||||
|
|
||||||
|
// Attempt a simple remove all first, this avoids the more expensive
|
||||||
|
// RecursiveUnmount call if not needed.
|
||||||
|
if err := os.RemoveAll(dir); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to unmount anything beneath this dir first
|
||||||
|
if err := mount.RecursiveUnmount(dir); err != nil {
|
||||||
|
logrus.Debugf("RecursiveUnmount on %s failed: %v", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := os.RemoveAll(dir)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the RemoveAll fails with a permission error, we
|
||||||
|
// may have immutable files so try to remove the
|
||||||
|
// immutable flag and redo the RemoveAll.
|
||||||
|
if errors.Is(err, syscall.EPERM) {
|
||||||
|
if err = resetFileFlags(dir); err != nil {
|
||||||
|
return fmt.Errorf("resetting file flags: %w", err)
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(dir)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pe, ok := err.(*os.PathError)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if notExistErr[pe.Path] {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
notExistErr[pe.Path] = true
|
||||||
|
|
||||||
|
// There is a race where some subdir can be removed but after the parent
|
||||||
|
// dir entries have been read.
|
||||||
|
// So the path could be from `os.Remove(subdir)`
|
||||||
|
// If the reported non-existent path is not the passed in `dir` we
|
||||||
|
// should just retry, but otherwise return with no error.
|
||||||
|
if pe.Path == dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsEBUSY(pe.Err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if e := mount.Unmount(pe.Path); e != nil {
|
||||||
|
return fmt.Errorf("while removing %s: %w", dir, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitOnErr[pe.Path] == maxRetry {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exitOnErr[pe.Path]++
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build !freebsd
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
// Reset file flags in a directory tree. This allows EnsureRemoveAll
|
||||||
|
// to delete trees which have the immutable flag set.
|
||||||
|
func resetFileFlags(dir string) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset file flags in a directory tree. This allows EnsureRemoveAll
|
||||||
|
// to delete trees which have the immutable flag set.
|
||||||
|
func resetFileFlags(dir string) error {
|
||||||
|
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err := Lchflags(path, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build !freebsd
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
type platformStatT struct{}
|
||||||
|
|
||||||
|
// Flags return file flags if supported or zero otherwise
|
||||||
|
func (s StatT) Flags() uint32 {
|
||||||
|
_ = s.platformStatT // Silence warnings that StatT.platformStatT is unused (on these platforms)
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||||
|
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||||
|
return &StatT{
|
||||||
|
size: s.Size,
|
||||||
|
mode: uint32(s.Mode),
|
||||||
|
uid: s.Uid,
|
||||||
|
gid: s.Gid,
|
||||||
|
rdev: uint64(s.Rdev),
|
||||||
|
mtim: s.Mtimespec,
|
||||||
|
}, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue