diff --git a/go.mod b/go.mod index f806ac3dbf..1bd79f1f5c 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( github.com/checkpoint-restore/checkpointctl v1.3.0 github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/containernetworking/plugins v1.5.1 - github.com/containers/buildah v1.38.1-0.20241119213149-52437ef15d33 - github.com/containers/common v0.61.1-0.20250120135258-06628cb958e9 + github.com/containers/buildah v1.38.1 + github.com/containers/common v0.61.1 github.com/containers/conmon v2.0.20+incompatible github.com/containers/gvisor-tap-vsock v0.8.2 github.com/containers/image/v5 v5.33.2-0.20250122201336-16f7e1e0e1fd diff --git a/go.sum b/go.sum index 0c6dee6cd1..494a9a048f 100644 --- a/go.sum +++ b/go.sum @@ -76,10 +76,10 @@ github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8F github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ= github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM= -github.com/containers/buildah v1.38.1-0.20241119213149-52437ef15d33 h1:Ih6KuyByK7ZGGzkS0M5rVBPLWIyeDvdL5klhsKBo8vA= -github.com/containers/buildah v1.38.1-0.20241119213149-52437ef15d33/go.mod h1:RxIuKhwTpRl3ma4d4BF6QzSSeg9zNNvo/xhYJOKeDQs= -github.com/containers/common v0.61.1-0.20250120135258-06628cb958e9 h1:aiup0MIiAi2Xnv15vApAPqgy4/49ZGkYOpevDgGHfxg= -github.com/containers/common v0.61.1-0.20250120135258-06628cb958e9/go.mod h1:1S+/XhAEOwMGePCUqoYYh1iZo9fU1IpuIwVzCCIdBVU= +github.com/containers/buildah v1.38.1 h1:CVmzOFYqyTd5N9TVuU1mrMWn4ZtzGF6rcFgkbKmSOqY= +github.com/containers/buildah v1.38.1/go.mod h1:+RSztLYyDbf1+4R4XKpItWzdWrIN2KZwGoKi86JgYro= +github.com/containers/common v0.61.1 h1:jpk385ZFEx3MAX+sjwOoTZElvpgsGi0YJHuRmrhF/j8= +github.com/containers/common v0.61.1/go.mod h1:C+TfkhTV+ADp1Hu+BMIAYPvSFix21swYo9PZuCKoSUM= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/gvisor-tap-vsock v0.8.2 h1:uQMBCCHlIIj62fPjbvgm6AL5EzsP6TP5eviByOJEsOg= diff --git a/vendor/github.com/containers/buildah/.cirrus.yml b/vendor/github.com/containers/buildah/.cirrus.yml index 5ac4bd8b71..6f05489cb9 100644 --- a/vendor/github.com/containers/buildah/.cirrus.yml +++ b/vendor/github.com/containers/buildah/.cirrus.yml @@ -6,7 +6,7 @@ env: #### Global variables used for all tasks #### # Name of the ultimate destination branch for this CI run, PR or post-merge. - DEST_BRANCH: "main" + DEST_BRANCH: "release-1.38" GOPATH: "/var/tmp/go" GOSRC: "${GOPATH}/src/github.com/containers/buildah" # Overrides default location (/tmp/cirrus) for repo clone @@ -32,7 +32,7 @@ env: DEBIAN_NAME: "debian-13" # Image identifiers - IMAGE_SUFFIX: "c20241107t210000z-f41f40d13" + IMAGE_SUFFIX: "c20241106t163000z-f41f40d13" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" DEBIAN_CACHE_IMAGE_NAME: "debian-${IMAGE_SUFFIX}" @@ -77,7 +77,6 @@ meta_task: ${FEDORA_CACHE_IMAGE_NAME} ${PRIOR_FEDORA_CACHE_IMAGE_NAME} ${DEBIAN_CACHE_IMAGE_NAME} - build-push-${IMAGE_SUFFIX} BUILDID: "${CIRRUS_BUILD_ID}" REPOREF: "${CIRRUS_CHANGE_IN_REPO}" GCPJSON: ENCRYPTED[d3614d6f5cc0e66be89d4252b3365fd84f14eee0259d4eb47e25fc0bc2842c7937f5ee8c882b7e547b4c5ec4b6733b14] @@ -120,14 +119,13 @@ vendor_task: # Runs within Cirrus's "community cluster" container: - image: docker.io/library/golang:1.22 + image: docker.io/library/golang:latest cpu: 1 memory: 1 timeout_in: 5m vendor_script: - - './hack/check_vendor_toolchain.sh Try updating the image used by the vendor_task in .cirrus.yml.' - 'make vendor' - './hack/tree_status.sh' diff --git a/vendor/github.com/containers/buildah/.codespellrc b/vendor/github.com/containers/buildah/.codespellrc deleted file mode 100644 index 64a29fe602..0000000000 --- a/vendor/github.com/containers/buildah/.codespellrc +++ /dev/null @@ -1,3 +0,0 @@ -[codespell] -skip = ./vendor,./.git,./go.sum,./docs/*.1,./docker/AUTHORS,./CHANGELOG.md,./changelog.txt,./tests/tools/vendor,./tests/tools/go.mod,./tests/tools/go.sum -ignore-words-list = fo,passt,secon,erro diff --git a/vendor/github.com/containers/buildah/CHANGELOG.md b/vendor/github.com/containers/buildah/CHANGELOG.md index 4594421545..33fe242525 100644 --- a/vendor/github.com/containers/buildah/CHANGELOG.md +++ b/vendor/github.com/containers/buildah/CHANGELOG.md @@ -2,6 +2,19 @@ # Changelog +## v1.38.1 (2025-01-20) + + Fix TOCTOU error when bind and cache mounts use "src" values + define.TempDirForURL(): always use an intermediate subdirectory + internal/volume.GetBindMount(): discard writes in bind mounts + pkg/overlay: add a MountLabel flag to Options + pkg/overlay: add a ForceMount flag to Options + Add internal/volumes.bindFromChroot() + Add an internal/open package + Allow cache mounts to be stages or additional build contexts + [release-1.38][CI:DOCS] Touch up changelogs + [release-1.38] Bump c/storage v1.56.1, c/image v5.33.1, c/common v0.61.1 + ## v1.38.0 (2024-11-08) Bump to c/common v0.61.0, c/image v5.33.0, c/storage v1.56.0 @@ -123,7 +136,7 @@ Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions integration tests: teach starthttpd() about TLS and pid files -## vv1.37.0 (2024-07-26) +## v1.37.0 (2024-07-26) Bump c/storage, c/image, c/common for v1.37.0 "build with basename resolving user arg" tests: correct ARG use diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile index 8ca47072da..bf5e44097c 100644 --- a/vendor/github.com/containers/buildah/Makefile +++ b/vendor/github.com/containers/buildah/Makefile @@ -1,7 +1,7 @@ export GOPROXY=https://proxy.golang.org APPARMORTAG := $(shell hack/apparmor_tag.sh) -STORAGETAGS := $(shell ./btrfs_tag.sh) $(shell ./btrfs_installed_tag.sh) $(shell ./hack/libsubid_tag.sh) +STORAGETAGS := exclude_graphdriver_devicemapper $(shell ./btrfs_tag.sh) $(shell ./btrfs_installed_tag.sh) $(shell ./hack/libsubid_tag.sh) SECURITYTAGS ?= seccomp $(APPARMORTAG) TAGS ?= $(SECURITYTAGS) $(STORAGETAGS) $(shell ./hack/systemd_tag.sh) ifeq ($(shell uname -s),FreeBSD) @@ -29,16 +29,19 @@ RACEFLAGS := $(shell $(GO_TEST) -race ./pkg/dummy > /dev/null 2>&1 && echo -race COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO}) SOURCE_DATE_EPOCH ?= $(if $(shell date +%s),$(shell date +%s),$(error "date failed")) +STATIC_STORAGETAGS = "containers_image_openpgp $(STORAGE_TAGS)" # we get GNU make 3.x in MacOS build envs, which wants # to be escaped in # strings, while the 4.x we have on Linux doesn't. this is the documented # workaround COMMENT := \# CNI_COMMIT := $(shell sed -n 's;^$(COMMENT) github.com/containernetworking/cni \([^ \n]*\).*$$;\1;p' vendor/modules.txt) +RUNC_COMMIT := $(shell sed -n 's;^$(COMMENT) github.com/opencontainers/runc \([^ \n]*\).*$$;\1;p' vendor/modules.txt) +LIBSECCOMP_COMMIT := release-2.3 EXTRA_LDFLAGS ?= BUILDAH_LDFLAGS := $(GO_LDFLAGS) '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(EXTRA_LDFLAGS)' -SOURCES=*.go imagebuildah/*.go bind/*.go chroot/*.go copier/*.go define/*.go docker/*.go internal/config/*.go internal/mkcw/*.go internal/mkcw/types/*.go internal/parse/*.go internal/sbom/*.go internal/source/*.go internal/tmpdir/*.go internal/*.go internal/util/*.go internal/volumes/*.go manifests/*.go pkg/binfmt/*.go pkg/blobcache/*.go pkg/chrootuser/*.go pkg/cli/*.go pkg/completion/*.go pkg/formats/*.go pkg/jail/*.go pkg/overlay/*.go pkg/parse/*.go pkg/rusage/*.go pkg/sshagent/*.go pkg/umask/*.go pkg/util/*.go pkg/volumes/*.go util/*.go +SOURCES=*.go imagebuildah/*.go bind/*.go chroot/*.go copier/*.go define/*.go docker/*.go internal/config/*.go internal/mkcw/*.go internal/mkcw/types/*.go internal/open/*.go internal/parse/*.go internal/sbom/*.go internal/source/*.go internal/tmpdir/*.go internal/*.go internal/util/*.go internal/volumes/*.go manifests/*.go pkg/binfmt/*.go pkg/blobcache/*.go pkg/chrootuser/*.go pkg/cli/*.go pkg/completion/*.go pkg/formats/*.go pkg/jail/*.go pkg/overlay/*.go pkg/parse/*.go pkg/rusage/*.go pkg/sshagent/*.go pkg/umask/*.go pkg/util/*.go pkg/volumes/*.go util/*.go LINTFLAGS ?= @@ -119,8 +122,14 @@ clean: docs: install.tools ## build the docs on the host $(MAKE) -C docs +# For vendoring to work right, the checkout directory must be such that our top +# level is at $GOPATH/src/github.com/containers/buildah. +.PHONY: gopath +gopath: + test $(shell pwd) = $(shell cd ../../../../src/github.com/containers/buildah ; pwd) + codespell: - codespell -w + codespell -S Makefile,buildah.spec.rpkg,AUTHORS,bin,vendor,.git,go.mod,go.sum,CHANGELOG.md,changelog.txt,seccomp.json,.cirrus.yml,"*.xz,*.gz,*.tar,*.tgz,*ico,*.png,*.1,*.5,*.orig,*.rej" -L secon,passt,bu,uint,iff,od,erro -w .PHONY: validate validate: install.tools @@ -133,6 +142,25 @@ validate: install.tools install.tools: $(MAKE) -C tests/tools +.PHONY: runc +runc: gopath + rm -rf ../../opencontainers/runc + git clone https://github.com/opencontainers/runc ../../opencontainers/runc + cd ../../opencontainers/runc && git checkout $(RUNC_COMMIT) && $(GO) build -tags "$(STORAGETAGS) $(SECURITYTAGS)" + ln -sf ../../opencontainers/runc/runc + +.PHONY: install.libseccomp.sudo +install.libseccomp.sudo: gopath + rm -rf ../../seccomp/libseccomp + git clone https://github.com/seccomp/libseccomp ../../seccomp/libseccomp + cd ../../seccomp/libseccomp && git checkout $(LIBSECCOMP_COMMIT) && ./autogen.sh && ./configure --prefix=/usr && make all && sudo make install + +.PHONY: install.cni.sudo +install.cni.sudo: gopath + rm -rf ../../containernetworking/plugins + git clone https://github.com/containernetworking/plugins ../../containernetworking/plugins + cd ../../containernetworking/plugins && ./build_linux.sh && sudo install -D -v -m755 -t /opt/cni/bin/ bin/* + .PHONY: install install: install -d -m 755 $(DESTDIR)/$(BINDIR) @@ -150,6 +178,10 @@ install.completions: install -m 755 -d $(DESTDIR)/$(BASHINSTALLDIR) install -m 644 contrib/completions/bash/buildah $(DESTDIR)/$(BASHINSTALLDIR)/buildah +.PHONY: install.runc +install.runc: + install -m 755 ../../opencontainers/runc/runc $(DESTDIR)/$(BINDIR)/ + .PHONY: test-conformance test-conformance: $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -timeout 60m ./tests/conformance @@ -170,11 +202,10 @@ test-unit: tests/testreport/testreport $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover $(RACEFLAGS) ./cmd/buildah -args --root $$tmp/root --runroot $$tmp/runroot --storage-driver vfs --signature-policy $(shell pwd)/tests/policy.json --registries-conf $(shell pwd)/tests/registries.conf vendor-in-container: - goversion=$(shell sed -e '/^go /!d' -e '/^go /s,.* ,,g' go.mod) ; \ if test -d `go env GOCACHE` && test -w `go env GOCACHE` ; then \ - podman run --privileged --rm --env HOME=/root -v `go env GOCACHE`:/root/.cache/go-build --env GOCACHE=/root/.cache/go-build -v `pwd`:/src -w /src docker.io/library/golang:$$goversion make vendor ; \ + podman run --privileged --rm --env HOME=/root -v `go env GOCACHE`:/root/.cache/go-build --env GOCACHE=/root/.cache/go-build -v `pwd`:/src -w /src docker.io/library/golang:1.21 make vendor ; \ else \ - podman run --privileged --rm --env HOME=/root -v `pwd`:/src -w /src docker.io/library/golang:$$goversion make vendor ; \ + podman run --privileged --rm --env HOME=/root -v `pwd`:/src -w /src docker.io/library/golang:1.21 make vendor ; \ fi .PHONY: vendor diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go index 2e884ad89c..cb317c428d 100644 --- a/vendor/github.com/containers/buildah/add.go +++ b/vendor/github.com/containers/buildah/add.go @@ -495,8 +495,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption wg.Add(1) if sourceIsGit(src) { go func() { - var cloneDir string - cloneDir, _, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src) + var cloneDir, subdir string + cloneDir, subdir, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src) getOptions := copier.GetOptions{ UIDMap: srcUIDMap, GIDMap: srcGIDMap, @@ -511,7 +511,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption StripStickyBit: options.StripStickyBit, } writer := io.WriteCloser(pipeWriter) - getErr = copier.Get(cloneDir, cloneDir, getOptions, []string{"."}, writer) + repositoryDir := filepath.Join(cloneDir, subdir) + getErr = copier.Get(repositoryDir, repositoryDir, getOptions, []string{"."}, writer) pipeWriter.Close() wg.Done() }() diff --git a/vendor/github.com/containers/buildah/changelog.txt b/vendor/github.com/containers/buildah/changelog.txt index 2c41aea4e6..58faefdf15 100644 --- a/vendor/github.com/containers/buildah/changelog.txt +++ b/vendor/github.com/containers/buildah/changelog.txt @@ -1,3 +1,15 @@ +- Changelog for v1.38.1 (2025-01-20) + * Fix TOCTOU error when bind and cache mounts use "src" values + * define.TempDirForURL(): always use an intermediate subdirectory + * internal/volume.GetBindMount(): discard writes in bind mounts + * pkg/overlay: add a MountLabel flag to Options + * pkg/overlay: add a ForceMount flag to Options + * Add internal/volumes.bindFromChroot() + * Add an internal/open package + * Allow cache mounts to be stages or additional build contexts + * [release-1.38][CI:DOCS] Touch up changelogs + * [release-1.38] Bump c/storage v1.56.1, c/image v5.33.1, c/common v0.61.1 + - Changelog for v1.38.0 (2024-11-08) * Bump to c/common v0.61.0, c/image v5.33.0, c/storage v1.56.0 * fix(deps): update module golang.org/x/crypto to v0.29.0 @@ -118,7 +130,7 @@ * Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions * integration tests: teach starthttpd() about TLS and pid files -- Changelog for vv1.37.0 (2024-07-26) +- Changelog for v1.37.0 (2024-07-26) * Bump c/storage, c/image, c/common for v1.37.0 * "build with basename resolving user arg" tests: correct ARG use * bud-multiple-platform-no-run test: correct ARG use diff --git a/vendor/github.com/containers/buildah/copier/syscall_unix.go b/vendor/github.com/containers/buildah/copier/syscall_unix.go index f03503b32f..30356caa2c 100644 --- a/vendor/github.com/containers/buildah/copier/syscall_unix.go +++ b/vendor/github.com/containers/buildah/copier/syscall_unix.go @@ -77,12 +77,12 @@ func sameDevice(a, b os.FileInfo) bool { if aSys == nil || bSys == nil { return true } - uA, okA := aSys.(*syscall.Stat_t) - uB, okB := bSys.(*syscall.Stat_t) - if !okA || !okB { + au, aok := aSys.(*syscall.Stat_t) + bu, bok := bSys.(*syscall.Stat_t) + if !aok || !bok { return true } - return uA.Dev == uB.Dev + return au.Dev == bu.Dev } const ( diff --git a/vendor/github.com/containers/buildah/define/build.go b/vendor/github.com/containers/buildah/define/build.go index 359eec7d16..68f3455b34 100644 --- a/vendor/github.com/containers/buildah/define/build.go +++ b/vendor/github.com/containers/buildah/define/build.go @@ -379,6 +379,4 @@ type BuildOptions struct { // provides a minimal initial configuration with a working directory // set in it. CompatScratchConfig types.OptionalBool - // NoPivotRoot inhibits the usage of pivot_root when setting up the rootfs - NoPivotRoot bool } diff --git a/vendor/github.com/containers/buildah/define/types.go b/vendor/github.com/containers/buildah/define/types.go index 1b85ba1dad..153d34e852 100644 --- a/vendor/github.com/containers/buildah/define/types.go +++ b/vendor/github.com/containers/buildah/define/types.go @@ -29,7 +29,7 @@ const ( // identify working containers. Package = "buildah" // Version for the Package. Also used by .packit.sh for Packit builds. - Version = "1.39.0-dev" + Version = "1.38.1" // DefaultRuntime if containers.conf fails. DefaultRuntime = "runc" @@ -169,13 +169,13 @@ type SBOMScanOptions struct { MergeStrategy SBOMMergeStrategy // how to merge the outputs of multiple scans } -// TempDirForURL checks if the passed-in string looks like a URL or -. If it is, -// TempDirForURL creates a temporary directory, arranges for its contents to be -// the contents of that URL, and returns the temporary directory's path, along -// with the name of a subdirectory which should be used as the build context -// (which may be empty or "."). Removal of the temporary directory is the -// responsibility of the caller. If the string doesn't look like a URL, -// TempDirForURL returns empty strings and a nil error code. +// TempDirForURL checks if the passed-in string looks like a URL or "-". If it +// is, TempDirForURL creates a temporary directory, arranges for its contents +// to be the contents of that URL, and returns the temporary directory's path, +// along with the relative name of a subdirectory which should be used as the +// build context (which may be empty or "."). Removal of the temporary +// directory is the responsibility of the caller. If the string doesn't look +// like a URL or "-", TempDirForURL returns empty strings and a nil error code. func TempDirForURL(dir, prefix, url string) (name string, subdir string, err error) { if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") && @@ -188,19 +188,24 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err if err != nil { return "", "", fmt.Errorf("creating temporary directory for %q: %w", url, err) } + downloadDir := filepath.Join(name, "download") + if err = os.MkdirAll(downloadDir, 0o700); err != nil { + return "", "", fmt.Errorf("creating directory %q for %q: %w", downloadDir, url, err) + } urlParsed, err := urlpkg.Parse(url) if err != nil { return "", "", fmt.Errorf("parsing url %q: %w", url, err) } if strings.HasPrefix(url, "git://") || strings.HasSuffix(urlParsed.Path, ".git") { - combinedOutput, gitSubDir, err := cloneToDirectory(url, name) + combinedOutput, gitSubDir, err := cloneToDirectory(url, downloadDir) if err != nil { if err2 := os.RemoveAll(name); err2 != nil { logrus.Debugf("error removing temporary directory %q: %v", name, err2) } return "", "", fmt.Errorf("cloning %q to %q:\n%s: %w", url, name, string(combinedOutput), err) } - return name, gitSubDir, nil + logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, gitSubDir)) + return name, filepath.Join(filepath.Base(downloadDir), gitSubDir), nil } if strings.HasPrefix(url, "github.com/") { ghurl := url @@ -209,28 +214,29 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err subdir = path.Base(ghurl) + "-master" } if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { - err = downloadToDirectory(url, name) + err = downloadToDirectory(url, downloadDir) if err != nil { if err2 := os.RemoveAll(name); err2 != nil { logrus.Debugf("error removing temporary directory %q: %v", name, err2) } - return "", subdir, err + return "", "", err } - return name, subdir, nil + logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir)) + return name, filepath.Join(filepath.Base(downloadDir), subdir), nil } if url == "-" { - err = stdinToDirectory(name) + err = stdinToDirectory(downloadDir) if err != nil { if err2 := os.RemoveAll(name); err2 != nil { logrus.Debugf("error removing temporary directory %q: %v", name, err2) } - return "", subdir, err + return "", "", err } - logrus.Debugf("Build context is at %q", name) - return name, subdir, nil + logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir)) + return name, filepath.Join(filepath.Base(downloadDir), subdir), nil } logrus.Debugf("don't know how to retrieve %q", url) - if err2 := os.Remove(name); err2 != nil { + if err2 := os.RemoveAll(name); err2 != nil { logrus.Debugf("error removing temporary directory %q: %v", name, err2) } return "", "", errors.New("unreachable code reached") diff --git a/vendor/github.com/containers/buildah/imagebuildah/executor.go b/vendor/github.com/containers/buildah/imagebuildah/executor.go index e3ee9fc4fa..b2526d0390 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/executor.go @@ -163,7 +163,6 @@ type Executor struct { compatSetParent types.OptionalBool compatVolumes types.OptionalBool compatScratchConfig types.OptionalBool - noPivotRoot bool } type imageTypeAndHistoryAndDiffIDs struct { @@ -323,7 +322,6 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o compatSetParent: options.CompatSetParent, compatVolumes: options.CompatVolumes, compatScratchConfig: options.CompatScratchConfig, - noPivotRoot: options.NoPivotRoot, } if exec.err == nil { exec.err = os.Stderr diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go index 3b1784e750..e218799e6c 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go @@ -639,7 +639,12 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte // to `mountPoint` replaced from additional // build-context. Reason: Parser will use this // `from` to refer from stageMountPoints map later. - stageMountPoints[from] = internal.StageMountDetails{IsStage: false, DidExecute: true, MountPoint: mountPoint} + stageMountPoints[from] = internal.StageMountDetails{ + IsAdditionalBuildContext: true, + IsImage: true, + DidExecute: true, + MountPoint: mountPoint, + } break } // Most likely this points to path on filesystem @@ -671,7 +676,11 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte mountPoint = additionalBuildContext.DownloadedCache } } - stageMountPoints[from] = internal.StageMountDetails{IsStage: true, DidExecute: true, MountPoint: mountPoint} + stageMountPoints[from] = internal.StageMountDetails{ + IsAdditionalBuildContext: true, + DidExecute: true, + MountPoint: mountPoint, + } break } // If the source's name corresponds to the @@ -683,7 +692,11 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte // If the source's name is a stage, return a // pointer to its rootfs. if otherStage, ok := s.executor.stages[from]; ok && otherStage.index < s.index { - stageMountPoints[from] = internal.StageMountDetails{IsStage: true, DidExecute: otherStage.didExecute, MountPoint: otherStage.mountPoint} + stageMountPoints[from] = internal.StageMountDetails{ + IsStage: true, + DidExecute: otherStage.didExecute, + MountPoint: otherStage.mountPoint, + } break } else { // Treat the source's name as the name of an image. @@ -691,7 +704,11 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte if err != nil { return nil, fmt.Errorf("%s from=%s: no stage or image found with that name", flag, from) } - stageMountPoints[from] = internal.StageMountDetails{IsStage: false, DidExecute: true, MountPoint: mountPoint} + stageMountPoints[from] = internal.StageMountDetails{ + IsImage: true, + DidExecute: true, + MountPoint: mountPoint, + } break } default: @@ -800,7 +817,7 @@ func (s *StageExecutor) Run(run imagebuilder.Run, config docker.Config) error { NamespaceOptions: namespaceOptions, NoHostname: s.executor.noHostname, NoHosts: s.executor.noHosts, - NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "" || s.executor.noPivotRoot, + NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "", Quiet: s.executor.quiet, CompatBuiltinVolumes: types.OptionalBoolFalse, RunMounts: run.Mounts, diff --git a/vendor/github.com/containers/buildah/install.md b/vendor/github.com/containers/buildah/install.md index df8a82560e..87657897e3 100644 --- a/vendor/github.com/containers/buildah/install.md +++ b/vendor/github.com/containers/buildah/install.md @@ -194,8 +194,11 @@ In Fedora, you can use this command: Then to install Buildah on Fedora follow the steps in this example: ``` - git clone https://github.com/containers/buildah - cd buildah + mkdir ~/buildah + cd ~/buildah + export GOPATH=`pwd` + git clone https://github.com/containers/buildah ./src/github.com/containers/buildah + cd ./src/github.com/containers/buildah make sudo make install buildah --help @@ -249,10 +252,18 @@ In Ubuntu 22.10 (Karmic) or Debian 12 (Bookworm) you can use these commands: ``` sudo apt-get -y -qq update - sudo apt-get -y install bats btrfs-progs git go-md2man golang libapparmor-dev libglib2.0-dev libgpgme11-dev libseccomp-dev libselinux1-dev make runc skopeo libbtrfs-dev + sudo apt-get -y install bats btrfs-progs git go-md2man golang libapparmor-dev libglib2.0-dev libgpgme11-dev libseccomp-dev libselinux1-dev make skopeo libbtrfs-dev ``` -The build steps for Buildah on Debian or Ubuntu are the same as for Fedora, above. +Then to install Buildah follow the steps in this example: + +``` + git clone https://github.com/containers/buildah + cd buildah + make runc all SECURITYTAGS="apparmor seccomp" + sudo make install install.runc + buildah --help +``` ## Vendoring - Dependency Management diff --git a/vendor/github.com/containers/buildah/internal/open/open.go b/vendor/github.com/containers/buildah/internal/open/open.go new file mode 100644 index 0000000000..863dc79f21 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open.go @@ -0,0 +1,39 @@ +package open + +import ( + "errors" + "fmt" + "syscall" +) + +// InChroot opens the file at `path` after chrooting to `root` and then +// changing its working directory to `wd`. Both `wd` and `path` are evaluated +// in the chroot. +// Returns a file handle, an Errno value if there was an error and the +// underlying error was a standard library error code, and a non-empty error if +// one was detected. +func InChroot(root, wd, path string, mode int, perm uint32) (fd int, errno syscall.Errno, err error) { + requests := requests{ + Root: root, + Wd: wd, + Open: []request{ + { + Path: path, + Mode: mode, + Perms: perm, + }, + }, + } + results := inChroot(requests) + if len(results.Open) != 1 { + return -1, 0, fmt.Errorf("got %d results back instead of 1", len(results.Open)) + } + if results.Open[0].Err != "" { + if results.Open[0].Errno != 0 { + err = fmt.Errorf("%s: %w", results.Open[0].Err, results.Open[0].Errno) + } else { + err = errors.New(results.Open[0].Err) + } + } + return int(results.Open[0].Fd), results.Open[0].Errno, err +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_linux.go b/vendor/github.com/containers/buildah/internal/open/open_linux.go new file mode 100644 index 0000000000..3d9d608b5f --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_linux.go @@ -0,0 +1,88 @@ +package open + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/containers/storage/pkg/reexec" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +const ( + bindFdToPathCommand = "buildah-bind-fd-to-path" +) + +func init() { + reexec.Register(bindFdToPathCommand, bindFdToPathMain) +} + +// BindFdToPath creates a bind mount from the open file (which is actually a +// directory) to the specified location. If it succeeds, the caller will need +// to unmount the targetPath when it's finished using it. Regardless, it +// closes the passed-in descriptor. +func BindFdToPath(fd uintptr, targetPath string) error { + f := os.NewFile(fd, "passed-in directory descriptor") + defer func() { + if err := f.Close(); err != nil { + logrus.Debugf("closing descriptor %d after attempting to bind to %q: %v", fd, targetPath, err) + } + }() + pipeReader, pipeWriter, err := os.Pipe() + if err != nil { + return err + } + cmd := reexec.Command(bindFdToPathCommand) + cmd.Stdin = pipeReader + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr + cmd.ExtraFiles = append(cmd.ExtraFiles, f) + + err = cmd.Start() + pipeReader.Close() + if err != nil { + pipeWriter.Close() + return fmt.Errorf("starting child: %w", err) + } + + encoder := json.NewEncoder(pipeWriter) + if err := encoder.Encode(&targetPath); err != nil { + return fmt.Errorf("sending target path to child: %w", err) + } + pipeWriter.Close() + err = cmd.Wait() + trimmedOutput := strings.TrimSpace(stdout.String()) + strings.TrimSpace(stderr.String()) + if err != nil { + if len(trimmedOutput) > 0 { + err = fmt.Errorf("%s: %w", trimmedOutput, err) + } + } else { + if len(trimmedOutput) > 0 { + err = errors.New(trimmedOutput) + } + } + return err +} + +func bindFdToPathMain() { + var targetPath string + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(&targetPath); err != nil { + fmt.Fprintf(os.Stderr, "error decoding target path") + os.Exit(1) + } + if err := unix.Fchdir(3); err != nil { + fmt.Fprintf(os.Stderr, "fchdir(): %v", err) + os.Exit(1) + } + if err := unix.Mount(".", targetPath, "bind", unix.MS_BIND, ""); err != nil { + fmt.Fprintf(os.Stderr, "bind-mounting passed-in directory to %q: %v", targetPath, err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_types.go b/vendor/github.com/containers/buildah/internal/open/open_types.go new file mode 100644 index 0000000000..11dbe38db9 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_types.go @@ -0,0 +1,28 @@ +package open + +import ( + "syscall" +) + +type request struct { + Path string + Mode int + Perms uint32 +} + +type requests struct { + Root string + Wd string + Open []request +} + +type result struct { + Fd uintptr // as returned by open() + Err string // if err was not `nil`, err.Error() + Errno syscall.Errno // if err was not `nil` and included a syscall.Errno, its value +} + +type results struct { + Err string + Open []result +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_unix.go b/vendor/github.com/containers/buildah/internal/open/open_unix.go new file mode 100644 index 0000000000..fd254e8745 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_unix.go @@ -0,0 +1,168 @@ +//go:build linux || freebsd || darwin + +package open + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "syscall" + + "github.com/containers/storage/pkg/reexec" + "golang.org/x/sys/unix" +) + +const ( + inChrootCommand = "buildah-open-in-chroot" +) + +func init() { + reexec.Register(inChrootCommand, inChrootMain) +} + +func inChroot(requests requests) results { + sock, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) + if err != nil { + return results{Err: fmt.Errorf("creating socket pair: %w", err).Error()} + } + parentSock := sock[0] + childSock := sock[1] + parentEnd := os.NewFile(uintptr(parentSock), "parent end of socket pair") + childEnd := os.NewFile(uintptr(childSock), "child end of socket pair") + cmd := reexec.Command(inChrootCommand) + cmd.ExtraFiles = append(cmd.ExtraFiles, childEnd) + err = cmd.Start() + childEnd.Close() + defer parentEnd.Close() + if err != nil { + return results{Err: err.Error()} + } + encoder := json.NewEncoder(parentEnd) + if err := encoder.Encode(&requests); err != nil { + return results{Err: fmt.Errorf("sending request down socket: %w", err).Error()} + } + if err := unix.Shutdown(parentSock, unix.SHUT_WR); err != nil { + return results{Err: fmt.Errorf("finishing sending request down socket: %w", err).Error()} + } + b := make([]byte, 65536) + oob := make([]byte, 65536) + n, oobn, _, _, err := unix.Recvmsg(parentSock, b, oob, 0) + if err != nil { + return results{Err: fmt.Errorf("receiving message: %w", err).Error()} + } + if err := unix.Shutdown(parentSock, unix.SHUT_RD); err != nil { + return results{Err: fmt.Errorf("finishing socket: %w", err).Error()} + } + if n > len(b) { + return results{Err: fmt.Errorf("too much regular data: %d > %d", n, len(b)).Error()} + } + if oobn > len(oob) { + return results{Err: fmt.Errorf("too much OOB data: %d > %d", oobn, len(oob)).Error()} + } + scms, err := unix.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return results{Err: fmt.Errorf("parsing control message: %w", err).Error()} + } + var receivedFds []int + for i := range scms { + fds, err := unix.ParseUnixRights(&scms[i]) + if err != nil { + return results{Err: fmt.Errorf("parsing rights message %d: %w", i, err).Error()} + } + receivedFds = append(receivedFds, fds...) + } + decoder := json.NewDecoder(bytes.NewReader(b[:n])) + var result results + if err := decoder.Decode(&result); err != nil { + return results{Err: fmt.Errorf("decoding results: %w", err).Error()} + } + j := 0 + for i := range result.Open { + if result.Open[i].Err == "" { + if j >= len(receivedFds) { + for _, fd := range receivedFds { + unix.Close(fd) + } + return results{Err: fmt.Errorf("didn't receive enough FDs").Error()} + } + result.Open[i].Fd = uintptr(receivedFds[j]) + j++ + } + } + return result +} + +func inChrootMain() { + var theseRequests requests + var theseResults results + sockFd := 3 + sock := os.NewFile(uintptr(sockFd), "socket connection to parent process") + defer sock.Close() + encoder := json.NewEncoder(sock) + decoder := json.NewDecoder(sock) + if err := decoder.Decode(&theseRequests); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("decoding request: %w", err).Error()}); err != nil { + os.Exit(1) + } + } + if theseRequests.Root != "" { + if err := os.Chdir(theseRequests.Root); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q: %w", theseRequests.Root, err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + if err := unix.Chroot(theseRequests.Root); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("chrooting to %q: %w", theseRequests.Root, err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + if err := os.Chdir("/"); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("changing to new root: %w", err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + } + if theseRequests.Wd != "" { + if err := os.Chdir(theseRequests.Wd); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q in chroot: %w", theseRequests.Wd, err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + } + var fds []int + for _, request := range theseRequests.Open { + fd, err := unix.Open(request.Path, request.Mode, request.Perms) + thisResult := result{Fd: uintptr(fd)} + if err == nil { + fds = append(fds, fd) + } else { + var errno syscall.Errno + thisResult.Err = err.Error() + if errors.As(err, &errno) { + thisResult.Errno = errno + } + } + theseResults.Open = append(theseResults.Open, thisResult) + } + rights := unix.UnixRights(fds...) + inband, err := json.Marshal(&theseResults) + if err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + if err := unix.Sendmsg(sockFd, inband, rights, nil, 0); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + os.Exit(0) +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_unsupported.go b/vendor/github.com/containers/buildah/internal/open/open_unsupported.go new file mode 100644 index 0000000000..111056a18b --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_unsupported.go @@ -0,0 +1,7 @@ +//go:build !linux && !freebsd && !darwin + +package open + +func inChroot(requests requests) results { + return results{Err: "open-in-chroot not available on this platform"} +} diff --git a/vendor/github.com/containers/buildah/internal/types.go b/vendor/github.com/containers/buildah/internal/types.go index 1c8ef72434..5b14285cde 100644 --- a/vendor/github.com/containers/buildah/internal/types.go +++ b/vendor/github.com/containers/buildah/internal/types.go @@ -12,7 +12,9 @@ const ( // StageExecutor has ability to mount stages/images in current context and // automatically clean them up. type StageMountDetails struct { - DidExecute bool // tells if the stage which is being mounted was freshly executed or was part of older cache - IsStage bool // true if the mountpoint is a temporary directory or a stage's rootfs, false if it's an image - MountPoint string // mountpoint of the stage or image's root directory + DidExecute bool // true if this is a freshly-executed stage, or an image, possibly from a non-local cache + IsStage bool // true if the mountpoint is a stage's rootfs + IsImage bool // true if the mountpoint is an image's rootfs + IsAdditionalBuildContext bool // true if the mountpoint is an additional build context + MountPoint string // mountpoint of the stage or image's root directory or path of the additional build context } diff --git a/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go b/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go new file mode 100644 index 0000000000..f8723eb080 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go @@ -0,0 +1,102 @@ +package volumes + +import ( + "errors" + "fmt" + "os" + + "github.com/containers/buildah/internal/open" + "github.com/containers/storage/pkg/mount" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// bindFromChroot opens "path" inside of "root" using a chrooted subprocess +// that returns a descriptor, then creates a uniquely-named temporary directory +// or file under "tmp" and bind-mounts the opened descriptor to it, returning +// the path of the temporary file or directory. The caller is responsible for +// unmounting and removing the temporary. +func bindFromChroot(root, path, tmp string) (string, error) { + fd, _, err := open.InChroot(root, "", path, unix.O_DIRECTORY|unix.O_RDONLY, 0) + if err != nil { + if !errors.Is(err, unix.ENOTDIR) { + return "", fmt.Errorf("opening directory %q under %q: %w", path, root, err) + } + fd, _, err = open.InChroot(root, "", path, unix.O_RDWR, 0) + if err != nil { + return "", fmt.Errorf("opening non-directory %q under %q: %w", path, root, err) + } + } + defer func() { + if err := unix.Close(fd); err != nil { + logrus.Debugf("closing %q under %q: %v", path, root, err) + } + }() + + succeeded := false + var dest string + var destF *os.File + defer func() { + if !succeeded { + if destF != nil { + if err := destF.Close(); err != nil { + logrus.Debugf("closing bind target %q: %v", dest, err) + } + } + if dest != "" { + if err := os.Remove(dest); err != nil { + logrus.Debugf("removing bind target %q: %v", dest, err) + } + } + } + }() + + var st unix.Stat_t + if err = unix.Fstat(fd, &st); err != nil { + return "", fmt.Errorf("checking if %q under %q was a directory: %w", path, root, err) + } + + if st.Mode&unix.S_IFDIR == unix.S_IFDIR { + if dest, err = os.MkdirTemp(tmp, "bind"); err != nil { + return "", fmt.Errorf("creating a bind target directory: %w", err) + } + } else { + if destF, err = os.CreateTemp(tmp, "bind"); err != nil { + return "", fmt.Errorf("creating a bind target non-directory: %w", err) + } + if err := destF.Close(); err != nil { + logrus.Debugf("closing bind target %q: %v", dest, err) + } + dest = destF.Name() + } + defer func() { + if !succeeded { + if err := os.Remove(dest); err != nil { + logrus.Debugf("removing bind target %q: %v", dest, err) + } + } + }() + + if err := unix.Mount(fmt.Sprintf("/proc/self/fd/%d", fd), dest, "bind", unix.MS_BIND, ""); err != nil { + return "", fmt.Errorf("bind-mounting passed-in descriptor to %q: %w", dest, err) + } + defer func() { + if !succeeded { + if err := mount.Unmount(dest); err != nil { + logrus.Debugf("unmounting bound target %q: %v", dest, err) + } + } + }() + + var st2 unix.Stat_t + if err = unix.Stat(dest, &st2); err != nil { + return "", fmt.Errorf("looking up device/inode of newly-bind-mounted %q: %w", dest, err) + } + + if st2.Dev != st.Dev || st2.Ino != st.Ino { + return "", fmt.Errorf("device/inode weren't what we expected after bind mounting: %w", err) + } + + succeeded = true + return dest, nil +} diff --git a/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go b/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go new file mode 100644 index 0000000000..d9340c188f --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go @@ -0,0 +1,15 @@ +//go:build !linux + +package volumes + +import "errors" + +// bindFromChroot would open "path" inside of "root" using a chrooted +// subprocess that returns a descriptor, then would create a uniquely-named +// temporary directory or file under "tmp" and bind-mount the opened descriptor +// to it, returning the path of the temporary file or directory. The caller +// would be responsible for unmounting and removing the temporary. For now, +// this just returns an error because it is not implemented for this platform. +func bindFromChroot(root, path, tmp string) (string, error) { + return "", errors.New("not available on this system") +} diff --git a/vendor/github.com/containers/buildah/internal/volumes/volumes.go b/vendor/github.com/containers/buildah/internal/volumes/volumes.go index fa9a8bb35e..8ad37af670 100644 --- a/vendor/github.com/containers/buildah/internal/volumes/volumes.go +++ b/vendor/github.com/containers/buildah/internal/volumes/volumes.go @@ -16,15 +16,19 @@ import ( internalParse "github.com/containers/buildah/internal/parse" "github.com/containers/buildah/internal/tmpdir" internalUtil "github.com/containers/buildah/internal/util" + "github.com/containers/buildah/pkg/overlay" "github.com/containers/common/pkg/parse" "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/lockfile" + "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/unshare" digest "github.com/opencontainers/go-digest" specs "github.com/opencontainers/runtime-spec/specs-go" selinux "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" ) const ( @@ -55,18 +59,84 @@ func CacheParent() string { return filepath.Join(tmpdir.GetTempDir(), buildahCacheDir+"-"+strconv.Itoa(unshare.GetRootlessUID())) } +func mountIsReadWrite(m specs.Mount) bool { + // in case of conflicts, the last one wins, so it's not enough + // to check for the presence of either "rw" or "ro" anywhere + // with e.g. slices.Contains() + rw := true + for _, option := range m.Options { + switch option { + case "rw": + rw = true + case "ro": + rw = false + } + } + return rw +} + +func convertToOverlay(m specs.Mount, store storage.Store, mountLabel, tmpDir string, uid, gid int) (specs.Mount, string, error) { + overlayDir, err := overlay.TempDir(tmpDir, uid, gid) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay for %q: %w", m.Destination, err) + } + options := overlay.Options{GraphOpts: slices.Clone(store.GraphOptions()), ForceMount: true, MountLabel: mountLabel} + fileInfo, err := os.Stat(m.Source) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", m.Source, err) + } + // we might be trying to "overlay" for a non-directory, and the kernel doesn't like that very much + var mountThisInstead specs.Mount + if fileInfo.IsDir() { + // do the normal thing of mounting this directory as a lower with a temporary upper + mountThisInstead, err = overlay.MountWithOptions(overlayDir, m.Source, m.Destination, &options) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", m.Source, err) + } + } else { + // mount the parent directory as the lower with a temporary upper, and return a + // bind mount from the non-directory in the merged directory to the destination + sourceDir := filepath.Dir(m.Source) + sourceBase := filepath.Base(m.Source) + destination := m.Destination + mountedOverlay, err := overlay.MountWithOptions(overlayDir, sourceDir, destination, &options) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", sourceDir, err) + } + if mountedOverlay.Type != define.TypeBind { + if err2 := overlay.RemoveTemp(overlayDir); err2 != nil { + return specs.Mount{}, "", fmt.Errorf("cleaning up after failing to set up overlay: %v, while setting up overlay for %q: %w", err2, destination, err) + } + return specs.Mount{}, "", fmt.Errorf("setting up overlay for %q at %q: %w", mountedOverlay.Source, destination, err) + } + mountThisInstead = mountedOverlay + mountThisInstead.Source = filepath.Join(mountedOverlay.Source, sourceBase) + mountThisInstead.Destination = destination + } + return mountThisInstead, overlayDir, nil +} + // FIXME: this code needs to be merged with pkg/parse/parse.go ValidateVolumeOpts +// // GetBindMount parses a single bind mount entry from the --mount flag. -// Returns specifiedMount and a string which contains name of image that we mounted otherwise its empty. -// Caller is expected to perform unmount of any mounted images -func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, store storage.Store, imageMountLabel string, additionalMountPoints map[string]internal.StageMountDetails, workDir string) (specs.Mount, string, error) { +// +// Returns a Mount to add to the runtime spec's list of mounts, the ID of the +// image we mounted if we mounted one, the path of a mounted location if one +// needs to be unmounted and removed, and the path of an overlay mount if one +// needs to be cleaned up, or an error. +// +// The caller is expected to, after the command which uses the mount exits, +// clean up the overlay filesystem (if we provided a path to it), unmount and +// remove the mountpoint for the mounted filesystem (if we provided the path to +// its mountpoint), and then unmount the image (if we mounted one). +func GetBindMount(sys *types.SystemContext, args []string, contextDir string, store storage.Store, mountLabel string, additionalMountPoints map[string]internal.StageMountDetails, workDir, tmpDir string) (specs.Mount, string, string, string, error) { newMount := specs.Mount{ Type: define.TypeBind, } - setRelabel := false - mountReadability := false - setDest := false + setRelabel := "" + mountReadability := "" + setDest := "" bindNonRecursive := false fromImage := "" @@ -79,86 +149,85 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st case "bind-nonrecursive": newMount.Options = append(newMount.Options, "bind") bindNonRecursive = true - case "ro", "nosuid", "nodev", "noexec": + case "nosuid", "nodev", "noexec": // TODO: detect duplication of these options. // (Is this necessary?) newMount.Options = append(newMount.Options, argName) - mountReadability = true case "rw", "readwrite": newMount.Options = append(newMount.Options, "rw") - mountReadability = true - case "readonly": - // Alias for "ro" + mountReadability = "rw" + case "ro", "readonly": newMount.Options = append(newMount.Options, "ro") - mountReadability = true + mountReadability = "ro" case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z", "U", "no-dereference": if hasArgValue { - return newMount, "", fmt.Errorf("%v: %w", val, errBadOptionArg) + return newMount, "", "", "", fmt.Errorf("%v: %w", val, errBadOptionArg) } newMount.Options = append(newMount.Options, argName) case "from": if !hasArgValue { - return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) } fromImage = argValue case "bind-propagation": if !hasArgValue { - return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) } switch argValue { default: - return newMount, "", fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption) + return newMount, "", "", "", fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption) case "shared", "rshared", "private", "rprivate", "slave", "rslave": // this should be the relevant parts of the same list of options we accepted above } newMount.Options = append(newMount.Options, argValue) case "src", "source": if !hasArgValue { - return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) } newMount.Source = argValue case "target", "dst", "destination": if !hasArgValue { - return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) } targetPath := argValue + setDest = targetPath if !path.IsAbs(targetPath) { targetPath = filepath.Join(workDir, targetPath) } if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { - return newMount, "", err + return newMount, "", "", "", err } newMount.Destination = targetPath - setDest = true case "relabel": - if setRelabel { - return newMount, "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg) + if setRelabel != "" { + return newMount, "", "", "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg) } - setRelabel = true + setRelabel = argValue switch argValue { case "private": newMount.Options = append(newMount.Options, "Z") case "shared": newMount.Options = append(newMount.Options, "z") default: - return newMount, "", fmt.Errorf("%s mount option must be 'private' or 'shared': %w", argName, errBadMntOption) + return newMount, "", "", "", fmt.Errorf("%s mount option must be 'private' or 'shared': %w", argName, errBadMntOption) } case "consistency": // Option for OS X only, has no meaning on other platforms // and can thus be safely ignored. // See also the handling of the equivalent "delegated" and "cached" in ValidateVolumeOpts default: - return newMount, "", fmt.Errorf("%v: %w", argName, errBadMntOption) + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadMntOption) } } // default mount readability is always readonly - if !mountReadability { + if mountReadability == "" { newMount.Options = append(newMount.Options, "ro") } // Following variable ensures that we return imagename only if we did additional mount - isImageMounted := false + succeeded := false + mountedImage := "" if fromImage != "" { mountPoint := "" if additionalMountPoints != nil { @@ -169,16 +238,23 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st // if mountPoint of image was not found in additionalMap // or additionalMap was nil, try mounting image if mountPoint == "" { - image, err := internalUtil.LookupImage(ctx, store, fromImage) + image, err := internalUtil.LookupImage(sys, store, fromImage) if err != nil { - return newMount, "", err + return newMount, "", "", "", err } - mountPoint, err = image.Mount(context.Background(), nil, imageMountLabel) + mountPoint, err = image.Mount(context.Background(), nil, mountLabel) if err != nil { - return newMount, "", err - } - isImageMounted = true + return newMount, "", "", "", err + } + mountedImage = image.ID() + defer func() { + if !succeeded { + if _, err := store.UnmountImage(mountedImage, false); err != nil { + logrus.Debugf("unmounting bind-mounted image %q: %v", fromImage, err) + } + } + }() } contextDir = mountPoint } @@ -189,48 +265,73 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st newMount.Options = append(newMount.Options, "rbind") } - if !setDest { - return newMount, fromImage, errBadVolDest + if setDest == "" { + return newMount, "", "", "", errBadVolDest } // buildkit parity: support absolute path for sources from current build context if contextDir != "" { // path should be /contextDir/specified path - evaluated, err := copier.Eval(contextDir, newMount.Source, copier.EvalOptions{}) + evaluated, err := copier.Eval(contextDir, contextDir+string(filepath.Separator)+newMount.Source, copier.EvalOptions{}) if err != nil { - return newMount, "", err + return newMount, "", "", "", err } newMount.Source = evaluated } else { // looks like its coming from `build run --mount=type=bind` allow using absolute path // error out if no source is set if newMount.Source == "" { - return newMount, "", errBadVolSrc + return newMount, "", "", "", errBadVolSrc } if err := parse.ValidateVolumeHostDir(newMount.Source); err != nil { - return newMount, "", err + return newMount, "", "", "", err } } opts, err := parse.ValidateVolumeOpts(newMount.Options) if err != nil { - return newMount, fromImage, err + return newMount, "", "", "", err } newMount.Options = opts - if !isImageMounted { - // we don't want any cleanups if image was not mounted explicitly - // so dont return anything - fromImage = "" + var intermediateMount string + if contextDir != "" && newMount.Source != contextDir { + rel, err := filepath.Rel(contextDir, newMount.Source) + if err != nil { + return newMount, "", "", "", fmt.Errorf("computing pathname of bind subdirectory: %w", err) + } + if rel != "." && rel != "/" { + mnt, err := bindFromChroot(contextDir, rel, tmpDir) + if err != nil { + return newMount, "", "", "", fmt.Errorf("sanitizing bind subdirectory %q: %w", newMount.Source, err) + } + logrus.Debugf("bind-mounted %q under %q to %q", rel, contextDir, mnt) + intermediateMount = mnt + newMount.Source = intermediateMount + } } - return newMount, fromImage, nil + overlayDir := "" + if mountedImage != "" || mountIsReadWrite(newMount) { + if newMount, overlayDir, err = convertToOverlay(newMount, store, mountLabel, tmpDir, 0, 0); err != nil { + return newMount, "", "", "", err + } + } + + succeeded = true + return newMount, mountedImage, intermediateMount, overlayDir, nil } // GetCacheMount parses a single cache mount entry from the --mount flag. // -// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??). -func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoints map[string]internal.StageMountDetails, workDir string) (specs.Mount, *lockfile.LockFile, error) { +// Returns a Mount to add to the runtime spec's list of mounts, the path of a +// mounted filesystem if one needs to be unmounted, and an optional lock that +// needs to be released, or an error. +// +// The caller is expected to, after the command which uses the mount exits, +// unmount and remove the mountpoint of the mounted filesystem (if we provided +// the path to its mountpoint) and release the lock (if we took one). +func GetCacheMount(args []string, additionalMountPoints map[string]internal.StageMountDetails, workDir, tmpDir string) (specs.Mount, string, *lockfile.LockFile, error) { var err error var mode uint64 var buildahLockFilesDir string @@ -281,69 +382,69 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin sharing = argValue case "bind-propagation": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } switch argValue { default: - return newMount, nil, fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption) + return newMount, "", nil, fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption) case "shared", "rshared", "private", "rprivate", "slave", "rslave": // this should be the relevant parts of the same list of options we accepted above } newMount.Options = append(newMount.Options, argValue) case "id": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } id = argValue case "from": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } fromStage = argValue case "target", "dst", "destination": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } targetPath := argValue if !path.IsAbs(targetPath) { targetPath = filepath.Join(workDir, targetPath) } if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { - return newMount, nil, err + return newMount, "", nil, err } newMount.Destination = targetPath setDest = true case "src", "source": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } newMount.Source = argValue case "mode": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } mode, err = strconv.ParseUint(argValue, 8, 32) if err != nil { - return newMount, nil, fmt.Errorf("unable to parse cache mode: %w", err) + return newMount, "", nil, fmt.Errorf("unable to parse cache mode: %w", err) } case "uid": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } uid, err = strconv.Atoi(argValue) if err != nil { - return newMount, nil, fmt.Errorf("unable to parse cache uid: %w", err) + return newMount, "", nil, fmt.Errorf("unable to parse cache uid: %w", err) } case "gid": if !hasArgValue { - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) } gid, err = strconv.Atoi(argValue) if err != nil { - return newMount, nil, fmt.Errorf("unable to parse cache gid: %w", err) + return newMount, "", nil, fmt.Errorf("unable to parse cache gid: %w", err) } default: - return newMount, nil, fmt.Errorf("%v: %w", argName, errBadMntOption) + return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadMntOption) } } @@ -354,9 +455,10 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin } if !setDest { - return newMount, nil, errBadVolDest + return newMount, "", nil, errBadVolDest } + thisCacheRoot := "" if fromStage != "" { // do not create and use a cache directory on the host, // instead use the location in the mounted stage or @@ -364,7 +466,7 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin mountPoint := "" if additionalMountPoints != nil { if val, ok := additionalMountPoints[fromStage]; ok { - if val.IsStage { + if !val.IsImage { mountPoint = val.MountPoint } } @@ -372,14 +474,9 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin // Cache does not support using an image so if there's no such // stage or temporary directory, return an error if mountPoint == "" { - return newMount, nil, fmt.Errorf("no stage found with name %s", fromStage) + return newMount, "", nil, fmt.Errorf("no stage or additional build context found with name %s", fromStage) } - // path should be /contextDir/specified path - evaluated, err := copier.Eval(mountPoint, string(filepath.Separator)+newMount.Source, copier.EvalOptions{}) - if err != nil { - return newMount, nil, err - } - newMount.Source = evaluated + thisCacheRoot = mountPoint } else { // we need to create the cache directory on the host if no image is being used @@ -389,64 +486,73 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin // cache parent directory: creates separate cache parent for each user. cacheParent := CacheParent() + // create cache on host if not present err = os.MkdirAll(cacheParent, os.FileMode(0o755)) if err != nil { - return newMount, nil, fmt.Errorf("unable to create build cache directory: %w", err) + return newMount, "", nil, fmt.Errorf("unable to create build cache directory: %w", err) } if id != "" { // Don't let the user control where we place the directory. dirID := digest.FromString(id).Encoded()[:16] - newMount.Source = filepath.Join(cacheParent, dirID) + thisCacheRoot = filepath.Join(cacheParent, dirID) buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID) } else { // Don't let the user control where we place the directory. dirID := digest.FromString(newMount.Destination).Encoded()[:16] - newMount.Source = filepath.Join(cacheParent, dirID) + thisCacheRoot = filepath.Join(cacheParent, dirID) buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID) } + idPair := idtools.IDPair{ UID: uid, GID: gid, } // buildkit parity: change uid and gid if specified, otherwise keep `0` - err = idtools.MkdirAllAndChownNew(newMount.Source, os.FileMode(mode), idPair) + err = idtools.MkdirAllAndChownNew(thisCacheRoot, os.FileMode(mode), idPair) if err != nil { - return newMount, nil, fmt.Errorf("unable to change uid,gid of cache directory: %w", err) + return newMount, "", nil, fmt.Errorf("unable to change uid,gid of cache directory: %w", err) } // create a subdirectory inside `cacheParent` just to store lockfiles buildahLockFilesDir = filepath.Join(cacheParent, buildahLockFilesDir) err = os.MkdirAll(buildahLockFilesDir, os.FileMode(0o700)) if err != nil { - return newMount, nil, fmt.Errorf("unable to create build cache lockfiles directory: %w", err) + return newMount, "", nil, fmt.Errorf("unable to create build cache lockfiles directory: %w", err) } } - var targetLock *lockfile.LockFile // = nil + // path should be /mountPoint/specified path + evaluated, err := copier.Eval(thisCacheRoot, thisCacheRoot+string(filepath.Separator)+newMount.Source, copier.EvalOptions{}) + if err != nil { + return newMount, "", nil, err + } + newMount.Source = evaluated + succeeded := false - defer func() { - if !succeeded && targetLock != nil { - targetLock.Unlock() - } - }() + var targetLock *lockfile.LockFile switch sharing { case "locked": // lock parent cache lockfile, err := lockfile.GetLockFile(filepath.Join(buildahLockFilesDir, BuildahCacheLockfile)) if err != nil { - return newMount, nil, fmt.Errorf("unable to acquire lock when sharing mode is locked: %w", err) + return newMount, "", nil, fmt.Errorf("unable to acquire lock when sharing mode is locked: %w", err) } // Will be unlocked after the RUN step is executed. lockfile.Lock() targetLock = lockfile + defer func() { + if !succeeded { + targetLock.Unlock() + } + }() case "shared": // do nothing since default is `shared` break default: // error out for unknown values - return newMount, nil, fmt.Errorf("unrecognized value %q for field `sharing`: %w", sharing, err) + return newMount, "", nil, fmt.Errorf("unrecognized value %q for field `sharing`: %w", sharing, err) } // buildkit parity: default sharing should be shared @@ -464,12 +570,29 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin opts, err := parse.ValidateVolumeOpts(newMount.Options) if err != nil { - return newMount, nil, err + return newMount, "", nil, err } newMount.Options = opts + var intermediateMount string + if newMount.Source != thisCacheRoot { + rel, err := filepath.Rel(thisCacheRoot, newMount.Source) + if err != nil { + return newMount, "", nil, fmt.Errorf("computing pathname of cache subdirectory: %w", err) + } + if rel != "." && rel != "/" { + mnt, err := bindFromChroot(thisCacheRoot, rel, tmpDir) + if err != nil { + return newMount, "", nil, fmt.Errorf("sanitizing cache subdirectory %q: %w", newMount.Source, err) + } + logrus.Debugf("bind-mounted %q under %q to %q", rel, thisCacheRoot, mnt) + intermediateMount = mnt + newMount.Source = intermediateMount + } + } + succeeded = true - return newMount, targetLock, nil + return newMount, intermediateMount, targetLock, nil } func getVolumeMounts(volumes []string) (map[string]specs.Mount, error) { @@ -495,27 +618,53 @@ func UnlockLockArray(locks []*lockfile.LockFile) { } } -// GetVolumes gets the volumes from --volume and --mount +// GetVolumes gets the volumes from --volume and --mount flags. // -// If this function succeeds, the caller must unlock the returned *lockfile.LockFile s if any (when??). -func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string, mounts []string, contextDir string, workDir string) ([]specs.Mount, []string, []*lockfile.LockFile, error) { - unifiedMounts, mountedImages, targetLocks, err := getMounts(ctx, store, mounts, contextDir, workDir) +// Returns a slice of Mounts to add to the runtime spec's list of mounts, the +// IDs of any images we mounted, a slice of bind-mounted paths, a slice of +// overlay directories and a slice of locks that we acquired, or an error. +// +// The caller is expected to, after the command which uses the mounts and +// volumes exits, clean up the overlay directories, unmount and remove the +// mountpoints for the bind-mounted paths, unmount any images we mounted, and +// release the locks we returned (either using UnlockLockArray() or by +// iterating over them and unlocking them). +func GetVolumes(ctx *types.SystemContext, store storage.Store, mountLabel string, volumes []string, mounts []string, contextDir, workDir, tmpDir string) ([]specs.Mount, []string, []string, []string, []*lockfile.LockFile, error) { + unifiedMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, err := getMounts(ctx, store, mountLabel, mounts, contextDir, workDir, tmpDir) if err != nil { - return nil, mountedImages, nil, err + return nil, nil, nil, nil, nil, err } succeeded := false defer func() { if !succeeded { + for _, overlayMount := range overlayMounts { + if err := overlay.RemoveTemp(overlayMount); err != nil { + logrus.Debugf("unmounting overlay at %q: %v", overlayMount, err) + } + } + for _, intermediateMount := range intermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil { + logrus.Debugf("unmounting intermediate mount point %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + logrus.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + for _, image := range mountedImages { + if _, err := store.UnmountImage(image, false); err != nil { + logrus.Debugf("unmounting image %q: %v", image, err) + } + } UnlockLockArray(targetLocks) } }() volumeMounts, err := getVolumeMounts(volumes) if err != nil { - return nil, mountedImages, nil, err + return nil, nil, nil, nil, nil, err } for dest, mount := range volumeMounts { if _, ok := unifiedMounts[dest]; ok { - return nil, mountedImages, nil, fmt.Errorf("%v: %w", dest, errDuplicateDest) + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", dest, errDuplicateDest) } unifiedMounts[dest] = mount } @@ -525,25 +674,53 @@ func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string, finalMounts = append(finalMounts, mount) } succeeded = true - return finalMounts, mountedImages, targetLocks, nil + return finalMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, nil } -// getMounts takes user-provided input from the --mount flag and creates OCI -// spec mounts. -// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... -// buildah run --mount type=cache,target=/var/cache ... -// buildah run --mount type=tmpfs,target=/dev/shm ... +// getMounts takes user-provided inputs from the --mount flag and returns a +// slice of OCI spec mounts, a slice of mounted image IDs, a slice of other +// mount locations, a slice of overlay mounts, and a slice of locks, or an +// error. // -// If this function succeeds, the caller must unlock the returned *lockfile.LockFile s if any (when??). -func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, contextDir string, workDir string) (map[string]specs.Mount, []string, []*lockfile.LockFile, error) { +// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... +// buildah run --mount type=cache,target=/var/cache ... +// buildah run --mount type=tmpfs,target=/dev/shm ... +// +// The caller is expected to, after the command which uses the mounts exits, +// unmount the overlay filesystems (if we mounted any), unmount the other +// mounted filesystems and remove their mountpoints (if we provided any paths +// to mountpoints), unmount any mounted images (if we provided the IDs of any), +// and then unlock the locks we returned (either using UnlockLockArray() or by +// iterating over them and unlocking them). +func getMounts(ctx *types.SystemContext, store storage.Store, mountLabel string, mounts []string, contextDir, workDir, tmpDir string) (map[string]specs.Mount, []string, []string, []string, []*lockfile.LockFile, error) { // If `type` is not set default to "bind" mountType := define.TypeBind - finalMounts := make(map[string]specs.Mount) - mountedImages := make([]string, 0) - targetLocks := make([]*lockfile.LockFile, 0) + finalMounts := make(map[string]specs.Mount, len(mounts)) + mountedImages := make([]string, 0, len(mounts)) + intermediateMounts := make([]string, 0, len(mounts)) + overlayMounts := make([]string, 0, len(mounts)) + targetLocks := make([]*lockfile.LockFile, 0, len(mounts)) succeeded := false defer func() { if !succeeded { + for _, overlayDir := range overlayMounts { + if err := overlay.RemoveTemp(overlayDir); err != nil { + logrus.Debugf("unmounting overlay mount at %q: %v", overlayDir, err) + } + } + for _, intermediateMount := range intermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil { + logrus.Debugf("unmounting intermediate mount point %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + logrus.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + for _, image := range mountedImages { + if _, err := store.UnmountImage(image, false); err != nil { + logrus.Debugf("unmounting image %q: %v", image, err) + } + } UnlockLockArray(targetLocks) } }() @@ -556,56 +733,67 @@ func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, c for _, mount := range mounts { tokens := strings.Split(mount, ",") if len(tokens) < 2 { - return nil, mountedImages, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax) + return nil, nil, nil, nil, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax) } for _, field := range tokens { if strings.HasPrefix(field, "type=") { kv := strings.Split(field, "=") if len(kv) != 2 { - return nil, mountedImages, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax) + return nil, nil, nil, nil, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax) } mountType = kv[1] } } switch mountType { case define.TypeBind: - mount, image, err := GetBindMount(ctx, tokens, contextDir, store, "", nil, workDir) + mount, image, intermediateMount, overlayMount, err := GetBindMount(ctx, tokens, contextDir, store, mountLabel, nil, workDir, tmpDir) if err != nil { - return nil, mountedImages, nil, err + return nil, nil, nil, nil, nil, err + } + if image != "" { + mountedImages = append(mountedImages, image) + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } + if overlayMount != "" { + overlayMounts = append(overlayMounts, overlayMount) } if _, ok := finalMounts[mount.Destination]; ok { - return nil, mountedImages, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) } finalMounts[mount.Destination] = mount - mountedImages = append(mountedImages, image) case TypeCache: - mount, tl, err := GetCacheMount(tokens, store, "", nil, workDir) + mount, intermediateMount, tl, err := GetCacheMount(tokens, nil, workDir, tmpDir) if err != nil { - return nil, mountedImages, nil, err + return nil, nil, nil, nil, nil, err + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) } if tl != nil { targetLocks = append(targetLocks, tl) } if _, ok := finalMounts[mount.Destination]; ok { - return nil, mountedImages, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) } finalMounts[mount.Destination] = mount case TypeTmpfs: mount, err := GetTmpfsMount(tokens, workDir) if err != nil { - return nil, mountedImages, nil, err + return nil, nil, nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, mountedImages, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) } finalMounts[mount.Destination] = mount default: - return nil, mountedImages, nil, fmt.Errorf("invalid filesystem type %q", mountType) + return nil, nil, nil, nil, nil, fmt.Errorf("invalid filesystem type %q", mountType) } } succeeded = true - return finalMounts, mountedImages, targetLocks, nil + return finalMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, nil } // GetTmpfsMount parses a single tmpfs mount entry from the --mount flag diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go index 653d6cf05c..8dac9d520e 100644 --- a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go @@ -10,6 +10,7 @@ import ( "syscall" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/system" "github.com/containers/storage/pkg/unshare" "github.com/opencontainers/runtime-spec/specs-go" @@ -48,6 +49,12 @@ type Options struct { RootUID int // RootGID is not used yet but keeping it here for legacy reasons. RootGID int + // Force overlay mounting and return a bind mount, rather than + // attempting to optimize by having the runtime actually mount and + // manage the overlay filesystem. + ForceMount bool + // MountLabel is a label to force for the overlay filesystem. + MountLabel string } // TempDir generates an overlay Temp directory in the container content @@ -144,6 +151,12 @@ func mountWithMountProgram(mountProgram, overlayOptions, mergeDir string) error return nil } +// mountNatively mounts an overlay at mergeDir using the kernel's mount() +// system call. +func mountNatively(overlayOptions, mergeDir string) error { + return mount.Mount("overlay", mergeDir, "overlay", overlayOptions) +} + // Convert ":" to "\:", the path which will be overlay mounted need to be escaped func escapeColon(source string) string { return strings.ReplaceAll(source, ":", "\\:") diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go index cb0e28fe18..6ba67b4b4a 100644 --- a/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go @@ -9,6 +9,7 @@ import ( "github.com/containers/storage/pkg/unshare" "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" ) // MountWithOptions creates a subdir of the contentDir based on the source directory @@ -55,6 +56,9 @@ func MountWithOptions(contentDir, source, dest string, opts *Options) (mount spe } overlayOptions = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,private", escapeColon(source), upperDir, workDir) } + if opts.MountLabel != "" { + overlayOptions = overlayOptions + "," + label.FormatMountLabel("", opts.MountLabel) + } mountProgram := findMountProgram(opts.GraphOpts) if mountProgram != "" { @@ -79,5 +83,17 @@ func MountWithOptions(contentDir, source, dest string, opts *Options) (mount spe mount.Type = "overlay" mount.Options = strings.Split(overlayOptions, ",") + if opts.ForceMount { + if err := mountNatively(overlayOptions, mergeDir); err != nil { + return mount, err + } + + mount.Source = mergeDir + mount.Destination = dest + mount.Type = "bind" + mount.Options = []string{"bind", "slave"} + return mount, nil + } + return mount, nil } diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index b162a81b8e..975bb7581f 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -180,18 +180,22 @@ type RunOptions struct { // RunMountArtifacts are the artifacts created when using a run mount. type runMountArtifacts struct { - // RunMountTargets are the run mount targets inside the container + // RunMountTargets are the run mount targets inside the container which should be removed RunMountTargets []string + // RunOverlayDirs are overlay directories which will need to be cleaned up using overlay.RemoveTemp() + RunOverlayDirs []string // TmpFiles are artifacts that need to be removed outside the container TmpFiles []string - // Any external images which were mounted inside container + // Any images which were mounted, which should be unmounted MountedImages []string - // Agents are the ssh agents started + // Agents are the ssh agents started, which should have their Shutdown() methods called Agents []*sshagent.AgentServer // SSHAuthSock is the path to the ssh auth sock inside the container SSHAuthSock string - // TargetLocks to be unlocked if there are any. + // Lock files, which should have their Unlock() methods called TargetLocks []*lockfile.LockFile + // Intermediate mount points, which should be Unmount()ed and Removed()d + IntermediateMounts []string } // RunMountInfo are the available run mounts for this run diff --git a/vendor/github.com/containers/buildah/run_common.go b/vendor/github.com/containers/buildah/run_common.go index 8ea14bdfc3..9f66d19079 100644 --- a/vendor/github.com/containers/buildah/run_common.go +++ b/vendor/github.com/containers/buildah/run_common.go @@ -27,7 +27,6 @@ import ( "github.com/containers/buildah/define" "github.com/containers/buildah/internal" "github.com/containers/buildah/internal/tmpdir" - internalUtil "github.com/containers/buildah/internal/util" "github.com/containers/buildah/internal/volumes" "github.com/containers/buildah/pkg/overlay" "github.com/containers/buildah/pkg/sshagent" @@ -40,15 +39,14 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/subscriptions" "github.com/containers/image/v5/types" - imageTypes "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/fileutils" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/lockfile" + "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/reexec" "github.com/containers/storage/pkg/unshare" - storageTypes "github.com/containers/storage/types" "github.com/opencontainers/go-digest" "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -1311,7 +1309,9 @@ func init() { reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain) } -// If this succeeds, the caller must call cleanupMounts(). +// If this succeeds, after the command which uses the spec finishes running, +// the caller must call b.cleanupRunMounts() on the returned runMountArtifacts +// structure. func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes []string, compatBuiltinVolumes types.OptionalBool, volumeMounts []string, runFileMounts []string, runMountInfo runMountInfo) (*runMountArtifacts, error) { // Start building a new list of mounts. var mounts []specs.Mount @@ -1370,14 +1370,16 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st processGID: int(processGID), } // Get the list of mounts that are just for this Run() call. - runMounts, mountArtifacts, err := b.runSetupRunMounts(mountPoint, runFileMounts, runMountInfo, idMaps) + runMounts, mountArtifacts, err := b.runSetupRunMounts(mountPoint, bundlePath, runFileMounts, runMountInfo, idMaps) if err != nil { return nil, err } succeeded := false defer func() { if !succeeded { - volumes.UnlockLockArray(mountArtifacts.TargetLocks) + if err := b.cleanupRunMounts(mountPoint, mountArtifacts); err != nil { + b.Logger.Debugf("cleaning up run mounts: %v", err) + } } }() // Add temporary copies of the contents of volume locations at the @@ -1532,28 +1534,61 @@ func checkIfMountDestinationPreExists(root string, dest string) (bool, error) { // runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs // -// If this function succeeds, the caller must unlock runMountArtifacts.TargetLocks (when??) -func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources runMountInfo, idMaps IDMaps) ([]specs.Mount, *runMountArtifacts, error) { - mountTargets := make([]string, 0, 10) +// If this function succeeds, the caller must free the returned +// runMountArtifacts by calling b.cleanupRunMounts() after the command being +// executed with those mounts has finished. +func (b *Builder) runSetupRunMounts(mountPoint, bundlePath string, mounts []string, sources runMountInfo, idMaps IDMaps) ([]specs.Mount, *runMountArtifacts, error) { + mountTargets := make([]string, 0, len(mounts)) tmpFiles := make([]string, 0, len(mounts)) - mountImages := make([]string, 0, 10) + mountImages := make([]string, 0, len(mounts)) + intermediateMounts := make([]string, 0, len(mounts)) finalMounts := make([]specs.Mount, 0, len(mounts)) agents := make([]*sshagent.AgentServer, 0, len(mounts)) - sshCount := 0 defaultSSHSock := "" targetLocks := []*lockfile.LockFile{} + var overlayDirs []string succeeded := false defer func() { if !succeeded { + for _, agent := range agents { + servePath := agent.ServePath() + if err := agent.Shutdown(); err != nil { + b.Logger.Errorf("shutting down SSH agent at %q: %v", servePath, err) + } + } + for _, overlayDir := range overlayDirs { + if err := overlay.RemoveTemp(overlayDir); err != nil { + b.Logger.Error(err.Error()) + } + } + for _, intermediateMount := range intermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil { + b.Logger.Errorf("unmounting %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + b.Logger.Errorf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + for _, mountImage := range mountImages { + if _, err := b.store.UnmountImage(mountImage, false); err != nil { + b.Logger.Error(err.Error()) + } + } + for _, tmpFile := range tmpFiles { + if err := os.Remove(tmpFile); err != nil && !errors.Is(err, os.ErrNotExist) { + b.Logger.Error(err.Error()) + } + } volumes.UnlockLockArray(targetLocks) } }() for _, mount := range mounts { var mountSpec *specs.Mount var err error - var envFile, image string + var envFile, image, bundleMountsDir, overlayDir, intermediateMount string var agent *sshagent.AgentServer var tl *lockfile.LockFile + tokens := strings.Split(mount, ",") // If `type` is not set default to TypeBind @@ -1581,29 +1616,37 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources } } case "ssh": - mountSpec, agent, err = b.getSSHMount(tokens, sshCount, sources.SSHSources, idMaps) + mountSpec, agent, err = b.getSSHMount(tokens, len(agents), sources.SSHSources, idMaps) if err != nil { return nil, nil, err } if mountSpec != nil { finalMounts = append(finalMounts, *mountSpec) - agents = append(agents, agent) - if sshCount == 0 { + if len(agents) == 0 { defaultSSHSock = mountSpec.Destination } - // Count is needed as the default destination of the ssh sock inside the container is /run/buildkit/ssh_agent.{i} - sshCount++ + agents = append(agents, agent) } case define.TypeBind: - mountSpec, image, err = b.getBindMount(tokens, sources.SystemContext, sources.ContextDir, sources.StageMountPoints, idMaps, sources.WorkDir) + if bundleMountsDir == "" { + if bundleMountsDir, err = os.MkdirTemp(bundlePath, "mounts"); err != nil { + return nil, nil, err + } + } + mountSpec, image, intermediateMount, overlayDir, err = b.getBindMount(tokens, sources.SystemContext, sources.ContextDir, sources.StageMountPoints, idMaps, sources.WorkDir, bundleMountsDir) if err != nil { return nil, nil, err } - finalMounts = append(finalMounts, *mountSpec) - // only perform cleanup if image was mounted ignore everything else if image != "" { mountImages = append(mountImages, image) } + if overlayDir != "" { + overlayDirs = append(overlayDirs, overlayDir) + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } + finalMounts = append(finalMounts, *mountSpec) case "tmpfs": mountSpec, err = b.getTmpfsMount(tokens, idMaps, sources.WorkDir) if err != nil { @@ -1611,14 +1654,22 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources } finalMounts = append(finalMounts, *mountSpec) case "cache": - mountSpec, tl, err = b.getCacheMount(tokens, sources.StageMountPoints, idMaps, sources.WorkDir) + if bundleMountsDir == "" { + if bundleMountsDir, err = os.MkdirTemp(bundlePath, "mounts"); err != nil { + return nil, nil, err + } + } + mountSpec, intermediateMount, tl, err = b.getCacheMount(tokens, sources.StageMountPoints, idMaps, sources.WorkDir, bundleMountsDir) if err != nil { return nil, nil, err } - finalMounts = append(finalMounts, *mountSpec) + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } if tl != nil { targetLocks = append(targetLocks, tl) } + finalMounts = append(finalMounts, *mountSpec) default: return nil, nil, fmt.Errorf("invalid mount type %q", mountType) } @@ -1638,31 +1689,33 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources } succeeded = true artifacts := &runMountArtifacts{ - RunMountTargets: mountTargets, - TmpFiles: tmpFiles, - Agents: agents, - MountedImages: mountImages, - SSHAuthSock: defaultSSHSock, - TargetLocks: targetLocks, + RunMountTargets: mountTargets, + RunOverlayDirs: overlayDirs, + TmpFiles: tmpFiles, + Agents: agents, + MountedImages: mountImages, + SSHAuthSock: defaultSSHSock, + TargetLocks: targetLocks, + IntermediateMounts: intermediateMounts, } return finalMounts, artifacts, nil } -func (b *Builder) getBindMount(tokens []string, context *imageTypes.SystemContext, contextDir string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*specs.Mount, string, error) { +func (b *Builder) getBindMount(tokens []string, sys *types.SystemContext, contextDir string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, string, string, error) { if contextDir == "" { - return nil, "", errors.New("Context Directory for current run invocation is not configured") + return nil, "", "", "", errors.New("context directory for current run invocation is not configured") } var optionMounts []specs.Mount - mount, image, err := volumes.GetBindMount(context, tokens, contextDir, b.store, b.MountLabel, stageMountPoints, workDir) + mount, image, intermediateMount, overlayMount, err := volumes.GetBindMount(sys, tokens, contextDir, b.store, b.MountLabel, stageMountPoints, workDir, tmpDir) if err != nil { - return nil, image, err + return nil, "", "", "", err } optionMounts = append(optionMounts, mount) volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps) if err != nil { - return nil, image, err + return nil, "", "", "", err } - return &volumes[0], image, nil + return &volumes[0], image, intermediateMount, overlayMount, nil } func (b *Builder) getTmpfsMount(tokens []string, idMaps IDMaps, workDir string) (*specs.Mount, error) { @@ -1939,52 +1992,53 @@ func (b *Builder) cleanupTempVolumes() { } // cleanupRunMounts cleans up run mounts so they only appear in this run. -func (b *Builder) cleanupRunMounts(context *imageTypes.SystemContext, mountpoint string, artifacts *runMountArtifacts) error { +func (b *Builder) cleanupRunMounts(mountpoint string, artifacts *runMountArtifacts) error { for _, agent := range artifacts.Agents { - err := agent.Shutdown() - if err != nil { + servePath := agent.ServePath() + if err := agent.Shutdown(); err != nil { + return fmt.Errorf("shutting down SSH agent at %q: %v", servePath, err) + } + } + // clean up any overlays we mounted + for _, overlayDirectory := range artifacts.RunOverlayDirs { + if err := overlay.RemoveTemp(overlayDirectory); err != nil { return err } } - - // cleanup any mounted images for this run + // unmount anything that needs unmounting + for _, intermediateMount := range artifacts.IntermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("unmounting %q: %w", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("removing should-be-empty directory %q: %w", intermediateMount, err) + } + } + // unmount any images we mounted for this run for _, image := range artifacts.MountedImages { - if image != "" { - // if flow hits here some image was mounted for this run - i, err := internalUtil.LookupImage(context, b.store, image) - if err == nil { - // silently try to unmount and do nothing - // if image is being used by something else - _ = i.Unmount(false) - } - if errors.Is(err, storageTypes.ErrImageUnknown) { - // Ignore only if ErrImageUnknown - // Reason: Image is already unmounted do nothing - continue - } - return err + if _, err := b.store.UnmountImage(image, false); err != nil { + logrus.Debugf("umounting image %q: %v", image, err) } } + // remove mount targets that were created for this run opts := copier.RemoveOptions{ All: true, } for _, path := range artifacts.RunMountTargets { - err := copier.Remove(mountpoint, path, opts) - if err != nil { - return err + if err := copier.Remove(mountpoint, path, opts); err != nil { + return fmt.Errorf("removing mount target %q %q: %w", mountpoint, path, err) } } var prevErr error for _, path := range artifacts.TmpFiles { - err := os.Remove(path) - if !errors.Is(err, os.ErrNotExist) { + if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { if prevErr != nil { logrus.Error(prevErr) } - prevErr = err + prevErr = fmt.Errorf("removing temporary file: %w", err) } } - // unlock if any locked files from this RUN statement + // unlock locks we took, most likely for cache mounts volumes.UnlockLockArray(artifacts.TargetLocks) return prevErr } diff --git a/vendor/github.com/containers/buildah/run_freebsd.go b/vendor/github.com/containers/buildah/run_freebsd.go index 79ea3a44d3..7188e0e214 100644 --- a/vendor/github.com/containers/buildah/run_freebsd.go +++ b/vendor/github.com/containers/buildah/run_freebsd.go @@ -280,7 +280,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { } defer func() { - if err := b.cleanupRunMounts(options.SystemContext, mountPoint, runArtifacts); err != nil { + if err := b.cleanupRunMounts(mountPoint, runArtifacts); err != nil { options.Logger.Errorf("unable to cleanup run mounts %v", err) } }() @@ -355,9 +355,12 @@ func setupSpecialMountSpecChanges(spec *spec.Spec, shmSize string) ([]specs.Moun return spec.Mounts, nil } -// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??). -func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*spec.Mount, *lockfile.LockFile, error) { - return nil, nil, errors.New("cache mounts not supported on freebsd") +// If this succeeded, the caller would be expected to, after the command which +// uses the mount exits, unmount the mounted filesystem and remove its +// mountpoint (if we provided the path to its mountpoint), and release the lock +// (if we took one). +func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, *lockfile.LockFile, error) { + return nil, "", nil, errors.New("cache mounts not supported on freebsd") } func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, idMaps IDMaps) (mounts []specs.Mount, Err error) { diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 71d92323e5..440af38549 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -39,6 +39,7 @@ import ( "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/lockfile" + "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/unshare" "github.com/docker/go-units" @@ -515,7 +516,7 @@ rootless=%d } defer func() { - if err := b.cleanupRunMounts(options.SystemContext, mountPoint, runArtifacts); err != nil { + if err := b.cleanupRunMounts(mountPoint, runArtifacts); err != nil { options.Logger.Errorf("unable to cleanup run mounts %v", err) } }() @@ -1141,7 +1142,7 @@ func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, RootGID: idMaps.rootGID, UpperDirOptionFragment: upperDir, WorkDirOptionFragment: workDir, - GraphOpts: b.store.GraphOptions(), + GraphOpts: slices.Clone(b.store.GraphOptions()), } overlayMount, err := overlay.MountWithOptions(contentDir, host, container, &overlayOpts) @@ -1150,7 +1151,7 @@ func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, } // If chown true, add correct ownership to the overlay temp directories. - if foundU { + if err == nil && foundU { if err := chown.ChangeHostPathOwnership(contentDir, true, idMaps.processUID, idMaps.processGID); err != nil { return specs.Mount{}, err } @@ -1402,24 +1403,39 @@ func checkIDsGreaterThan5(ids []specs.LinuxIDMapping) bool { return false } -// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??). -func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*specs.Mount, *lockfile.LockFile, error) { +// Returns a Mount to add to the runtime spec's list of mounts, an optional +// path of a mounted filesystem, unmounted, and an optional lock, or an error. +// +// The caller is expected to, after the command which uses the mount exits, +// unmount the mounted filesystem (if we provided the path to its mountpoint) +// and remove its mountpoint, , and release the lock (if we took one). +func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, *lockfile.LockFile, error) { var optionMounts []specs.Mount - mount, targetLock, err := volumes.GetCacheMount(tokens, b.store, b.MountLabel, stageMountPoints, workDir) + optionMount, intermediateMount, targetLock, err := volumes.GetCacheMount(tokens, stageMountPoints, workDir, tmpDir) if err != nil { - return nil, nil, err + return nil, "", nil, err } succeeded := false defer func() { - if !succeeded && targetLock != nil { - targetLock.Unlock() + if !succeeded { + if intermediateMount != "" { + if err := mount.Unmount(intermediateMount); err != nil { + b.Logger.Debugf("unmounting %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + b.Logger.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + if targetLock != nil { + targetLock.Unlock() + } } }() - optionMounts = append(optionMounts, mount) + optionMounts = append(optionMounts, optionMount) volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps) if err != nil { - return nil, nil, err + return nil, "", nil, err } succeeded = true - return &volumes[0], targetLock, nil + return &volumes[0], intermediateMount, targetLock, nil } diff --git a/vendor/github.com/containers/common/libimage/push.go b/vendor/github.com/containers/common/libimage/push.go index cac8fb6024..dc99344808 100644 --- a/vendor/github.com/containers/common/libimage/push.go +++ b/vendor/github.com/containers/common/libimage/push.go @@ -12,7 +12,6 @@ import ( dockerDaemonTransport "github.com/containers/image/v5/docker/daemon" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" - compressiontypes "github.com/containers/image/v5/pkg/compression/types" "github.com/containers/image/v5/transports/alltransports" "github.com/sirupsen/logrus" ) @@ -87,16 +86,8 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options destRef = dockerRef } - // docker-archive and DockerV2Schema2MediaType support only Gzip compression - // If the CompressionFormat has come from containers.conf (set as a default), - // but isn't supported for this push, we want to ignore it. - // If the CompressionFormat has come from the CLI (ForceCompressionFormat - // requires CompressionFormat to be set), we want to strip the invalid value - // so that the push attempt fails. - // - // Ideally this should all happen at a much higher layer, where the code can differentiate - // between a value coming from containers.conf vs. the CLI. - if options.CompressionFormat != nil && options.CompressionFormat.Name() != compressiontypes.GzipAlgorithmName && + // docker-archive and only DockerV2Schema2MediaType support Gzip compression + if options.CompressionFormat != nil && (destRef.Transport().Name() == dockerArchiveTransport.Transport.Name() || destRef.Transport().Name() == dockerDaemonTransport.Transport.Name() || options.ManifestMIMEType == manifest.DockerV2Schema2MediaType) { diff --git a/vendor/github.com/containers/common/libnetwork/netavark/config.go b/vendor/github.com/containers/common/libnetwork/netavark/config.go index 3305258b6c..8b43a787e4 100644 --- a/vendor/github.com/containers/common/libnetwork/netavark/config.go +++ b/vendor/github.com/containers/common/libnetwork/netavark/config.go @@ -30,9 +30,6 @@ func sliceRemoveDuplicates(strList []string) []string { } func (n *netavarkNetwork) commitNetwork(network *types.Network) error { - if err := os.MkdirAll(n.networkConfigDir, 0o755); err != nil { - return nil - } confPath := filepath.Join(n.networkConfigDir, network.Name+".json") f, err := os.Create(confPath) if err != nil { @@ -210,10 +207,6 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo if len(value) == 0 { return nil, errors.New("invalid vrf name") } - case types.ModeOption: - if !slices.Contains(types.ValidBridgeModes, value) { - return nil, fmt.Errorf("unknown bridge mode %q", value) - } default: return nil, fmt.Errorf("unsupported bridge network option %s", key) } diff --git a/vendor/github.com/containers/common/libnetwork/netavark/network.go b/vendor/github.com/containers/common/libnetwork/netavark/network.go index 985d0db2dd..6ec4a9d15b 100644 --- a/vendor/github.com/containers/common/libnetwork/netavark/network.go +++ b/vendor/github.com/containers/common/libnetwork/netavark/network.go @@ -135,6 +135,10 @@ func NewNetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) { return nil, fmt.Errorf("failed to parse default subnet: %w", err) } + if err := os.MkdirAll(conf.NetworkConfigDir, 0o755); err != nil { + return nil, err + } + if err := os.MkdirAll(conf.NetworkRunDir, 0o755); err != nil { return nil, err } @@ -183,21 +187,6 @@ func (n *netavarkNetwork) loadNetworks() error { // check the mod time of the config dir f, err := os.Stat(n.networkConfigDir) if err != nil { - // the directory may not exists which is fine. It will be created on the first network create - if errors.Is(err, os.ErrNotExist) { - // networks are already loaded - if n.networks != nil { - return nil - } - networks := make(map[string]*types.Network, 1) - networkInfo, err := n.createDefaultNetwork() - if err != nil { - return fmt.Errorf("failed to create default network %s: %w", n.defaultNetwork, err) - } - networks[n.defaultNetwork] = networkInfo - n.networks = networks - return nil - } return err } modTime := f.ModTime() diff --git a/vendor/github.com/containers/common/libnetwork/types/const.go b/vendor/github.com/containers/common/libnetwork/types/const.go index 6e2c3fbf4b..a916182007 100644 --- a/vendor/github.com/containers/common/libnetwork/types/const.go +++ b/vendor/github.com/containers/common/libnetwork/types/const.go @@ -24,9 +24,6 @@ const ( // DefaultSubnet is the subnet that will be used for the default CNI network. DefaultSubnet = "10.88.0.0/16" - BridgeModeManaged = "managed" - BridgeModeUnmanaged = "unmanaged" - // valid macvlan driver mode values MacVLANModeBridge = "bridge" MacVLANModePrivate = "private" @@ -56,9 +53,6 @@ const ( Netavark NetworkBackend = "netavark" ) -// ValidBridgeModes is the list of valid mode options for the bridge driver -var ValidBridgeModes = []string{BridgeModeManaged, BridgeModeUnmanaged} - // ValidMacVLANModes is the list of valid mode options for the macvlan driver var ValidMacVLANModes = []string{MacVLANModeBridge, MacVLANModePrivate, MacVLANModeVepa, MacVLANModePassthru} diff --git a/vendor/github.com/containers/common/libnetwork/types/network.go b/vendor/github.com/containers/common/libnetwork/types/network.go index b949928dab..77c76bf787 100644 --- a/vendor/github.com/containers/common/libnetwork/types/network.go +++ b/vendor/github.com/containers/common/libnetwork/types/network.go @@ -269,15 +269,13 @@ type PerNetworkOptions struct { // InterfaceName for this container. Required in the backend. // Optional in the frontend. Will be filled with ethX (where X is a integer) when empty. InterfaceName string `json:"interface_name"` - // Driver-specific options for this container. - Options map[string]string `json:"options,omitempty"` } // NetworkOptions for a given container. type NetworkOptions struct { // ContainerID is the container id, used for iptables comments and ipam allocation. ContainerID string `json:"container_id"` - // ContainerName is the container name. + // ContainerName is the container name, used as dns name. ContainerName string `json:"container_name"` // PortMappings contains the port mappings for this container PortMappings []PortMapping `json:"port_mappings,omitempty"` @@ -287,8 +285,6 @@ type NetworkOptions struct { // List of custom DNS server for podman's DNS resolver. // Priority order will be kept as defined by user in the configuration. DNSServers []string `json:"dns_servers,omitempty"` - // ContainerHostname is the configured DNS hostname of the container. - ContainerHostname string `json:"container_hostname"` } // PortMapping is one or more ports that will be mapped into the container. diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index a035c7a77e..2d6bf276af 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -14,7 +14,6 @@ import ( "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/capabilities" "github.com/containers/storage/pkg/fileutils" - "github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/unshare" units "github.com/docker/go-units" selinux "github.com/opencontainers/selinux/go-selinux" @@ -97,13 +96,6 @@ type ContainersConfig struct { // "memory.high=1073741824" sets the memory.high limit to 1GB. CgroupConf attributedstring.Slice `toml:"cgroup_conf,omitempty"` - // When no hostname is set for a container, use the container's name, with - // characters not valid for a hostname removed, as the hostname instead of - // the first 12 characters of the container's ID. Containers not running - // in a private UTS namespace will have their hostname set to the host's - // hostname regardless of this setting. - ContainerNameAsHostName bool `toml:"container_name_as_hostname,omitempty"` - // Capabilities to add to all containers. DefaultCapabilities attributedstring.Slice `toml:"default_capabilities,omitempty"` @@ -741,13 +733,7 @@ func (c *Config) CheckCgroupsAndAdjustConfig() { session, found := os.LookupEnv("DBUS_SESSION_BUS_ADDRESS") if !found { - xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR") - if xdgRuntimeDir == "" { - if dir, err := homedir.GetRuntimeDir(); err == nil { - xdgRuntimeDir = dir - } - } - sessionAddr := filepath.Join(xdgRuntimeDir, "bus") + sessionAddr := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "bus") if err := fileutils.Exists(sessionAddr); err == nil { sessionAddr, err = filepath.EvalSymlinks(sessionAddr) if err == nil { diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf index 0d22bcf380..236b51204a 100644 --- a/vendor/github.com/containers/common/pkg/config/containers.conf +++ b/vendor/github.com/containers/common/pkg/config/containers.conf @@ -58,14 +58,6 @@ # #cgroups = "enabled" -# When no hostname is set for a container, use the container's name, with -# characters not valid for a hostname removed, as the hostname instead of -# the first 12 characters of the container's ID. Containers not running -# in a private UTS namespace will have their hostname set to the host's -# hostname regardless of this setting. -# -#container_name_as_hostname = false - # List of default capabilities for containers. If it is empty or commented out, # the default capabilities defined in the container engine will be added. # diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf-freebsd b/vendor/github.com/containers/common/pkg/config/containers.conf-freebsd index f5b51dd226..894153ed33 100644 --- a/vendor/github.com/containers/common/pkg/config/containers.conf-freebsd +++ b/vendor/github.com/containers/common/pkg/config/containers.conf-freebsd @@ -29,12 +29,6 @@ # #base_hosts_file = "" -# When no hostname is set for a container, use the container's name, with -# characters not valid for a hostname removed, as the hostname instead of -# the first 12 characters of the container's ID. -# -#container_name_as_hostname = false - # The database backend of Podman. Supported values are "" (default), "boltdb" # and "sqlite". An empty value means it will check whenever a boltdb already # exists and use it when it does, otherwise it will use sqlite as default diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index 22620cf37d..02ff1284c7 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -46,6 +46,7 @@ var ( "/proc/scsi", "/proc/timer_list", "/proc/timer_stats", + "/sys/dev/block", "/sys/devices/virtual/powercap", "/sys/firmware", "/sys/fs/selinux", diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 2cdbff5b29..26ecbd1460 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.62.0-dev" +const Version = "0.61.1" diff --git a/vendor/modules.txt b/vendor/modules.txt index 8c2a319320..eed6d7f698 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -147,7 +147,7 @@ github.com/containernetworking/cni/pkg/version # github.com/containernetworking/plugins v1.5.1 ## explicit; go 1.20 github.com/containernetworking/plugins/pkg/ns -# github.com/containers/buildah v1.38.1-0.20241119213149-52437ef15d33 +# github.com/containers/buildah v1.38.1 ## explicit; go 1.22.6 github.com/containers/buildah github.com/containers/buildah/bind @@ -160,6 +160,7 @@ github.com/containers/buildah/internal github.com/containers/buildah/internal/config github.com/containers/buildah/internal/mkcw github.com/containers/buildah/internal/mkcw/types +github.com/containers/buildah/internal/open github.com/containers/buildah/internal/parse github.com/containers/buildah/internal/sbom github.com/containers/buildah/internal/tmpdir @@ -178,7 +179,7 @@ github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/volumes github.com/containers/buildah/util -# github.com/containers/common v0.61.1-0.20250120135258-06628cb958e9 +# github.com/containers/common v0.61.1 ## explicit; go 1.22.6 github.com/containers/common/internal github.com/containers/common/internal/attributedstring