Merge pull request #25069 from TomSweeneyRedHat/dev/tsweeney/v1.38.1

[v5.3] Bump Buildah to v1.38.1 with CVE-2024-11218
This commit is contained in:
openshift-merge-bot[bot]
2025-01-21 16:45:36 +00:00
committed by GitHub
25 changed files with 1025 additions and 233 deletions

2
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/checkpoint-restore/checkpointctl v1.3.0 github.com/checkpoint-restore/checkpointctl v1.3.0
github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containernetworking/plugins v1.5.1 github.com/containernetworking/plugins v1.5.1
github.com/containers/buildah v1.38.0 github.com/containers/buildah v1.38.1
github.com/containers/common v0.61.1 github.com/containers/common v0.61.1
github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.8.0 github.com/containers/gvisor-tap-vsock v0.8.0

4
go.sum
View File

@ -79,8 +79,8 @@ 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/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 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ=
github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM= github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM=
github.com/containers/buildah v1.38.0 h1:FmciZMwzhdcvtWj+8IE+61+lfTG2JfgrbZ2DUnEMnTE= github.com/containers/buildah v1.38.1 h1:CVmzOFYqyTd5N9TVuU1mrMWn4ZtzGF6rcFgkbKmSOqY=
github.com/containers/buildah v1.38.0/go.mod h1:tUsHC2bcgR5Q/R76qZUn7x0FRglqPFry2g5KhWfH4LI= 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 h1:jpk385ZFEx3MAX+sjwOoTZElvpgsGi0YJHuRmrhF/j8=
github.com/containers/common v0.61.1/go.mod h1:C+TfkhTV+ADp1Hu+BMIAYPvSFix21swYo9PZuCKoSUM= 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 h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=

View File

@ -6,7 +6,7 @@ env:
#### Global variables used for all tasks #### Global variables used for all tasks
#### ####
# Name of the ultimate destination branch for this CI run, PR or post-merge. # 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" GOPATH: "/var/tmp/go"
GOSRC: "${GOPATH}/src/github.com/containers/buildah" GOSRC: "${GOPATH}/src/github.com/containers/buildah"
# Overrides default location (/tmp/cirrus) for repo clone # Overrides default location (/tmp/cirrus) for repo clone
@ -77,7 +77,6 @@ meta_task:
${FEDORA_CACHE_IMAGE_NAME} ${FEDORA_CACHE_IMAGE_NAME}
${PRIOR_FEDORA_CACHE_IMAGE_NAME} ${PRIOR_FEDORA_CACHE_IMAGE_NAME}
${DEBIAN_CACHE_IMAGE_NAME} ${DEBIAN_CACHE_IMAGE_NAME}
build-push-${IMAGE_SUFFIX}
BUILDID: "${CIRRUS_BUILD_ID}" BUILDID: "${CIRRUS_BUILD_ID}"
REPOREF: "${CIRRUS_CHANGE_IN_REPO}" REPOREF: "${CIRRUS_CHANGE_IN_REPO}"
GCPJSON: ENCRYPTED[d3614d6f5cc0e66be89d4252b3365fd84f14eee0259d4eb47e25fc0bc2842c7937f5ee8c882b7e547b4c5ec4b6733b14] GCPJSON: ENCRYPTED[d3614d6f5cc0e66be89d4252b3365fd84f14eee0259d4eb47e25fc0bc2842c7937f5ee8c882b7e547b4c5ec4b6733b14]

View File

@ -2,6 +2,19 @@
# Changelog # 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) ## v1.38.0 (2024-11-08)
Bump to c/common v0.61.0, c/image v5.33.0, c/storage v1.56.0 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 Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions
integration tests: teach starthttpd() about TLS and pid files 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 Bump c/storage, c/image, c/common for v1.37.0
"build with basename resolving user arg" tests: correct ARG use "build with basename resolving user arg" tests: correct ARG use

View File

@ -41,7 +41,7 @@ LIBSECCOMP_COMMIT := release-2.3
EXTRA_LDFLAGS ?= EXTRA_LDFLAGS ?=
BUILDAH_LDFLAGS := $(GO_LDFLAGS) '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(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 ?= LINTFLAGS ?=

View File

@ -495,8 +495,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
wg.Add(1) wg.Add(1)
if sourceIsGit(src) { if sourceIsGit(src) {
go func() { go func() {
var cloneDir string var cloneDir, subdir string
cloneDir, _, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src) cloneDir, subdir, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src)
getOptions := copier.GetOptions{ getOptions := copier.GetOptions{
UIDMap: srcUIDMap, UIDMap: srcUIDMap,
GIDMap: srcGIDMap, GIDMap: srcGIDMap,
@ -511,7 +511,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
StripStickyBit: options.StripStickyBit, StripStickyBit: options.StripStickyBit,
} }
writer := io.WriteCloser(pipeWriter) 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() pipeWriter.Close()
wg.Done() wg.Done()
}() }()

View File

@ -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) - 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 * 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 * fix(deps): update module golang.org/x/crypto to v0.29.0
@ -118,7 +130,7 @@
* Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions * Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions
* integration tests: teach starthttpd() about TLS and pid files * 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 * Bump c/storage, c/image, c/common for v1.37.0
* "build with basename resolving user arg" tests: correct ARG use * "build with basename resolving user arg" tests: correct ARG use
* bud-multiple-platform-no-run test: correct ARG use * bud-multiple-platform-no-run test: correct ARG use

View File

@ -29,7 +29,7 @@ const (
// identify working containers. // identify working containers.
Package = "buildah" Package = "buildah"
// Version for the Package. Also used by .packit.sh for Packit builds. // Version for the Package. Also used by .packit.sh for Packit builds.
Version = "1.38.0" Version = "1.38.1"
// DefaultRuntime if containers.conf fails. // DefaultRuntime if containers.conf fails.
DefaultRuntime = "runc" DefaultRuntime = "runc"
@ -169,13 +169,13 @@ type SBOMScanOptions struct {
MergeStrategy SBOMMergeStrategy // how to merge the outputs of multiple scans 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 checks if the passed-in string looks like a URL or "-". If it
// TempDirForURL creates a temporary directory, arranges for its contents to be // is, TempDirForURL creates a temporary directory, arranges for its contents
// the contents of that URL, and returns the temporary directory's path, along // to be the contents of that URL, and returns the temporary directory's path,
// with the name of a subdirectory which should be used as the build context // along with the relative name of a subdirectory which should be used as the
// (which may be empty or "."). Removal of the temporary directory is the // build context (which may be empty or "."). Removal of the temporary
// responsibility of the caller. If the string doesn't look like a URL, // directory is the responsibility of the caller. If the string doesn't look
// TempDirForURL returns empty strings and a nil error code. // 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) { func TempDirForURL(dir, prefix, url string) (name string, subdir string, err error) {
if !strings.HasPrefix(url, "http://") && if !strings.HasPrefix(url, "http://") &&
!strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "https://") &&
@ -188,19 +188,24 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err
if err != nil { if err != nil {
return "", "", fmt.Errorf("creating temporary directory for %q: %w", url, err) 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) urlParsed, err := urlpkg.Parse(url)
if err != nil { if err != nil {
return "", "", fmt.Errorf("parsing url %q: %w", url, err) return "", "", fmt.Errorf("parsing url %q: %w", url, err)
} }
if strings.HasPrefix(url, "git://") || strings.HasSuffix(urlParsed.Path, ".git") { 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 err != nil {
if err2 := os.RemoveAll(name); err2 != nil { if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2) 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 "", "", 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/") { if strings.HasPrefix(url, "github.com/") {
ghurl := url ghurl := url
@ -209,28 +214,29 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err
subdir = path.Base(ghurl) + "-master" subdir = path.Base(ghurl) + "-master"
} }
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
err = downloadToDirectory(url, name) err = downloadToDirectory(url, downloadDir)
if err != nil { if err != nil {
if err2 := os.RemoveAll(name); err2 != nil { if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2) 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 == "-" { if url == "-" {
err = stdinToDirectory(name) err = stdinToDirectory(downloadDir)
if err != nil { if err != nil {
if err2 := os.RemoveAll(name); err2 != nil { if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2) logrus.Debugf("error removing temporary directory %q: %v", name, err2)
} }
return "", subdir, err return "", "", err
} }
logrus.Debugf("Build context is at %q", name) logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir))
return name, subdir, nil return name, filepath.Join(filepath.Base(downloadDir), subdir), nil
} }
logrus.Debugf("don't know how to retrieve %q", url) 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) logrus.Debugf("error removing temporary directory %q: %v", name, err2)
} }
return "", "", errors.New("unreachable code reached") return "", "", errors.New("unreachable code reached")

View File

@ -639,7 +639,12 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
// to `mountPoint` replaced from additional // to `mountPoint` replaced from additional
// build-context. Reason: Parser will use this // build-context. Reason: Parser will use this
// `from` to refer from stageMountPoints map later. // `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 break
} }
// Most likely this points to path on filesystem // Most likely this points to path on filesystem
@ -671,7 +676,11 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
mountPoint = additionalBuildContext.DownloadedCache mountPoint = additionalBuildContext.DownloadedCache
} }
} }
stageMountPoints[from] = internal.StageMountDetails{IsStage: true, DidExecute: true, MountPoint: mountPoint} stageMountPoints[from] = internal.StageMountDetails{
IsAdditionalBuildContext: true,
DidExecute: true,
MountPoint: mountPoint,
}
break break
} }
// If the source's name corresponds to the // 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 // If the source's name is a stage, return a
// pointer to its rootfs. // pointer to its rootfs.
if otherStage, ok := s.executor.stages[from]; ok && otherStage.index < s.index { 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 break
} else { } else {
// Treat the source's name as the name of an image. // 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 { if err != nil {
return nil, fmt.Errorf("%s from=%s: no stage or image found with that name", flag, from) 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 break
} }
default: default:

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"}
}

View File

@ -12,7 +12,9 @@ const (
// StageExecutor has ability to mount stages/images in current context and // StageExecutor has ability to mount stages/images in current context and
// automatically clean them up. // automatically clean them up.
type StageMountDetails struct { type StageMountDetails struct {
DidExecute bool // tells if the stage which is being mounted was freshly executed or was part of older cache 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 temporary directory or a stage's rootfs, false if it's an image IsStage bool // true if the mountpoint is a stage's rootfs
MountPoint string // mountpoint of the stage or image's root directory 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
} }

View File

@ -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
}

View File

@ -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")
}

View File

@ -16,15 +16,19 @@ import (
internalParse "github.com/containers/buildah/internal/parse" internalParse "github.com/containers/buildah/internal/parse"
"github.com/containers/buildah/internal/tmpdir" "github.com/containers/buildah/internal/tmpdir"
internalUtil "github.com/containers/buildah/internal/util" internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/parse"
"github.com/containers/image/v5/types" "github.com/containers/image/v5/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/lockfile"
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/unshare" "github.com/containers/storage/pkg/unshare"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
selinux "github.com/opencontainers/selinux/go-selinux" selinux "github.com/opencontainers/selinux/go-selinux"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
) )
const ( const (
@ -55,18 +59,84 @@ func CacheParent() string {
return filepath.Join(tmpdir.GetTempDir(), buildahCacheDir+"-"+strconv.Itoa(unshare.GetRootlessUID())) 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 // FIXME: this code needs to be merged with pkg/parse/parse.go ValidateVolumeOpts
//
// GetBindMount parses a single bind mount entry from the --mount flag. // 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 // Returns a Mount to add to the runtime spec's list of mounts, the ID of the
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) { // 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{ newMount := specs.Mount{
Type: define.TypeBind, Type: define.TypeBind,
} }
setRelabel := false setRelabel := ""
mountReadability := false mountReadability := ""
setDest := false setDest := ""
bindNonRecursive := false bindNonRecursive := false
fromImage := "" fromImage := ""
@ -79,86 +149,85 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st
case "bind-nonrecursive": case "bind-nonrecursive":
newMount.Options = append(newMount.Options, "bind") newMount.Options = append(newMount.Options, "bind")
bindNonRecursive = true bindNonRecursive = true
case "ro", "nosuid", "nodev", "noexec": case "nosuid", "nodev", "noexec":
// TODO: detect duplication of these options. // TODO: detect duplication of these options.
// (Is this necessary?) // (Is this necessary?)
newMount.Options = append(newMount.Options, argName) newMount.Options = append(newMount.Options, argName)
mountReadability = true
case "rw", "readwrite": case "rw", "readwrite":
newMount.Options = append(newMount.Options, "rw") newMount.Options = append(newMount.Options, "rw")
mountReadability = true mountReadability = "rw"
case "readonly": case "ro", "readonly":
// Alias for "ro"
newMount.Options = append(newMount.Options, "ro") newMount.Options = append(newMount.Options, "ro")
mountReadability = true mountReadability = "ro"
case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z", "U", "no-dereference": case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z", "U", "no-dereference":
if hasArgValue { if hasArgValue {
return newMount, "", fmt.Errorf("%v: %w", val, errBadOptionArg) return newMount, "", "", "", fmt.Errorf("%v: %w", val, errBadOptionArg)
} }
newMount.Options = append(newMount.Options, argName) newMount.Options = append(newMount.Options, argName)
case "from": case "from":
if !hasArgValue { if !hasArgValue {
return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
fromImage = argValue fromImage = argValue
case "bind-propagation": case "bind-propagation":
if !hasArgValue { if !hasArgValue {
return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
switch argValue { switch argValue {
default: 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": case "shared", "rshared", "private", "rprivate", "slave", "rslave":
// this should be the relevant parts of the same list of options we accepted above // this should be the relevant parts of the same list of options we accepted above
} }
newMount.Options = append(newMount.Options, argValue) newMount.Options = append(newMount.Options, argValue)
case "src", "source": case "src", "source":
if !hasArgValue { if !hasArgValue {
return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
newMount.Source = argValue newMount.Source = argValue
case "target", "dst", "destination": case "target", "dst", "destination":
if !hasArgValue { if !hasArgValue {
return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
targetPath := argValue targetPath := argValue
setDest = targetPath
if !path.IsAbs(targetPath) { if !path.IsAbs(targetPath) {
targetPath = filepath.Join(workDir, targetPath) targetPath = filepath.Join(workDir, targetPath)
} }
if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { if err := parse.ValidateVolumeCtrDir(targetPath); err != nil {
return newMount, "", err return newMount, "", "", "", err
} }
newMount.Destination = targetPath newMount.Destination = targetPath
setDest = true
case "relabel": case "relabel":
if setRelabel { if setRelabel != "" {
return newMount, "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg) return newMount, "", "", "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg)
} }
setRelabel = true setRelabel = argValue
switch argValue { switch argValue {
case "private": case "private":
newMount.Options = append(newMount.Options, "Z") newMount.Options = append(newMount.Options, "Z")
case "shared": case "shared":
newMount.Options = append(newMount.Options, "z") newMount.Options = append(newMount.Options, "z")
default: 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": case "consistency":
// Option for OS X only, has no meaning on other platforms // Option for OS X only, has no meaning on other platforms
// and can thus be safely ignored. // and can thus be safely ignored.
// See also the handling of the equivalent "delegated" and "cached" in ValidateVolumeOpts // See also the handling of the equivalent "delegated" and "cached" in ValidateVolumeOpts
default: default:
return newMount, "", fmt.Errorf("%v: %w", argName, errBadMntOption) return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadMntOption)
} }
} }
// default mount readability is always readonly // default mount readability is always readonly
if !mountReadability { if mountReadability == "" {
newMount.Options = append(newMount.Options, "ro") newMount.Options = append(newMount.Options, "ro")
} }
// Following variable ensures that we return imagename only if we did additional mount // Following variable ensures that we return imagename only if we did additional mount
isImageMounted := false succeeded := false
mountedImage := ""
if fromImage != "" { if fromImage != "" {
mountPoint := "" mountPoint := ""
if additionalMountPoints != nil { 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 // if mountPoint of image was not found in additionalMap
// or additionalMap was nil, try mounting image // or additionalMap was nil, try mounting image
if mountPoint == "" { if mountPoint == "" {
image, err := internalUtil.LookupImage(ctx, store, fromImage) image, err := internalUtil.LookupImage(sys, store, fromImage)
if err != nil { 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 { if err != nil {
return newMount, "", err return newMount, "", "", "", err
} }
isImageMounted = true 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 contextDir = mountPoint
} }
@ -189,48 +265,73 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st
newMount.Options = append(newMount.Options, "rbind") newMount.Options = append(newMount.Options, "rbind")
} }
if !setDest { if setDest == "" {
return newMount, fromImage, errBadVolDest return newMount, "", "", "", errBadVolDest
} }
// buildkit parity: support absolute path for sources from current build context // buildkit parity: support absolute path for sources from current build context
if contextDir != "" { if contextDir != "" {
// path should be /contextDir/specified path // 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 { if err != nil {
return newMount, "", err return newMount, "", "", "", err
} }
newMount.Source = evaluated newMount.Source = evaluated
} else { } else {
// looks like its coming from `build run --mount=type=bind` allow using absolute path // looks like its coming from `build run --mount=type=bind` allow using absolute path
// error out if no source is set // error out if no source is set
if newMount.Source == "" { if newMount.Source == "" {
return newMount, "", errBadVolSrc return newMount, "", "", "", errBadVolSrc
} }
if err := parse.ValidateVolumeHostDir(newMount.Source); err != nil { if err := parse.ValidateVolumeHostDir(newMount.Source); err != nil {
return newMount, "", err return newMount, "", "", "", err
} }
} }
opts, err := parse.ValidateVolumeOpts(newMount.Options) opts, err := parse.ValidateVolumeOpts(newMount.Options)
if err != nil { if err != nil {
return newMount, fromImage, err return newMount, "", "", "", err
} }
newMount.Options = opts newMount.Options = opts
if !isImageMounted { var intermediateMount string
// we don't want any cleanups if image was not mounted explicitly if contextDir != "" && newMount.Source != contextDir {
// so dont return anything rel, err := filepath.Rel(contextDir, newMount.Source)
fromImage = "" 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. // 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??). // Returns a Mount to add to the runtime spec's list of mounts, the path of a
func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoints map[string]internal.StageMountDetails, workDir string) (specs.Mount, *lockfile.LockFile, error) { // 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 err error
var mode uint64 var mode uint64
var buildahLockFilesDir string var buildahLockFilesDir string
@ -281,69 +382,69 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin
sharing = argValue sharing = argValue
case "bind-propagation": case "bind-propagation":
if !hasArgValue { if !hasArgValue {
return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
switch argValue { switch argValue {
default: 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": case "shared", "rshared", "private", "rprivate", "slave", "rslave":
// this should be the relevant parts of the same list of options we accepted above // this should be the relevant parts of the same list of options we accepted above
} }
newMount.Options = append(newMount.Options, argValue) newMount.Options = append(newMount.Options, argValue)
case "id": case "id":
if !hasArgValue { if !hasArgValue {
return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
id = argValue id = argValue
case "from": case "from":
if !hasArgValue { if !hasArgValue {
return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
fromStage = argValue fromStage = argValue
case "target", "dst", "destination": case "target", "dst", "destination":
if !hasArgValue { if !hasArgValue {
return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
targetPath := argValue targetPath := argValue
if !path.IsAbs(targetPath) { if !path.IsAbs(targetPath) {
targetPath = filepath.Join(workDir, targetPath) targetPath = filepath.Join(workDir, targetPath)
} }
if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { if err := parse.ValidateVolumeCtrDir(targetPath); err != nil {
return newMount, nil, err return newMount, "", nil, err
} }
newMount.Destination = targetPath newMount.Destination = targetPath
setDest = true setDest = true
case "src", "source": case "src", "source":
if !hasArgValue { if !hasArgValue {
return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
} }
newMount.Source = argValue newMount.Source = argValue
case "mode": case "mode":
if !hasArgValue { 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) mode, err = strconv.ParseUint(argValue, 8, 32)
if err != nil { 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": case "uid":
if !hasArgValue { 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) uid, err = strconv.Atoi(argValue)
if err != nil { 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": case "gid":
if !hasArgValue { 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) gid, err = strconv.Atoi(argValue)
if err != nil { 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: 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 { if !setDest {
return newMount, nil, errBadVolDest return newMount, "", nil, errBadVolDest
} }
thisCacheRoot := ""
if fromStage != "" { if fromStage != "" {
// do not create and use a cache directory on the host, // do not create and use a cache directory on the host,
// instead use the location in the mounted stage or // instead use the location in the mounted stage or
@ -364,7 +466,7 @@ func GetCacheMount(args []string, _ storage.Store, _ string, additionalMountPoin
mountPoint := "" mountPoint := ""
if additionalMountPoints != nil { if additionalMountPoints != nil {
if val, ok := additionalMountPoints[fromStage]; ok { if val, ok := additionalMountPoints[fromStage]; ok {
if val.IsStage { if !val.IsImage {
mountPoint = val.MountPoint 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 // Cache does not support using an image so if there's no such
// stage or temporary directory, return an error // stage or temporary directory, return an error
if mountPoint == "" { 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 thisCacheRoot = mountPoint
evaluated, err := copier.Eval(mountPoint, string(filepath.Separator)+newMount.Source, copier.EvalOptions{})
if err != nil {
return newMount, nil, err
}
newMount.Source = evaluated
} else { } else {
// we need to create the cache directory on the host if no image is being used // 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. // cache parent directory: creates separate cache parent for each user.
cacheParent := CacheParent() cacheParent := CacheParent()
// create cache on host if not present // create cache on host if not present
err = os.MkdirAll(cacheParent, os.FileMode(0o755)) err = os.MkdirAll(cacheParent, os.FileMode(0o755))
if err != nil { 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 != "" { if id != "" {
// Don't let the user control where we place the directory. // Don't let the user control where we place the directory.
dirID := digest.FromString(id).Encoded()[:16] dirID := digest.FromString(id).Encoded()[:16]
newMount.Source = filepath.Join(cacheParent, dirID) thisCacheRoot = filepath.Join(cacheParent, dirID)
buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID) buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID)
} else { } else {
// Don't let the user control where we place the directory. // Don't let the user control where we place the directory.
dirID := digest.FromString(newMount.Destination).Encoded()[:16] dirID := digest.FromString(newMount.Destination).Encoded()[:16]
newMount.Source = filepath.Join(cacheParent, dirID) thisCacheRoot = filepath.Join(cacheParent, dirID)
buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID) buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID)
} }
idPair := idtools.IDPair{ idPair := idtools.IDPair{
UID: uid, UID: uid,
GID: gid, GID: gid,
} }
// buildkit parity: change uid and gid if specified, otherwise keep `0` // 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 { 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 // create a subdirectory inside `cacheParent` just to store lockfiles
buildahLockFilesDir = filepath.Join(cacheParent, buildahLockFilesDir) buildahLockFilesDir = filepath.Join(cacheParent, buildahLockFilesDir)
err = os.MkdirAll(buildahLockFilesDir, os.FileMode(0o700)) err = os.MkdirAll(buildahLockFilesDir, os.FileMode(0o700))
if err != nil { 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 succeeded := false
defer func() { var targetLock *lockfile.LockFile
if !succeeded && targetLock != nil {
targetLock.Unlock()
}
}()
switch sharing { switch sharing {
case "locked": case "locked":
// lock parent cache // lock parent cache
lockfile, err := lockfile.GetLockFile(filepath.Join(buildahLockFilesDir, BuildahCacheLockfile)) lockfile, err := lockfile.GetLockFile(filepath.Join(buildahLockFilesDir, BuildahCacheLockfile))
if err != nil { 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. // Will be unlocked after the RUN step is executed.
lockfile.Lock() lockfile.Lock()
targetLock = lockfile targetLock = lockfile
defer func() {
if !succeeded {
targetLock.Unlock()
}
}()
case "shared": case "shared":
// do nothing since default is `shared` // do nothing since default is `shared`
break break
default: default:
// error out for unknown values // 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 // 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) opts, err := parse.ValidateVolumeOpts(newMount.Options)
if err != nil { if err != nil {
return newMount, nil, err return newMount, "", nil, err
} }
newMount.Options = opts 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 succeeded = true
return newMount, targetLock, nil return newMount, intermediateMount, targetLock, nil
} }
func getVolumeMounts(volumes []string) (map[string]specs.Mount, error) { 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??). // Returns a slice of Mounts to add to the runtime spec's list of mounts, the
func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string, mounts []string, contextDir string, workDir string) ([]specs.Mount, []string, []*lockfile.LockFile, error) { // IDs of any images we mounted, a slice of bind-mounted paths, a slice of
unifiedMounts, mountedImages, targetLocks, err := getMounts(ctx, store, mounts, contextDir, workDir) // 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 { if err != nil {
return nil, mountedImages, nil, err return nil, nil, nil, nil, nil, err
} }
succeeded := false succeeded := false
defer func() { defer func() {
if !succeeded { 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) UnlockLockArray(targetLocks)
} }
}() }()
volumeMounts, err := getVolumeMounts(volumes) volumeMounts, err := getVolumeMounts(volumes)
if err != nil { if err != nil {
return nil, mountedImages, nil, err return nil, nil, nil, nil, nil, err
} }
for dest, mount := range volumeMounts { for dest, mount := range volumeMounts {
if _, ok := unifiedMounts[dest]; ok { 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 unifiedMounts[dest] = mount
} }
@ -525,25 +674,53 @@ func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string,
finalMounts = append(finalMounts, mount) finalMounts = append(finalMounts, mount)
} }
succeeded = true 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 // getMounts takes user-provided inputs from the --mount flag and returns a
// spec mounts. // slice of OCI spec mounts, a slice of mounted image IDs, a slice of other
// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... // mount locations, a slice of overlay mounts, and a slice of locks, or an
// buildah run --mount type=cache,target=/var/cache ... // error.
// buildah run --mount type=tmpfs,target=/dev/shm ...
// //
// If this function succeeds, the caller must unlock the returned *lockfile.LockFile s if any (when??). // buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
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=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" // If `type` is not set default to "bind"
mountType := define.TypeBind mountType := define.TypeBind
finalMounts := make(map[string]specs.Mount) finalMounts := make(map[string]specs.Mount, len(mounts))
mountedImages := make([]string, 0) mountedImages := make([]string, 0, len(mounts))
targetLocks := make([]*lockfile.LockFile, 0) intermediateMounts := make([]string, 0, len(mounts))
overlayMounts := make([]string, 0, len(mounts))
targetLocks := make([]*lockfile.LockFile, 0, len(mounts))
succeeded := false succeeded := false
defer func() { defer func() {
if !succeeded { 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) UnlockLockArray(targetLocks)
} }
}() }()
@ -556,56 +733,67 @@ func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, c
for _, mount := range mounts { for _, mount := range mounts {
tokens := strings.Split(mount, ",") tokens := strings.Split(mount, ",")
if len(tokens) < 2 { 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 { for _, field := range tokens {
if strings.HasPrefix(field, "type=") { if strings.HasPrefix(field, "type=") {
kv := strings.Split(field, "=") kv := strings.Split(field, "=")
if len(kv) != 2 { 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] mountType = kv[1]
} }
} }
switch mountType { switch mountType {
case define.TypeBind: 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 { 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 { 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 finalMounts[mount.Destination] = mount
mountedImages = append(mountedImages, image)
case TypeCache: case TypeCache:
mount, tl, err := GetCacheMount(tokens, store, "", nil, workDir) mount, intermediateMount, tl, err := GetCacheMount(tokens, nil, workDir, tmpDir)
if err != nil { if err != nil {
return nil, mountedImages, nil, err return nil, nil, nil, nil, nil, err
}
if intermediateMount != "" {
intermediateMounts = append(intermediateMounts, intermediateMount)
} }
if tl != nil { if tl != nil {
targetLocks = append(targetLocks, tl) targetLocks = append(targetLocks, tl)
} }
if _, ok := finalMounts[mount.Destination]; ok { 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 finalMounts[mount.Destination] = mount
case TypeTmpfs: case TypeTmpfs:
mount, err := GetTmpfsMount(tokens, workDir) mount, err := GetTmpfsMount(tokens, workDir)
if err != nil { if err != nil {
return nil, mountedImages, nil, err return nil, nil, nil, nil, nil, err
} }
if _, ok := finalMounts[mount.Destination]; ok { 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 finalMounts[mount.Destination] = mount
default: 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 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 // GetTmpfsMount parses a single tmpfs mount entry from the --mount flag

View File

@ -10,6 +10,7 @@ import (
"syscall" "syscall"
"github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/system" "github.com/containers/storage/pkg/system"
"github.com/containers/storage/pkg/unshare" "github.com/containers/storage/pkg/unshare"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
@ -48,6 +49,12 @@ type Options struct {
RootUID int RootUID int
// RootGID is not used yet but keeping it here for legacy reasons. // RootGID is not used yet but keeping it here for legacy reasons.
RootGID int 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 // TempDir generates an overlay Temp directory in the container content
@ -144,6 +151,12 @@ func mountWithMountProgram(mountProgram, overlayOptions, mergeDir string) error
return nil 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 // Convert ":" to "\:", the path which will be overlay mounted need to be escaped
func escapeColon(source string) string { func escapeColon(source string) string {
return strings.ReplaceAll(source, ":", "\\:") return strings.ReplaceAll(source, ":", "\\:")

View File

@ -9,6 +9,7 @@ import (
"github.com/containers/storage/pkg/unshare" "github.com/containers/storage/pkg/unshare"
"github.com/opencontainers/runtime-spec/specs-go" "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 // 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) 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) mountProgram := findMountProgram(opts.GraphOpts)
if mountProgram != "" { if mountProgram != "" {
@ -79,5 +83,17 @@ func MountWithOptions(contentDir, source, dest string, opts *Options) (mount spe
mount.Type = "overlay" mount.Type = "overlay"
mount.Options = strings.Split(overlayOptions, ",") 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 return mount, nil
} }

View File

@ -180,18 +180,22 @@ type RunOptions struct {
// RunMountArtifacts are the artifacts created when using a run mount. // RunMountArtifacts are the artifacts created when using a run mount.
type runMountArtifacts struct { 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 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 are artifacts that need to be removed outside the container
TmpFiles []string TmpFiles []string
// Any external images which were mounted inside container // Any images which were mounted, which should be unmounted
MountedImages []string MountedImages []string
// Agents are the ssh agents started // Agents are the ssh agents started, which should have their Shutdown() methods called
Agents []*sshagent.AgentServer Agents []*sshagent.AgentServer
// SSHAuthSock is the path to the ssh auth sock inside the container // SSHAuthSock is the path to the ssh auth sock inside the container
SSHAuthSock string SSHAuthSock string
// TargetLocks to be unlocked if there are any. // Lock files, which should have their Unlock() methods called
TargetLocks []*lockfile.LockFile 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 // RunMountInfo are the available run mounts for this run

View File

@ -27,7 +27,6 @@ import (
"github.com/containers/buildah/define" "github.com/containers/buildah/define"
"github.com/containers/buildah/internal" "github.com/containers/buildah/internal"
"github.com/containers/buildah/internal/tmpdir" "github.com/containers/buildah/internal/tmpdir"
internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/buildah/internal/volumes" "github.com/containers/buildah/internal/volumes"
"github.com/containers/buildah/pkg/overlay" "github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/sshagent" "github.com/containers/buildah/pkg/sshagent"
@ -40,15 +39,14 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/subscriptions" "github.com/containers/common/pkg/subscriptions"
"github.com/containers/image/v5/types" "github.com/containers/image/v5/types"
imageTypes "github.com/containers/image/v5/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/fileutils" "github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/lockfile"
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/reexec" "github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/unshare" "github.com/containers/storage/pkg/unshare"
storageTypes "github.com/containers/storage/types"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/generate"
@ -1311,7 +1309,9 @@ func init() {
reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain) 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) { 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. // Start building a new list of mounts.
var mounts []specs.Mount var mounts []specs.Mount
@ -1370,14 +1370,16 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
processGID: int(processGID), processGID: int(processGID),
} }
// Get the list of mounts that are just for this Run() call. // 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 { if err != nil {
return nil, err return nil, err
} }
succeeded := false succeeded := false
defer func() { defer func() {
if !succeeded { 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 // 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 // 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??) // If this function succeeds, the caller must free the returned
func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources runMountInfo, idMaps IDMaps) ([]specs.Mount, *runMountArtifacts, error) { // runMountArtifacts by calling b.cleanupRunMounts() after the command being
mountTargets := make([]string, 0, 10) // 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)) 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)) finalMounts := make([]specs.Mount, 0, len(mounts))
agents := make([]*sshagent.AgentServer, 0, len(mounts)) agents := make([]*sshagent.AgentServer, 0, len(mounts))
sshCount := 0
defaultSSHSock := "" defaultSSHSock := ""
targetLocks := []*lockfile.LockFile{} targetLocks := []*lockfile.LockFile{}
var overlayDirs []string
succeeded := false succeeded := false
defer func() { defer func() {
if !succeeded { 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) volumes.UnlockLockArray(targetLocks)
} }
}() }()
for _, mount := range mounts { for _, mount := range mounts {
var mountSpec *specs.Mount var mountSpec *specs.Mount
var err error var err error
var envFile, image string var envFile, image, bundleMountsDir, overlayDir, intermediateMount string
var agent *sshagent.AgentServer var agent *sshagent.AgentServer
var tl *lockfile.LockFile var tl *lockfile.LockFile
tokens := strings.Split(mount, ",") tokens := strings.Split(mount, ",")
// If `type` is not set default to TypeBind // If `type` is not set default to TypeBind
@ -1581,29 +1616,37 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources
} }
} }
case "ssh": 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
if mountSpec != nil { if mountSpec != nil {
finalMounts = append(finalMounts, *mountSpec) finalMounts = append(finalMounts, *mountSpec)
agents = append(agents, agent) if len(agents) == 0 {
if sshCount == 0 {
defaultSSHSock = mountSpec.Destination defaultSSHSock = mountSpec.Destination
} }
// Count is needed as the default destination of the ssh sock inside the container is /run/buildkit/ssh_agent.{i} agents = append(agents, agent)
sshCount++
} }
case define.TypeBind: 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
finalMounts = append(finalMounts, *mountSpec)
// only perform cleanup if image was mounted ignore everything else
if image != "" { if image != "" {
mountImages = append(mountImages, image) mountImages = append(mountImages, image)
} }
if overlayDir != "" {
overlayDirs = append(overlayDirs, overlayDir)
}
if intermediateMount != "" {
intermediateMounts = append(intermediateMounts, intermediateMount)
}
finalMounts = append(finalMounts, *mountSpec)
case "tmpfs": case "tmpfs":
mountSpec, err = b.getTmpfsMount(tokens, idMaps, sources.WorkDir) mountSpec, err = b.getTmpfsMount(tokens, idMaps, sources.WorkDir)
if err != nil { if err != nil {
@ -1611,14 +1654,22 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources
} }
finalMounts = append(finalMounts, *mountSpec) finalMounts = append(finalMounts, *mountSpec)
case "cache": 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
finalMounts = append(finalMounts, *mountSpec) if intermediateMount != "" {
intermediateMounts = append(intermediateMounts, intermediateMount)
}
if tl != nil { if tl != nil {
targetLocks = append(targetLocks, tl) targetLocks = append(targetLocks, tl)
} }
finalMounts = append(finalMounts, *mountSpec)
default: default:
return nil, nil, fmt.Errorf("invalid mount type %q", mountType) 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 succeeded = true
artifacts := &runMountArtifacts{ artifacts := &runMountArtifacts{
RunMountTargets: mountTargets, RunMountTargets: mountTargets,
TmpFiles: tmpFiles, RunOverlayDirs: overlayDirs,
Agents: agents, TmpFiles: tmpFiles,
MountedImages: mountImages, Agents: agents,
SSHAuthSock: defaultSSHSock, MountedImages: mountImages,
TargetLocks: targetLocks, SSHAuthSock: defaultSSHSock,
TargetLocks: targetLocks,
IntermediateMounts: intermediateMounts,
} }
return finalMounts, artifacts, nil 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 == "" { 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 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 { if err != nil {
return nil, image, err return nil, "", "", "", err
} }
optionMounts = append(optionMounts, mount) optionMounts = append(optionMounts, mount)
volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps) volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps)
if err != nil { 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) { 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. // 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 { for _, agent := range artifacts.Agents {
err := agent.Shutdown() servePath := agent.ServePath()
if err != nil { 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 return err
} }
} }
// unmount anything that needs unmounting
// cleanup any mounted images for this run 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 { for _, image := range artifacts.MountedImages {
if image != "" { if _, err := b.store.UnmountImage(image, false); err != nil {
// if flow hits here some image was mounted for this run logrus.Debugf("umounting image %q: %v", image, err)
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
} }
} }
// remove mount targets that were created for this run
opts := copier.RemoveOptions{ opts := copier.RemoveOptions{
All: true, All: true,
} }
for _, path := range artifacts.RunMountTargets { for _, path := range artifacts.RunMountTargets {
err := copier.Remove(mountpoint, path, opts) if err := copier.Remove(mountpoint, path, opts); err != nil {
if err != nil { return fmt.Errorf("removing mount target %q %q: %w", mountpoint, path, err)
return err
} }
} }
var prevErr error var prevErr error
for _, path := range artifacts.TmpFiles { for _, path := range artifacts.TmpFiles {
err := os.Remove(path) if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
if !errors.Is(err, os.ErrNotExist) {
if prevErr != nil { if prevErr != nil {
logrus.Error(prevErr) 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) volumes.UnlockLockArray(artifacts.TargetLocks)
return prevErr return prevErr
} }

View File

@ -280,7 +280,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
} }
defer func() { 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) 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 return spec.Mounts, nil
} }
// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??). // If this succeeded, the caller would be expected to, after the command which
func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*spec.Mount, *lockfile.LockFile, error) { // uses the mount exits, unmount the mounted filesystem and remove its
return nil, nil, errors.New("cache mounts not supported on freebsd") // 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) { func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, idMaps IDMaps) (mounts []specs.Mount, Err error) {

View File

@ -39,6 +39,7 @@ import (
"github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/lockfile"
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/stringid"
"github.com/containers/storage/pkg/unshare" "github.com/containers/storage/pkg/unshare"
"github.com/docker/go-units" "github.com/docker/go-units"
@ -515,7 +516,7 @@ rootless=%d
} }
defer func() { 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) 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, RootGID: idMaps.rootGID,
UpperDirOptionFragment: upperDir, UpperDirOptionFragment: upperDir,
WorkDirOptionFragment: workDir, WorkDirOptionFragment: workDir,
GraphOpts: b.store.GraphOptions(), GraphOpts: slices.Clone(b.store.GraphOptions()),
} }
overlayMount, err := overlay.MountWithOptions(contentDir, host, container, &overlayOpts) 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 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 { if err := chown.ChangeHostPathOwnership(contentDir, true, idMaps.processUID, idMaps.processGID); err != nil {
return specs.Mount{}, err return specs.Mount{}, err
} }
@ -1402,24 +1403,39 @@ func checkIDsGreaterThan5(ids []specs.LinuxIDMapping) bool {
return false return false
} }
// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??). // Returns a Mount to add to the runtime spec's list of mounts, an optional
func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*specs.Mount, *lockfile.LockFile, error) { // 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 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 { if err != nil {
return nil, nil, err return nil, "", nil, err
} }
succeeded := false succeeded := false
defer func() { defer func() {
if !succeeded && targetLock != nil { if !succeeded {
targetLock.Unlock() 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) volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps)
if err != nil { if err != nil {
return nil, nil, err return nil, "", nil, err
} }
succeeded = true succeeded = true
return &volumes[0], targetLock, nil return &volumes[0], intermediateMount, targetLock, nil
} }

3
vendor/modules.txt vendored
View File

@ -143,7 +143,7 @@ github.com/containernetworking/cni/pkg/version
# github.com/containernetworking/plugins v1.5.1 # github.com/containernetworking/plugins v1.5.1
## explicit; go 1.20 ## explicit; go 1.20
github.com/containernetworking/plugins/pkg/ns github.com/containernetworking/plugins/pkg/ns
# github.com/containers/buildah v1.38.0 # github.com/containers/buildah v1.38.1
## explicit; go 1.22.6 ## explicit; go 1.22.6
github.com/containers/buildah github.com/containers/buildah
github.com/containers/buildah/bind github.com/containers/buildah/bind
@ -156,6 +156,7 @@ github.com/containers/buildah/internal
github.com/containers/buildah/internal/config github.com/containers/buildah/internal/config
github.com/containers/buildah/internal/mkcw github.com/containers/buildah/internal/mkcw
github.com/containers/buildah/internal/mkcw/types github.com/containers/buildah/internal/mkcw/types
github.com/containers/buildah/internal/open
github.com/containers/buildah/internal/parse github.com/containers/buildah/internal/parse
github.com/containers/buildah/internal/sbom github.com/containers/buildah/internal/sbom
github.com/containers/buildah/internal/tmpdir github.com/containers/buildah/internal/tmpdir