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"
|
||||
|
||||
# VM Image built in containers/automation_images
|
||||
_BUILT_IMAGE_SUFFIX: "c5878804328480768"
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${_BUILT_IMAGE_SUFFIX}"
|
||||
IMAGE_SUFFIX: "c20250324t111922z-f41f40d13"
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
|
||||
|
||||
# Must be defined true when testing w/in containers
|
||||
CONTAINER: "false"
|
||||
|
@ -21,16 +21,6 @@ env:
|
|||
gcp_credentials: ENCRYPTED[2ba9cffb563741f8538eab6d4a8b2d4684c0de23693a8ade80aced34596669a87a6c01e45ce45fad7f7db1d995e8c777]
|
||||
|
||||
|
||||
# Default VM to use unless set or modified by task
|
||||
gce_instance:
|
||||
image_project: "${IMAGE_PROJECT}"
|
||||
zone: "us-central1-c" # Required by Cirrus for the time being
|
||||
cpu: 2
|
||||
memory: "4Gb"
|
||||
disk: 200 # Required for performance reasons
|
||||
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||
|
||||
|
||||
# Update metadata on VM images referenced by this repository state
|
||||
meta_task:
|
||||
|
||||
|
@ -58,7 +48,7 @@ meta_task:
|
|||
vendor_task:
|
||||
|
||||
container:
|
||||
image: golang:1.18
|
||||
image: golang:1.23
|
||||
|
||||
script:
|
||||
- make vendor
|
||||
|
@ -68,7 +58,17 @@ vendor_task:
|
|||
build_and_test_task:
|
||||
|
||||
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
|
||||
gocache_cache:
|
||||
|
@ -100,6 +100,10 @@ build_and_test_task:
|
|||
- make test-unit
|
||||
- sudo make test-integration
|
||||
|
||||
# The hook runs in the background, errors will be in the journal.
|
||||
always:
|
||||
journal_script: journalctl -b
|
||||
|
||||
binaries_artifacts:
|
||||
path: "bin/*"
|
||||
type: "application/octet-stream"
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Renovate is a service similar to GitHub Dependabot, but with
|
||||
(fantastically) more configuration options. So many options
|
||||
in fact, if you're new I recommend glossing over this cheat-sheet
|
||||
prior to the official documentation:
|
||||
|
||||
https://www.augmentedmind.de/2021/07/25/renovate-bot-cheat-sheet
|
||||
|
||||
Configuration Update/Change Procedure:
|
||||
1. Make changes
|
||||
2. Manually validate changes (from repo-root):
|
||||
|
||||
podman run -it \
|
||||
-v ./.github/renovate.json5:/usr/src/app/renovate.json5:z \
|
||||
docker.io/renovate/renovate:latest \
|
||||
renovate-config-validator
|
||||
3. Commit.
|
||||
|
||||
Configuration Reference:
|
||||
https://docs.renovatebot.com/configuration-options/
|
||||
|
||||
Monitoring Dashboard:
|
||||
https://app.renovatebot.com/dashboard#github/containers
|
||||
|
||||
Note: The Renovate bot will create/manage it's business on
|
||||
branches named 'renovate/*'. Otherwise, and by
|
||||
default, the only the copy of this file that matters
|
||||
is the one on the `main` branch. No other branches
|
||||
will be monitored or touched in any way.
|
||||
*/
|
||||
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
|
||||
/*************************************************
|
||||
****** Global/general configuration options *****
|
||||
*************************************************/
|
||||
|
||||
// Re-use predefined sets of configuration options to DRY
|
||||
"extends": [
|
||||
// https://github.com/containers/automation/blob/main/renovate/defaults.json5
|
||||
"github>containers/automation//renovate/defaults.json5"
|
||||
],
|
||||
|
||||
// Permit automatic rebasing when base-branch changes by more than
|
||||
// one commit.
|
||||
"rebaseWhen": "behind-base-branch",
|
||||
|
||||
/*************************************************
|
||||
*** Repository-specific configuration options ***
|
||||
*************************************************/
|
||||
|
||||
"assignees": ["vrothberg", "rhatdan"],
|
||||
}
|
7
Makefile
7
Makefile
|
@ -6,6 +6,7 @@ GO_BUILD=$(GO) build
|
|||
ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true)
|
||||
GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor
|
||||
endif
|
||||
BUILDDIR ?= .
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
|
||||
|
@ -46,7 +47,7 @@ docs:
|
|||
|
||||
.PHONY: binary
|
||||
binary:
|
||||
$(GO_BUILD) -mod=vendor -o bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
||||
$(GO_BUILD) -mod=vendor -o $(BUILDDIR)/bin/oci-seccomp-bpf-hook -ldflags "-X main.version=$(OSBH_VERSION)" $(PROJECT)
|
||||
|
||||
.PHONY: validate
|
||||
validate:
|
||||
|
@ -73,7 +74,7 @@ test-unit:
|
|||
.PHONY: install.tools
|
||||
install.tools: .install.golangci-lint .install.md2man
|
||||
|
||||
.install.golangci-lint: VERSION=v1.45.2
|
||||
.install.golangci-lint: VERSION=v1.60.3
|
||||
.install.golangci-lint:
|
||||
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 $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_BIN_DIR)
|
||||
install $(SELINUXOPT) -d -m 755 $(DESTDIR)$(HOOK_DIR)
|
||||
install $(SELINUXOPT) -m 755 bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
||||
install $(SELINUXOPT) -m 755 $(BUILDDIR)/bin/oci-seccomp-bpf-hook $(DESTDIR)$(HOOK_BIN_DIR)
|
||||
install $(SELINUXOPT) -m 644 oci-seccomp-bpf-hook.json $(DESTDIR)$(HOOK_DIR)
|
||||
sed -i 's|HOOK_BIN_DIR|$(HOOK_BIN_DIR)|g' $(DESTDIR)$(HOOK_DIR)/oci-seccomp-bpf-hook.json
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM fedora:31
|
||||
FROM fedora:latest
|
||||
|
||||
ENV GOPATH=/var/tmp/go
|
||||
ENV GOSRC=$GOPATH/src/github.com/containers/oci-seccomp-bpf-hook
|
||||
|
|
|
@ -36,12 +36,6 @@ fi
|
|||
GOSRC="${GOSRC:-$(realpath $(dirname $0)/../../)}"
|
||||
SCRIPT_BASE="${SCRIPT_BASE:-./contrib/cirrus}"
|
||||
|
||||
# GCE image-name compatible string representation of distribution name
|
||||
OS_RELEASE_ID="$(source /etc/os-release; echo $ID)"
|
||||
# GCE image-name compatible string representation of distribution _major_ version
|
||||
OS_RELEASE_VER="$(source /etc/os-release; echo $VERSION_ID | cut -d '.' -f 1)"
|
||||
# Combined to ease soe usage
|
||||
OS_REL_VER="${OS_RELEASE_ID}-${OS_RELEASE_VER}"
|
||||
# Set 'true' when operating inside a container
|
||||
CONTAINER="${CONTAINER:-false}"
|
||||
|
||||
|
|
8
ebpf.go
8
ebpf.go
|
@ -18,7 +18,6 @@ type event struct {
|
|||
// Complete documentation is available at
|
||||
// https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
|
||||
const source string = `
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/pid_namespace.h>
|
||||
#include <linux/ns_common.h>
|
||||
|
@ -112,6 +111,13 @@ int enter_trace(struct tracepoint__raw_syscalls__sys_enter* args)
|
|||
// The syscall was already notified.
|
||||
if (seen > 0)
|
||||
return 0;
|
||||
|
||||
// if prctl was not seen, ignore the current syscall.
|
||||
u64 prctl = __NR_prctl;
|
||||
u64 *prctl_seen = seen_syscalls.lookup(&prctl);
|
||||
if (prctl_seen == NULL) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
data.stopTracing = false;
|
||||
|
|
30
go.mod
30
go.mod
|
@ -1,12 +1,28 @@
|
|||
module github.com/containers/oci-seccomp-bpf-hook
|
||||
|
||||
go 1.14
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.8
|
||||
|
||||
require (
|
||||
github.com/containers/common v0.48.0
|
||||
github.com/iovisor/gobpf v0.2.0
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/containers/common v0.62.3
|
||||
github.com/containers/storage v1.58.0
|
||||
github.com/iovisor/gobpf v0.2.1-0.20221005153822-16120a1bf4d4
|
||||
github.com/opencontainers/runtime-spec v1.2.1
|
||||
github.com/seccomp/libseccomp-golang v0.10.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/moby/sys/capability v0.4.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -23,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
types "github.com/containers/common/pkg/seccomp"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/iovisor/gobpf/bcc"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
seccomp "github.com/seccomp/libseccomp-golang"
|
||||
|
@ -57,6 +57,11 @@ func main() {
|
|||
logrus.AddHook(hook)
|
||||
}
|
||||
|
||||
if os.Getuid() != 0 || unshare.IsRootless() {
|
||||
logrus.Errorf("running the hook requires root privileges")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
runBPF := flag.Int("r", 0, "Trace the specified PID")
|
||||
outputFile := flag.String("o", "", "Path of the output file")
|
||||
inputFile := flag.String("i", "", "Path of the input file")
|
||||
|
@ -223,6 +228,13 @@ func runBPFSource(pid int, profilePath string, inputFile string) (finalErr error
|
|||
m := bcc.NewModule(src, []string{})
|
||||
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")
|
||||
enterTrace, err := m.LoadTracepoint("enter_trace")
|
||||
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)
|
||||
}
|
||||
|
||||
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.
|
||||
wg.Add(1)
|
||||
var events []event
|
||||
|
@ -322,7 +327,7 @@ func generateProfile(syscalls map[string]int, profilePath string, inputFile stri
|
|||
inputProfile := types.Seccomp{}
|
||||
|
||||
if inputFile != "" {
|
||||
input, err := ioutil.ReadFile(inputFile)
|
||||
input, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return 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 {
|
||||
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 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)
|
||||
}
|
||||
inputProfile := types.Seccomp{}
|
||||
input, err := ioutil.ReadFile(inputFile)
|
||||
input, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("%v: error reading input file: %q", errInvalidAnnotation, inputFile)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
@ -15,7 +14,7 @@ func TestParseAnnotation(t *testing.T) {
|
|||
testProfile := types.Seccomp{}
|
||||
testProfile.DefaultAction = types.ActErrno
|
||||
|
||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "input-*.json")
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), "input-*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create temporary file")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -71,11 +70,12 @@ var (
|
|||
// https://github.com/opencontainers/runtime-spec/pull/1064
|
||||
// specs.ActKillProcess ActKillProcess,
|
||||
// specs.ActKillThread ActKillThread,
|
||||
specs.ActErrno: ActErrno,
|
||||
specs.ActTrap: ActTrap,
|
||||
specs.ActAllow: ActAllow,
|
||||
specs.ActTrace: ActTrace,
|
||||
specs.ActLog: ActLog,
|
||||
specs.ActErrno: ActErrno,
|
||||
specs.ActTrap: ActTrap,
|
||||
specs.ActAllow: ActAllow,
|
||||
specs.ActTrace: ActTrace,
|
||||
specs.ActLog: ActLog,
|
||||
specs.ActNotify: ActNotify,
|
||||
}
|
||||
specOperatorToSeccompOperatorMap = map[specs.LinuxSeccompOperator]Operator{
|
||||
specs.OpNotEqual: OpNotEqual,
|
||||
|
@ -107,7 +107,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
|||
for _, arch := range spec.Architectures {
|
||||
newArch, err := specArchToSeccompArch(arch)
|
||||
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)
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
|||
// Convert default action
|
||||
newDefaultAction, err := specActionToSeccompAction(spec.DefaultAction)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert default action")
|
||||
return nil, fmt.Errorf("convert default action: %w", err)
|
||||
}
|
||||
res.DefaultAction = newDefaultAction
|
||||
res.DefaultErrnoRet = spec.DefaultErrnoRet
|
||||
|
@ -124,7 +124,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
|||
for _, call := range spec.Syscalls {
|
||||
newAction, err := specActionToSeccompAction(call.Action)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert action")
|
||||
return nil, fmt.Errorf("convert action: %w", err)
|
||||
}
|
||||
|
||||
for _, name := range call.Names {
|
||||
|
@ -139,7 +139,7 @@ func specToSeccomp(spec *specs.LinuxSeccomp) (*Seccomp, error) {
|
|||
for _, arg := range call.Args {
|
||||
newOp, err := specOperatorToSeccompOperator(arg.Op)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert operator")
|
||||
return nil, fmt.Errorf("convert operator: %w", err)
|
||||
}
|
||||
|
||||
newArg := Arg{
|
||||
|
@ -163,7 +163,7 @@ func specArchToLibseccompArch(arch specs.Arch) (string, error) {
|
|||
if res, ok := specArchToLibseccompArchMap[arch]; ok {
|
||||
return res, nil
|
||||
}
|
||||
return "", errors.Errorf(
|
||||
return "", fmt.Errorf(
|
||||
"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 {
|
||||
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.
|
||||
|
@ -181,7 +181,7 @@ func specActionToSeccompAction(action specs.LinuxSeccompAction) (Action, error)
|
|||
if res, ok := specActionToSeccompActionMap[action]; ok {
|
||||
return res, nil
|
||||
}
|
||||
return "", errors.Errorf(
|
||||
return "", fmt.Errorf(
|
||||
"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 {
|
||||
return op, nil
|
||||
}
|
||||
return "", errors.Errorf(
|
||||
return "", fmt.Errorf(
|
||||
"spec operator %q is not a valid internal operator", operator,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -51,9 +51,16 @@ func DefaultProfile() *Seccomp {
|
|||
{
|
||||
Names: []string{
|
||||
"bdflush",
|
||||
"cachestat",
|
||||
"futex_requeue",
|
||||
"futex_wait",
|
||||
"futex_waitv",
|
||||
"futex_wake",
|
||||
"io_pgetevents",
|
||||
"io_pgetevents_time64",
|
||||
"kexec_file_load",
|
||||
"kexec_load",
|
||||
"map_shadow_stack",
|
||||
"migrate_pages",
|
||||
"move_pages",
|
||||
"nfsservctl",
|
||||
|
@ -68,9 +75,9 @@ func DefaultProfile() *Seccomp {
|
|||
"pciconfig_write",
|
||||
"sgetmask",
|
||||
"ssetmask",
|
||||
"swapcontext",
|
||||
"swapoff",
|
||||
"swapon",
|
||||
"syscall",
|
||||
"sysfs",
|
||||
"uselib",
|
||||
"userfaultfd",
|
||||
|
@ -142,6 +149,7 @@ func DefaultProfile() *Seccomp {
|
|||
"fchdir",
|
||||
"fchmod",
|
||||
"fchmodat",
|
||||
"fchmodat2",
|
||||
"fchown",
|
||||
"fchown32",
|
||||
"fchownat",
|
||||
|
@ -221,6 +229,9 @@ func DefaultProfile() *Seccomp {
|
|||
"ipc",
|
||||
"keyctl",
|
||||
"kill",
|
||||
"landlock_add_rule",
|
||||
"landlock_create_ruleset",
|
||||
"landlock_restrict_self",
|
||||
"lchown",
|
||||
"lchown32",
|
||||
"lgetxattr",
|
||||
|
@ -306,7 +317,6 @@ func DefaultProfile() *Seccomp {
|
|||
"pwritev2",
|
||||
"read",
|
||||
"readahead",
|
||||
"readdir",
|
||||
"readlink",
|
||||
"readlinkat",
|
||||
"readv",
|
||||
|
@ -394,15 +404,12 @@ func DefaultProfile() *Seccomp {
|
|||
"shmdt",
|
||||
"shmget",
|
||||
"shutdown",
|
||||
"sigaction",
|
||||
"sigaltstack",
|
||||
"signal",
|
||||
"signalfd",
|
||||
"signalfd4",
|
||||
"sigpending",
|
||||
"sigprocmask",
|
||||
"sigreturn",
|
||||
"sigsuspend",
|
||||
"socketcall",
|
||||
"socketpair",
|
||||
"splice",
|
||||
|
@ -416,7 +423,6 @@ func DefaultProfile() *Seccomp {
|
|||
"sync",
|
||||
"sync_file_range",
|
||||
"syncfs",
|
||||
"syscall",
|
||||
"sysinfo",
|
||||
"syslog",
|
||||
"tee",
|
||||
|
@ -429,7 +435,6 @@ func DefaultProfile() *Seccomp {
|
|||
"timer_gettime64",
|
||||
"timer_settime",
|
||||
"timer_settime64",
|
||||
"timerfd",
|
||||
"timerfd_create",
|
||||
"timerfd_gettime",
|
||||
"timerfd_gettime64",
|
||||
|
@ -519,6 +524,7 @@ func DefaultProfile() *Seccomp {
|
|||
{
|
||||
Names: []string{
|
||||
"sync_file_range2",
|
||||
"swapcontext",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
|
@ -573,6 +579,16 @@ func DefaultProfile() *Seccomp {
|
|||
Arches: []string{"s390", "s390x"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"riscv_flush_icache",
|
||||
},
|
||||
Action: ActAllow,
|
||||
Args: []*Arg{},
|
||||
Includes: Filter{
|
||||
Arches: []string{"riscv64"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{
|
||||
"open_by_handle_at",
|
||||
|
@ -600,8 +616,8 @@ func DefaultProfile() *Seccomp {
|
|||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
"setdomainname",
|
||||
"sethostname",
|
||||
"setns",
|
||||
|
@ -614,11 +630,11 @@ func DefaultProfile() *Seccomp {
|
|||
},
|
||||
{
|
||||
Names: []string{
|
||||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
"setdomainname",
|
||||
"sethostname",
|
||||
"setns",
|
||||
|
@ -881,6 +897,50 @@ func DefaultProfile() *Seccomp {
|
|||
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{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build linux && seccomp
|
||||
// +build linux,seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build seccomp
|
||||
// +build seccomp
|
||||
|
||||
// NOTE: this package has originally been copied from
|
||||
// github.com/opencontainers/runc and modified to work for other use cases
|
||||
|
@ -7,8 +6,10 @@
|
|||
package seccomp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -39,39 +40,39 @@ func BuildFilter(spec *specs.LinuxSeccomp) (*libseccomp.ScmpFilter, error) {
|
|||
|
||||
profile, err := specToSeccomp(spec)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
for _, arch := range spec.Architectures {
|
||||
libseccompArch, err := specArchToLibseccompArch(arch)
|
||||
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)
|
||||
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 {
|
||||
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
|
||||
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
|
||||
|
@ -81,7 +82,7 @@ func BuildFilter(spec *specs.LinuxSeccomp) (*libseccomp.ScmpFilter, error) {
|
|||
}
|
||||
|
||||
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
|
||||
callAct, err := toAction(call.Action, call.ErrnoRet)
|
||||
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
|
||||
if len(call.Args) == 0 {
|
||||
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 {
|
||||
// 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 {
|
||||
newCond, err := toCondition(cond)
|
||||
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)
|
||||
}
|
||||
|
@ -150,13 +151,13 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
|
|||
condArr := []libseccomp.ScmpCondition{cond}
|
||||
|
||||
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 {
|
||||
// No conditions share same argument
|
||||
// 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) {
|
||||
switch act {
|
||||
case ActKill:
|
||||
return libseccomp.ActKill, nil
|
||||
// lint was not passing until this was changed from ActKill to ActKilThread.
|
||||
return libseccomp.ActKillThread, nil
|
||||
case ActKillProcess:
|
||||
return libseccomp.ActKillProcess, nil
|
||||
case ActErrno:
|
||||
|
@ -188,7 +190,7 @@ func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
|
|||
case ActLog:
|
||||
return libseccomp.ActLog, nil
|
||||
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)
|
||||
if err != nil {
|
||||
return cond, errors.Wrap(err, "convert compare operator")
|
||||
return cond, fmt.Errorf("convert compare operator: %w", err)
|
||||
}
|
||||
|
||||
condition, err := libseccomp.MakeCondition(
|
||||
arg.Index, op, arg.Value, arg.ValueTwo,
|
||||
)
|
||||
if err != nil {
|
||||
return cond, errors.Wrap(err, "make condition")
|
||||
return cond, fmt.Errorf("make condition: %w", err)
|
||||
}
|
||||
|
||||
return condition, nil
|
||||
|
@ -233,6 +235,6 @@ func toCompareOp(op Operator) (libseccomp.ScmpCompareOp, error) {
|
|||
case OpMaskedEqual:
|
||||
return libseccomp.CompareMaskedEqual, nil
|
||||
default:
|
||||
return libseccomp.CompareInvalid, errors.Errorf("invalid operator %s", op)
|
||||
return libseccomp.CompareInvalid, fmt.Errorf("invalid operator %s", op)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,9 +55,16 @@
|
|||
{
|
||||
"names": [
|
||||
"bdflush",
|
||||
"cachestat",
|
||||
"futex_requeue",
|
||||
"futex_wait",
|
||||
"futex_waitv",
|
||||
"futex_wake",
|
||||
"io_pgetevents",
|
||||
"io_pgetevents_time64",
|
||||
"kexec_file_load",
|
||||
"kexec_load",
|
||||
"map_shadow_stack",
|
||||
"migrate_pages",
|
||||
"move_pages",
|
||||
"nfsservctl",
|
||||
|
@ -72,9 +79,9 @@
|
|||
"pciconfig_write",
|
||||
"sgetmask",
|
||||
"ssetmask",
|
||||
"swapcontext",
|
||||
"swapoff",
|
||||
"swapon",
|
||||
"syscall",
|
||||
"sysfs",
|
||||
"uselib",
|
||||
"userfaultfd",
|
||||
|
@ -149,6 +156,7 @@
|
|||
"fchdir",
|
||||
"fchmod",
|
||||
"fchmodat",
|
||||
"fchmodat2",
|
||||
"fchown",
|
||||
"fchown32",
|
||||
"fchownat",
|
||||
|
@ -228,6 +236,9 @@
|
|||
"ipc",
|
||||
"keyctl",
|
||||
"kill",
|
||||
"landlock_add_rule",
|
||||
"landlock_create_ruleset",
|
||||
"landlock_restrict_self",
|
||||
"lchown",
|
||||
"lchown32",
|
||||
"lgetxattr",
|
||||
|
@ -313,7 +324,6 @@
|
|||
"pwritev2",
|
||||
"read",
|
||||
"readahead",
|
||||
"readdir",
|
||||
"readlink",
|
||||
"readlinkat",
|
||||
"readv",
|
||||
|
@ -401,15 +411,12 @@
|
|||
"shmdt",
|
||||
"shmget",
|
||||
"shutdown",
|
||||
"sigaction",
|
||||
"sigaltstack",
|
||||
"signal",
|
||||
"signalfd",
|
||||
"signalfd4",
|
||||
"sigpending",
|
||||
"sigprocmask",
|
||||
"sigreturn",
|
||||
"sigsuspend",
|
||||
"socketcall",
|
||||
"socketpair",
|
||||
"splice",
|
||||
|
@ -423,7 +430,6 @@
|
|||
"sync",
|
||||
"sync_file_range",
|
||||
"syncfs",
|
||||
"syscall",
|
||||
"sysinfo",
|
||||
"syslog",
|
||||
"tee",
|
||||
|
@ -436,7 +442,6 @@
|
|||
"timer_gettime64",
|
||||
"timer_settime",
|
||||
"timer_settime64",
|
||||
"timerfd",
|
||||
"timerfd_create",
|
||||
"timerfd_gettime",
|
||||
"timerfd_gettime64",
|
||||
|
@ -558,7 +563,8 @@
|
|||
},
|
||||
{
|
||||
"names": [
|
||||
"sync_file_range2"
|
||||
"sync_file_range2",
|
||||
"swapcontext"
|
||||
],
|
||||
"action": "SCMP_ACT_ALLOW",
|
||||
"args": [],
|
||||
|
@ -638,6 +644,20 @@
|
|||
},
|
||||
"excludes": {}
|
||||
},
|
||||
{
|
||||
"names": [
|
||||
"riscv_flush_icache"
|
||||
],
|
||||
"action": "SCMP_ACT_ALLOW",
|
||||
"args": [],
|
||||
"comment": "",
|
||||
"includes": {
|
||||
"arches": [
|
||||
"riscv64"
|
||||
]
|
||||
},
|
||||
"excludes": {}
|
||||
},
|
||||
{
|
||||
"names": [
|
||||
"open_by_handle_at"
|
||||
|
@ -673,8 +693,8 @@
|
|||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
"setdomainname",
|
||||
"sethostname",
|
||||
"setns"
|
||||
|
@ -691,11 +711,11 @@
|
|||
},
|
||||
{
|
||||
"names": [
|
||||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
"setdomainname",
|
||||
"sethostname",
|
||||
"setns"
|
||||
|
@ -1043,6 +1063,68 @@
|
|||
]
|
||||
},
|
||||
"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
|
||||
// +build seccomp
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !linux || !seccomp
|
||||
// +build !linux !seccomp
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
@ -15,12 +14,12 @@ import (
|
|||
|
||||
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) {
|
||||
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) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build linux && seccomp
|
||||
// +build linux,seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ const (
|
|||
ActTrace Action = "SCMP_ACT_TRACE"
|
||||
ActAllow Action = "SCMP_ACT_ALLOW"
|
||||
ActLog Action = "SCMP_ACT_LOG"
|
||||
ActNotify Action = "SCMP_ACT_NOTIFY"
|
||||
)
|
||||
|
||||
// Operator used to match syscall arguments in Seccomp
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
//go:build seccomp
|
||||
// +build seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ValidateProfile does a basic validation for the provided seccomp profile
|
||||
|
@ -14,16 +12,16 @@ import (
|
|||
func ValidateProfile(content string) error {
|
||||
profile := &Seccomp{}
|
||||
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)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create seccomp spec")
|
||||
return fmt.Errorf("create seccomp spec: %w", err)
|
||||
}
|
||||
|
||||
if _, err := BuildFilter(spec); err != nil {
|
||||
return errors.Wrap(err, "build seccomp filter")
|
||||
return fmt.Errorf("build seccomp filter: %w", err)
|
||||
}
|
||||
|
||||
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