diff --git a/build/build.go b/build/build.go index a0e2974dd90..50be831ef18 100644 --- a/build/build.go +++ b/build/build.go @@ -595,10 +595,6 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true" } - for k, v := range getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath) { - so.FrontendAttrs[k] = v - } - // set platforms if len(opt.Platforms) != 0 { pp := make([]string, len(opt.Platforms)) @@ -853,6 +849,10 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s for k, opt := range opt { multiDriver := len(m[k]) > 1 hasMobyDriver := false + gitattrs, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath) + if err != nil { + logrus.Warn(err) + } for i, np := range m[k] { node := nodes[np.driverIndex] if node.Driver.IsMobyDriver() { @@ -862,6 +862,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s so, release, err := toSolveOpt(ctx, node, multiDriver, opt, np.bopts, configDir, w, func(name string) (io.WriteCloser, func(), error) { return docker.LoadImage(ctx, name, w) }) + for k, v := range gitattrs { + so.FrontendAttrs[k] = v + } if err != nil { return nil, err } diff --git a/build/git.go b/build/git.go index 8cabbb072ca..9332526962f 100644 --- a/build/git.go +++ b/build/git.go @@ -3,18 +3,19 @@ package build import ( "context" "os" + "path" "path/filepath" "strconv" "strings" "github.com/docker/buildx/util/gitutil" specs "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/sirupsen/logrus" + "github.com/pkg/errors" ) const DockerfileLabel = "com.docker.image.source.entrypoint" -func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string) { +func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string, _ error) { res = make(map[string]string) if contextPath == "" { return @@ -48,27 +49,50 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st wd, _ = filepath.Abs(filepath.Join(cwd, contextPath)) } - gitc := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd)) - if !gitc.IsInsideWorkTree() { - logrus.Warnf("Unable to determine Git information") + gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd)) + if err != nil { + if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { + return res, errors.New("git was not found in the system. Current commit information was not captured by the build") + } return } - var resRevision, resSource, resDockerfilePath string + if !gitc.IsInsideWorkTree() { + if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { + return res, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree") + } + return res, nil + } - if sha, err := gitc.FullCommit(); err == nil && sha != "" { - resRevision = sha + if sha, err := gitc.FullCommit(); err != nil { + return res, errors.Wrapf(err, "failed to get git commit") + } else if sha != "" { if gitc.IsDirty() { - resRevision += "-dirty" + sha += "-dirty" + } + if setGitLabels { + res["label:"+specs.AnnotationRevision] = sha + } + if setGitInfo { + res["vcs:revision"] = sha } } - if rurl, err := gitc.RemoteURL(); err == nil && rurl != "" { - resSource = rurl + if rurl, err := gitc.RemoteURL(); err != nil { + return res, errors.Wrapf(err, "failed to get git remote url") + } else if rurl != "" { + if setGitLabels { + res["label:"+specs.AnnotationSource] = rurl + } + if setGitInfo { + res["vcs:source"] = rurl + } } if setGitLabels { - if root, err := gitc.RootDir(); err == nil && root != "" { + if root, err := gitc.RootDir(); err != nil { + return res, errors.Wrapf(err, "failed to get git root dir") + } else if root != "" { if dockerfilePath == "" { dockerfilePath = filepath.Join(wd, "Dockerfile") } @@ -78,32 +102,10 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st } dockerfilePath, _ = filepath.Rel(root, dockerfilePath) if !strings.HasPrefix(dockerfilePath, "..") { - resDockerfilePath = dockerfilePath + res["label:"+DockerfileLabel] = dockerfilePath } } } - if resSource != "" { - if setGitLabels { - res["label:"+specs.AnnotationSource] = resSource - } - if setGitInfo { - res["vcs:source"] = resSource - } - } - if resRevision != "" { - if setGitLabels { - res["label:"+specs.AnnotationRevision] = resRevision - } - if setGitInfo { - res["vcs:revision"] = resRevision - } - } - if resDockerfilePath != "" { - if setGitLabels { - res["label:"+DockerfileLabel] = resDockerfilePath - } - } - return } diff --git a/build/git_test.go b/build/git_test.go index 6b5318372b3..9f2f732427d 100644 --- a/build/git_test.go +++ b/build/git_test.go @@ -3,6 +3,7 @@ package build import ( "context" "os" + "path" "path/filepath" "strings" "testing" @@ -10,21 +11,42 @@ import ( "github.com/docker/buildx/util/gitutil" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func setupTest(tb testing.TB) { gitutil.Mktmp(tb) - gitutil.GitInit(tb) + + c, err := gitutil.New() + require.NoError(tb, err) + gitutil.GitInit(c, tb) + df := []byte("FROM alpine:latest\n") assert.NoError(tb, os.WriteFile("Dockerfile", df, 0644)) - gitutil.GitAdd(tb, "Dockerfile") - gitutil.GitCommit(tb, "initial commit") + + gitutil.GitAdd(c, tb, "Dockerfile") + gitutil.GitCommit(c, tb, "initial commit") + gitutil.GitSetRemote(c, tb, "git@github.com:docker/buildx.git") +} + +func TestGetGitAttributesNotGitRepo(t *testing.T) { + _, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile") + assert.NoError(t, err) +} + +func TestGetGitAttributesBadGitRepo(t *testing.T) { + tmp := t.TempDir() + require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755)) + + _, err := getGitAttributes(context.Background(), tmp, "Dockerfile") + assert.Error(t, err) } func TestGetGitAttributesNoContext(t *testing.T) { setupTest(t) - gitattrs := getGitAttributes(context.Background(), "", "Dockerfile") + gitattrs, err := getGitAttributes(context.Background(), "", "Dockerfile") + assert.NoError(t, err) assert.Empty(t, gitattrs) } @@ -41,6 +63,7 @@ func TestGetGitAttributes(t *testing.T) { envGitInfo: "", expected: []string{ "vcs:revision", + "vcs:source", }, }, { @@ -55,6 +78,7 @@ func TestGetGitAttributes(t *testing.T) { envGitInfo: "true", expected: []string{ "vcs:revision", + "vcs:source", }, }, { @@ -64,6 +88,7 @@ func TestGetGitAttributes(t *testing.T) { expected: []string{ "label:" + DockerfileLabel, "label:" + specs.AnnotationRevision, + "label:" + specs.AnnotationSource, }, }, { @@ -73,7 +98,9 @@ func TestGetGitAttributes(t *testing.T) { expected: []string{ "label:" + DockerfileLabel, "label:" + specs.AnnotationRevision, + "label:" + specs.AnnotationSource, "vcs:revision", + "vcs:source", }, }, } @@ -87,52 +114,42 @@ func TestGetGitAttributes(t *testing.T) { if tt.envGitInfo != "" { t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo) } - gitattrs := getGitAttributes(context.Background(), ".", "Dockerfile") + gitattrs, err := getGitAttributes(context.Background(), ".", "Dockerfile") + require.NoError(t, err) for _, e := range tt.expected { assert.Contains(t, gitattrs, e) assert.NotEmpty(t, gitattrs[e]) if e == "label:"+DockerfileLabel { assert.Equal(t, "Dockerfile", gitattrs[e]) + } else if e == "label:"+specs.AnnotationSource || e == "vcs:source" { + assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs[e]) } } }) } } -func TestGetGitAttributesWithRemote(t *testing.T) { - setupTest(t) - gitutil.GitSetRemote(t, "git@github.com:docker/buildx.git") - - t.Setenv("BUILDX_GIT_LABELS", "true") - gitattrs := getGitAttributes(context.Background(), ".", "Dockerfile") - assert.Equal(t, 5, len(gitattrs)) - assert.Contains(t, gitattrs, "label:"+DockerfileLabel) - assert.Equal(t, "Dockerfile", gitattrs["label:"+DockerfileLabel]) - assert.Contains(t, gitattrs, "label:"+specs.AnnotationRevision) - assert.NotEmpty(t, gitattrs["label:"+specs.AnnotationRevision]) - assert.Contains(t, gitattrs, "label:"+specs.AnnotationSource) - assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["label:"+specs.AnnotationSource]) - assert.Contains(t, gitattrs, "vcs:revision") - assert.NotEmpty(t, gitattrs["vcs:revision"]) - assert.Contains(t, gitattrs, "vcs:source") - assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["vcs:source"]) -} - func TestGetGitAttributesDirty(t *testing.T) { setupTest(t) // make a change to test dirty flag df := []byte("FROM alpine:edge\n") - assert.NoError(t, os.Mkdir("dir", 0755)) - assert.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644)) + require.NoError(t, os.Mkdir("dir", 0755)) + require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644)) t.Setenv("BUILDX_GIT_LABELS", "true") - gitattrs := getGitAttributes(context.Background(), ".", "Dockerfile") - assert.Equal(t, 3, len(gitattrs)) + gitattrs, _ := getGitAttributes(context.Background(), ".", "Dockerfile") + assert.Equal(t, 5, len(gitattrs)) + assert.Contains(t, gitattrs, "label:"+DockerfileLabel) assert.Equal(t, "Dockerfile", gitattrs["label:"+DockerfileLabel]) + assert.Contains(t, gitattrs, "label:"+specs.AnnotationSource) + assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["label:"+specs.AnnotationSource]) assert.Contains(t, gitattrs, "label:"+specs.AnnotationRevision) assert.True(t, strings.HasSuffix(gitattrs["label:"+specs.AnnotationRevision], "-dirty")) + + assert.Contains(t, gitattrs, "vcs:source") + assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["vcs:source"]) assert.Contains(t, gitattrs, "vcs:revision") assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty")) } diff --git a/go.mod b/go.mod index 28c4c45abf0..cbd6653164c 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 github.com/hashicorp/hcl/v2 v2.8.2 github.com/moby/buildkit v0.11.0-rc1.0.20221213193744-862b22d7e7cf + github.com/moby/sys/mountinfo v0.6.2 github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 diff --git a/go.sum b/go.sum index 116d8a54164..01c745982e1 100644 --- a/go.sum +++ b/go.sum @@ -411,6 +411,7 @@ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8 github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= @@ -780,6 +781,7 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/util/gitutil/gitpath_unix.go b/util/gitutil/gitpath_unix.go new file mode 100644 index 00000000000..898cb654dd0 --- /dev/null +++ b/util/gitutil/gitpath_unix.go @@ -0,0 +1,42 @@ +//go:build !windows +// +build !windows + +package gitutil + +import ( + "os" + "os/exec" + "path/filepath" + + "github.com/moby/sys/mountinfo" +) + +func gitPath(wd string) (string, error) { + // On WSL2 we need to check if the current working directory is mounted on + // a Windows drive and if so, we need to use the Windows git executable. + if os.Getenv("WSL_DISTRO_NAME") != "" && wd != "" { + // ensure any symlinks are resolved + wdPath, err := filepath.EvalSymlinks(wd) + if err != nil { + return "", err + } + mi, err := mountinfo.GetMounts(mountinfo.ParentsFilter(wdPath)) + if err != nil { + return "", err + } + // find the longest mount point + var idx, maxlen int + for i := range mi { + if len(mi[i].Mountpoint) > maxlen { + maxlen = len(mi[i].Mountpoint) + idx = i + } + } + if mi[idx].FSType == "9p" { + if p, err := exec.LookPath("git.exe"); err == nil { + return p, nil + } + } + } + return exec.LookPath("git") +} diff --git a/util/gitutil/gitpath_windows.go b/util/gitutil/gitpath_windows.go new file mode 100644 index 00000000000..a1a9153846c --- /dev/null +++ b/util/gitutil/gitpath_windows.go @@ -0,0 +1,9 @@ +package gitutil + +import ( + "os/exec" +) + +func gitPath(wd string) (string, error) { + return exec.LookPath("git.exe") +} diff --git a/util/gitutil/gitutil.go b/util/gitutil/gitutil.go index 953d282f290..abf591d4a39 100644 --- a/util/gitutil/gitutil.go +++ b/util/gitutil/gitutil.go @@ -11,8 +11,9 @@ import ( // Git represents an active git object type Git struct { - ctx context.Context - wd string + ctx context.Context + wd string + gitpath string } // Option provides a variadic option for configuring the git client. @@ -33,14 +34,22 @@ func WithWorkingDir(wd string) Option { } // New initializes a new git client -func New(opts ...Option) *Git { +func New(opts ...Option) (*Git, error) { + var err error c := &Git{ ctx: context.Background(), } + for _, opt := range opts { opt(c) } - return c + + c.gitpath, err = gitPath(c.wd) + if err != nil { + return nil, errors.New("git not found in PATH") + } + + return c, nil } func (c *Git) IsInsideWorkTree() bool { @@ -89,16 +98,12 @@ func (c *Git) Tag() (string, error) { } func (c *Git) run(args ...string) (string, error) { - if _, err := exec.LookPath("git"); err != nil { - return "", errors.New("git not present in PATH") - } - var extraArgs = []string{ "-c", "log.showSignature=false", } args = append(extraArgs, args...) - cmd := exec.Command("git", args...) + cmd := exec.CommandContext(c.ctx, c.gitpath, args...) if c.wd != "" { cmd.Dir = c.wd } diff --git a/util/gitutil/gitutil_test.go b/util/gitutil/gitutil_test.go index b1113a8a1a6..9948089c249 100644 --- a/util/gitutil/gitutil_test.go +++ b/util/gitutil/gitutil_test.go @@ -7,7 +7,9 @@ import ( ) func TestGit(t *testing.T) { - c := New() + c, err := New() + require.NoError(t, err) + out, err := c.run("status") require.NoError(t, err) require.NotEmpty(t, out) @@ -20,10 +22,12 @@ func TestGit(t *testing.T) { func TestGitFullCommit(t *testing.T) { Mktmp(t) - GitInit(t) - GitCommit(t, "bar") + c, err := New() + require.NoError(t, err) + + GitInit(c, t) + GitCommit(c, t, "bar") - c := New() out, err := c.FullCommit() require.NoError(t, err) require.Equal(t, 40, len(out)) @@ -31,10 +35,12 @@ func TestGitFullCommit(t *testing.T) { func TestGitShortCommit(t *testing.T) { Mktmp(t) - GitInit(t) - GitCommit(t, "bar") + c, err := New() + require.NoError(t, err) + + GitInit(c, t) + GitCommit(c, t, "bar") - c := New() out, err := c.ShortCommit() require.NoError(t, err) require.Equal(t, 7, len(out)) @@ -42,13 +48,15 @@ func TestGitShortCommit(t *testing.T) { func TestGitTagsPointsAt(t *testing.T) { Mktmp(t) - GitInit(t) - GitCommit(t, "bar") - GitTag(t, "v0.8.0") - GitCommit(t, "foo") - GitTag(t, "v0.9.0") + c, err := New() + require.NoError(t, err) + + GitInit(c, t) + GitCommit(c, t, "bar") + GitTag(c, t, "v0.8.0") + GitCommit(c, t, "foo") + GitTag(c, t, "v0.9.0") - c := New() out, err := c.clean(c.run("tag", "--points-at", "HEAD", "--sort", "-version:creatordate")) require.NoError(t, err) require.Equal(t, "v0.9.0", out) @@ -56,13 +64,15 @@ func TestGitTagsPointsAt(t *testing.T) { func TestGitDescribeTags(t *testing.T) { Mktmp(t) - GitInit(t) - GitCommit(t, "bar") - GitTag(t, "v0.8.0") - GitCommit(t, "foo") - GitTag(t, "v0.9.0") + c, err := New() + require.NoError(t, err) + + GitInit(c, t) + GitCommit(c, t, "bar") + GitTag(c, t, "v0.8.0") + GitCommit(c, t, "foo") + GitTag(c, t, "v0.9.0") - c := New() out, err := c.clean(c.run("describe", "--tags", "--abbrev=0")) require.NoError(t, err) require.Equal(t, "v0.9.0", out) diff --git a/util/gitutil/testutil.go b/util/gitutil/testutil.go index 7560e5b0450..91448a0df9a 100644 --- a/util/gitutil/testutil.go +++ b/util/gitutil/testutil.go @@ -7,46 +7,46 @@ import ( "github.com/stretchr/testify/require" ) -func GitInit(tb testing.TB) { +func GitInit(c *Git, tb testing.TB) { tb.Helper() - out, err := fakeGit("init") + out, err := fakeGit(c, "init") require.NoError(tb, err) require.Contains(tb, out, "Initialized empty Git repository") require.NoError(tb, err) - GitCheckoutBranch(tb, "main") - _, _ = fakeGit("branch", "-D", "master") + GitCheckoutBranch(c, tb, "main") + _, _ = fakeGit(c, "branch", "-D", "master") } -func GitCommit(tb testing.TB, msg string) { +func GitCommit(c *Git, tb testing.TB, msg string) { tb.Helper() - out, err := fakeGit("commit", "--allow-empty", "-m", msg) + out, err := fakeGit(c, "commit", "--allow-empty", "-m", msg) require.NoError(tb, err) require.Contains(tb, out, "main", msg) } -func GitTag(tb testing.TB, tag string) { +func GitTag(c *Git, tb testing.TB, tag string) { tb.Helper() - out, err := fakeGit("tag", tag) + out, err := fakeGit(c, "tag", tag) require.NoError(tb, err) require.Empty(tb, out) } -func GitCheckoutBranch(tb testing.TB, name string) { +func GitCheckoutBranch(c *Git, tb testing.TB, name string) { tb.Helper() - out, err := fakeGit("checkout", "-b", name) + out, err := fakeGit(c, "checkout", "-b", name) require.NoError(tb, err) require.Empty(tb, out) } -func GitAdd(tb testing.TB, file string) { +func GitAdd(c *Git, tb testing.TB, file string) { tb.Helper() - _, err := fakeGit("add", file) + _, err := fakeGit(c, "add", file) require.NoError(tb, err) } -func GitSetRemote(tb testing.TB, url string) { +func GitSetRemote(c *Git, tb testing.TB, url string) { tb.Helper() - _, err := fakeGit("remote", "add", "origin", url) + _, err := fakeGit(c, "remote", "add", "origin", url) require.NoError(tb, err) } @@ -62,7 +62,7 @@ func Mktmp(tb testing.TB) string { return folder } -func fakeGit(args ...string) (string, error) { +func fakeGit(c *Git, args ...string) (string, error) { allArgs := []string{ "-c", "user.name=buildx", "-c", "user.email=buildx@docker.com", @@ -71,6 +71,5 @@ func fakeGit(args ...string) (string, error) { "-c", "log.showSignature=false", } allArgs = append(allArgs, args...) - c := New() return c.clean(c.run(allArgs...)) } diff --git a/vendor/github.com/moby/sys/mountinfo/LICENSE b/vendor/github.com/moby/sys/mountinfo/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/moby/sys/mountinfo/doc.go b/vendor/github.com/moby/sys/mountinfo/doc.go new file mode 100644 index 00000000000..b80e05efd05 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/doc.go @@ -0,0 +1,44 @@ +// Package mountinfo provides a set of functions to retrieve information about OS mounts. +// +// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD and OpenBSD, +// and a shallow implementation for Windows, but in general this is Linux-only package, so +// the rest of the document only applies to Linux, unless explicitly specified otherwise. +// +// In Linux, information about mounts seen by the current process is available from +// /proc/self/mountinfo. Note that due to mount namespaces, different processes can +// see different mounts. A per-process mountinfo table is available from /proc//mountinfo, +// where is a numerical process identifier. +// +// In general, /proc is not a very efficient interface, and mountinfo is not an exception. +// For example, there is no way to get information about a specific mount point (i.e. it +// is all-or-nothing). This package tries to hide the /proc ineffectiveness by using +// parse filters while reading mountinfo. A filter can skip some entries, or stop +// processing the rest of the file once the needed information is found. +// +// For mountinfo filters that accept path as an argument, the path must be absolute, +// having all symlinks resolved, and being cleaned (i.e. no extra slashes or dots). +// One way to achieve all of the above is to employ filepath.Abs followed by +// filepath.EvalSymlinks (the latter calls filepath.Clean on the result so +// there is no need to explicitly call filepath.Clean). +// +// NOTE that in many cases there is no need to consult mountinfo at all. Here are some +// of the cases where mountinfo should not be parsed: +// +// 1. Before performing a mount. Usually, this is not needed, but if required (say to +// prevent over-mounts), to check whether a directory is mounted, call os.Lstat +// on it and its parent directory, and compare their st.Sys().(*syscall.Stat_t).Dev +// fields -- if they differ, then the directory is the mount point. NOTE this does +// not work for bind mounts. Optionally, the filesystem type can also be checked +// by calling unix.Statfs and checking the Type field (i.e. filesystem type). +// +// 2. After performing a mount. If there is no error returned, the mount succeeded; +// checking the mount table for a new mount is redundant and expensive. +// +// 3. Before performing an unmount. It is more efficient to do an unmount and ignore +// a specific error (EINVAL) which tells the directory is not mounted. +// +// 4. After performing an unmount. If there is no error returned, the unmount succeeded. +// +// 5. To find the mount point root of a specific directory. You can perform os.Stat() +// on the directory and traverse up until the Dev field of a parent directory differs. +package mountinfo diff --git a/vendor/github.com/moby/sys/mountinfo/mounted_linux.go b/vendor/github.com/moby/sys/mountinfo/mounted_linux.go new file mode 100644 index 00000000000..e78e726196e --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mounted_linux.go @@ -0,0 +1,101 @@ +package mountinfo + +import ( + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +// MountedFast is a method of detecting a mount point without reading +// mountinfo from procfs. A caller can only trust the result if no error +// and sure == true are returned. Otherwise, other methods (e.g. parsing +// /proc/mounts) have to be used. If unsure, use Mounted instead (which +// uses MountedFast, but falls back to parsing mountinfo if needed). +// +// If a non-existent path is specified, an appropriate error is returned. +// In case the caller is not interested in this particular error, it should +// be handled separately using e.g. errors.Is(err, fs.ErrNotExist). +// +// This function is only available on Linux. When available (since kernel +// v5.6), openat2(2) syscall is used to reliably detect all mounts. Otherwise, +// the implementation falls back to using stat(2), which can reliably detect +// normal (but not bind) mounts. +func MountedFast(path string) (mounted, sure bool, err error) { + // Root is always mounted. + if path == string(os.PathSeparator) { + return true, true, nil + } + + path, err = normalizePath(path) + if err != nil { + return false, false, err + } + mounted, sure, err = mountedFast(path) + return +} + +// mountedByOpenat2 is a method of detecting a mount that works for all kinds +// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel. +func mountedByOpenat2(path string) (bool, error) { + dir, last := filepath.Split(path) + + dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + }) + if err != nil { + return false, &os.PathError{Op: "openat2", Path: dir, Err: err} + } + fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW, + Resolve: unix.RESOLVE_NO_XDEV, + }) + _ = unix.Close(dirfd) + switch err { //nolint:errorlint // unix errors are bare + case nil: // definitely not a mount + _ = unix.Close(fd) + return false, nil + case unix.EXDEV: // definitely a mount + return true, nil + } + // not sure + return false, &os.PathError{Op: "openat2", Path: path, Err: err} +} + +// mountedFast is similar to MountedFast, except it expects a normalized path. +func mountedFast(path string) (mounted, sure bool, err error) { + // Root is always mounted. + if path == string(os.PathSeparator) { + return true, true, nil + } + + // Try a fast path, using openat2() with RESOLVE_NO_XDEV. + mounted, err = mountedByOpenat2(path) + if err == nil { + return mounted, true, nil + } + + // Another fast path: compare st.st_dev fields. + mounted, err = mountedByStat(path) + // This does not work for bind mounts, so false negative + // is possible, therefore only trust if return is true. + if mounted && err == nil { + return true, true, nil + } + + return +} + +func mounted(path string) (bool, error) { + path, err := normalizePath(path) + if err != nil { + return false, err + } + mounted, sure, err := mountedFast(path) + if sure && err == nil { + return mounted, nil + } + + // Fallback to parsing mountinfo. + return mountedByMountinfo(path) +} diff --git a/vendor/github.com/moby/sys/mountinfo/mounted_unix.go b/vendor/github.com/moby/sys/mountinfo/mounted_unix.go new file mode 100644 index 00000000000..c7b7678f9a0 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mounted_unix.go @@ -0,0 +1,53 @@ +//go:build linux || freebsd || openbsd || darwin +// +build linux freebsd openbsd darwin + +package mountinfo + +import ( + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +func mountedByStat(path string) (bool, error) { + var st unix.Stat_t + + if err := unix.Lstat(path, &st); err != nil { + return false, &os.PathError{Op: "stat", Path: path, Err: err} + } + dev := st.Dev + parent := filepath.Dir(path) + if err := unix.Lstat(parent, &st); err != nil { + return false, &os.PathError{Op: "stat", Path: parent, Err: err} + } + if dev != st.Dev { + // Device differs from that of parent, + // so definitely a mount point. + return true, nil + } + // NB: this does not detect bind mounts on Linux. + return false, nil +} + +func normalizePath(path string) (realPath string, err error) { + if realPath, err = filepath.Abs(path); err != nil { + return "", err + } + if realPath, err = filepath.EvalSymlinks(realPath); err != nil { + return "", err + } + if _, err := os.Stat(realPath); err != nil { + return "", err + } + return realPath, nil +} + +func mountedByMountinfo(path string) (bool, error) { + entries, err := GetMounts(SingleEntryFilter(path)) + if err != nil { + return false, err + } + + return len(entries) > 0, nil +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo.go b/vendor/github.com/moby/sys/mountinfo/mountinfo.go new file mode 100644 index 00000000000..574aeb8767a --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo.go @@ -0,0 +1,67 @@ +package mountinfo + +import ( + "os" +) + +// GetMounts retrieves a list of mounts for the current running process, +// with an optional filter applied (use nil for no filter). +func GetMounts(f FilterFunc) ([]*Info, error) { + return parseMountTable(f) +} + +// Mounted determines if a specified path is a mount point. In case of any +// error, false (and an error) is returned. +// +// If a non-existent path is specified, an appropriate error is returned. +// In case the caller is not interested in this particular error, it should +// be handled separately using e.g. errors.Is(err, fs.ErrNotExist). +func Mounted(path string) (bool, error) { + // root is always mounted + if path == string(os.PathSeparator) { + return true, nil + } + return mounted(path) +} + +// Info reveals information about a particular mounted filesystem. This +// struct is populated from the content in the /proc//mountinfo file. +type Info struct { + // ID is a unique identifier of the mount (may be reused after umount). + ID int + + // Parent is the ID of the parent mount (or of self for the root + // of this mount namespace's mount tree). + Parent int + + // Major and Minor are the major and the minor components of the Dev + // field of unix.Stat_t structure returned by unix.*Stat calls for + // files on this filesystem. + Major, Minor int + + // Root is the pathname of the directory in the filesystem which forms + // the root of this mount. + Root string + + // Mountpoint is the pathname of the mount point relative to the + // process's root directory. + Mountpoint string + + // Options is a comma-separated list of mount options. + Options string + + // Optional are zero or more fields of the form "tag[:value]", + // separated by a space. Currently, the possible optional fields are + // "shared", "master", "propagate_from", and "unbindable". For more + // information, see mount_namespaces(7) Linux man page. + Optional string + + // FSType is the filesystem type in the form "type[.subtype]". + FSType string + + // Source is filesystem-specific information, or "none". + Source string + + // VFSOptions is a comma-separated list of superblock options. + VFSOptions string +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go new file mode 100644 index 00000000000..8420f58c7a9 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go @@ -0,0 +1,56 @@ +//go:build freebsd || openbsd || darwin +// +build freebsd openbsd darwin + +package mountinfo + +import "golang.org/x/sys/unix" + +// parseMountTable returns information about mounted filesystems +func parseMountTable(filter FilterFunc) ([]*Info, error) { + count, err := unix.Getfsstat(nil, unix.MNT_WAIT) + if err != nil { + return nil, err + } + + entries := make([]unix.Statfs_t, count) + _, err = unix.Getfsstat(entries, unix.MNT_WAIT) + if err != nil { + return nil, err + } + + var out []*Info + for _, entry := range entries { + var skip, stop bool + mountinfo := getMountinfo(&entry) + + if filter != nil { + // filter out entries we're not interested in + skip, stop = filter(mountinfo) + if skip { + continue + } + } + + out = append(out, mountinfo) + if stop { + break + } + } + return out, nil +} + +func mounted(path string) (bool, error) { + path, err := normalizePath(path) + if err != nil { + return false, err + } + // Fast path: compare st.st_dev fields. + // This should always work for FreeBSD and OpenBSD. + mounted, err := mountedByStat(path) + if err == nil { + return mounted, nil + } + + // Fallback to parsing mountinfo + return mountedByMountinfo(path) +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go new file mode 100644 index 00000000000..16079c3c541 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go @@ -0,0 +1,63 @@ +package mountinfo + +import "strings" + +// FilterFunc is a type defining a callback function for GetMount(), +// used to filter out mountinfo entries we're not interested in, +// and/or stop further processing if we found what we wanted. +// +// It takes a pointer to the Info struct (fully populated with all available +// fields on the GOOS platform), and returns two booleans: +// +// skip: true if the entry should be skipped; +// +// stop: true if parsing should be stopped after the entry. +type FilterFunc func(*Info) (skip, stop bool) + +// PrefixFilter discards all entries whose mount points do not start with, or +// are equal to the path specified in prefix. The prefix path must be absolute, +// have all symlinks resolved, and cleaned (i.e. no extra slashes or dots). +// +// PrefixFilter treats prefix as a path, not a partial prefix, which means that +// given "/foo", "/foo/bar" and "/foobar" entries, PrefixFilter("/foo") returns +// "/foo" and "/foo/bar", and discards "/foobar". +func PrefixFilter(prefix string) FilterFunc { + return func(m *Info) (bool, bool) { + skip := !strings.HasPrefix(m.Mountpoint+"/", prefix+"/") + return skip, false + } +} + +// SingleEntryFilter looks for a specific entry. +func SingleEntryFilter(mp string) FilterFunc { + return func(m *Info) (bool, bool) { + if m.Mountpoint == mp { + return false, true // don't skip, stop now + } + return true, false // skip, keep going + } +} + +// ParentsFilter returns all entries whose mount points +// can be parents of a path specified, discarding others. +// +// For example, given /var/lib/docker/something, entries +// like /var/lib/docker, /var and / are returned. +func ParentsFilter(path string) FilterFunc { + return func(m *Info) (bool, bool) { + skip := !strings.HasPrefix(path, m.Mountpoint) + return skip, false + } +} + +// FSTypeFilter returns all entries that match provided fstype(s). +func FSTypeFilter(fstype ...string) FilterFunc { + return func(m *Info) (bool, bool) { + for _, t := range fstype { + if m.FSType == t { + return false, false // don't skip, keep going + } + } + return true, false // skip, keep going + } +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsdlike.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsdlike.go new file mode 100644 index 00000000000..ecaaa7a9c11 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsdlike.go @@ -0,0 +1,14 @@ +//go:build freebsd || darwin +// +build freebsd darwin + +package mountinfo + +import "golang.org/x/sys/unix" + +func getMountinfo(entry *unix.Statfs_t) *Info { + return &Info{ + Mountpoint: unix.ByteSliceToString(entry.Mntonname[:]), + FSType: unix.ByteSliceToString(entry.Fstypename[:]), + Source: unix.ByteSliceToString(entry.Mntfromname[:]), + } +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go new file mode 100644 index 00000000000..59332b07bf4 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go @@ -0,0 +1,214 @@ +package mountinfo + +import ( + "bufio" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +// GetMountsFromReader retrieves a list of mounts from the +// reader provided, with an optional filter applied (use nil +// for no filter). This can be useful in tests or benchmarks +// that provide fake mountinfo data, or when a source other +// than /proc/self/mountinfo needs to be read from. +// +// This function is Linux-specific. +func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) { + s := bufio.NewScanner(r) + out := []*Info{} + for s.Scan() { + var err error + + /* + See http://man7.org/linux/man-pages/man5/proc.5.html + + 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) + + (1) mount ID: unique identifier of the mount (may be reused after umount) + (2) parent ID: ID of parent (or of self for the top of the mount tree) + (3) major:minor: value of st_dev for files on filesystem + (4) root: root of the mount within the filesystem + (5) mount point: mount point relative to the process's root + (6) mount options: per mount options + (7) optional fields: zero or more fields of the form "tag[:value]" + (8) separator: marks the end of the optional fields + (9) filesystem type: name of filesystem of the form "type[.subtype]" + (10) mount source: filesystem specific information or "none" + (11) super options: per super block options + + In other words, we have: + * 6 mandatory fields (1)..(6) + * 0 or more optional fields (7) + * a separator field (8) + * 3 mandatory fields (9)..(11) + */ + + text := s.Text() + fields := strings.Split(text, " ") + numFields := len(fields) + if numFields < 10 { + // should be at least 10 fields + return nil, fmt.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields) + } + + // separator field + sepIdx := numFields - 4 + // In Linux <= 3.9 mounting a cifs with spaces in a share + // name (like "//srv/My Docs") _may_ end up having a space + // in the last field of mountinfo (like "unc=//serv/My Docs"). + // Since kernel 3.10-rc1, cifs option "unc=" is ignored, + // so spaces should not appear. + // + // Check for a separator, and work around the spaces bug + for fields[sepIdx] != "-" { + sepIdx-- + if sepIdx == 5 { + return nil, fmt.Errorf("parsing '%s' failed: missing - separator", text) + } + } + + p := &Info{} + + p.Mountpoint, err = unescape(fields[4]) + if err != nil { + return nil, fmt.Errorf("parsing '%s' failed: mount point: %w", fields[4], err) + } + p.FSType, err = unescape(fields[sepIdx+1]) + if err != nil { + return nil, fmt.Errorf("parsing '%s' failed: fstype: %w", fields[sepIdx+1], err) + } + p.Source, err = unescape(fields[sepIdx+2]) + if err != nil { + return nil, fmt.Errorf("parsing '%s' failed: source: %w", fields[sepIdx+2], err) + } + p.VFSOptions = fields[sepIdx+3] + + // ignore any numbers parsing errors, as there should not be any + p.ID, _ = strconv.Atoi(fields[0]) + p.Parent, _ = strconv.Atoi(fields[1]) + mm := strings.SplitN(fields[2], ":", 3) + if len(mm) != 2 { + return nil, fmt.Errorf("parsing '%s' failed: unexpected major:minor pair %s", text, mm) + } + p.Major, _ = strconv.Atoi(mm[0]) + p.Minor, _ = strconv.Atoi(mm[1]) + + p.Root, err = unescape(fields[3]) + if err != nil { + return nil, fmt.Errorf("parsing '%s' failed: root: %w", fields[3], err) + } + + p.Options = fields[5] + + // zero or more optional fields + p.Optional = strings.Join(fields[6:sepIdx], " ") + + // Run the filter after parsing all fields. + var skip, stop bool + if filter != nil { + skip, stop = filter(p) + if skip { + continue + } + } + + out = append(out, p) + if stop { + break + } + } + if err := s.Err(); err != nil { + return nil, err + } + return out, nil +} + +func parseMountTable(filter FilterFunc) ([]*Info, error) { + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return nil, err + } + defer f.Close() + + return GetMountsFromReader(f, filter) +} + +// PidMountInfo retrieves the list of mounts from a given process' mount +// namespace. Unless there is a need to get mounts from a mount namespace +// different from that of a calling process, use GetMounts. +// +// This function is Linux-specific. +// +// Deprecated: this will be removed before v1; use GetMountsFromReader with +// opened /proc//mountinfo as an argument instead. +func PidMountInfo(pid int) ([]*Info, error) { + f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid)) + if err != nil { + return nil, err + } + defer f.Close() + + return GetMountsFromReader(f, nil) +} + +// A few specific characters in mountinfo path entries (root and mountpoint) +// are escaped using a backslash followed by a character's ascii code in octal. +// +// space -- as \040 +// tab (aka \t) -- as \011 +// newline (aka \n) -- as \012 +// backslash (aka \\) -- as \134 +// +// This function converts path from mountinfo back, i.e. it unescapes the above sequences. +func unescape(path string) (string, error) { + // try to avoid copying + if strings.IndexByte(path, '\\') == -1 { + return path, nil + } + + // The following code is UTF-8 transparent as it only looks for some + // specific characters (backslash and 0..7) with values < utf8.RuneSelf, + // and everything else is passed through as is. + buf := make([]byte, len(path)) + bufLen := 0 + for i := 0; i < len(path); i++ { + if path[i] != '\\' { + buf[bufLen] = path[i] + bufLen++ + continue + } + s := path[i:] + if len(s) < 4 { + // too short + return "", fmt.Errorf("bad escape sequence %q: too short", s) + } + c := s[1] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7': + v := c - '0' + for j := 2; j < 4; j++ { // one digit already; two more + if s[j] < '0' || s[j] > '7' { + return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3]) + } + x := s[j] - '0' + v = (v << 3) | x + } + if v > 255 { + return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3]) + } + buf[bufLen] = v + bufLen++ + i += 3 + continue + default: + return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3]) + + } + } + + return string(buf[:bufLen]), nil +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_openbsd.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_openbsd.go new file mode 100644 index 00000000000..f682c2d3b59 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_openbsd.go @@ -0,0 +1,11 @@ +package mountinfo + +import "golang.org/x/sys/unix" + +func getMountinfo(entry *unix.Statfs_t) *Info { + return &Info{ + Mountpoint: unix.ByteSliceToString(entry.F_mntonname[:]), + FSType: unix.ByteSliceToString(entry.F_fstypename[:]), + Source: unix.ByteSliceToString(entry.F_mntfromname[:]), + } +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go new file mode 100644 index 00000000000..c2e64bc81c7 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go @@ -0,0 +1,19 @@ +//go:build !windows && !linux && !freebsd && !openbsd && !darwin +// +build !windows,!linux,!freebsd,!openbsd,!darwin + +package mountinfo + +import ( + "fmt" + "runtime" +) + +var errNotImplemented = fmt.Errorf("not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) + +func parseMountTable(_ FilterFunc) ([]*Info, error) { + return nil, errNotImplemented +} + +func mounted(path string) (bool, error) { + return false, errNotImplemented +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go new file mode 100644 index 00000000000..13fad165e5d --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go @@ -0,0 +1,10 @@ +package mountinfo + +func parseMountTable(_ FilterFunc) ([]*Info, error) { + // Do NOT return an error! + return nil, nil +} + +func mounted(_ string) (bool, error) { + return false, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a67671f2fb3..86856ac19f0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -511,6 +511,9 @@ github.com/moby/patternmatcher ## explicit; go 1.13 github.com/moby/spdystream github.com/moby/spdystream/spdy +# github.com/moby/sys/mountinfo v0.6.2 +## explicit; go 1.16 +github.com/moby/sys/mountinfo # github.com/moby/sys/sequential v0.5.0 ## explicit; go 1.17 github.com/moby/sys/sequential