From 26be6f9751abf62d39df94aba2301b53fe1f5866 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:37:28 +0200 Subject: [PATCH 1/2] driver: use stdlib tar for container config Use a small tar writer for docker-container config files instead of the go-archive archiver. The helper only handles the generated config tree and reads file contents through os.Root. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- driver/docker-container/driver.go | 5 +- driver/docker-container/tar.go | 95 ++ driver/docker-container/tar_test.go | 85 ++ go.mod | 4 +- go.sum | 4 +- .../github.com/moby/go-archive/.gitattributes | 2 - vendor/github.com/moby/go-archive/.gitignore | 1 - .../github.com/moby/go-archive/.golangci.yml | 45 - vendor/github.com/moby/go-archive/archive.go | 1147 ----------------- .../moby/go-archive/archive_linux.go | 129 -- .../moby/go-archive/archive_other.go | 7 - .../moby/go-archive/archive_unix.go | 99 -- .../moby/go-archive/archive_windows.go | 62 - vendor/github.com/moby/go-archive/changes.go | 430 ------ .../moby/go-archive/changes_linux.go | 274 ---- .../moby/go-archive/changes_other.go | 95 -- .../moby/go-archive/changes_unix.go | 43 - .../moby/go-archive/changes_windows.go | 33 - .../go-archive/compression/compression.go | 4 +- vendor/github.com/moby/go-archive/copy.go | 497 ------- .../github.com/moby/go-archive/copy_unix.go | 11 - .../moby/go-archive/copy_windows.go | 9 - .../github.com/moby/go-archive/dev_freebsd.go | 9 - vendor/github.com/moby/go-archive/dev_unix.go | 9 - vendor/github.com/moby/go-archive/diff.go | 258 ---- .../github.com/moby/go-archive/diff_unix.go | 21 - .../moby/go-archive/diff_windows.go | 6 - vendor/github.com/moby/go-archive/path.go | 20 - .../github.com/moby/go-archive/path_unix.go | 9 - .../moby/go-archive/path_windows.go | 22 - .../moby/go-archive/tarheader/tarheader.go | 67 - .../go-archive/tarheader/tarheader_unix.go | 47 - .../go-archive/tarheader/tarheader_windows.go | 12 - vendor/github.com/moby/go-archive/time.go | 38 - .../moby/go-archive/time_nonwindows.go | 41 - .../moby/go-archive/time_windows.go | 32 - .../github.com/moby/go-archive/whiteouts.go | 23 - vendor/github.com/moby/go-archive/wrap.go | 59 - .../moby/go-archive/xattr_supported.go | 52 - .../moby/go-archive/xattr_supported_linux.go | 5 - .../moby/go-archive/xattr_supported_unix.go | 7 - .../moby/go-archive/xattr_unsupported.go | 11 - vendor/github.com/moby/sys/user/LICENSE | 202 --- vendor/github.com/moby/sys/user/idtools.go | 141 -- .../github.com/moby/sys/user/idtools_unix.go | 143 -- .../moby/sys/user/idtools_windows.go | 13 - .../github.com/moby/sys/user/lookup_unix.go | 156 --- vendor/github.com/moby/sys/user/user.go | 607 --------- .../github.com/moby/sys/user/user_fuzzer.go | 42 - vendor/github.com/moby/sys/user/user_utils.go | 64 - vendor/github.com/moby/sys/userns/LICENSE | 202 --- vendor/github.com/moby/sys/userns/userns.go | 16 - .../moby/sys/userns/userns_linux.go | 53 - .../moby/sys/userns/userns_linux_fuzzer.go | 8 - .../moby/sys/userns/userns_unsupported.go | 6 - vendor/modules.txt | 12 +- 56 files changed, 188 insertions(+), 5306 deletions(-) create mode 100644 driver/docker-container/tar.go create mode 100644 driver/docker-container/tar_test.go delete mode 100644 vendor/github.com/moby/go-archive/.gitattributes delete mode 100644 vendor/github.com/moby/go-archive/.gitignore delete mode 100644 vendor/github.com/moby/go-archive/.golangci.yml delete mode 100644 vendor/github.com/moby/go-archive/archive.go delete mode 100644 vendor/github.com/moby/go-archive/archive_linux.go delete mode 100644 vendor/github.com/moby/go-archive/archive_other.go delete mode 100644 vendor/github.com/moby/go-archive/archive_unix.go delete mode 100644 vendor/github.com/moby/go-archive/archive_windows.go delete mode 100644 vendor/github.com/moby/go-archive/changes.go delete mode 100644 vendor/github.com/moby/go-archive/changes_linux.go delete mode 100644 vendor/github.com/moby/go-archive/changes_other.go delete mode 100644 vendor/github.com/moby/go-archive/changes_unix.go delete mode 100644 vendor/github.com/moby/go-archive/changes_windows.go delete mode 100644 vendor/github.com/moby/go-archive/copy.go delete mode 100644 vendor/github.com/moby/go-archive/copy_unix.go delete mode 100644 vendor/github.com/moby/go-archive/copy_windows.go delete mode 100644 vendor/github.com/moby/go-archive/dev_freebsd.go delete mode 100644 vendor/github.com/moby/go-archive/dev_unix.go delete mode 100644 vendor/github.com/moby/go-archive/diff.go delete mode 100644 vendor/github.com/moby/go-archive/diff_unix.go delete mode 100644 vendor/github.com/moby/go-archive/diff_windows.go delete mode 100644 vendor/github.com/moby/go-archive/path.go delete mode 100644 vendor/github.com/moby/go-archive/path_unix.go delete mode 100644 vendor/github.com/moby/go-archive/path_windows.go delete mode 100644 vendor/github.com/moby/go-archive/tarheader/tarheader.go delete mode 100644 vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go delete mode 100644 vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go delete mode 100644 vendor/github.com/moby/go-archive/time.go delete mode 100644 vendor/github.com/moby/go-archive/time_nonwindows.go delete mode 100644 vendor/github.com/moby/go-archive/time_windows.go delete mode 100644 vendor/github.com/moby/go-archive/whiteouts.go delete mode 100644 vendor/github.com/moby/go-archive/wrap.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_supported.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_supported_linux.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_supported_unix.go delete mode 100644 vendor/github.com/moby/go-archive/xattr_unsupported.go delete mode 100644 vendor/github.com/moby/sys/user/LICENSE delete mode 100644 vendor/github.com/moby/sys/user/idtools.go delete mode 100644 vendor/github.com/moby/sys/user/idtools_unix.go delete mode 100644 vendor/github.com/moby/sys/user/idtools_windows.go delete mode 100644 vendor/github.com/moby/sys/user/lookup_unix.go delete mode 100644 vendor/github.com/moby/sys/user/user.go delete mode 100644 vendor/github.com/moby/sys/user/user_fuzzer.go delete mode 100644 vendor/github.com/moby/sys/user/user_utils.go delete mode 100644 vendor/github.com/moby/sys/userns/LICENSE delete mode 100644 vendor/github.com/moby/sys/userns/userns.go delete mode 100644 vendor/github.com/moby/sys/userns/userns_linux.go delete mode 100644 vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go delete mode 100644 vendor/github.com/moby/sys/userns/userns_unsupported.go diff --git a/driver/docker-container/driver.go b/driver/docker-container/driver.go index 2e287721bda6..385ddee1f87a 100644 --- a/driver/docker-container/driver.go +++ b/driver/docker-container/driver.go @@ -26,7 +26,6 @@ import ( contextstore "github.com/docker/cli/cli/context/store" "github.com/docker/cli/opts" "github.com/moby/buildkit/client" - mobyarchive "github.com/moby/go-archive" "github.com/moby/moby/api/pkg/stdcopy" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/mount" @@ -376,9 +375,7 @@ func (d *Driver) copyToContainer(ctx context.Context, files map[string][]byte) e if srcPath != "" { defer os.RemoveAll(srcPath) } - srcArchive, err := mobyarchive.TarWithOptions(srcPath, &mobyarchive.TarOptions{ - ChownOpts: &mobyarchive.ChownOpts{UID: 0, GID: 0}, - }) + srcArchive, err := tarDirectory(srcPath) if err != nil { return err } diff --git a/driver/docker-container/tar.go b/driver/docker-container/tar.go new file mode 100644 index 000000000000..1b7cae4b1ebd --- /dev/null +++ b/driver/docker-container/tar.go @@ -0,0 +1,95 @@ +package docker + +import ( + "archive/tar" + "io" + "io/fs" + "os" + + "github.com/pkg/errors" +) + +const ( + tarDirMode int64 = 0o755 + tarFileMode int64 = 0o644 +) + +func tarDirectory(srcPath string) (io.ReadCloser, error) { + stat, err := os.Lstat(srcPath) + if err != nil { + return nil, err + } + if !stat.IsDir() { + return nil, errors.Errorf("%s is not a directory", srcPath) + } + root, err := os.OpenRoot(srcPath) + if err != nil { + return nil, err + } + + pr, pw := io.Pipe() + tw := tar.NewWriter(pw) + go func() { + defer root.Close() + err := fs.WalkDir(root.FS(), ".", func(name string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if name == "." && d.IsDir() { + return nil + } + return writeTarFile(root, tw, name, d) + }) + if err == nil { + err = tw.Close() + } + if err != nil { + _ = pw.CloseWithError(err) + return + } + _ = pw.Close() + }() + return pr, nil +} + +func writeTarFile(root *os.Root, tw *tar.Writer, name string, d fs.DirEntry) error { + fi, err := d.Info() + if err != nil { + return err + } + + mode := fi.Mode() + hdr := &tar.Header{ + Name: name, + Mode: tarFileMode, + ModTime: fi.ModTime(), + } + switch { + case fi.IsDir(): + hdr.Typeflag = tar.TypeDir + hdr.Name += "/" + hdr.Mode = tarDirMode + case mode.IsRegular(): + hdr.Typeflag = tar.TypeReg + hdr.Size = fi.Size() + default: + return errors.Errorf("unsupported file type %s", name) + } + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + if !mode.IsRegular() || hdr.Size == 0 { + return nil + } + + f, err := root.Open(name) + if err != nil { + return err + } + if _, err := io.Copy(tw, f); err != nil { + _ = f.Close() + return err + } + return f.Close() +} diff --git a/driver/docker-container/tar_test.go b/driver/docker-container/tar_test.go new file mode 100644 index 000000000000..f9029898628b --- /dev/null +++ b/driver/docker-container/tar_test.go @@ -0,0 +1,85 @@ +package docker + +import ( + "archive/tar" + "io" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +type tarEntry struct { + header *tar.Header + body []byte +} + +func TestTarDirectoryConfigFiles(t *testing.T) { + files := map[string][]byte{ + "buildkitd.toml": []byte("debug = true\n"), + "certs/example.com/ca.pem": []byte("certificate"), + } + srcPath, err := writeConfigFiles(files) + require.NoError(t, err) + defer os.RemoveAll(srcPath) + + rc, err := tarDirectory(srcPath) + require.NoError(t, err) + defer rc.Close() + + entries := readTarEntries(t, rc) + expected := []string{ + "buildkit/", + "buildkit/buildkitd.toml", + "buildkit/certs/", + "buildkit/certs/example.com/", + "buildkit/certs/example.com/ca.pem", + } + require.Len(t, entries, len(expected)) + for _, name := range expected { + entry, ok := entries[name] + require.Truef(t, ok, "missing archive entry %q", name) + require.Equal(t, 0, entry.header.Uid) + require.Equal(t, 0, entry.header.Gid) + require.Empty(t, entry.header.Uname) + require.Empty(t, entry.header.Gname) + } + + require.Equal(t, byte(tar.TypeDir), entries["buildkit/"].header.Typeflag) + require.Equal(t, tarDirMode, entries["buildkit/"].header.Mode) + require.Equal(t, files["buildkitd.toml"], entries["buildkit/buildkitd.toml"].body) + require.Equal(t, tarFileMode, entries["buildkit/buildkitd.toml"].header.Mode) + require.Equal(t, files["certs/example.com/ca.pem"], entries["buildkit/certs/example.com/ca.pem"].body) +} + +func TestTarDirectoryEmpty(t *testing.T) { + rc, err := tarDirectory(t.TempDir()) + require.NoError(t, err) + defer rc.Close() + + tr := tar.NewReader(rc) + _, err = tr.Next() + require.ErrorIs(t, err, io.EOF) +} + +func readTarEntries(t *testing.T, r io.Reader) map[string]tarEntry { + t.Helper() + + tr := tar.NewReader(r) + entries := map[string]tarEntry{} + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + body, err := io.ReadAll(tr) + require.NoError(t, err) + hdrCopy := *hdr + entries[hdr.Name] = tarEntry{ + header: &hdrCopy, + body: body, + } + } + return entries +} diff --git a/go.mod b/go.mod index 19077b592cc5..5c8782bce47f 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,6 @@ require ( github.com/in-toto/in-toto-golang v0.11.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/moby/buildkit v0.32.2 - github.com/moby/go-archive v0.2.1 github.com/moby/moby/api v1.55.0 github.com/moby/moby/client v0.5.0 github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4 @@ -167,13 +166,12 @@ require ( github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.1 // indirect github.com/moby/spdystream v0.5.1 // indirect github.com/moby/sys/sequential v0.7.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect - github.com/moby/sys/user v0.4.1 // indirect - github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect diff --git a/go.sum b/go.sum index b698383160e0..8f704b2a5b11 100644 --- a/go.sum +++ b/go.sum @@ -399,8 +399,8 @@ github.com/moby/buildkit v0.32.2 h1:Sfy7+u6dUv/2yuBc9KCoK70Re8atuV8aPZ5UOC068Vc= github.com/moby/buildkit v0.32.2/go.mod h1:0GB/EJ1d+4VIVqIAgy3asaoGkVXy7IrDfVy7mPhOvg8= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.2.1 h1:fAa0wUS/ikZKyx7o/1fhUYmhZ7RgpthdeoDhJvunTLc= -github.com/moby/go-archive v0.2.1/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= diff --git a/vendor/github.com/moby/go-archive/.gitattributes b/vendor/github.com/moby/go-archive/.gitattributes deleted file mode 100644 index 4bb139d900bd..000000000000 --- a/vendor/github.com/moby/go-archive/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.go -text diff=golang -*.go text eol=lf diff --git a/vendor/github.com/moby/go-archive/.gitignore b/vendor/github.com/moby/go-archive/.gitignore deleted file mode 100644 index 3f2bc47416e7..000000000000 --- a/vendor/github.com/moby/go-archive/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/coverage.txt diff --git a/vendor/github.com/moby/go-archive/.golangci.yml b/vendor/github.com/moby/go-archive/.golangci.yml deleted file mode 100644 index 761e36d9d944..000000000000 --- a/vendor/github.com/moby/go-archive/.golangci.yml +++ /dev/null @@ -1,45 +0,0 @@ -version: "2" - -issues: - # Disable maximum issues count per one linter. - max-issues-per-linter: 0 - # Disable maximum count of issues with the same text. - max-same-issues: 0 - -linters: - enable: - - errorlint - - gosec - - unconvert - - unparam - exclusions: - generated: disable - presets: - - comments - - std-error-handling - rules: - # Ignore "G204: Subprocess launched with a potential tainted input or cmd arguments" - - path: '(.+)_test\.go' - linters: - - gosec - text: 'G204: Subprocess launched' - settings: - gosec: - excludes: - - G301 # Expect directory permissions to be 0750 or less - - G304 # Potential file inclusion via variable - - G306 # Expect WriteFile permissions to be 0600 or less - staticcheck: - # Enable all options, with some exceptions. - # For defaults, see https://golangci-lint.run/usage/linters/#staticcheck - checks: - - all - - -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008 - - -ST1003 # Poorly chosen identifier; https://staticcheck.dev/docs/checks/#ST1003 - -formatters: - enable: - - gofumpt - - goimports - exclusions: - generated: disable diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go deleted file mode 100644 index e59cfbb89d13..000000000000 --- a/vendor/github.com/moby/go-archive/archive.go +++ /dev/null @@ -1,1147 +0,0 @@ -// Package archive provides helper functions for dealing with archive files. -package archive - -import ( - "archive/tar" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - "syscall" - "time" - - "github.com/containerd/log" - "github.com/moby/patternmatcher" - "github.com/moby/sys/sequential" - "github.com/moby/sys/user" - - "github.com/moby/go-archive/compression" - "github.com/moby/go-archive/tarheader" -) - -// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a -// tar, but that do not have their own header entry. -// -// The permissions mask is stored in a constant instead of locally to ensure that magic numbers do not -// proliferate in the codebase. The default value 0755 has been selected based on the default umask of 0022, and -// a convention of mkdir(1) calling mkdir(2) with permissions of 0777, resulting in a final value of 0755. -// -// This value is currently implementation-defined, and not captured in any cross-runtime specification. Thus, it is -// subject to change in Moby at any time -- image authors who require consistent or known directory permissions -// should explicitly control them by ensuring that header entries exist for any applicable path. -const ImpliedDirectoryMode = 0o755 - -type ( - // WhiteoutFormat is the format of whiteouts unpacked - WhiteoutFormat int - - ChownOpts struct { - UID int - GID int - } - - // TarOptions wraps the tar options. - TarOptions struct { - // IncludeFiles lists archive-relative paths to include. - // Paths use POSIX ('/') separators. - IncludeFiles []string - - // ExcludePatterns lists archive-relative exclude patterns. - // Patterns use POSIX ('/') separators, matching patternmatcher semantics. - ExcludePatterns []string - Compression compression.Compression - // NoLchown disables applying ownership from the archive to extracted files - // and directories. Despite its historical name, it applies to all ownership - // changes, leaving extracted filesystem objects owned by the user performing - // the extraction. - NoLchown bool - IDMap user.IdentityMapping - ChownOpts *ChownOpts - IncludeSourceDir bool - // WhiteoutFormat is the expected on disk format for whiteout files. - // This format will be converted to the standard format on pack - // and from the standard format on unpack. - WhiteoutFormat WhiteoutFormat - // When unpacking, specifies whether overwriting a directory with a - // non-directory is allowed and vice versa. - NoOverwriteDirNonDir bool - // For each include when creating an archive, the included name will be - // replaced with the matching name from this map. - RebaseNames map[string]string - InUserNS bool - // Allow unpacking to succeed in spite of failures to set extended - // attributes on the unpacked files due to the destination filesystem - // not supporting them or a lack of permissions. Extended attributes - // were probably in the archive for a reason, so set this option at - // your own peril. - BestEffortXattrs bool - } -) - -// Archiver implements the Archiver interface and allows the reuse of most utility functions of -// this package with a pluggable Untar function. Also, to facilitate the passing of specific id -// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. -type Archiver struct { - Untar func(io.Reader, string, *TarOptions) error - IDMapping user.IdentityMapping -} - -// NewDefaultArchiver returns a new Archiver without any IdentityMapping -func NewDefaultArchiver() *Archiver { - return &Archiver{Untar: Untar} -} - -// breakoutErr marks errors caused by archive breakout attempts. -// Unit tests use it to distinguish expected breakout failures from other -// errors. -type breakoutErr struct{ error } - -func breakoutError(err error) error { - return &breakoutErr{error: err} -} - -const ( - AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts - OverlayWhiteoutFormat WhiteoutFormat = 1 // OverlayWhiteoutFormat formats whiteout according to the overlay standard. -) - -// IsArchivePath checks if the (possibly compressed) file at the given path -// starts with a tar file header. -func IsArchivePath(filePath string) bool { - file, err := os.Open(filePath) - if err != nil { - return false - } - defer func() { _ = file.Close() }() - rdr, err := compression.DecompressStream(file) - if err != nil { - return false - } - defer func() { _ = rdr.Close() }() - r := tar.NewReader(rdr) - _, err = r.Next() - return err == nil -} - -// TarModifierFunc is a function that can be passed to ReplaceFileTarWrapper to -// modify the contents or header of an entry in the archive. If the file already -// exists in the archive the TarModifierFunc will be called with the Header and -// a reader which will return the files content. If the file does not exist both -// header and content will be nil. -type TarModifierFunc func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) - -// ReplaceFileTarWrapper converts inputTarStream to a new tar stream. Files in the -// tar stream are modified if they match any of the keys in mods. -func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModifierFunc) io.ReadCloser { - pipeReader, pipeWriter := io.Pipe() - - go func() { - tarReader := tar.NewReader(inputTarStream) - tarWriter := tar.NewWriter(pipeWriter) - defer func() { - _ = tarWriter.Close() - _ = inputTarStream.Close() - }() - - modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { - header, data, err := modifier(name, original, tarReader) - switch { - case err != nil: - return err - case header == nil: - return nil - } - - if header.Name == "" { - header.Name = name - } - header.Size = int64(len(data)) - if err := tarWriter.WriteHeader(header); err != nil { - return err - } - if len(data) != 0 { - if _, err := tarWriter.Write(data); err != nil { - return err - } - } - return nil - } - - var err error - var originalHeader *tar.Header - for { - originalHeader, err = tarReader.Next() - if errors.Is(err, io.EOF) { - break - } - if err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - - modifier, ok := mods[originalHeader.Name] - if !ok { - // No modifiers for this file, copy the header and data - if err := tarWriter.WriteHeader(originalHeader); err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - if err := copyWithBuffer(tarWriter, tarReader); err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - continue - } - delete(mods, originalHeader.Name) - - if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - } - - // Apply the modifiers that haven't matched any files in the archive - for name, modifier := range mods { - if err := modify(name, nil, modifier, nil); err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - } - - _ = pipeWriter.Close() - }() - return pipeReader -} - -// FileInfoHeader creates a populated Header from fi. -// -// Compared to the archive/tar package, this function fills in less information -// but is safe to call from a chrooted process. The AccessTime and ChangeTime -// fields are not set in the returned header, ModTime is truncated to one-second -// precision, and the Uname and Gname fields are only set when fi is a FileInfo -// value returned from tar.Header.FileInfo(). -func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tarheader.FileInfoHeaderNoLookups(fi, link) - if err != nil { - return nil, err - } - hdr.Format = tar.FormatPAX - hdr.ModTime = hdr.ModTime.Truncate(time.Second) - hdr.AccessTime = time.Time{} - hdr.ChangeTime = time.Time{} - hdr.Mode = chmodTarEntry(hdr.Mode) - hdr.Name = canonicalTarName(name, fi.IsDir()) - return hdr, nil -} - -const paxSchilyXattr = "SCHILY.xattr." - -// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem -// to a tar header -func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error { - const ( - // Values based on linux/include/uapi/linux/capability.h - xattrCapsSz2 = 20 - versionOffset = 3 - vfsCapRevision2 = 2 - vfsCapRevision3 = 3 - ) - capability, _ := lgetxattr(filePath, "security.capability") - if capability != nil { - if capability[versionOffset] == vfsCapRevision3 { - // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no - // sense outside the user namespace the archive is built in. - capability[versionOffset] = vfsCapRevision2 - capability = capability[:xattrCapsSz2] - } - if hdr.PAXRecords == nil { - hdr.PAXRecords = make(map[string]string) - } - hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability) - } - return nil -} - -type tarWhiteoutConverter interface { - ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) - ConvertRead(*tar.Header, string) (bool, error) -} - -type tarAppender struct { - TarWriter *tar.Writer - - // for hardlink mapping - SeenFiles map[uint64]string - IdentityMapping user.IdentityMapping - ChownOpts *ChownOpts - - // For packing and unpacking whiteout files in the - // non standard format. The whiteout files defined - // by the AUFS standard are used as the tar whiteout - // standard. - WhiteoutConverter tarWhiteoutConverter -} - -func newTarAppender(idMapping user.IdentityMapping, writer io.Writer, chownOpts *ChownOpts) *tarAppender { - return &tarAppender{ - SeenFiles: make(map[uint64]string), - TarWriter: tar.NewWriter(writer), - IdentityMapping: idMapping, - ChownOpts: chownOpts, - } -} - -// canonicalTarName provides a platform-independent and consistent POSIX-style -// path for files and directories to be archived regardless of the platform. -func canonicalTarName(name string, isDir bool) string { - name = filepath.ToSlash(name) - - // suffix with '/' for directories - if isDir && !strings.HasSuffix(name, "/") { - name += "/" - } - return name -} - -// addTarFile adds to the tar archive a file from `srcPath` as `name` -func (ta *tarAppender) addTarFile(srcPath, archivePath string) error { - archivePath = filepath.ToSlash(archivePath) - fi, err := os.Lstat(srcPath) - if err != nil { - return err - } - - var link string - if fi.Mode()&os.ModeSymlink != 0 { - var err error - link, err = os.Readlink(srcPath) - if err != nil { - return err - } - } - - hdr, err := FileInfoHeader(archivePath, fi, link) - if err != nil { - return err - } - if err := ReadSecurityXattrToTarHeader(srcPath, hdr); err != nil { - return err - } - - // if it's not a directory and has more than 1 link, - // it's hard linked, so set the type flag accordingly - if !fi.IsDir() && hasHardlinks(fi) { - inode, err := getInodeFromStat(fi.Sys()) - if err != nil { - return fmt.Errorf("unexpected file info for %q: %w", srcPath, err) - } - // a link should have a name that it links too - // and that linked name should be first in the tar archive - if oldpath, ok := ta.SeenFiles[inode]; ok { - hdr.Typeflag = tar.TypeLink - hdr.Linkname = oldpath - hdr.Size = 0 // This Must be here for the writer math to add up! - } else { - ta.SeenFiles[inode] = hdr.Name - } - } - - // check whether the file is overlayfs whiteout - // if yes, skip re-mapping container ID mappings. - isOverlayWhiteout := fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 - - // handle re-mapping container ID mappings back to host ID mappings before - // writing tar headers/files. We skip whiteout files because they were written - // by the kernel and already have proper ownership relative to the host - if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() { - uid, gid, err := getFileUIDGID(fi.Sys()) - if err != nil { - return err - } - hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(uid, gid) - if err != nil { - return err - } - } - - // explicitly override with ChownOpts - if ta.ChownOpts != nil { - hdr.Uid = ta.ChownOpts.UID - hdr.Gid = ta.ChownOpts.GID - } - - if ta.WhiteoutConverter != nil { - wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, srcPath, fi) - if err != nil { - return err - } - - // If a new whiteout file exists, write original hdr, then - // replace hdr with wo to be written after. Whiteouts should - // always be written after the original. Note the original - // hdr may have been updated to be a whiteout with returning - // a whiteout header - if wo != nil { - if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { - return fmt.Errorf("tar: cannot use whiteout for non-empty file %q", hdr.Name) - } - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - return err - } - hdr = wo - } - } - - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - return err - } - - if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { - // We use sequential file access to avoid depleting the standby list on - // Windows. On Linux, this equates to a regular os.Open. - file, err := sequential.Open(srcPath) - if err != nil { - return err - } - - err = copyWithBuffer(ta.TarWriter, file) - _ = file.Close() - if err != nil { - return err - } - } - - return nil -} - -func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { - var ( - Lchown = true - inUserns, bestEffortXattrs bool - chownOpts *ChownOpts - ) - - // TODO(thaJeztah): make opts a required argument. - if opts != nil { - Lchown = !opts.NoLchown - inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally. - chownOpts = opts.ChownOpts - bestEffortXattrs = opts.BestEffortXattrs - } - - // hdr.Mode is in linux format, which we can use for sycalls, - // but for os.Foo() calls we need the mode converted to os.FileMode, - // so use hdrInfo.Mode() (they differ for e.g. setuid bits) - hdrInfo := hdr.FileInfo() - - switch hdr.Typeflag { - case tar.TypeDir: - // Create directory unless it exists as a directory already. - // In that case we just want to merge the two - if fi, err := os.Lstat(dstPath); err != nil || !fi.IsDir() { - if err := os.Mkdir(dstPath, hdrInfo.Mode()); err != nil { - return err - } - } - - case tar.TypeReg: - // Source is regular file. We use sequential file access to avoid depleting - // the standby list on Windows. On Linux, this equates to a regular os.OpenFile. - file, err := sequential.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) - if err != nil { - return err - } - if err := copyWithBuffer(file, reader); err != nil { - _ = file.Close() - return err - } - _ = file.Close() - - case tar.TypeBlock, tar.TypeChar: - if inUserns { // cannot create devices in a userns - log.G(context.TODO()).WithFields(log.Fields{"path": dstPath, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns") - return nil - } - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil { - return err - } - - case tar.TypeFifo: - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil { - if inUserns && errors.Is(err, syscall.EPERM) { - // In most cases, cannot create a fifo if running in user namespace - log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": dstPath, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") - return nil - } - return err - } - - case tar.TypeLink: - // #nosec G305 -- The target path is checked for path traversal. - linkTarget := filepath.Join(extractDir, hdr.Linkname) - // check for hardlink breakout - if !strings.HasPrefix(linkTarget, extractDir) { - return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", linkTarget, hdr.Linkname)) - } - if err := os.Link(linkTarget, dstPath); err != nil { - return err - } - - case tar.TypeSymlink: - // path -> hdr.Linkname = targetPath - // e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file - targetPath := filepath.Join(filepath.Dir(dstPath), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. - - // the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because - // that symlink would first have to be created, which would be caught earlier, at this very check: - if !strings.HasPrefix(targetPath, extractDir) { - return breakoutError(fmt.Errorf("invalid symlink %q -> %q", dstPath, hdr.Linkname)) - } - if err := os.Symlink(hdr.Linkname, dstPath); err != nil { - return err - } - - case tar.TypeXGlobalHeader: - log.G(context.TODO()).Debug("PAX Global Extended Headers found and ignored") - return nil - - default: - return fmt.Errorf("unhandled tar header type %d", hdr.Typeflag) - } - - // Lchown is not supported on Windows. - if Lchown && runtime.GOOS != "windows" { - if chownOpts == nil { - chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid} - } - if err := os.Lchown(dstPath, chownOpts.UID, chownOpts.GID); err != nil { - var msg string - if inUserns && errors.Is(err, syscall.EINVAL) { - msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)" - } - return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", dstPath, hdr.Uid, hdr.Gid, msg, err) - } - } - - var xattrErrs []string - for key, value := range hdr.PAXRecords { - xattr, ok := strings.CutPrefix(key, paxSchilyXattr) - if !ok { - continue - } - if err := lsetxattr(dstPath, xattr, []byte(value), 0); err != nil { - if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) { - // EPERM occurs if modifying xattrs is not allowed. This can - // happen when running in userns with restrictions (ChromeOS). - xattrErrs = append(xattrErrs, err.Error()) - continue - } - return err - } - } - - if len(xattrErrs) > 0 { - log.G(context.TODO()).WithFields(log.Fields{ - "errors": xattrErrs, - }).Warn("ignored xattrs in archive: underlying filesystem doesn't support them") - } - - // There is no LChmod, so ignore mode for symlink. Also, this - // must happen after chown, as that can modify the file mode - if err := handleLChmod(hdr, dstPath, hdrInfo); err != nil { - return err - } - - aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime)) - mTime := boundTime(hdr.ModTime) - - switch hdr.Typeflag { - case tar.TypeSymlink: - // Apply timestamps to the symlink itself (AT_SYMLINK_NOFOLLOW). - if err := lchtimes(dstPath, aTime, mTime); err != nil { - return err - } - case tar.TypeLink: - // Follow the hardlink only when its target is not itself a symlink. - fi, err := os.Lstat(hdr.Linkname) - if err == nil && fi.Mode()&os.ModeSymlink == 0 { - if err := chtimes(dstPath, aTime, mTime); err != nil { - return err - } - } - default: - // All other file types follow symlinks. - if err := chtimes(dstPath, aTime, mTime); err != nil { - return err - } - } - return nil -} - -// Tar creates an archive from the directory at `srcPath`, and returns it as a -// stream of bytes. -func Tar(srcPath string, comp compression.Compression) (io.ReadCloser, error) { - return TarWithOptions(srcPath, &TarOptions{Compression: comp}) -} - -// TarWithOptions creates an archive from the directory at `srcPath`, only including files whose relative -// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. -func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { - tb, err := NewTarballer(srcPath, options) - if err != nil { - return nil, err - } - go tb.Do() - return tb.Reader(), nil -} - -// Tarballer is a lower-level interface to TarWithOptions which gives the caller -// control over which goroutine the archiving operation executes on. -type Tarballer struct { - srcPath string - options *TarOptions - pm *patternmatcher.PatternMatcher - pipeReader *io.PipeReader - pipeWriter *io.PipeWriter - compressWriter io.WriteCloser - whiteoutConverter tarWhiteoutConverter -} - -// NewTarballer constructs a new tarballer. The arguments are the same as for -// TarWithOptions. -func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) { - pm, err := patternmatcher.New(options.ExcludePatterns) - if err != nil { - return nil, err - } - - pipeReader, pipeWriter := io.Pipe() - - compressWriter, err := compression.CompressStream(pipeWriter, options.Compression) - if err != nil { - return nil, err - } - - return &Tarballer{ - // Fix the source path to work with long path names. This is a no-op - // on platforms other than Windows. - srcPath: addLongPathPrefix(srcPath), - options: options, - pm: pm, - pipeReader: pipeReader, - pipeWriter: pipeWriter, - compressWriter: compressWriter, - whiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat), - }, nil -} - -// Reader returns the reader for the created archive. -func (t *Tarballer) Reader() io.ReadCloser { - return t.pipeReader -} - -// Do performs the archiving operation in the background. The resulting archive -// can be read from t.Reader(). Do should only be called once on each Tarballer -// instance. -func (t *Tarballer) Do() { - ta := newTarAppender( - t.options.IDMap, - t.compressWriter, - t.options.ChownOpts, - ) - ta.WhiteoutConverter = t.whiteoutConverter - - defer func() { - // Make sure to check the error on Close. - if err := ta.TarWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close tar writer: %s", err) - } - if err := t.compressWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close compress writer: %s", err) - } - if err := t.pipeWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close pipe writer: %s", err) - } - }() - - // In general we log errors here but ignore them because - // during e.g. a diff operation the container can continue - // mutating the filesystem and we can see transient errors - // from this - - stat, err := os.Lstat(t.srcPath) - if err != nil { - return - } - - if !stat.IsDir() { - // We can't later join a non-dir with any includes because the - // 'walk' will error if "file/." is stat-ed and "file" is not a - // directory. So, we must split the source path and use the - // basename as the include. - if len(t.options.IncludeFiles) > 0 { - log.G(context.TODO()).Warn("Tar: Can't archive a file with includes") - } - - dir, base := SplitPathDirEntry(t.srcPath) - t.srcPath = dir - t.options.IncludeFiles = []string{base} - } - - if len(t.options.IncludeFiles) == 0 { - t.options.IncludeFiles = []string{"."} - } - - seen := make(map[string]bool) - - for _, include := range t.options.IncludeFiles { - rebaseName := t.options.RebaseNames[include] - - var ( - parentMatchInfo []patternmatcher.MatchInfo - parentDirs []string - ) - - walkRoot := getWalkRoot(t.srcPath, include) - // TODO(thaJeztah): should this error be handled? - _ = filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error { - if err != nil { - log.G(context.TODO()).Errorf("Tar: Can't stat file %s to tar: %s", t.srcPath, err) - return nil - } - - relFilePath, err := filepath.Rel(t.srcPath, filePath) - if err != nil || (!t.options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { - // Error getting relative path OR we are looking - // at the source directory path. Skip in both situations. - return nil - } - - if t.options.IncludeSourceDir && include == "." && relFilePath != "." { - relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) - } - - skip := false - - // If "include" is an exact match for the current file - // then even if there's an "excludePatterns" pattern that - // matches it, don't skip it. IOW, assume an explicit 'include' - // is asking for that file no matter what - which is true - // for some files, like .dockerignore and Dockerfile (sometimes) - if include != relFilePath { - for len(parentDirs) != 0 { - lastParentDir := parentDirs[len(parentDirs)-1] - if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) { - break - } - parentDirs = parentDirs[:len(parentDirs)-1] - parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1] - } - - var matchInfo patternmatcher.MatchInfo - if len(parentMatchInfo) != 0 { - skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1]) - } else { - skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{}) - } - if err != nil { - log.G(context.TODO()).Errorf("Error matching %s: %v", relFilePath, err) - return err - } - - if f.IsDir() { - parentDirs = append(parentDirs, relFilePath) - parentMatchInfo = append(parentMatchInfo, matchInfo) - } - } - - if skip { - // If we want to skip this file and its a directory - // then we should first check to see if there's an - // excludes pattern (e.g. !dir/file) that starts with this - // dir. If so then we can't skip this dir. - - // Its not a dir then so we can just return/skip. - if !f.IsDir() { - return nil - } - - // No exceptions (!...) in patterns so just skip dir - if !t.pm.Exclusions() { - return filepath.SkipDir - } - - dirSlash := relFilePath + string(filepath.Separator) - - for _, pat := range t.pm.Patterns() { - if !pat.Exclusion() { - continue - } - if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) { - // found a match - so can't skip this dir - return nil - } - } - - // No matching exclusion dir so just skip dir - return filepath.SkipDir - } - - if seen[relFilePath] { - return nil - } - seen[relFilePath] = true - - // Rename the base resource. - if rebaseName != "" { - var replacement string - if rebaseName != string(filepath.Separator) { - // Special case the root directory to replace with an - // empty string instead so that we don't end up with - // double slashes in the paths. - replacement = rebaseName - } - - relFilePath = strings.Replace(relFilePath, include, replacement, 1) - } - - if err := ta.addTarFile(filePath, relFilePath); err != nil { - log.G(context.TODO()).Errorf("Can't add file %s to tar: %s", filePath, err) - // if pipe is broken, stop writing tar stream to it - if errors.Is(err, io.ErrClosedPipe) { - return err - } - } - return nil - }) - } -} - -// Unpack unpacks the decompressedArchive to dest with options. -func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { - if options == nil { - options = &TarOptions{} - } - tr := tar.NewReader(decompressedArchive) - - var dirs []*tar.Header - whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) - - // Iterate through the files in the archive. -loop: - for { - hdr, err := tr.Next() - if errors.Is(err, io.EOF) { - // end of tar archive - break - } - if err != nil { - return err - } - - // ignore XGlobalHeader early to avoid creating parent directories for them - if hdr.Typeflag == tar.TypeXGlobalHeader { - log.G(context.TODO()).Debugf("PAX Global Extended Headers found for %s and ignored", hdr.Name) - continue - } - - // Normalize name, for safety and for a simple is-root check - // This keeps "../" as-is, but normalizes "/../" to "/". Or Windows: - // This keeps "..\" as-is, but normalizes "\..\" to "\". - hdr.Name = filepath.Clean(hdr.Name) - - for _, exclude := range options.ExcludePatterns { - if strings.HasPrefix(hdr.Name, exclude) { - continue loop - } - } - - // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return err - } - - // #nosec G305 -- The joined path is checked for path traversal. - dstPath := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, dstPath) - if err != nil { - return err - } - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - - // If dstPath exists we almost always just want to remove and replace it. - // The only exception is when it is a directory *and* the file from - // the layer is also a directory. Then we want to merge them (i.e. - // just apply the metadata from the layer). - if fi, err := os.Lstat(dstPath); err == nil { - if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir { - // If NoOverwriteDirNonDir is true then we cannot replace - // an existing directory with a non-directory from the archive. - return fmt.Errorf("cannot overwrite directory %q with non-directory %q", dstPath, dest) - } - - if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir { - // If NoOverwriteDirNonDir is true then we cannot replace - // an existing non-directory with a directory from the archive. - return fmt.Errorf("cannot overwrite non-directory %q with directory %q", dstPath, dest) - } - - if fi.IsDir() && hdr.Name == "." { - continue - } - - if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(dstPath); err != nil { - return err - } - } - } - - if err := remapIDs(options.IDMap, hdr); err != nil { - return err - } - - if whiteoutConverter != nil { - writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath) - if err != nil { - return err - } - if !writeFile { - continue - } - } - - if err := createTarFile(dstPath, dest, hdr, tr, options); err != nil { - return err - } - - // Directory mtimes must be handled at the end to avoid further - // file creation in them to modify the directory mtime - if hdr.Typeflag == tar.TypeDir { - dirs = append(dirs, hdr) - } - } - - for _, hdr := range dirs { - // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. - dstPath := filepath.Join(dest, hdr.Name) - if err := chtimes(dstPath, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil { - return err - } - } - return nil -} - -// createImpliedDirectories will create all parent directories of the current path with default permissions, if they do -// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is -// defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus -// we most both create them and choose metadata like permissions. -func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error { - // For non-directory entries, ensure that the parent directory exists. - if hdr.Typeflag != tar.TypeDir { - parent := filepath.Dir(hdr.Name) - parentPath := filepath.Join(dest, parent) - if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - if options.NoLchown { - return os.MkdirAll(parentPath, ImpliedDirectoryMode) - } - // RootPair() is confined inside this loop as most cases will not require a call, so we can spend some - // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche - // usage that reduces the portability of an image. - uid, gid := options.IDMap.RootPair() - - err = user.MkdirAllAndChown(parentPath, ImpliedDirectoryMode, uid, gid, user.WithOnlyNew) - if err != nil { - return err - } - } - } - - return nil -} - -// Untar reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive may be compressed with one of the following algorithms: -// identity (uncompressed), gzip, bzip2, xz. -// -// FIXME: specify behavior when target path exists vs. doesn't exist. -func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { - return untarHandler(tarArchive, dest, options, true) -} - -// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive must be an uncompressed stream. -func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { - return untarHandler(tarArchive, dest, options, false) -} - -// Handler for teasing out the automatic decompression -func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { - if tarArchive == nil { - return errors.New("empty archive") - } - dest = filepath.Clean(dest) - if options == nil { - options = &TarOptions{} - } - - r := tarArchive - if decompress { - decompressedArchive, err := compression.DecompressStream(tarArchive) - if err != nil { - return err - } - defer func() { _ = decompressedArchive.Close() }() - r = decompressedArchive - } - - return Unpack(r, dest, options) -} - -// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. -// If either Tar or Untar fails, TarUntar aborts and returns the error. -func (archiver *Archiver) TarUntar(src, dst string) error { - archive, err := Tar(src, compression.None) - if err != nil { - return err - } - defer func() { _ = archive.Close() }() - return archiver.Untar(archive, dst, &TarOptions{ - IDMap: archiver.IDMapping, - }) -} - -// UntarPath untar a file from path to a destination, src is the source tar file path. -func (archiver *Archiver) UntarPath(src, dst string) error { - archive, err := os.Open(src) - if err != nil { - return err - } - defer func() { _ = archive.Close() }() - return archiver.Untar(archive, dst, &TarOptions{ - IDMap: archiver.IDMapping, - }) -} - -// CopyWithTar creates a tar archive of filesystem path `src`, and -// unpacks it at filesystem path `dst`. -// The archive is streamed directly with fixed buffering and no -// intermediary disk IO. -func (archiver *Archiver) CopyWithTar(src, dst string) error { - srcSt, err := os.Stat(src) - if err != nil { - return err - } - if !srcSt.IsDir() { - return archiver.CopyFileWithTar(src, dst) - } - - // if this Archiver is set up with ID mapping we need to create - // the new destination directory with the remapped root UID/GID pair - // as owner - uid, gid := archiver.IDMapping.RootPair() - // Create dst, copy src's content into it - if err := user.MkdirAllAndChown(dst, 0o755, uid, gid, user.WithOnlyNew); err != nil { - return err - } - return archiver.TarUntar(src, dst) -} - -// CopyFileWithTar emulates the behavior of the 'cp' command-line -// for a single file. It copies a regular file from path `src` to -// path `dst`, and preserves all its metadata. -func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { - srcSt, err := os.Stat(src) - if err != nil { - return err - } - - if srcSt.IsDir() { - return errors.New("can't copy a directory") - } - - // Clean up the trailing slash. This must be done in an operating - // system specific manner. - if dst[len(dst)-1] == os.PathSeparator { - dst = filepath.Join(dst, filepath.Base(src)) - } - // Create the holding directory if necessary - if err := os.MkdirAll(filepath.Dir(dst), 0o700); err != nil { - return err - } - - r, w := io.Pipe() - errC := make(chan error, 1) - - go func() { - defer close(errC) - - errC <- func() error { - defer func() { _ = w.Close() }() - - srcF, err := os.Open(src) - if err != nil { - return err - } - defer func() { _ = srcF.Close() }() - - hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") - if err != nil { - return err - } - hdr.Format = tar.FormatPAX - hdr.ModTime = hdr.ModTime.Truncate(time.Second) - hdr.AccessTime = time.Time{} - hdr.ChangeTime = time.Time{} - hdr.Name = filepath.Base(dst) - hdr.Mode = chmodTarEntry(hdr.Mode) - - if err := remapIDs(archiver.IDMapping, hdr); err != nil { - return err - } - - tw := tar.NewWriter(w) - defer func() { _ = tw.Close() }() - if err := tw.WriteHeader(hdr); err != nil { - return err - } - if err := copyWithBuffer(tw, srcF); err != nil { - return err - } - return nil - }() - }() - defer func() { - if er := <-errC; err == nil && er != nil { - err = er - } - }() - - err = archiver.Untar(r, filepath.Dir(dst), nil) - if err != nil { - _ = r.CloseWithError(err) - } - return err -} - -// IdentityMapping returns the IdentityMapping of the archiver. -func (archiver *Archiver) IdentityMapping() user.IdentityMapping { - return archiver.IDMapping -} - -func remapIDs(idMapping user.IdentityMapping, hdr *tar.Header) error { - uid, gid, err := idMapping.ToHost(hdr.Uid, hdr.Gid) - hdr.Uid, hdr.Gid = uid, gid - return err -} diff --git a/vendor/github.com/moby/go-archive/archive_linux.go b/vendor/github.com/moby/go-archive/archive_linux.go deleted file mode 100644 index bddef788cca5..000000000000 --- a/vendor/github.com/moby/go-archive/archive_linux.go +++ /dev/null @@ -1,129 +0,0 @@ -package archive - -import ( - "archive/tar" - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "github.com/moby/sys/userns" - "golang.org/x/sys/unix" -) - -func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { - if format == OverlayWhiteoutFormat { - return newOverlayWhiteoutConverter() - } - return nil -} - -type overlayWhiteoutConverter struct { - opaqueXattr string -} - -func newOverlayWhiteoutConverter() overlayWhiteoutConverter { - opaqueXattr := "trusted.overlay.opaque" - if userns.RunningInUserNS() { - opaqueXattr = "user.overlay.opaque" - } - return overlayWhiteoutConverter{ - opaqueXattr: opaqueXattr, - } -} - -func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, fi os.FileInfo) (wo *tar.Header, _ error) { - // convert whiteouts to AUFS format - if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { - // we just rename the file and make it normal - dir, filename := path.Split(hdr.Name) - hdr.Name = path.Join(dir, WhiteoutPrefix+filename) - hdr.Mode = 0o600 - hdr.Typeflag = tar.TypeReg - hdr.Size = 0 - } - - if !fi.IsDir() { - // FIXME(thaJeztah): return a sentinel error instead of nil, nil - return nil, nil - } - - // convert opaque dirs to AUFS format by writing an empty file with the prefix - opaque, err := lgetxattr(filePath, c.opaqueXattr) - if err != nil { - return nil, err - } - if len(opaque) != 1 || opaque[0] != 'y' { - // FIXME(thaJeztah): return a sentinel error instead of nil, nil - return nil, nil - } - delete(hdr.PAXRecords, paxSchilyXattr+c.opaqueXattr) - - // create a header for the whiteout file - // it should inherit some properties from the parent, but be a regular file - return &tar.Header{ - Typeflag: tar.TypeReg, - Mode: hdr.Mode & int64(os.ModePerm), - Name: path.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. - Size: 0, - Uid: hdr.Uid, - Uname: hdr.Uname, - Gid: hdr.Gid, - Gname: hdr.Gname, - AccessTime: hdr.AccessTime, - ChangeTime: hdr.ChangeTime, - }, nil -} - -func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) { - base := filepath.Base(filePath) - dir := filepath.Dir(filePath) - - switch base { - case WhiteoutPrefix, WhiteoutPrefix + ".", WhiteoutPrefix + "..": - return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name) - - case WhiteoutOpaqueDir: - // If a directory is marked as opaque by the AUFS special file, we need to translate that to overlay. - if err := unix.Setxattr(dir, c.opaqueXattr, []byte{'y'}, 0); err != nil { - return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, c.opaqueXattr, err) - } - // Don't write the whiteout file itself. - return false, nil - - default: - originalBase, ok := strings.CutPrefix(base, WhiteoutPrefix) - if !ok { - // Regular file. - return true, nil - } - // If a file was deleted, and we are using overlay, we need to create a character device. - originalPath := filepath.Join(dir, originalBase) - if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { - return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err) - } - - // Header IDs have already been remapped. Optimize the common non-remapped - // root-owned (0:0) case by assuming the created whiteout has the expected - // ownership, rather than comparing against the effective UID/GID or stat'ing - // the created node to verify it. - if hdr.Uid != 0 || hdr.Gid != 0 { - // TODO(thaJeztah): Revisit whether whiteout ownership needs to be preserved. - // - // This was added in the original overlay whiteout implementation: - // https://github.com/moby/moby/pull/18560 / https://github.com/moby/moby/pull/22126 - // - // OverlayFS documents whiteouts in terms of a character device with device - // number 0:0, not ownership: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories - // - // If ownership is not required, this Lchown can be removed to avoid the remaining TOCTOU window. - if err := os.Lchown(originalPath, hdr.Uid, hdr.Gid); err != nil { - return false, err - } - } - - // Don't write the whiteout file itself. - return false, nil - } -} diff --git a/vendor/github.com/moby/go-archive/archive_other.go b/vendor/github.com/moby/go-archive/archive_other.go deleted file mode 100644 index 6495549f60e8..000000000000 --- a/vendor/github.com/moby/go-archive/archive_other.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package archive - -func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { - return nil -} diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go deleted file mode 100644 index b8b4cadab26f..000000000000 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ /dev/null @@ -1,99 +0,0 @@ -//go:build !windows - -package archive - -import ( - "archive/tar" - "errors" - "fmt" - "math" - "os" - "path/filepath" - "strings" - "syscall" - - "golang.org/x/sys/unix" -) - -var errInvalidArchive = errors.New("invalid archive") - -// addLongPathPrefix adds the Windows long path prefix to the path provided if -// it does not already have it. It is a no-op on platforms other than Windows. -func addLongPathPrefix(srcPath string) string { - return srcPath -} - -// getWalkRoot calculates the root path when performing a TarWithOptions. -// We use a separate function as this is platform specific. On Linux, we -// can't use filepath.Join(srcPath,include) because this will clean away -// a trailing "." or "/" which may be important. -func getWalkRoot(srcPath string, include string) string { - return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include -} - -// chmodTarEntry is used to adjust the file permissions used in tar header based -// on the platform the archival is done. -func chmodTarEntry(mode int64) int64 { - return mode // noop for unix as golang APIs provide perm bits correctly -} - -func getInodeFromStat(stat any) (uint64, error) { - s, ok := stat.(*syscall.Stat_t) - if !ok { - return 0, fmt.Errorf("unexpected stat type %T", stat) - } - return s.Ino, nil -} - -func getFileUIDGID(stat any) (int, int, error) { - s, ok := stat.(*syscall.Stat_t) - - if !ok { - return 0, 0, errors.New("cannot convert stat value to syscall.Stat_t") - } - return int(s.Uid), int(s.Gid), nil -} - -// handleTarTypeBlockCharFifo is an OS-specific helper function used by -// createTarFile to handle the following types of header: Block; Char; Fifo. -// -// Creating device nodes is not supported when running in a user namespace, -// produces a [syscall.EPERM] in most cases. -func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error { - mode := uint32(hdr.Mode & 0o7777) - switch hdr.Typeflag { - case tar.TypeBlock: - mode |= unix.S_IFBLK - case tar.TypeChar: - mode |= unix.S_IFCHR - case tar.TypeFifo: - mode |= unix.S_IFIFO - } - - // Devmajor and Devminor come straight from the (untrusted) tar header as - // int64, but Mkdev only takes uint32. Casting a value that does not fit - // silently truncates it, so the node created on disk would carry a - // different major/minor than the header declares. Reject those instead of - // creating a mismatched device. - if hdr.Devmajor < 0 || hdr.Devmajor > math.MaxUint32 || - hdr.Devminor < 0 || hdr.Devminor > math.MaxUint32 { - return fmt.Errorf("device number %d:%d for %q out of range: %w", hdr.Devmajor, hdr.Devminor, hdr.Name, errInvalidArchive) - } - - return mknod(dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) -} - -func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error { - if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { - return err - } - } - } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil { - return err - } - } - return nil -} diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go deleted file mode 100644 index ee8dbd6830ca..000000000000 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ /dev/null @@ -1,62 +0,0 @@ -package archive - -import ( - "archive/tar" - "os" - "path/filepath" - "strings" -) - -// longPathPrefix is the longpath prefix for Windows file paths. -const longPathPrefix = `\\?\` - -// addLongPathPrefix adds the Windows long path prefix to the path provided if -// it does not already have it. It is a no-op on platforms other than Windows. -// -// addLongPathPrefix is a copy of [github.com/docker/docker/pkg/longpath.AddPrefix]. -func addLongPathPrefix(srcPath string) string { - if strings.HasPrefix(srcPath, longPathPrefix) { - return srcPath - } - if strings.HasPrefix(srcPath, `\\`) { - // This is a UNC path, so we need to add 'UNC' to the path as well. - return longPathPrefix + `UNC` + srcPath[1:] - } - return longPathPrefix + srcPath -} - -// getWalkRoot calculates the root path when performing a TarWithOptions. -// We use a separate function as this is platform specific. -func getWalkRoot(srcPath string, include string) string { - return filepath.Join(srcPath, include) -} - -// chmodTarEntry is used to adjust the file permissions used in tar header based -// on the platform the archival is done. -func chmodTarEntry(mode int64) int64 { - // Remove group- and world-writable bits. - mode &= 0o755 - - // Add the x bit: make everything +x on Windows - return mode | 0o111 -} - -func getInodeFromStat(stat any) (uint64, error) { - // do nothing. no notion of Inode in stat on Windows - return 0, nil -} - -// handleTarTypeBlockCharFifo is an OS-specific helper function used by -// createTarFile to handle the following types of header: Block; Char; Fifo -func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { - return nil -} - -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { - return nil -} - -func getFileUIDGID(stat any) (int, int, error) { - // no notion of file ownership mapping yet on Windows - return 0, 0, nil -} diff --git a/vendor/github.com/moby/go-archive/changes.go b/vendor/github.com/moby/go-archive/changes.go deleted file mode 100644 index c5d647c030d2..000000000000 --- a/vendor/github.com/moby/go-archive/changes.go +++ /dev/null @@ -1,430 +0,0 @@ -package archive - -import ( - "archive/tar" - "bytes" - "context" - "fmt" - "io" - "io/fs" - "maps" - "os" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/containerd/log" - "github.com/moby/sys/user" -) - -// ChangeType represents the change type. -type ChangeType int - -const ( - ChangeModify = 0 // ChangeModify represents the modify operation. - ChangeAdd = 1 // ChangeAdd represents the add operation. - ChangeDelete = 2 // ChangeDelete represents the delete operation. -) - -func (c ChangeType) String() string { - switch c { - case ChangeModify: - return "C" - case ChangeAdd: - return "A" - case ChangeDelete: - return "D" - } - return "" -} - -// Change represents a change, it wraps the change type and path. -// It describes changes of the files in the path respect to the -// parent layers. The change could be modify, add, delete. -// This is used for layer diff. -type Change struct { - Path string - Kind ChangeType -} - -func (change *Change) String() string { - return fmt.Sprintf("%s %s", change.Kind, change.Path) -} - -// for sort.Sort -type changesByPath []Change - -func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path } -func (c changesByPath) Len() int { return len(c) } -func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] } - -// Gnu tar doesn't have sub-second mtime precision. The go tar -// writer (1.10+) does when using PAX format, but we round times to seconds -// to ensure archives have the same hashes for backwards compatibility. -// See https://github.com/moby/moby/pull/35739/commits/fb170206ba12752214630b269a40ac7be6115ed4. -// -// Non-sub-second is problematic when we apply changes via tar -// files. We handle this by comparing for exact times, *or* same -// second count and either a or b having exactly 0 nanoseconds -func sameFsTime(a, b time.Time) bool { - return a.Equal(b) || - (a.Unix() == b.Unix() && - (a.Nanosecond() == 0 || b.Nanosecond() == 0)) -} - -// Changes walks the path rw and determines changes for the files in the path, -// with respect to the parent layers -func Changes(layers []string, rw string) ([]Change, error) { - return collectChanges(layers, rw, aufsDeletedFile, aufsMetadataSkip) -} - -func aufsMetadataSkip(path string) (skip bool, err error) { - skip, err = filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path) - if err != nil { - skip = true - } - return skip, err -} - -func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) { - f := filepath.Base(path) - - // If there is a whiteout, then the file was removed - if strings.HasPrefix(f, WhiteoutPrefix) { - originalFile := f[len(WhiteoutPrefix):] - return filepath.Join(filepath.Dir(path), originalFile), nil - } - - return "", nil -} - -type ( - skipChange func(string) (bool, error) - deleteChange func(string, string, os.FileInfo) (string, error) -) - -func collectChanges(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) { - var ( - changes []Change - changedDirs = make(map[string]struct{}) - ) - - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - - // As this runs on the daemon side, file paths are OS specific. - path = filepath.Join(string(os.PathSeparator), path) - - // Skip root - if path == string(os.PathSeparator) { - return nil - } - - if sc != nil { - if skip, err := sc(path); skip { - return err - } - } - - change := Change{ - Path: path, - } - - deletedFile, err := dc(rw, path, f) - if err != nil { - return err - } - - // Find out what kind of modification happened - if deletedFile != "" { - change.Path = deletedFile - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files. - // This block is here to ensure the change is recorded even if the - // modify time, mode and size of the parent directory in the rw and ro layers are all equal. - // Check https://github.com/docker/docker/pull/13590 for details. - if f.IsDir() { - changedDirs[path] = struct{}{} - } - if change.Kind == ChangeAdd || change.Kind == ChangeDelete { - parent := filepath.Dir(path) - if _, ok := changedDirs[parent]; !ok && parent != "/" { - changes = append(changes, Change{Path: parent, Kind: ChangeModify}) - changedDirs[parent] = struct{}{} - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - -// FileInfo describes the information of a file. -type FileInfo struct { - parent *FileInfo - name string - stat fs.FileInfo - children map[string]*FileInfo - capability []byte - added bool -} - -// LookUp looks up the file information of a file. -func (info *FileInfo) LookUp(path string) *FileInfo { - // As this runs on the daemon side, file paths are OS specific. - parent := info - if path == string(os.PathSeparator) { - return info - } - - pathElements := strings.SplitSeq(path, string(os.PathSeparator)) - for elem := range pathElements { - if elem != "" { - child := parent.children[elem] - if child == nil { - return nil - } - parent = child - } - } - return parent -} - -func (info *FileInfo) path() string { - if info.parent == nil { - // As this runs on the daemon side, file paths are OS specific. - return string(os.PathSeparator) - } - return filepath.Join(info.parent.path(), info.name) -} - -func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { - sizeAtEntry := len(*changes) - - if oldInfo == nil { - // add - change := Change{ - Path: info.path(), - Kind: ChangeAdd, - } - *changes = append(*changes, change) - info.added = true - } - - // We make a copy so we can modify it to detect additions - // also, we only recurse on the old dir if the new info is a directory - // otherwise any previous delete/change is considered recursive - oldChildren := make(map[string]*FileInfo) - if oldInfo != nil && info.isDir() { - maps.Copy(oldChildren, oldInfo.children) - } - - for name, newChild := range info.children { - oldChild := oldChildren[name] - if oldChild != nil { - // change? - oldStat := oldChild.stat - newStat := newChild.stat - // Note: We can't compare inode or ctime or blocksize here, because these change - // when copying a file into a container. However, that is not generally a problem - // because any content change will change mtime, and any status change should - // be visible when actually comparing the stat fields. The only time this - // breaks down is if some code intentionally hides a change by setting - // back mtime - if statDifferent(oldStat, newStat) || - !bytes.Equal(oldChild.capability, newChild.capability) { - change := Change{ - Path: newChild.path(), - Kind: ChangeModify, - } - *changes = append(*changes, change) - newChild.added = true - } - - // Remove from copy so we can detect deletions - delete(oldChildren, name) - } - - newChild.addChanges(oldChild, changes) - } - for _, oldChild := range oldChildren { - // delete - change := Change{ - Path: oldChild.path(), - Kind: ChangeDelete, - } - *changes = append(*changes, change) - } - - // If there were changes inside this directory, we need to add it, even if the directory - // itself wasn't changed. This is needed to properly save and restore filesystem permissions. - // As this runs on the daemon side, file paths are OS specific. - if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != string(os.PathSeparator) { - change := Change{ - Path: info.path(), - Kind: ChangeModify, - } - // Let's insert the directory entry before the recently added entries located inside this dir - *changes = append(*changes, change) // just to resize the slice, will be overwritten - copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:]) - (*changes)[sizeAtEntry] = change - } -} - -// Changes add changes to file information. -func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { - var changes []Change - - info.addChanges(oldInfo, &changes) - - return changes -} - -func newRootFileInfo() *FileInfo { - // As this runs on the daemon side, file paths are OS specific. - root := &FileInfo{ - name: string(os.PathSeparator), - children: make(map[string]*FileInfo), - } - return root -} - -// ChangesDirs compares two directories and generates an array of Change objects describing the changes. -// If oldDir is "", then all files in newDir will be Add-Changes. -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var oldRoot, newRoot *FileInfo - if oldDir == "" { - emptyDir, err := os.MkdirTemp("", "empty") - if err != nil { - return nil, err - } - defer os.Remove(emptyDir) - oldDir = emptyDir - } - oldRoot, newRoot, err := collectFileInfoForChanges(oldDir, newDir) - if err != nil { - return nil, err - } - - return newRoot.Changes(oldRoot), nil -} - -// ChangesSize calculates the size in bytes of the provided changes, based on newDir. -func ChangesSize(newDir string, changes []Change) int64 { - var ( - size int64 - sf = make(map[uint64]struct{}) - ) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - file := filepath.Join(newDir, change.Path) - fileInfo, err := os.Lstat(file) - if err != nil { - log.G(context.TODO()).Errorf("Can not stat %q: %s", file, err) - continue - } - - if fileInfo != nil && !fileInfo.IsDir() { - if hasHardlinks(fileInfo) { - inode := getIno(fileInfo) - if _, ok := sf[inode]; !ok { - size += fileInfo.Size() - sf[inode] = struct{}{} - } - } else { - size += fileInfo.Size() - } - } - } - } - return size -} - -// ExportChanges produces an Archive from the provided changes, relative to dir. -func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io.ReadCloser, error) { - reader, writer := io.Pipe() - go func() { - ta := newTarAppender(idMap, writer, nil) - - sort.Sort(changesByPath(changes)) - - // In general we log errors here but ignore them because - // during e.g. a diff operation the container can continue - // mutating the filesystem and we can see transient errors - // from this - for _, change := range changes { - if change.Kind == ChangeDelete { - whiteOutDir := filepath.Dir(change.Path) - whiteOutBase := filepath.Base(change.Path) - whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) - timestamp := time.Now() - hdr := &tar.Header{ - Name: strings.TrimPrefix(filepath.ToSlash(whiteOut), "/"), - Size: 0, - ModTime: timestamp, - AccessTime: timestamp, - ChangeTime: timestamp, - } - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err) - } - } else { - srcPath := filepath.Join(dir, change.Path) - archivePath := strings.TrimPrefix(filepath.ToSlash(change.Path), "/") - if err := ta.addTarFile(srcPath, archivePath); err != nil { - log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", srcPath, err) - } - } - } - - // Make sure to check the error on Close. - if err := ta.TarWriter.Close(); err != nil { - log.G(context.TODO()).Debugf("Can't close layer: %s", err) - } - if err := writer.Close(); err != nil { - log.G(context.TODO()).Debugf("failed close Changes writer: %s", err) - } - }() - return reader, nil -} diff --git a/vendor/github.com/moby/go-archive/changes_linux.go b/vendor/github.com/moby/go-archive/changes_linux.go deleted file mode 100644 index 39ec7475f4ea..000000000000 --- a/vendor/github.com/moby/go-archive/changes_linux.go +++ /dev/null @@ -1,274 +0,0 @@ -package archive - -import ( - "fmt" - "os" - "path/filepath" - "sort" - "strings" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -// walker is used to implement collectFileInfoForChanges on linux. Where this -// method in general returns the entire contents of two directory trees, we -// optimize some FS calls out on linux. In particular, we take advantage of the -// fact that getdents(2) returns the inode of each file in the directory being -// walked, which, when walking two trees in parallel to generate a list of -// changes, can be used to prune subtrees without ever having to lstat(2) them -// directly. Eliminating stat calls in this way can save up to seconds on large -// images. -type walker struct { - dir1 string - dir2 string - root1 *FileInfo - root2 *FileInfo -} - -// collectFileInfoForChanges returns a complete representation of the trees -// rooted at dir1 and dir2, with one important exception: any subtree or -// leaf where the inode and device numbers are an exact match between dir1 -// and dir2 will be pruned from the results. This method is *only* to be used -// to generating a list of changes between the two directories, as it does not -// reflect the full contents. -func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) { - w := &walker{ - dir1: dir1, - dir2: dir2, - root1: newRootFileInfo(), - root2: newRootFileInfo(), - } - - i1, err := os.Lstat(w.dir1) - if err != nil { - return nil, nil, err - } - i2, err := os.Lstat(w.dir2) - if err != nil { - return nil, nil, err - } - - if err := w.walk("/", i1, i2); err != nil { - return nil, nil, err - } - - return w.root1, w.root2, nil -} - -// Given a FileInfo, its path info, and a reference to the root of the tree -// being constructed, register this file with the tree. -func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error { - if fi == nil { - return nil - } - parent := root.LookUp(filepath.Dir(path)) - if parent == nil { - return fmt.Errorf("walkchunk: Unexpectedly no parent for %s", path) - } - info := &FileInfo{ - name: filepath.Base(path), - children: make(map[string]*FileInfo), - parent: parent, - } - cpath := filepath.Join(dir, path) - info.stat = fi - info.capability, _ = lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access - parent.children[info.name] = info - return nil -} - -// Walk a subtree rooted at the same path in both trees being iterated. For -// example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d -func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) { - // Register these nodes with the return trees, unless we're still at the - // (already-created) roots: - if path != "/" { - if err := walkchunk(path, i1, w.dir1, w.root1); err != nil { - return err - } - if err := walkchunk(path, i2, w.dir2, w.root2); err != nil { - return err - } - } - - is1Dir := i1 != nil && i1.IsDir() - is2Dir := i2 != nil && i2.IsDir() - - sameDevice := false - if i1 != nil && i2 != nil { - si1 := i1.Sys().(*syscall.Stat_t) - si2 := i2.Sys().(*syscall.Stat_t) - if si1.Dev == si2.Dev { - sameDevice = true - } - } - - // If these files are both non-existent, or leaves (non-dirs), we are done. - if !is1Dir && !is2Dir { - return nil - } - - // Fetch the names of all the files contained in both directories being walked: - var names1, names2 []nameIno - if is1Dir { - names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access - if err != nil { - return err - } - } - if is2Dir { - names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access - if err != nil { - return err - } - } - - // We have lists of the files contained in both parallel directories, sorted - // in the same order. Walk them in parallel, generating a unique merged list - // of all items present in either or both directories. - var names []string - ix1 := 0 - ix2 := 0 - - for ix1 < len(names1) && ix2 < len(names2) { - ni1 := names1[ix1] - ni2 := names2[ix2] - - switch strings.Compare(ni1.name, ni2.name) { - case -1: // ni1 < ni2 -- advance ni1 - // we will not encounter ni1 in names2 - names = append(names, ni1.name) - ix1++ - case 0: // ni1 == ni2 - if ni1.ino != ni2.ino || !sameDevice { - names = append(names, ni1.name) - } - ix1++ - ix2++ - case 1: // ni1 > ni2 -- advance ni2 - // we will not encounter ni2 in names1 - names = append(names, ni2.name) - ix2++ - } - } - for ix1 < len(names1) { - names = append(names, names1[ix1].name) - ix1++ - } - for ix2 < len(names2) { - names = append(names, names2[ix2].name) - ix2++ - } - - // For each of the names present in either or both of the directories being - // iterated, stat the name under each root, and recurse the pair of them: - for _, name := range names { - fname := filepath.Join(path, name) - var cInfo1, cInfo2 os.FileInfo - if is1Dir { - cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access - if err != nil && !os.IsNotExist(err) { - return err - } - } - if is2Dir { - cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access - if err != nil && !os.IsNotExist(err) { - return err - } - } - if err = w.walk(fname, cInfo1, cInfo2); err != nil { - return err - } - } - return nil -} - -// {name,inode} pairs used to support the early-pruning logic of the walker type -type nameIno struct { - name string - ino uint64 -} - -type nameInoSlice []nameIno - -func (s nameInoSlice) Len() int { return len(s) } -func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name } - -// readdirnames is a hacked-apart version of the Go stdlib code, exposing inode -// numbers further up the stack when reading directory contents. Unlike -// os.Readdirnames, which returns a list of filenames, this function returns a -// list of {filename,inode} pairs. -func readdirnames(dirname string) (names []nameIno, err error) { - var ( - size = 100 - buf = make([]byte, 4096) - nbuf int - bufp int - nb int - ) - - f, err := os.Open(dirname) - if err != nil { - return nil, err - } - defer f.Close() - - names = make([]nameIno, 0, size) // Empty with room to grow. - for { - // Refill the buffer if necessary - if bufp >= nbuf { - bufp = 0 - nbuf, err = unix.ReadDirent(int(f.Fd()), buf) // getdents on linux - if nbuf < 0 { - nbuf = 0 - } - if err != nil { - return nil, os.NewSyscallError("readdirent", err) - } - if nbuf <= 0 { - break // EOF - } - } - - // Drain the buffer - nb, names = parseDirent(buf[bufp:nbuf], names) - bufp += nb - } - - sl := nameInoSlice(names) - sort.Sort(sl) - return sl, nil -} - -// parseDirent is a minor modification of unix.ParseDirent (linux version) -// which returns {name,inode} pairs instead of just names. -func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) { - origlen := len(buf) - for len(buf) > 0 { - dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" - buf = buf[dirent.Reclen:] - if dirent.Ino == 0 { // File absent in directory. - continue - } - b := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" - name := string(b[0:clen(b[:])]) - if name == "." || name == ".." { // Useless names - continue - } - names = append(names, nameIno{name, dirent.Ino}) - } - return origlen - len(buf), names -} - -func clen(n []byte) int { - for i := range n { - if n[i] == 0 { - return i - } - } - return len(n) -} diff --git a/vendor/github.com/moby/go-archive/changes_other.go b/vendor/github.com/moby/go-archive/changes_other.go deleted file mode 100644 index 7eb195d500ec..000000000000 --- a/vendor/github.com/moby/go-archive/changes_other.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build !linux - -package archive - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" -) - -func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, error) { - var ( - oldRoot, newRoot *FileInfo - err1, err2 error - errs = make(chan error, 2) - ) - go func() { - oldRoot, err1 = collectFileInfo(oldDir) - errs <- err1 - }() - go func() { - newRoot, err2 = collectFileInfo(newDir) - errs <- err2 - }() - - // block until both routines have returned - for range 2 { - if err := <-errs; err != nil { - return nil, nil, err - } - } - - return oldRoot, newRoot, nil -} - -func collectFileInfo(sourceDir string) (*FileInfo, error) { - root := newRootFileInfo() - - err := filepath.WalkDir(sourceDir, func(path string, _ os.DirEntry, err error) error { - if err != nil { - return err - } - - // Rebase path - relPath, err := filepath.Rel(sourceDir, path) - if err != nil { - return err - } - - // As this runs on the daemon side, file paths are OS specific. - relPath = filepath.Join(string(os.PathSeparator), relPath) - - // See https://github.com/golang/go/issues/9168 - bug in filepath.Join. - // Temporary workaround. If the returned path starts with two backslashes, - // trim it down to a single backslash. Only relevant on Windows. - if runtime.GOOS == "windows" { - if strings.HasPrefix(relPath, `\\`) { - relPath = relPath[1:] - } - } - - if relPath == string(os.PathSeparator) { - return nil - } - - parent := root.LookUp(filepath.Dir(relPath)) - if parent == nil { - return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) - } - - s, err := os.Lstat(path) - if err != nil { - return err - } - - info := &FileInfo{ - name: filepath.Base(relPath), - children: make(map[string]*FileInfo), - parent: parent, - stat: s, - } - - info.capability, _ = lgetxattr(path, "security.capability") - - parent.children[info.name] = info - - return nil - }) - if err != nil { - return nil, err - } - return root, nil -} diff --git a/vendor/github.com/moby/go-archive/changes_unix.go b/vendor/github.com/moby/go-archive/changes_unix.go deleted file mode 100644 index 4dd98bd2935f..000000000000 --- a/vendor/github.com/moby/go-archive/changes_unix.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !windows - -package archive - -import ( - "io/fs" - "os" - "syscall" -) - -func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { - oldSys := oldStat.Sys().(*syscall.Stat_t) - newSys := newStat.Sys().(*syscall.Stat_t) - // Don't look at size for dirs, its not a good measure of change - if oldStat.Mode() != newStat.Mode() || - oldSys.Uid != newSys.Uid || - oldSys.Gid != newSys.Gid || - oldSys.Rdev != newSys.Rdev || - // Don't look at size or modification time for dirs, its not a good - // measure of change. See https://github.com/moby/moby/issues/9874 - // for a description of the issue with modification time, and - // https://github.com/moby/moby/pull/11422 for the change. - // (Note that in the Windows implementation of this function, - // modification time IS taken as a change). See - // https://github.com/moby/moby/pull/37982 for more information. - (!oldStat.Mode().IsDir() && - (!sameFsTime(oldStat.ModTime(), newStat.ModTime()) || (oldStat.Size() != newStat.Size()))) { - return true - } - return false -} - -func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode().IsDir() -} - -func getIno(fi os.FileInfo) uint64 { - return fi.Sys().(*syscall.Stat_t).Ino -} - -func hasHardlinks(fi os.FileInfo) bool { - return fi.Sys().(*syscall.Stat_t).Nlink > 1 -} diff --git a/vendor/github.com/moby/go-archive/changes_windows.go b/vendor/github.com/moby/go-archive/changes_windows.go deleted file mode 100644 index c89605c78fed..000000000000 --- a/vendor/github.com/moby/go-archive/changes_windows.go +++ /dev/null @@ -1,33 +0,0 @@ -package archive - -import ( - "io/fs" - "os" -) - -func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { - // Note there is slight difference between the Linux and Windows - // implementations here. Due to https://github.com/moby/moby/issues/9874, - // and the fix at https://github.com/moby/moby/pull/11422, Linux does not - // consider a change to the directory time as a change. Windows on NTFS - // does. See https://github.com/moby/moby/pull/37982 for more information. - - if !sameFsTime(oldStat.ModTime(), newStat.ModTime()) || - oldStat.Mode() != newStat.Mode() || - oldStat.Size() != newStat.Size() && !oldStat.Mode().IsDir() { - return true - } - return false -} - -func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode().IsDir() -} - -func getIno(fi os.FileInfo) (inode uint64) { - return -} - -func hasHardlinks(fi os.FileInfo) bool { - return false -} diff --git a/vendor/github.com/moby/go-archive/compression/compression.go b/vendor/github.com/moby/go-archive/compression/compression.go index 824267924d8c..e298cefb3249 100644 --- a/vendor/github.com/moby/go-archive/compression/compression.go +++ b/vendor/github.com/moby/go-archive/compression/compression.go @@ -66,7 +66,7 @@ type nopWriteCloser struct { func (nopWriteCloser) Close() error { return nil } var bufioReader32KPool = &sync.Pool{ - New: func() any { return bufio.NewReaderSize(nil, 32*1024) }, + New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, } type bufferedReader struct { @@ -217,7 +217,7 @@ func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { log.G(ctx).Debugf("Using %s to decompress", unpigzPath) - return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) // #nosec G204 -- Subprocess launched with variable + return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) } // cmdStream executes a command, and returns its stdout as a stream. diff --git a/vendor/github.com/moby/go-archive/copy.go b/vendor/github.com/moby/go-archive/copy.go deleted file mode 100644 index b4ee74ef47c9..000000000000 --- a/vendor/github.com/moby/go-archive/copy.go +++ /dev/null @@ -1,497 +0,0 @@ -package archive - -import ( - "archive/tar" - "context" - "errors" - "io" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/containerd/log" -) - -// Errors used or returned by this file. -var ( - ErrNotDirectory = errors.New("not a directory") - ErrDirNotExists = errors.New("no such directory") - ErrCannotCopyDir = errors.New("cannot copy directory") - ErrInvalidCopySource = errors.New("invalid copy source content") -) - -var copyPool = sync.Pool{ - New: func() any { s := make([]byte, 32*1024); return &s }, -} - -func copyWithBuffer(dst io.Writer, src io.Reader) error { - buf := copyPool.Get().(*[]byte) - _, err := io.CopyBuffer(dst, src, *buf) - copyPool.Put(buf) - return err -} - -// PreserveTrailingDotOrSeparator returns the given cleaned path (after -// processing using any utility functions from the path or filepath stdlib -// packages) and appends a trailing `/.` or `/` if its corresponding original -// path (from before being processed by utility functions from the path or -// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned -// path already ends in a `.` path segment, then another is not added. If the -// clean path already ends in a path separator, then another is not added. -func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string) string { - // Ensure paths are in platform semantics - cleanedPath = normalizePath(cleanedPath) - originalPath = normalizePath(originalPath) - - if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) { - if !hasTrailingPathSeparator(cleanedPath) { - // Add a separator if it doesn't already end with one (a cleaned - // path would only end in a separator if it is the root). - cleanedPath += string(filepath.Separator) - } - cleanedPath += "." - } - - if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) { - cleanedPath += string(filepath.Separator) - } - - return cleanedPath -} - -// assertsDirectory returns whether the given path is -// asserted to be a directory, i.e., the path ends with -// a trailing '/' or `/.`, assuming a path separator of `/`. -func assertsDirectory(path string) bool { - return hasTrailingPathSeparator(path) || specifiesCurrentDir(path) -} - -// hasTrailingPathSeparator returns whether the given -// path ends with the system's path separator character. -func hasTrailingPathSeparator(path string) bool { - return len(path) > 0 && path[len(path)-1] == filepath.Separator -} - -// specifiesCurrentDir returns whether the given path specifies -// a "current directory", i.e., the last path segment is `.`. -func specifiesCurrentDir(path string) bool { - return filepath.Base(path) == "." -} - -// SplitPathDirEntry splits the given path between its directory name and its -// basename by first cleaning the path but preserves a trailing "." if the -// original path specified the current directory. -func SplitPathDirEntry(path string) (dir, base string) { - cleanedPath := filepath.Clean(filepath.FromSlash(path)) - - if specifiesCurrentDir(path) { - cleanedPath += string(os.PathSeparator) + "." - } - - return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) -} - -// TarResource archives the resource described by the given CopyInfo to a Tar -// archive. A non-nil error is returned if sourcePath does not exist or is -// asserted to be a directory but exists as another type of file. -// -// This function acts as a convenient wrapper around TarWithOptions, which -// requires a directory as the source path. TarResource accepts either a -// directory or a file path and correctly sets the Tar options. -func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) { - return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName) -} - -// TarResourceRebase is like TarResource but renames the first path element of -// items in the resulting tar archive to match the given rebaseName if not "". -func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, _ error) { - sourcePath = normalizePath(sourcePath) - if _, err := os.Lstat(sourcePath); err != nil { - // Catches the case where the source does not exist or is not a - // directory if asserted to be a directory, as this also causes an - // error. - return nil, err - } - - // Separate the source path between its directory and - // the entry in that directory which we are archiving. - sourceDir, sourceBase := SplitPathDirEntry(sourcePath) - opts := TarResourceRebaseOpts(sourceBase, rebaseName) - - log.G(context.TODO()).Debugf("copying %q from %q", sourceBase, sourceDir) - return TarWithOptions(sourceDir, opts) -} - -// TarResourceRebaseOpts does not preform the Tar, but instead just creates the rebase -// parameters to be sent to TarWithOptions (the TarOptions struct) -func TarResourceRebaseOpts(sourceBase string, rebaseName string) *TarOptions { - filter := []string{sourceBase} - return &TarOptions{ - IncludeFiles: filter, - IncludeSourceDir: true, - RebaseNames: map[string]string{ - sourceBase: rebaseName, - }, - } -} - -// CopyInfo holds basic info about the source -// or destination path of a copy operation. -type CopyInfo struct { - Path string - Exists bool - IsDir bool - RebaseName string -} - -// CopyInfoSourcePath stats the given path to create a CopyInfo -// struct representing that resource for the source of an archive copy -// operation. The given path should be an absolute local path. A source path -// has all symlinks evaluated that appear before the last path separator ("/" -// on Unix). As it is to be a copy source, the path must exist. -func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) { - // normalize the file path and then evaluate the symbol link - // we will use the target file instead of the symbol link if - // followLink is set - path = normalizePath(path) - - resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink) - if err != nil { - return CopyInfo{}, err - } - - stat, err := os.Lstat(resolvedPath) - if err != nil { - return CopyInfo{}, err - } - - return CopyInfo{ - Path: resolvedPath, - Exists: true, - IsDir: stat.IsDir(), - RebaseName: rebaseName, - }, nil -} - -// CopyInfoDestinationPath stats the given path to create a CopyInfo -// struct representing that resource for the destination of an archive copy -// operation. The given path should be an absolute local path. -func CopyInfoDestinationPath(path string) (info CopyInfo, err error) { - maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot. - path = normalizePath(path) - originalPath := path - - stat, err := os.Lstat(path) - - if err == nil && stat.Mode()&os.ModeSymlink == 0 { - // The path exists and is not a symlink. - return CopyInfo{ - Path: path, - Exists: true, - IsDir: stat.IsDir(), - }, nil - } - - // While the path is a symlink. - for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ { - if n > maxSymlinkIter { - // Don't follow symlinks more than this arbitrary number of times. - return CopyInfo{}, errors.New("too many symlinks in " + originalPath) - } - - // The path is a symbolic link. We need to evaluate it so that the - // destination of the copy operation is the link target and not the - // link itself. This is notably different than CopyInfoSourcePath which - // only evaluates symlinks before the last appearing path separator. - // Also note that it is okay if the last path element is a broken - // symlink as the copy operation should create the target. - var linkTarget string - - linkTarget, err = os.Readlink(path) - if err != nil { - return CopyInfo{}, err - } - - if !filepath.IsAbs(linkTarget) { - // Join with the parent directory. - dstParent, _ := SplitPathDirEntry(path) - linkTarget = filepath.Join(dstParent, linkTarget) - } - - path = linkTarget - stat, err = os.Lstat(path) - } - - if err != nil { - // It's okay if the destination path doesn't exist. We can still - // continue the copy operation if the parent directory exists. - if !os.IsNotExist(err) { - return CopyInfo{}, err - } - - // Ensure destination parent dir exists. - dstParent, _ := SplitPathDirEntry(path) - - parentDirStat, err := os.Stat(dstParent) - if err != nil { - return CopyInfo{}, err - } - if !parentDirStat.IsDir() { - return CopyInfo{}, ErrNotDirectory - } - - return CopyInfo{Path: path}, nil - } - - // The path exists after resolving symlinks. - return CopyInfo{ - Path: path, - Exists: true, - IsDir: stat.IsDir(), - }, nil -} - -// PrepareArchiveCopy prepares the given srcContent archive, which should -// contain the archived resource described by srcInfo, to the destination -// described by dstInfo. Returns the possibly modified content archive along -// with the path to the destination directory which it should be extracted to. -func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content io.ReadCloser, err error) { - // Ensure in platform semantics - srcInfo.Path = normalizePath(srcInfo.Path) - dstInfo.Path = normalizePath(dstInfo.Path) - - // Separate the destination path between its directory and base - // components in case the source archive contents need to be rebased. - dstDir, dstBase := SplitPathDirEntry(dstInfo.Path) - _, srcBase := SplitPathDirEntry(srcInfo.Path) - - switch { - case dstInfo.Exists && dstInfo.IsDir: - // The destination exists as a directory. No alteration - // to srcContent is needed as its contents can be - // simply extracted to the destination directory. - return dstInfo.Path, io.NopCloser(srcContent), nil - case dstInfo.Exists && srcInfo.IsDir: - // The destination exists as some type of file and the source - // content is a directory. This is an error condition since - // you cannot copy a directory to an existing file location. - return "", nil, ErrCannotCopyDir - case dstInfo.Exists: - // The destination exists as some type of file and the source content - // is also a file. The source content entry will have to be renamed to - // have a basename which matches the destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case srcInfo.IsDir: - // The destination does not exist and the source content is an archive - // of a directory. The archive should be extracted to the parent of - // the destination path instead, and when it is, the directory that is - // created as a result should take the name of the destination path. - // The source content entries will have to be renamed to have a - // basename which matches the destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case assertsDirectory(dstInfo.Path): - // The destination does not exist and is asserted to be created as a - // directory, but the source content is not a directory. This is an - // error condition since you cannot create a directory from a file - // source. - return "", nil, ErrDirNotExists - default: - // The last remaining case is when the destination does not exist, is - // not asserted to be a directory, and the source content is not an - // archive of a directory. It this case, the destination file will need - // to be created when the archive is extracted and the source content - // entry will have to be renamed to have a basename which matches the - // destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - } -} - -// RebaseArchiveEntries rewrites the given srcContent archive replacing -// an occurrence of oldBase with newBase at the beginning of entry names. -func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser { - oldBase = filepath.ToSlash(oldBase) - newBase = filepath.ToSlash(newBase) - - if oldBase == "/" { - // If oldBase specifies the root directory, use an empty string as - // oldBase instead so that newBase doesn't replace the path separator - // that all paths will start with. - oldBase = "" - } - - rebased, w := io.Pipe() - - go func() { - srcTar := tar.NewReader(srcContent) - rebasedTar := tar.NewWriter(w) - - for { - hdr, err := srcTar.Next() - if errors.Is(err, io.EOF) { - // Signals end of archive. - _ = rebasedTar.Close() - _ = w.Close() - return - } - if err != nil { - _ = w.CloseWithError(err) - return - } - - // srcContent tar stream, as served by TarWithOptions(), is - // definitely in PAX format, but tar.Next() mistakenly guesses it - // as USTAR, which creates a problem: if the newBase is >100 - // characters long, WriteHeader() returns an error like - // "archive/tar: cannot encode header: Format specifies USTAR; and USTAR cannot encode Name=...". - // - // To fix, set the format to PAX here. See docker/for-linux issue #484. - hdr.Format = tar.FormatPAX - hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1) - if hdr.Typeflag == tar.TypeLink { - hdr.Linkname = strings.Replace(hdr.Linkname, oldBase, newBase, 1) - } - - if err = rebasedTar.WriteHeader(hdr); err != nil { - _ = w.CloseWithError(err) - return - } - - // Ignoring GoSec G110. See https://github.com/securego/gosec/pull/433 - // and https://cure53.de/pentest-report_opa.pdf, which recommends to - // replace io.Copy with io.CopyN7. The latter allows to specify the - // maximum number of bytes that should be read. By properly defining - // the limit, it can be assured that a GZip compression bomb cannot - // easily cause a Denial-of-Service. - // After reviewing with @tonistiigi and @cpuguy83, this should not - // affect us, because here we do not read into memory, hence should - // not be vulnerable to this code consuming memory. - //nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec) - if _, err = io.Copy(rebasedTar, srcTar); err != nil { - _ = w.CloseWithError(err) - return - } - } - }() - - return rebased -} - -// CopyResource performs an archive copy from the given source path to the -// given destination path. The source path MUST exist and the destination -// path's parent directory must exist. -func CopyResource(srcPath, dstPath string, followLink bool) error { - var ( - srcInfo CopyInfo - err error - ) - - // Ensure in platform semantics - srcPath = normalizePath(srcPath) - dstPath = normalizePath(dstPath) - - // Clean the source and destination paths. - srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) - dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) - - if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil { - return err - } - - content, err := TarResource(srcInfo) - if err != nil { - return err - } - defer func() { _ = content.Close() }() - - return CopyTo(content, srcInfo, dstPath) -} - -// CopyTo handles extracting the given content whose -// entries should be sourced from srcInfo to dstPath. -func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { - // The destination path need not exist, but CopyInfoDestinationPath will - // ensure that at least the parent directory exists. - dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath)) - if err != nil { - return err - } - - dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) - if err != nil { - return err - } - defer func() { _ = copyArchive.Close() }() - - return Untar(copyArchive, dstDir, &TarOptions{ - NoLchown: true, - NoOverwriteDirNonDir: true, - }) -} - -// ResolveHostSourcePath decides real path need to be copied with parameters such as -// whether to follow symbol link or not, if followLink is true, resolvedPath will return -// link target of any symbol link file, else it will only resolve symlink of directory -// but return symbol link file itself without resolving. -func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, _ error) { - if followLink { - var err error - resolvedPath, err = filepath.EvalSymlinks(path) - if err != nil { - return "", "", err - } - - resolvedPath, rebaseName = GetRebaseName(path, resolvedPath) - } else { - dirPath, basePath := filepath.Split(path) - - // if not follow symbol link, then resolve symbol link of parent dir - resolvedDirPath, err := filepath.EvalSymlinks(dirPath) - if err != nil { - return "", "", err - } - // resolvedDirPath will have been cleaned (no trailing path separators) so - // we can manually join it with the base path element. - resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath - if hasTrailingPathSeparator(path) && - filepath.Base(path) != filepath.Base(resolvedPath) { - rebaseName = filepath.Base(path) - } - } - return resolvedPath, rebaseName, nil -} - -// GetRebaseName normalizes and compares path and resolvedPath, -// return completed resolved path and rebased file name -func GetRebaseName(path, resolvedPath string) (string, string) { - // linkTarget will have been cleaned (no trailing path separators and dot) so - // we can manually join it with them - var rebaseName string - if specifiesCurrentDir(path) && - !specifiesCurrentDir(resolvedPath) { - resolvedPath += string(filepath.Separator) + "." - } - - if hasTrailingPathSeparator(path) && - !hasTrailingPathSeparator(resolvedPath) { - resolvedPath += string(filepath.Separator) - } - - if filepath.Base(path) != filepath.Base(resolvedPath) { - // In the case where the path had a trailing separator and a symlink - // evaluation has changed the last path component, we will need to - // rebase the name in the archive that is being copied to match the - // originally requested name. - rebaseName = filepath.Base(path) - } - return resolvedPath, rebaseName -} diff --git a/vendor/github.com/moby/go-archive/copy_unix.go b/vendor/github.com/moby/go-archive/copy_unix.go deleted file mode 100644 index f579282449af..000000000000 --- a/vendor/github.com/moby/go-archive/copy_unix.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !windows - -package archive - -import ( - "path/filepath" -) - -func normalizePath(path string) string { - return filepath.ToSlash(path) -} diff --git a/vendor/github.com/moby/go-archive/copy_windows.go b/vendor/github.com/moby/go-archive/copy_windows.go deleted file mode 100644 index 2b775b45c4f1..000000000000 --- a/vendor/github.com/moby/go-archive/copy_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -package archive - -import ( - "path/filepath" -) - -func normalizePath(path string) string { - return filepath.FromSlash(path) -} diff --git a/vendor/github.com/moby/go-archive/dev_freebsd.go b/vendor/github.com/moby/go-archive/dev_freebsd.go deleted file mode 100644 index b3068fce9110..000000000000 --- a/vendor/github.com/moby/go-archive/dev_freebsd.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build freebsd - -package archive - -import "golang.org/x/sys/unix" - -func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, dev) -} diff --git a/vendor/github.com/moby/go-archive/dev_unix.go b/vendor/github.com/moby/go-archive/dev_unix.go deleted file mode 100644 index 2f9833b85b2c..000000000000 --- a/vendor/github.com/moby/go-archive/dev_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows && !freebsd - -package archive - -import "golang.org/x/sys/unix" - -func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API. -} diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go deleted file mode 100644 index b2fdf770a651..000000000000 --- a/vendor/github.com/moby/go-archive/diff.go +++ /dev/null @@ -1,258 +0,0 @@ -package archive - -import ( - "archive/tar" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/containerd/log" - - "github.com/moby/go-archive/compression" -) - -// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be -// compressed or uncompressed. -// Returns the size in bytes of the contents of the layer. -func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) { - tr := tar.NewReader(layer) - - var dirs []*tar.Header - unpackedPaths := make(map[string]struct{}) - - if options == nil { - options = &TarOptions{} - } - - aufsTempdir := "" - aufsHardlinks := make(map[string]*tar.Header) - - // Iterate through the files in the archive. - for { - hdr, err := tr.Next() - if errors.Is(err, io.EOF) { - // end of tar archive - break - } - if err != nil { - return 0, err - } - - size += hdr.Size - - // Normalize name, for safety and for a simple is-root check - hdr.Name = filepath.Clean(hdr.Name) - - // Windows does not support filenames with colons in them. Ignore - // these files. This is not a problem though (although it might - // appear that it is). Let's suppose a client is running docker pull. - // The daemon it points to is Windows. Would it make sense for the - // client to be doing a docker pull Ubuntu for example (which has files - // with colons in the name under /usr/share/man/man3)? No, absolutely - // not as it would really only make sense that they were pulling a - // Windows image. However, for development, it is necessary to be able - // to pull Linux images which are in the repository. - // - // TODO Windows. Once the registry is aware of what images are Windows- - // specific or Linux-specific, this warning should be changed to an error - // to cater for the situation where someone does manage to upload a Linux - // image but have it tagged as Windows inadvertently. - if runtime.GOOS == "windows" { - if strings.Contains(hdr.Name, ":") { - log.G(context.TODO()).Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) - continue - } - } - - // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return 0, err - } - - // Skip AUFS metadata dirs - if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) { - // Regular files inside /.wh..wh.plnk can be used as hardlink targets - // We don't want this directory, but we need the files in them so that - // such hardlinks can be resolved. - if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg { - basename := filepath.Base(hdr.Name) - aufsHardlinks[basename] = hdr - if aufsTempdir == "" { - if aufsTempdir, err = os.MkdirTemp(dest, "dockerplnk"); err != nil { - return 0, err - } - defer os.RemoveAll(aufsTempdir) - } - if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, options); err != nil { - return 0, err - } - } - - if hdr.Name != WhiteoutOpaqueDir { - continue - } - } - // #nosec G305 -- The joined path is guarded against path traversal. - dstPath := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, dstPath) - if err != nil { - return 0, err - } - - // Note as these operations are platform specific, so must the slash be. - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - base := filepath.Base(dstPath) - - if strings.HasPrefix(base, WhiteoutPrefix) { - dir := filepath.Dir(dstPath) - if base == WhiteoutOpaqueDir { - _, err := os.Lstat(dir) - if err != nil { - return 0, err - } - err = filepath.WalkDir(dir, func(path string, info os.DirEntry, err error) error { - if err != nil { - if os.IsNotExist(err) { - err = nil // parent was deleted - } - return err - } - if path == dir { - return nil - } - if _, exists := unpackedPaths[path]; !exists { - return os.RemoveAll(path) // #nosec G122 -- FIXME: consider root-scoped APIs (e.g. os.Root) to prevent symlink TOCTOU traversal - } - return nil - }) - if err != nil { - return 0, err - } - } else { - originalBase := base[len(WhiteoutPrefix):] - originalPath := filepath.Join(dir, originalBase) - if err := os.RemoveAll(originalPath); err != nil { - return 0, err - } - } - } else { - // If dstPath exists we almost always just want to remove and replace it. - // The only exception is when it is a directory *and* the file from - // the layer is also a directory. Then we want to merge them (i.e. - // just apply the metadata from the layer). - if fi, err := os.Lstat(dstPath); err == nil { - if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(dstPath); err != nil { - return 0, err - } - } - } - - srcData := io.Reader(tr) - srcHdr := hdr - - // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so - // we manually retarget these into the temporary files we extracted them into - if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) { - linkBasename := filepath.Base(hdr.Linkname) - srcHdr = aufsHardlinks[linkBasename] - if srcHdr == nil { - return 0, errors.New("invalid aufs hardlink") - } - tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) - if err != nil { - return 0, err - } - defer tmpFile.Close() - srcData = tmpFile - } - - if err := remapIDs(options.IDMap, srcHdr); err != nil { - return 0, err - } - - if err := createTarFile(dstPath, dest, srcHdr, srcData, options); err != nil { - return 0, err - } - - // Directory mtimes must be handled at the end to avoid further - // file creation in them to modify the directory mtime - if hdr.Typeflag == tar.TypeDir { - dirs = append(dirs, hdr) - } - unpackedPaths[dstPath] = struct{}{} - } - } - - for _, hdr := range dirs { - // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. - dstPath := filepath.Join(dest, hdr.Name) - if err := chtimes(dstPath, hdr.AccessTime, hdr.ModTime); err != nil { - return 0, err - } - } - - return size, nil -} - -// ApplyLayer parses a diff in the standard layer format from `layer`, -// and applies it to the directory `dest`. The stream `layer` can be -// compressed or uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyLayer(dest string, layer io.Reader) (int64, error) { - return applyLayerHandler(dest, layer, &TarOptions{}, true) -} - -// ApplyUncompressedLayer parses a diff in the standard layer format from -// `layer`, and applies it to the directory `dest`. The stream `layer` -// can only be uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) { - return applyLayerHandler(dest, layer, options, false) -} - -// IsEmpty checks if the tar archive is empty (doesn't contain any entries). -func IsEmpty(rd io.Reader) (bool, error) { - decompRd, err := compression.DecompressStream(rd) - if err != nil { - return true, fmt.Errorf("failed to decompress archive: %w", err) - } - defer decompRd.Close() - - tarReader := tar.NewReader(decompRd) - if _, err := tarReader.Next(); err != nil { - if errors.Is(err, io.EOF) { - return true, nil - } - return false, fmt.Errorf("failed to read next archive header: %w", err) - } - - return false, nil -} - -// do the bulk load of ApplyLayer, but allow for not calling DecompressStream -func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) { - dest = filepath.Clean(dest) - - // We need to be able to set any perms - restore := overrideUmask(0) - defer restore() - - if decompress { - decompLayer, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompLayer.Close() - layer = decompLayer - } - return UnpackLayer(dest, layer, options) -} diff --git a/vendor/github.com/moby/go-archive/diff_unix.go b/vendor/github.com/moby/go-archive/diff_unix.go deleted file mode 100644 index 7216f2f4f97c..000000000000 --- a/vendor/github.com/moby/go-archive/diff_unix.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !windows - -package archive - -import "golang.org/x/sys/unix" - -// overrideUmask sets current process's file mode creation mask to newmask -// and returns a function to restore it. -// -// WARNING for readers stumbling upon this code. Changing umask in a multi- -// threaded environment isn't safe. Don't use this without understanding the -// risks, and don't export this function for others to use (we shouldn't even -// be using this ourself). -// -// FIXME(thaJeztah): we should get rid of these hacks if possible. -func overrideUmask(newMask int) func() { - oldMask := unix.Umask(newMask) - return func() { - unix.Umask(oldMask) - } -} diff --git a/vendor/github.com/moby/go-archive/diff_windows.go b/vendor/github.com/moby/go-archive/diff_windows.go deleted file mode 100644 index d28f5b2dfdd9..000000000000 --- a/vendor/github.com/moby/go-archive/diff_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -package archive - -// overrideUmask is a no-op on windows. -func overrideUmask(newmask int) func() { - return func() {} -} diff --git a/vendor/github.com/moby/go-archive/path.go b/vendor/github.com/moby/go-archive/path.go deleted file mode 100644 index 888a697581d2..000000000000 --- a/vendor/github.com/moby/go-archive/path.go +++ /dev/null @@ -1,20 +0,0 @@ -package archive - -// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter, -// is the system drive. -// On Linux: this is a no-op. -// On Windows: this does the following> -// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path. -// This is used, for example, when validating a user provided path in docker cp. -// If a drive letter is supplied, it must be the system drive. The drive letter -// is always removed. Also, it translates it to OS semantics (IOW / to \). We -// need the path in this syntax so that it can ultimately be concatenated with -// a Windows long-path which doesn't support drive-letters. Examples: -// C: --> Fail -// C:\ --> \ -// a --> a -// /a --> \a -// d:\ --> Fail -func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) { - return checkSystemDriveAndRemoveDriveLetter(path) -} diff --git a/vendor/github.com/moby/go-archive/path_unix.go b/vendor/github.com/moby/go-archive/path_unix.go deleted file mode 100644 index 390264bf8580..000000000000 --- a/vendor/github.com/moby/go-archive/path_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows - -package archive - -// checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation -// of CheckSystemDriveAndRemoveDriveLetter -func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { - return path, nil -} diff --git a/vendor/github.com/moby/go-archive/path_windows.go b/vendor/github.com/moby/go-archive/path_windows.go deleted file mode 100644 index 7e18c8e4493d..000000000000 --- a/vendor/github.com/moby/go-archive/path_windows.go +++ /dev/null @@ -1,22 +0,0 @@ -package archive - -import ( - "fmt" - "path/filepath" - "strings" -) - -// checkSystemDriveAndRemoveDriveLetter is the Windows implementation -// of CheckSystemDriveAndRemoveDriveLetter -func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { - if len(path) == 2 && string(path[1]) == ":" { - return "", fmt.Errorf("no relative path specified in %q", path) - } - if !filepath.IsAbs(path) || len(path) < 2 { - return filepath.FromSlash(path), nil - } - if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") { - return "", fmt.Errorf("the specified path is not on the system drive (C:)") - } - return filepath.FromSlash(path[2:]), nil -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader.go b/vendor/github.com/moby/go-archive/tarheader/tarheader.go deleted file mode 100644 index 78ab55cd410f..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader.go +++ /dev/null @@ -1,67 +0,0 @@ -package tarheader - -import ( - "archive/tar" - "os" -) - -// assert that we implement [tar.FileInfoNames]. -var _ tar.FileInfoNames = (*nosysFileInfo)(nil) - -// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to -// prevent tar.FileInfoHeader from introspecting it and potentially calling into -// glibc. -// -// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader] -// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102 -type nosysFileInfo struct { - os.FileInfo -} - -// Uname stubs out looking up username. It implements [tar.FileInfoNames] -// to prevent [tar.FileInfoHeader] from loading libraries to perform -// username lookups. -func (fi nosysFileInfo) Uname() (string, error) { - return "", nil -} - -// Gname stubs out looking up group-name. It implements [tar.FileInfoNames] -// to prevent [tar.FileInfoHeader] from loading libraries to perform -// username lookups. -func (fi nosysFileInfo) Gname() (string, error) { - return "", nil -} - -func (fi nosysFileInfo) Sys() any { - // A Sys value of type *tar.Header is safe as it is system-independent. - // The tar.FileInfoHeader function copies the fields into the returned - // header without performing any OS lookups. - if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok { - return sys - } - return nil -} - -// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi. -// -// Compared to the archive/tar.FileInfoHeader function, this function is safe to -// call from a chrooted process as it does not populate fields which would -// require operating system lookups. It behaves identically to -// tar.FileInfoHeader when fi is a FileInfo value returned from -// tar.Header.FileInfo(). -// -// When fi is a FileInfo for a native file, such as returned from os.Stat() and -// os.Lstat(), the returned Header value differs from one returned from -// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not -// set as OS lookups would be required to populate them. The AccessTime and -// ChangeTime fields are not currently set (not yet implemented) although that -// is subject to change. Callers which require the AccessTime or ChangeTime -// fields to be zeroed should explicitly zero them out in the returned Header -// value to avoid any compatibility issues in the future. -func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link) - if err != nil { - return nil, err - } - return hdr, sysStat(fi, hdr) -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go deleted file mode 100644 index bd12d46a8a67..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build !windows - -package tarheader - -import ( - "archive/tar" - "os" - "runtime" - "syscall" - - "golang.org/x/sys/unix" -) - -// sysStat populates hdr from system-dependent fields of fi without performing -// any OS lookups. -func sysStat(fi os.FileInfo, hdr *tar.Header) error { - // Devmajor and Devminor are only needed for special devices. - - // In FreeBSD, RDev for regular files is -1 (unless overridden by FS): - // https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531 - // (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241). - - // ZFS in particular does not override the default: - // https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027 - - // Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1). - // Such large values cannot be encoded in a tar header. - if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar { - return nil - } - s, ok := fi.Sys().(*syscall.Stat_t) - if !ok { - return nil - } - - hdr.Uid = int(s.Uid) - hdr.Gid = int(s.Gid) - - if s.Mode&unix.S_IFBLK != 0 || s.Mode&unix.S_IFCHR != 0 { - // #nosec G115 -- Rdev type varies by platform. - rdev := uint64(s.Rdev) //nolint:unconvert // Rdev type varies by platform. - hdr.Devmajor = int64(unix.Major(rdev)) - hdr.Devminor = int64(unix.Minor(rdev)) - } - - return nil -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go deleted file mode 100644 index 5d4483ce2618..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -package tarheader - -import ( - "archive/tar" - "os" -) - -// sysStat populates hdr from system-dependent fields of fi without performing -// any OS lookups. It is a no-op on Windows. -func sysStat(os.FileInfo, *tar.Header) error { - return nil -} diff --git a/vendor/github.com/moby/go-archive/time.go b/vendor/github.com/moby/go-archive/time.go deleted file mode 100644 index 4e9ae9508432..000000000000 --- a/vendor/github.com/moby/go-archive/time.go +++ /dev/null @@ -1,38 +0,0 @@ -package archive - -import ( - "syscall" - "time" - "unsafe" -) - -var ( - minTime = time.Unix(0, 0) - maxTime time.Time -) - -func init() { - if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 { - // This is a 64 bit timespec - // os.Chtimes limits time to the following - maxTime = time.Unix(0, 1<<63-1) - } else { - // This is a 32 bit timespec - maxTime = time.Unix(1<<31-1, 0) - } -} - -func boundTime(t time.Time) time.Time { - if t.Before(minTime) || t.After(maxTime) { - return minTime - } - - return t -} - -func latestTime(t1, t2 time.Time) time.Time { - if t1.Before(t2) { - return t2 - } - return t1 -} diff --git a/vendor/github.com/moby/go-archive/time_nonwindows.go b/vendor/github.com/moby/go-archive/time_nonwindows.go deleted file mode 100644 index 5bfdfa2f17e8..000000000000 --- a/vendor/github.com/moby/go-archive/time_nonwindows.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build !windows - -package archive - -import ( - "os" - "time" - - "golang.org/x/sys/unix" -) - -// chtimes changes the access time and modified time of a file at the given path. -// If the modified time is prior to the Unix Epoch (unixMinTime), or after the -// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this -// case, Chtimes defaults to Unix Epoch, just in case. -func chtimes(name string, atime time.Time, mtime time.Time) error { - return os.Chtimes(name, atime, mtime) -} - -func timeToTimespec(time time.Time) unix.Timespec { - if time.IsZero() { - // Return UTIME_OMIT special value - return unix.Timespec{ - Sec: 0, - Nsec: (1 << 30) - 2, - } - } - return unix.NsecToTimespec(time.UnixNano()) -} - -func lchtimes(name string, atime time.Time, mtime time.Time) error { - utimes := [2]unix.Timespec{ - timeToTimespec(atime), - timeToTimespec(mtime), - } - err := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW) - if err != nil && err != unix.ENOSYS { - return err - } - return err -} diff --git a/vendor/github.com/moby/go-archive/time_windows.go b/vendor/github.com/moby/go-archive/time_windows.go deleted file mode 100644 index af1f7c8f3a07..000000000000 --- a/vendor/github.com/moby/go-archive/time_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -package archive - -import ( - "os" - "time" - - "golang.org/x/sys/windows" -) - -func chtimes(name string, atime time.Time, mtime time.Time) error { - if err := os.Chtimes(name, atime, mtime); err != nil { - return err - } - - pathp, err := windows.UTF16PtrFromString(name) - if err != nil { - return err - } - h, err := windows.CreateFile(pathp, - windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil, - windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0) - if err != nil { - return err - } - defer windows.Close(h) - c := windows.NsecToFiletime(mtime.UnixNano()) - return windows.SetFileTime(h, &c, nil, nil) -} - -func lchtimes(name string, atime time.Time, mtime time.Time) error { - return nil -} diff --git a/vendor/github.com/moby/go-archive/whiteouts.go b/vendor/github.com/moby/go-archive/whiteouts.go deleted file mode 100644 index d20478a10dc1..000000000000 --- a/vendor/github.com/moby/go-archive/whiteouts.go +++ /dev/null @@ -1,23 +0,0 @@ -package archive - -// Whiteouts are files with a special meaning for the layered filesystem. -// Docker uses AUFS whiteout files inside exported archives. In other -// filesystems these files are generated/handled on tar creation/extraction. - -// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a -// filename this means that file has been removed from the base layer. -const WhiteoutPrefix = ".wh." - -// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not -// for removing an actual file. Normally these files are excluded from exported -// archives. -const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix - -// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other -// layers. Normally these should not go into exported archives and all changed -// hardlinks should be copied to the top layer. -const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk" - -// WhiteoutOpaqueDir file means directory has been made opaque - meaning -// readdir calls to this directory do not follow to lower layers. -const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq" diff --git a/vendor/github.com/moby/go-archive/wrap.go b/vendor/github.com/moby/go-archive/wrap.go deleted file mode 100644 index f8a97254eede..000000000000 --- a/vendor/github.com/moby/go-archive/wrap.go +++ /dev/null @@ -1,59 +0,0 @@ -package archive - -import ( - "archive/tar" - "bytes" - "io" -) - -// Generate generates a new archive from the content provided -// as input. -// -// `files` is a sequence of path/content pairs. A new file is -// added to the archive for each pair. -// If the last pair is incomplete, the file is created with an -// empty content. For example: -// -// Generate("foo.txt", "hello world", "emptyfile") -// -// The above call will return an archive with 2 files: -// - ./foo.txt with content "hello world" -// - ./empty with empty content -// -// FIXME: stream content instead of buffering -// FIXME: specify permissions and other archive metadata -func Generate(input ...string) (io.Reader, error) { - files := parseStringPairs(input...) - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - for _, file := range files { - name, content := file[0], file[1] - hdr := &tar.Header{ - Name: name, - Size: int64(len(content)), - } - if err := tw.WriteHeader(hdr); err != nil { - return nil, err - } - if _, err := tw.Write([]byte(content)); err != nil { - return nil, err - } - } - if err := tw.Close(); err != nil { - return nil, err - } - return buf, nil -} - -func parseStringPairs(input ...string) [][2]string { - output := make([][2]string, 0, len(input)/2+1) - for i := 0; i < len(input); i += 2 { - var pair [2]string - pair[0] = input[i] - if i+1 < len(input) { - pair[1] = input[i+1] - } - output = append(output, pair) - } - return output -} diff --git a/vendor/github.com/moby/go-archive/xattr_supported.go b/vendor/github.com/moby/go-archive/xattr_supported.go deleted file mode 100644 index 8478fe1446da..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported.go +++ /dev/null @@ -1,52 +0,0 @@ -//go:build linux || darwin || freebsd || netbsd - -package archive - -import ( - "errors" - "fmt" - "io/fs" - - "golang.org/x/sys/unix" -) - -// lgetxattr retrieves the value of the extended attribute identified by attr -// and associated with the given path in the file system. -// It returns a nil slice and nil error if the xattr is not set. -func lgetxattr(filePath string, attr string) ([]byte, error) { - // Start with a 128 length byte array - dest := make([]byte, 128) - sz, err := unix.Lgetxattr(filePath, attr, dest) - - for errors.Is(err, unix.ERANGE) { - // Buffer too small, use zero-sized buffer to get the actual size - sz, err = unix.Lgetxattr(filePath, attr, []byte{}) - if err != nil { - return nil, wrapPathError("lgetxattr", filePath, attr, err) - } - dest = make([]byte, sz) - sz, err = unix.Lgetxattr(filePath, attr, dest) - } - - if err != nil { - if errors.Is(err, noattr) { - return nil, nil - } - return nil, wrapPathError("lgetxattr", filePath, attr, err) - } - - return dest[:sz], nil -} - -// lsetxattr sets the value of the extended attribute identified by attr -// and associated with the given path in the file system. -func lsetxattr(filePath string, attr string, data []byte, flags int) error { - return wrapPathError("lsetxattr", filePath, attr, unix.Lsetxattr(filePath, attr, data, flags)) -} - -func wrapPathError(op, filePath, attr string, err error) error { - if err == nil { - return nil - } - return &fs.PathError{Op: op, Path: filePath, Err: fmt.Errorf("xattr %q: %w", attr, err)} -} diff --git a/vendor/github.com/moby/go-archive/xattr_supported_linux.go b/vendor/github.com/moby/go-archive/xattr_supported_linux.go deleted file mode 100644 index f2e76465ae56..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported_linux.go +++ /dev/null @@ -1,5 +0,0 @@ -package archive - -import "golang.org/x/sys/unix" - -var noattr = unix.ENODATA diff --git a/vendor/github.com/moby/go-archive/xattr_supported_unix.go b/vendor/github.com/moby/go-archive/xattr_supported_unix.go deleted file mode 100644 index 58a03e4a6371..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported_unix.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build darwin || freebsd || netbsd - -package archive - -import "golang.org/x/sys/unix" - -var noattr = unix.ENOATTR diff --git a/vendor/github.com/moby/go-archive/xattr_unsupported.go b/vendor/github.com/moby/go-archive/xattr_unsupported.go deleted file mode 100644 index b0d9165cd94f..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_unsupported.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !linux && !darwin && !freebsd && !netbsd - -package archive - -func lgetxattr(path string, attr string) ([]byte, error) { - return nil, nil -} - -func lsetxattr(path string, attr string, data []byte, flags int) error { - return nil -} diff --git a/vendor/github.com/moby/sys/user/LICENSE b/vendor/github.com/moby/sys/user/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/vendor/github.com/moby/sys/user/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - 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/user/idtools.go b/vendor/github.com/moby/sys/user/idtools.go deleted file mode 100644 index 595b7a927263..000000000000 --- a/vendor/github.com/moby/sys/user/idtools.go +++ /dev/null @@ -1,141 +0,0 @@ -package user - -import ( - "fmt" - "os" -) - -// MkdirOpt is a type for options to pass to Mkdir calls -type MkdirOpt func(*mkdirOptions) - -type mkdirOptions struct { - onlyNew bool -} - -// WithOnlyNew is an option for MkdirAllAndChown that will only change ownership and permissions -// on newly created directories. If the directory already exists, it will not be modified -func WithOnlyNew(o *mkdirOptions) { - o.onlyNew = true -} - -// MkdirAllAndChown creates a directory (include any along the path) and then modifies -// ownership to the requested uid/gid. By default, if the directory already exists, this -// function will still change ownership and permissions. If WithOnlyNew is passed as an -// option, then only the newly created directories will have ownership and permissions changed. -func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { - var options mkdirOptions - for _, opt := range opts { - opt(&options) - } - - return mkdirAs(path, mode, uid, gid, true, options.onlyNew) -} - -// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. -// By default, if the directory already exists, this function still changes ownership and permissions. -// If WithOnlyNew is passed as an option, then only the newly created directory will have ownership -// and permissions changed. -// Note that unlike os.Mkdir(), this function does not return IsExist error -// in case path already exists. -func MkdirAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { - var options mkdirOptions - for _, opt := range opts { - opt(&options) - } - return mkdirAs(path, mode, uid, gid, false, options.onlyNew) -} - -// getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. -// If the maps are empty, then the root uid/gid will default to "real" 0/0 -func getRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { - uid, err := toHost(0, uidMap) - if err != nil { - return -1, -1, err - } - gid, err := toHost(0, gidMap) - if err != nil { - return -1, -1, err - } - return uid, gid, nil -} - -// toContainer takes an id mapping, and uses it to translate a -// host ID to the remapped ID. If no map is provided, then the translation -// assumes a 1-to-1 mapping and returns the passed in id -func toContainer(hostID int, idMap []IDMap) (int, error) { - if idMap == nil { - return hostID, nil - } - for _, m := range idMap { - if (int64(hostID) >= m.ParentID) && (int64(hostID) <= (m.ParentID + m.Count - 1)) { - contID := int(m.ID + (int64(hostID) - m.ParentID)) - return contID, nil - } - } - return -1, fmt.Errorf("host ID %d cannot be mapped to a container ID", hostID) -} - -// toHost takes an id mapping and a remapped ID, and translates the -// ID to the mapped host ID. If no map is provided, then the translation -// assumes a 1-to-1 mapping and returns the passed in id # -func toHost(contID int, idMap []IDMap) (int, error) { - if idMap == nil { - return contID, nil - } - for _, m := range idMap { - if (int64(contID) >= m.ID) && (int64(contID) <= (m.ID + m.Count - 1)) { - hostID := int(m.ParentID + (int64(contID) - m.ID)) - return hostID, nil - } - } - return -1, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID) -} - -// IdentityMapping contains a mappings of UIDs and GIDs. -// The zero value represents an empty mapping. -type IdentityMapping struct { - UIDMaps []IDMap `json:"UIDMaps"` - GIDMaps []IDMap `json:"GIDMaps"` -} - -// RootPair returns a uid and gid pair for the root user. The error is ignored -// because a root user always exists, and the defaults are correct when the uid -// and gid maps are empty. -func (i IdentityMapping) RootPair() (int, int) { - uid, gid, _ := getRootUIDGID(i.UIDMaps, i.GIDMaps) - return uid, gid -} - -// ToHost returns the host UID and GID for the container uid, gid. -// Remapping is only performed if the ids aren't already the remapped root ids -func (i IdentityMapping) ToHost(uid, gid int) (int, int, error) { - var err error - ruid, rgid := i.RootPair() - - if uid != ruid { - ruid, err = toHost(uid, i.UIDMaps) - if err != nil { - return ruid, rgid, err - } - } - - if gid != rgid { - rgid, err = toHost(gid, i.GIDMaps) - } - return ruid, rgid, err -} - -// ToContainer returns the container UID and GID for the host uid and gid -func (i IdentityMapping) ToContainer(uid, gid int) (int, int, error) { - ruid, err := toContainer(uid, i.UIDMaps) - if err != nil { - return -1, -1, err - } - rgid, err := toContainer(gid, i.GIDMaps) - return ruid, rgid, err -} - -// Empty returns true if there are no id mappings -func (i IdentityMapping) Empty() bool { - return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0 -} diff --git a/vendor/github.com/moby/sys/user/idtools_unix.go b/vendor/github.com/moby/sys/user/idtools_unix.go deleted file mode 100644 index 4e39d2446b23..000000000000 --- a/vendor/github.com/moby/sys/user/idtools_unix.go +++ /dev/null @@ -1,143 +0,0 @@ -//go:build !windows - -package user - -import ( - "fmt" - "os" - "path/filepath" - "strconv" - "syscall" -) - -func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll, onlyNew bool) error { - path, err := filepath.Abs(path) - if err != nil { - return err - } - - stat, err := os.Stat(path) - if err == nil { - if !stat.IsDir() { - return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} - } - if onlyNew { - return nil - } - - // short-circuit -- we were called with an existing directory and chown was requested - return setPermissions(path, mode, uid, gid, stat) - } - - // make an array containing the original path asked for, plus (for mkAll == true) - // all path components leading up to the complete path that don't exist before we MkdirAll - // so that we can chown all of them properly at the end. If onlyNew is true, we won't - // chown the full directory path if it exists - var paths []string - if os.IsNotExist(err) { - paths = append(paths, path) - } - - if mkAll { - // walk back to "/" looking for directories which do not exist - // and add them to the paths array for chown after creation - dirPath := path - for { - dirPath = filepath.Dir(dirPath) - if dirPath == "/" { - break - } - if _, err = os.Stat(dirPath); os.IsNotExist(err) { - paths = append(paths, dirPath) - } - } - if err = os.MkdirAll(path, mode); err != nil { - return err - } - } else if err = os.Mkdir(path, mode); err != nil { - return err - } - // even if it existed, we will chown the requested path + any subpaths that - // didn't exist when we called MkdirAll - for _, pathComponent := range paths { - if err = setPermissions(pathComponent, mode, uid, gid, nil); err != nil { - return err - } - } - return nil -} - -// setPermissions performs a chown/chmod only if the uid/gid don't match what's requested -// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the -// dir is on an NFS share, so don't call chown unless we absolutely must. -// Likewise for setting permissions. -func setPermissions(p string, mode os.FileMode, uid, gid int, stat os.FileInfo) error { - if stat == nil { - var err error - stat, err = os.Stat(p) - if err != nil { - return err - } - } - if stat.Mode().Perm() != mode.Perm() { - if err := os.Chmod(p, mode.Perm()); err != nil { - return err - } - } - ssi := stat.Sys().(*syscall.Stat_t) - if ssi.Uid == uint32(uid) && ssi.Gid == uint32(gid) { - return nil - } - return os.Chown(p, uid, gid) -} - -// LoadIdentityMapping takes a requested username and -// using the data from /etc/sub{uid,gid} ranges, creates the -// proper uid and gid remapping ranges for that user/group pair -func LoadIdentityMapping(name string) (IdentityMapping, error) { - // TODO: Consider adding support for calling out to "getent" - usr, err := LookupUser(name) - if err != nil { - return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %w", name, err) - } - - subuidRanges, err := lookupSubRangesFile("/etc/subuid", usr) - if err != nil { - return IdentityMapping{}, err - } - subgidRanges, err := lookupSubRangesFile("/etc/subgid", usr) - if err != nil { - return IdentityMapping{}, err - } - - return IdentityMapping{ - UIDMaps: subuidRanges, - GIDMaps: subgidRanges, - }, nil -} - -func lookupSubRangesFile(path string, usr User) ([]IDMap, error) { - uidstr := strconv.Itoa(usr.Uid) - rangeList, err := ParseSubIDFileFilter(path, func(sid SubID) bool { - return sid.Name == usr.Name || sid.Name == uidstr - }) - if err != nil { - return nil, err - } - if len(rangeList) == 0 { - return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name) - } - - idMap := []IDMap{} - - var containerID int64 - for _, idrange := range rangeList { - idMap = append(idMap, IDMap{ - ID: containerID, - ParentID: idrange.SubID, - Count: idrange.Count, - }) - containerID = containerID + idrange.Count - } - return idMap, nil -} diff --git a/vendor/github.com/moby/sys/user/idtools_windows.go b/vendor/github.com/moby/sys/user/idtools_windows.go deleted file mode 100644 index 9de730cafbe0..000000000000 --- a/vendor/github.com/moby/sys/user/idtools_windows.go +++ /dev/null @@ -1,13 +0,0 @@ -package user - -import ( - "os" -) - -// This is currently a wrapper around [os.MkdirAll] since currently -// permissions aren't set through this path, the identity isn't utilized. -// Ownership is handled elsewhere, but in the future could be support here -// too. -func mkdirAs(path string, _ os.FileMode, _, _ int, _, _ bool) error { - return os.MkdirAll(path, 0) -} diff --git a/vendor/github.com/moby/sys/user/lookup_unix.go b/vendor/github.com/moby/sys/user/lookup_unix.go deleted file mode 100644 index 4540747d28fb..000000000000 --- a/vendor/github.com/moby/sys/user/lookup_unix.go +++ /dev/null @@ -1,156 +0,0 @@ -//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris - -package user - -import ( - "io" - "os" - "strconv" - - "golang.org/x/sys/unix" -) - -// Unix-specific path to the passwd and group formatted files. -const ( - unixPasswdPath = "/etc/passwd" - unixGroupPath = "/etc/group" -) - -// LookupUser looks up a user by their username in /etc/passwd. If the user -// cannot be found (or there is no /etc/passwd file on the filesystem), then -// LookupUser returns an error. -func LookupUser(username string) (User, error) { - return lookupUserFunc(func(u User) bool { - return u.Name == username - }) -} - -// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot -// be found (or there is no /etc/passwd file on the filesystem), then LookupId -// returns an error. -func LookupUid(uid int) (User, error) { - return lookupUserFunc(func(u User) bool { - return u.Uid == uid - }) -} - -func lookupUserFunc(filter func(u User) bool) (User, error) { - // Get operating system-specific passwd reader-closer. - passwd, err := GetPasswd() - if err != nil { - return User{}, err - } - defer passwd.Close() - - // Get the users. - users, err := ParsePasswdFilter(passwd, filter) - if err != nil { - return User{}, err - } - - // No user entries found. - if len(users) == 0 { - return User{}, ErrNoPasswdEntries - } - - // Assume the first entry is the "correct" one. - return users[0], nil -} - -// LookupGroup looks up a group by its name in /etc/group. If the group cannot -// be found (or there is no /etc/group file on the filesystem), then LookupGroup -// returns an error. -func LookupGroup(groupname string) (Group, error) { - return lookupGroupFunc(func(g Group) bool { - return g.Name == groupname - }) -} - -// LookupGid looks up a group by its group id in /etc/group. If the group cannot -// be found (or there is no /etc/group file on the filesystem), then LookupGid -// returns an error. -func LookupGid(gid int) (Group, error) { - return lookupGroupFunc(func(g Group) bool { - return g.Gid == gid - }) -} - -func lookupGroupFunc(filter func(g Group) bool) (Group, error) { - // Get operating system-specific group reader-closer. - group, err := GetGroup() - if err != nil { - return Group{}, err - } - defer group.Close() - - // Get the users. - groups, err := ParseGroupFilter(group, filter) - if err != nil { - return Group{}, err - } - - // No user entries found. - if len(groups) == 0 { - return Group{}, ErrNoGroupEntries - } - - // Assume the first entry is the "correct" one. - return groups[0], nil -} - -func GetPasswdPath() (string, error) { - return unixPasswdPath, nil -} - -func GetPasswd() (io.ReadCloser, error) { - return os.Open(unixPasswdPath) -} - -func GetGroupPath() (string, error) { - return unixGroupPath, nil -} - -func GetGroup() (io.ReadCloser, error) { - return os.Open(unixGroupPath) -} - -// CurrentUser looks up the current user by their user id in /etc/passwd. If the -// user cannot be found (or there is no /etc/passwd file on the filesystem), -// then CurrentUser returns an error. -func CurrentUser() (User, error) { - return LookupUid(unix.Getuid()) -} - -// CurrentGroup looks up the current user's group by their primary group id's -// entry in /etc/passwd. If the group cannot be found (or there is no -// /etc/group file on the filesystem), then CurrentGroup returns an error. -func CurrentGroup() (Group, error) { - return LookupGid(unix.Getgid()) -} - -func currentUserSubIDs(fileName string) ([]SubID, error) { - u, err := CurrentUser() - if err != nil { - return nil, err - } - filter := func(entry SubID) bool { - return entry.Name == u.Name || entry.Name == strconv.Itoa(u.Uid) - } - return ParseSubIDFileFilter(fileName, filter) -} - -func CurrentUserSubUIDs() ([]SubID, error) { - return currentUserSubIDs("/etc/subuid") -} - -func CurrentUserSubGIDs() ([]SubID, error) { - return currentUserSubIDs("/etc/subgid") -} - -func CurrentProcessUIDMap() ([]IDMap, error) { - return ParseIDMapFile("/proc/self/uid_map") -} - -func CurrentProcessGIDMap() ([]IDMap, error) { - return ParseIDMapFile("/proc/self/gid_map") -} diff --git a/vendor/github.com/moby/sys/user/user.go b/vendor/github.com/moby/sys/user/user.go deleted file mode 100644 index 857711ebfd21..000000000000 --- a/vendor/github.com/moby/sys/user/user.go +++ /dev/null @@ -1,607 +0,0 @@ -package user - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "os" - "strconv" - "strings" -) - -const ( - minID = 0 - maxID = 1<<31 - 1 // for 32-bit systems compatibility -) - -var ( - // ErrNoPasswdEntries is returned if no matching entries were found in /etc/group. - ErrNoPasswdEntries = errors.New("no matching entries in passwd file") - // ErrNoGroupEntries is returned if no matching entries were found in /etc/passwd. - ErrNoGroupEntries = errors.New("no matching entries in group file") - // ErrRange is returned if a UID or GID is outside of the valid range. - ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minID, maxID) -) - -type User struct { - Name string - Pass string - Uid int - Gid int - Gecos string - Home string - Shell string -} - -type Group struct { - Name string - Pass string - Gid int - List []string -} - -// SubID represents an entry in /etc/sub{u,g}id -type SubID struct { - Name string - SubID int64 - Count int64 -} - -// IDMap represents an entry in /proc/PID/{u,g}id_map -type IDMap struct { - ID int64 - ParentID int64 - Count int64 -} - -func parseLine(line []byte, v ...any) { - parseParts(bytes.Split(line, []byte(":")), v...) -} - -func parseParts(parts [][]byte, v ...any) { - if len(parts) == 0 { - return - } - - for i, p := range parts { - // Ignore cases where we don't have enough fields to populate the arguments. - // Some configuration files like to misbehave. - if len(v) <= i { - break - } - - // Use the type of the argument to figure out how to parse it, scanf() style. - // This is legit. - switch e := v[i].(type) { - case *string: - *e = string(p) - case *int: - // "numbers", with conversion errors ignored because of some misbehaving configuration files. - *e, _ = strconv.Atoi(string(p)) - case *int64: - *e, _ = strconv.ParseInt(string(p), 10, 64) - case *[]string: - // Comma-separated lists. - if len(p) != 0 { - *e = strings.Split(string(p), ",") - } else { - *e = []string{} - } - default: - // Someone goof'd when writing code using this function. Scream so they can hear us. - panic(fmt.Sprintf("parseLine only accepts {*string, *int, *int64, *[]string} as arguments! %#v is not a pointer!", e)) - } - } -} - -func ParsePasswdFile(path string) ([]User, error) { - return ParsePasswdFileFilter(path, nil) -} - -func ParsePasswd(passwd io.Reader) ([]User, error) { - return ParsePasswdFilter(passwd, nil) -} - -func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) { - passwd, err := os.Open(path) - if err != nil { - return nil, err - } - defer passwd.Close() - return ParsePasswdFilter(passwd, filter) -} - -func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { - if r == nil { - return nil, errors.New("nil source for passwd-formatted data") - } - - var ( - s = bufio.NewScanner(r) - out = []User{} - ) - - for s.Scan() { - line := bytes.TrimSpace(s.Bytes()) - if len(line) == 0 { - continue - } - - // see: man 5 passwd - // name:password:UID:GID:GECOS:directory:shell - // Name:Pass:Uid:Gid:Gecos:Home:Shell - // root:x:0:0:root:/root:/bin/bash - // adm:x:3:4:adm:/var/adm:/bin/false - p := User{} - parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell) - - if filter == nil || filter(p) { - out = append(out, p) - } - } - if err := s.Err(); err != nil { - return nil, err - } - - return out, nil -} - -func ParseGroupFile(path string) ([]Group, error) { - return ParseGroupFileFilter(path, nil) -} - -func ParseGroup(group io.Reader) ([]Group, error) { - return ParseGroupFilter(group, nil) -} - -func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) { - group, err := openUserFile(path) - if err != nil { - return nil, err - } - defer group.Close() - return ParseGroupFilter(group, filter) -} - -func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { - if r == nil { - return nil, errors.New("nil source for group-formatted data") - } - - var ( - s = bufio.NewScanner(r) - out = []Group{} - ) - - // A group's user_list may be arbitrarily long, so allow lines that are - // much larger than bufio.Scanner's default maximum token size (64 KiB). - s.Buffer(nil, 1024*1024) - - for s.Scan() { - // There's no spec for /etc/passwd or /etc/group, but we try to follow - // the same rules as the glibc parser, which allows comments and blank - // space at the beginning of a line. - line := bytes.TrimSpace(s.Bytes()) - if len(line) == 0 || line[0] == '#' { - continue - } - - // see: man 5 group - // group_name:password:GID:user_list - // Name:Pass:Gid:List - // root:x:0:root - // adm:x:4:root,adm,daemon - p := Group{} - parseLine(line, &p.Name, &p.Pass, &p.Gid, &p.List) - - if filter == nil || filter(p) { - out = append(out, p) - } - } - if err := s.Err(); err != nil { - return nil, err - } - - return out, nil -} - -type ExecUser struct { - Uid int - Gid int - Sgids []int - Home string -} - -// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the -// given file paths and uses that data as the arguments to GetExecUser. If the -// files cannot be opened for any reason, the error is ignored and a nil -// io.Reader is passed instead. -func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { - var passwd, group io.Reader - - if passwdFile, err := openUserFile(passwdPath); err == nil { - passwd = passwdFile - defer passwdFile.Close() - } - - if groupFile, err := openUserFile(groupPath); err == nil { - group = groupFile - defer groupFile.Close() - } - - return GetExecUser(userSpec, defaults, passwd, group) -} - -// parseNumeric parses the given UID or GID value to an integer and within -// the minID - maxID range. -// -// While the Linux kernel allows the max UID to be MaxUint32 - 2, -// and the OCI Runtime Spec has no definition about the max UID, we require -// the UID to be <= MaxInt32. -// -// See https://github.com/containerd/containerd/commit/de1341c201ffb0effebbf51d00376181968c8779 -func parseNumeric(val string) (int, bool, error) { - if val == "" { - return 0, false, nil - } - id, err := strconv.Atoi(val) - if err != nil { - if errors.Is(err, strconv.ErrSyntax) { - // Discard the error, because non-numeric values are expected - // when passing a username or group-name. - return 0, false, nil - } - if errors.Is(err, strconv.ErrRange) { - return 0, true, ErrRange - } - // Other errors ("invalid base", "invalid bit size"); should never - // happen with strconv.Atoi. - return 0, false, err - } - if id < minID || id > maxID { - return 0, true, ErrRange - } - return id, true, nil -} - -// GetExecUser parses a user specification string (using the passwd and group -// readers as sources for /etc/passwd and /etc/group data, respectively). In -// the case of blank fields or missing data from the sources, the values in -// defaults is used. -// -// GetExecUser will return an error if a user or group literal could not be -// found in any entry in passwd and group respectively. -// -// Examples of valid user specifications are: -// - "" -// - "user" -// - "uid" -// - "user:group" -// - "uid:gid -// - "user:gid" -// - "uid:group" -// -// It should be noted that if you specify a numeric user or group id, they will -// not be evaluated as usernames (only the metadata will be filled). So attempting -// to parse a user with user.Name = "1337" will produce the user with a UID of -// 1337. -func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { - if defaults == nil { - defaults = new(ExecUser) - } - - // Copy over defaults. - user := &ExecUser{ - Uid: defaults.Uid, - Gid: defaults.Gid, - Sgids: defaults.Sgids, - Home: defaults.Home, - } - - // Sgids slice *cannot* be nil. - if user.Sgids == nil { - user.Sgids = []int{} - } - - // Allow for userArg to have either "user" syntax, or optionally "user:group" syntax - var userArg, groupArg string - parseLine([]byte(userSpec), &userArg, &groupArg) - - // Convert userArg and groupArg to be numeric, so we don't have to execute - // Atoi *twice* for each iteration over lines. - uidArg, isUID, err := parseNumeric(userArg) - if err != nil { - return nil, err - } - gidArg, isGID, err := parseNumeric(groupArg) - if err != nil { - return nil, err - } - - // Find the matching user. - users, err := ParsePasswdFilter(passwd, func(u User) bool { - if userArg == "" { - // Default to current state of the user. - return u.Uid == user.Uid - } - - if isUID { - // If the userArg is a valid numeric value, always treat it as a UID. - return uidArg == u.Uid - } - - return u.Name == userArg - }) - - // If we can't find the user, we have to bail. - if err != nil && passwd != nil { - if userArg == "" { - userArg = strconv.Itoa(user.Uid) - } - return nil, fmt.Errorf("unable to find user %s: %w", userArg, err) - } - - var matchedUserName string - if len(users) > 0 { - // First match wins, even if there's more than one matching entry. - matchedUserName = users[0].Name - user.Uid = users[0].Uid - user.Gid = users[0].Gid - user.Home = users[0].Home - } else if userArg != "" { - // If we can't find a user with the given username, the only other valid - // option is if it's a numeric username with no associated entry in passwd. - - if !isUID { - // Not numeric. - return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries) - } - user.Uid = uidArg - } - - // On to the groups. If we matched a username, we need to do this because of - // the supplementary group IDs. - if groupArg != "" || matchedUserName != "" { - groups, err := ParseGroupFilter(group, func(g Group) bool { - // If the group argument isn't explicit, we'll just search for it. - if groupArg == "" { - // Check if user is a member of this group. - for _, u := range g.List { - if u == matchedUserName { - return true - } - } - return false - } - - if isGID { - // If the groupArg is numeric, always treat it as a GID. - return gidArg == g.Gid - } - - return g.Name == groupArg - }) - if err != nil && group != nil { - return nil, fmt.Errorf("unable to find groups for spec %v: %w", matchedUserName, err) - } - - // Only start modifying user.Gid if it is in explicit form. - if groupArg != "" { - if len(groups) > 0 { - // First match wins, even if there's more than one matching entry. - user.Gid = groups[0].Gid - } else { - // If we can't find a group with the given name, the only other valid - // option is if it's a numeric group name with no associated entry in group. - - if !isGID { - // Not numeric. - return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries) - } - user.Gid = gidArg - } - } else if len(groups) > 0 { - // Supplementary group ids only make sense if in the implicit form. - user.Sgids = make([]int, len(groups)) - for i, group := range groups { - user.Sgids[i] = group.Gid - } - } - } - - return user, nil -} - -// groupArg is a parsed group argument for [GetAdditionalGroups]. -type groupArg struct { - name string - gid int - isNumeric bool -} - -// matches reports whether group g satisfies the argument. Numeric arguments -// are matched by GID only, others by name. -func (ag groupArg) matches(g Group) bool { - if ag.isNumeric { - return g.Gid == ag.gid - } - return g.Name == ag.name -} - -// GetAdditionalGroups looks up a list of groups by name or group id -// against the given /etc/group formatted data. If a group name cannot -// be found, an error will be returned. If a group id cannot be found, -// or the given group data is nil, the id will be returned as-is -// provided it is in the legal range. -func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) { - addtlGroups := make([]groupArg, len(additionalGroups)) - for i, ag := range additionalGroups { - gid, ok, err := parseNumeric(ag) - if err != nil { - return nil, err - } - addtlGroups[i] = groupArg{ - name: ag, - gid: gid, - isNumeric: ok, - } - } - - groups := []Group{} - if group != nil { - var err error - groups, err = ParseGroupFilter(group, func(g Group) bool { - for _, ag := range addtlGroups { - if ag.matches(g) { - return true - } - } - return false - }) - if err != nil { - return nil, fmt.Errorf("unable to find additional groups %v: %w", additionalGroups, err) - } - } - - gidMap := make(map[int]struct{}) - for _, ag := range addtlGroups { - var found bool - for _, g := range groups { - if ag.matches(g) { - // take the first matched group as correct - if g.Gid < minID || g.Gid > maxID { - return nil, ErrRange - } - gidMap[g.Gid] = struct{}{} - found = true - break - } - } - // We asked for a group but didn't find it. Numeric group IDs may be - // used as-is even when they are not present in /etc/group; non-numeric - // group names must be found. - if !found { - if !ag.isNumeric { - // Not a numeric ID either. - return nil, fmt.Errorf("unable to find group %s: %w", ag.name, ErrNoGroupEntries) - } - gidMap[ag.gid] = struct{}{} - } - } - gids := []int{} - for gid := range gidMap { - gids = append(gids, gid) - } - return gids, nil -} - -// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups -// that opens the groupPath given and gives it as an argument to -// GetAdditionalGroups. -func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) { - var group io.Reader - - if groupFile, err := os.Open(groupPath); err == nil { - group = groupFile - defer groupFile.Close() - } - return GetAdditionalGroups(additionalGroups, group) -} - -func ParseSubIDFile(path string) ([]SubID, error) { - return ParseSubIDFileFilter(path, nil) -} - -func ParseSubID(subid io.Reader) ([]SubID, error) { - return ParseSubIDFilter(subid, nil) -} - -func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) { - subid, err := os.Open(path) - if err != nil { - return nil, err - } - defer subid.Close() - return ParseSubIDFilter(subid, filter) -} - -func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) { - if r == nil { - return nil, errors.New("nil source for subid-formatted data") - } - - var ( - s = bufio.NewScanner(r) - out = []SubID{} - ) - - for s.Scan() { - line := bytes.TrimSpace(s.Bytes()) - if len(line) == 0 { - continue - } - - // see: man 5 subuid - p := SubID{} - parseLine(line, &p.Name, &p.SubID, &p.Count) - - if filter == nil || filter(p) { - out = append(out, p) - } - } - if err := s.Err(); err != nil { - return nil, err - } - - return out, nil -} - -func ParseIDMapFile(path string) ([]IDMap, error) { - return ParseIDMapFileFilter(path, nil) -} - -func ParseIDMap(r io.Reader) ([]IDMap, error) { - return ParseIDMapFilter(r, nil) -} - -func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) { - r, err := os.Open(path) - if err != nil { - return nil, err - } - defer r.Close() - return ParseIDMapFilter(r, filter) -} - -func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) { - if r == nil { - return nil, errors.New("nil source for idmap-formatted data") - } - - var ( - s = bufio.NewScanner(r) - out = []IDMap{} - ) - - for s.Scan() { - line := bytes.TrimSpace(s.Bytes()) - if len(line) == 0 { - continue - } - - // see: man 7 user_namespaces - p := IDMap{} - parseParts(bytes.Fields(line), &p.ID, &p.ParentID, &p.Count) - - if filter == nil || filter(p) { - out = append(out, p) - } - } - if err := s.Err(); err != nil { - return nil, err - } - - return out, nil -} diff --git a/vendor/github.com/moby/sys/user/user_fuzzer.go b/vendor/github.com/moby/sys/user/user_fuzzer.go deleted file mode 100644 index 5f56cc00a6c0..000000000000 --- a/vendor/github.com/moby/sys/user/user_fuzzer.go +++ /dev/null @@ -1,42 +0,0 @@ -//go:build gofuzz - -package user - -import ( - "io" - "strings" -) - -func IsDivisbleBy(n int, divisibleby int) bool { - return (n % divisibleby) == 0 -} - -func FuzzUser(data []byte) int { - if len(data) == 0 { - return -1 - } - if !IsDivisbleBy(len(data), 5) { - return -1 - } - - var divided [][]byte - - chunkSize := len(data) / 5 - - for i := 0; i < len(data); i += chunkSize { - end := i + chunkSize - - divided = append(divided, data[i:end]) - } - - _, _ = ParsePasswdFilter(strings.NewReader(string(divided[0])), nil) - - var passwd, group io.Reader - - group = strings.NewReader(string(divided[1])) - _, _ = GetAdditionalGroups([]string{string(divided[2])}, group) - - passwd = strings.NewReader(string(divided[3])) - _, _ = GetExecUser(string(divided[4]), nil, passwd, group) - return 1 -} diff --git a/vendor/github.com/moby/sys/user/user_utils.go b/vendor/github.com/moby/sys/user/user_utils.go deleted file mode 100644 index 978aa927ed30..000000000000 --- a/vendor/github.com/moby/sys/user/user_utils.go +++ /dev/null @@ -1,64 +0,0 @@ -package user - -import ( - "errors" - "fmt" - "io" - "os" -) - -// maxUserFileBytes caps how much data is read from any user-database file. -// User database files are expected to be relatively small. 10 MiB provides -// generous headroom while bounding memory usage. -const maxUserFileBytes = 10 << 20 - -// openUserFile attempts to open a user-database file with a limitedFile -// capped at maxUserFileBytes. It produces an error if the given path is -// a non-regular file. -func openUserFile(path string) (*limitedFile, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - - info, err := f.Stat() - if err != nil { - _ = f.Close() - return nil, err - } - if !info.Mode().IsRegular() { - _ = f.Close() - return nil, &os.PathError{ - Op: "open", - Path: path, - Err: errors.New("not a regular file"), - } - } - - return &limitedFile{ - File: f, - // Allow one byte past the cap so an overflow surfaces as an - // error rather than a silent EOF that the parser would treat as - // a clean end-of-file (and miss any entries past the cap). - LimitedReader: &io.LimitedReader{R: f, N: maxUserFileBytes + 1}, - name: path, - }, nil -} - -type limitedFile struct { - *os.File - *io.LimitedReader - name string -} - -func (l *limitedFile) Read(p []byte) (int, error) { - n, err := l.LimitedReader.Read(p) - if l.LimitedReader.N == 0 { - return n, &os.PathError{ - Op: "read", - Path: l.name, - Err: fmt.Errorf("file exceeds %d bytes", maxUserFileBytes), - } - } - return n, err -} diff --git a/vendor/github.com/moby/sys/userns/LICENSE b/vendor/github.com/moby/sys/userns/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/vendor/github.com/moby/sys/userns/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - 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/userns/userns.go b/vendor/github.com/moby/sys/userns/userns.go deleted file mode 100644 index 56b24c44ad0d..000000000000 --- a/vendor/github.com/moby/sys/userns/userns.go +++ /dev/null @@ -1,16 +0,0 @@ -// Package userns provides utilities to detect whether we are currently running -// in a Linux user namespace. -// -// This code was migrated from [libcontainer/runc], which based its implementation -// on code from [lcx/incus]. -// -// [libcontainer/runc]: https://github.com/opencontainers/runc/blob/3778ae603c706494fd1e2c2faf83b406e38d687d/libcontainer/userns/userns_linux.go#L12-L49 -// [lcx/incus]: https://github.com/lxc/incus/blob/e45085dd42f826b3c8c3228e9733c0b6f998eafe/shared/util.go#L678-L700 -package userns - -// RunningInUserNS detects whether we are currently running in a Linux -// user namespace and memoizes the result. It returns false on non-Linux -// platforms. -func RunningInUserNS() bool { - return inUserNS() -} diff --git a/vendor/github.com/moby/sys/userns/userns_linux.go b/vendor/github.com/moby/sys/userns/userns_linux.go deleted file mode 100644 index 87c1c38eec27..000000000000 --- a/vendor/github.com/moby/sys/userns/userns_linux.go +++ /dev/null @@ -1,53 +0,0 @@ -package userns - -import ( - "bufio" - "fmt" - "os" - "sync" -) - -var inUserNS = sync.OnceValue(runningInUserNS) - -// runningInUserNS detects whether we are currently running in a user namespace. -// -// This code was migrated from [libcontainer/runc] and based on an implementation -// from [lcx/incus]. -// -// [libcontainer/runc]: https://github.com/opencontainers/runc/blob/3778ae603c706494fd1e2c2faf83b406e38d687d/libcontainer/userns/userns_linux.go#L12-L49 -// [lcx/incus]: https://github.com/lxc/incus/blob/e45085dd42f826b3c8c3228e9733c0b6f998eafe/shared/util.go#L678-L700 -func runningInUserNS() bool { - file, err := os.Open("/proc/self/uid_map") - if err != nil { - // This kernel-provided file only exists if user namespaces are supported. - return false - } - defer file.Close() - - buf := bufio.NewReader(file) - l, _, err := buf.ReadLine() - if err != nil { - return false - } - - return uidMapInUserNS(string(l)) -} - -func uidMapInUserNS(uidMap string) bool { - if uidMap == "" { - // File exist but empty (the initial state when userns is created, - // see user_namespaces(7)). - return true - } - - var a, b, c int64 - if _, err := fmt.Sscanf(uidMap, "%d %d %d", &a, &b, &c); err != nil { - // Assume we are in a regular, non user namespace. - return false - } - - // As per user_namespaces(7), /proc/self/uid_map of - // the initial user namespace shows 0 0 4294967295. - initNS := a == 0 && b == 0 && c == 4294967295 - return !initNS -} diff --git a/vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go b/vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go deleted file mode 100644 index 26ba2e16ec4e..000000000000 --- a/vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build linux && gofuzz - -package userns - -func FuzzUIDMap(uidmap []byte) int { - _ = uidMapInUserNS(string(uidmap)) - return 1 -} diff --git a/vendor/github.com/moby/sys/userns/userns_unsupported.go b/vendor/github.com/moby/sys/userns/userns_unsupported.go deleted file mode 100644 index 8ed83072c233..000000000000 --- a/vendor/github.com/moby/sys/userns/userns_unsupported.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build !linux - -package userns - -// inUserNS is a stub for non-Linux systems. Always returns false. -func inUserNS() bool { return false } diff --git a/vendor/modules.txt b/vendor/modules.txt index de7bc26b0790..4b3d4fb22f94 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -747,11 +747,9 @@ github.com/moby/buildkit/version # github.com/moby/docker-image-spec v1.3.1 ## explicit; go 1.18 github.com/moby/docker-image-spec/specs-go/v1 -# github.com/moby/go-archive v0.2.1 -## explicit; go 1.25 -github.com/moby/go-archive +# github.com/moby/go-archive v0.2.0 +## explicit; go 1.23.0 github.com/moby/go-archive/compression -github.com/moby/go-archive/tarheader # github.com/moby/locker v1.0.1 ## explicit; go 1.13 github.com/moby/locker @@ -812,12 +810,6 @@ github.com/moby/sys/sequential # github.com/moby/sys/signal v0.7.1 ## explicit; go 1.17 github.com/moby/sys/signal -# github.com/moby/sys/user v0.4.1 -## explicit; go 1.18 -github.com/moby/sys/user -# github.com/moby/sys/userns v0.1.0 -## explicit; go 1.21 -github.com/moby/sys/userns # github.com/moby/term v0.5.2 ## explicit; go 1.18 github.com/moby/term From d3d2609a1df1d31c5dfcd83aab5e01b86814af94 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:38:47 +0200 Subject: [PATCH 2/2] driver: build container config tar in memory Create the docker-container BuildKit config archive directly from the in-memory file map instead of staging files through a temporary directory. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- driver/docker-container/driver.go | 34 +------ driver/docker-container/tar.go | 152 +++++++++++++++++----------- driver/docker-container/tar_test.go | 104 ++++++++++++++++--- tests/create.go | 73 +++++++++++++ 4 files changed, 256 insertions(+), 107 deletions(-) diff --git a/driver/docker-container/driver.go b/driver/docker-container/driver.go index 385ddee1f87a..a7688606995d 100644 --- a/driver/docker-container/driver.go +++ b/driver/docker-container/driver.go @@ -7,7 +7,6 @@ import ( "net" "os" "path" - "path/filepath" "strings" "sync/atomic" "time" @@ -368,14 +367,7 @@ func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error { } func (d *Driver) copyToContainer(ctx context.Context, files map[string][]byte) error { - srcPath, err := writeConfigFiles(files) - if err != nil { - return err - } - if srcPath != "" { - defer os.RemoveAll(srcPath) - } - srcArchive, err := tarDirectory(srcPath) + srcArchive, err := tarConfigFiles(files) if err != nil { return err } @@ -619,30 +611,6 @@ func (l *logWriter) Write(dt []byte) (int, error) { return len(dt), nil } -func writeConfigFiles(m map[string][]byte) (_ string, err error) { - // Temp dir that will be copied to the container - tmpDir, err := os.MkdirTemp("", "buildkitd-config") - if err != nil { - return "", err - } - defer func() { - if err != nil { - os.RemoveAll(tmpDir) - } - }() - configDir := filepath.Base(confutil.DefaultBuildKitConfigDir) - for f, dt := range m { - p := filepath.Join(tmpDir, configDir, f) - if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { - return "", err - } - if err := os.WriteFile(p, dt, 0644); err != nil { - return "", err - } - } - return tmpDir, nil -} - func getBuildkitFlags(initConfig driver.InitConfig) []string { flags := initConfig.BuildkitdFlags if _, ok := initConfig.Files[buildkitdConfigFile]; ok { diff --git a/driver/docker-container/tar.go b/driver/docker-container/tar.go index 1b7cae4b1ebd..f491a64131d7 100644 --- a/driver/docker-container/tar.go +++ b/driver/docker-container/tar.go @@ -2,10 +2,15 @@ package docker import ( "archive/tar" + "bytes" "io" "io/fs" - "os" + "maps" + "path" + "slices" + "strings" + "github.com/docker/buildx/util/confutil" "github.com/pkg/errors" ) @@ -14,82 +19,109 @@ const ( tarFileMode int64 = 0o644 ) -func tarDirectory(srcPath string) (io.ReadCloser, error) { - stat, err := os.Lstat(srcPath) +type configTarEntry struct { + name string + data []byte +} + +func tarConfigFiles(files map[string][]byte) (io.ReadCloser, error) { + entries, dirs, err := configTarEntries(path.Base(confutil.DefaultBuildKitConfigDir), files) if err != nil { return nil, err } - if !stat.IsDir() { - return nil, errors.Errorf("%s is not a directory", srcPath) + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + if err := writeConfigTar(tw, dirs, entries); err != nil { + _ = tw.Close() + return nil, err } - root, err := os.OpenRoot(srcPath) - if err != nil { + if err := tw.Close(); err != nil { return nil, err } - - pr, pw := io.Pipe() - tw := tar.NewWriter(pw) - go func() { - defer root.Close() - err := fs.WalkDir(root.FS(), ".", func(name string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if name == "." && d.IsDir() { - return nil - } - return writeTarFile(root, tw, name, d) - }) - if err == nil { - err = tw.Close() - } - if err != nil { - _ = pw.CloseWithError(err) - return - } - _ = pw.Close() - }() - return pr, nil + return io.NopCloser(bytes.NewReader(buf.Bytes())), nil } -func writeTarFile(root *os.Root, tw *tar.Writer, name string, d fs.DirEntry) error { - fi, err := d.Info() +func configTarEntries(configDir string, files map[string][]byte) ([]configTarEntry, []string, error) { + configDir, err := configArchiveRoot(configDir) if err != nil { - return err + return nil, nil, err + } + + entries := make([]configTarEntry, 0, len(files)) + dirs := map[string]struct{}{} + filePaths := make(map[string]struct{}, len(files)) + if len(files) > 0 { + dirs[configDir] = struct{}{} + } + + for _, name := range slices.Sorted(maps.Keys(files)) { + archivePath, err := configArchivePath(configDir, name) + if err != nil { + return nil, nil, err + } + entries = append(entries, configTarEntry{ + name: archivePath, + data: files[name], + }) + filePaths[archivePath] = struct{}{} + for dir := path.Dir(archivePath); dir != "."; dir = path.Dir(dir) { + dirs[dir] = struct{}{} + } } - mode := fi.Mode() - hdr := &tar.Header{ - Name: name, - Mode: tarFileMode, - ModTime: fi.ModTime(), + for filePath := range filePaths { + if _, ok := dirs[filePath]; ok { + return nil, nil, errors.Errorf("config file path %q conflicts with directory", strings.TrimPrefix(filePath, configDir+"/")) + } } - switch { - case fi.IsDir(): - hdr.Typeflag = tar.TypeDir - hdr.Name += "/" - hdr.Mode = tarDirMode - case mode.IsRegular(): - hdr.Typeflag = tar.TypeReg - hdr.Size = fi.Size() - default: - return errors.Errorf("unsupported file type %s", name) + + return entries, slices.Sorted(maps.Keys(dirs)), nil +} + +func configArchiveRoot(name string) (string, error) { + if name == "." || strings.Contains(name, "/") || strings.Contains(name, `\`) || strings.ContainsRune(name, 0) || !fs.ValidPath(name) { + return "", errors.Errorf("invalid config archive root %q", name) } + return name, nil +} - if err := tw.WriteHeader(hdr); err != nil { - return err +func configArchivePath(configDir, name string) (string, error) { + if strings.Contains(name, `\`) || strings.ContainsRune(name, 0) { + return "", errors.Errorf("invalid config file path %q", name) } - if !mode.IsRegular() || hdr.Size == 0 { - return nil + // Validate the literal slash path. path.Join would clean traversal first. + archivePath := configDir + "/" + name + if !fs.ValidPath(archivePath) { + return "", errors.Errorf("invalid config file path %q", name) } + return archivePath, nil +} - f, err := root.Open(name) - if err != nil { - return err +func writeConfigTar(tw *tar.Writer, dirs []string, entries []configTarEntry) error { + for _, dir := range dirs { + if err := tw.WriteHeader(&tar.Header{ + Name: dir + "/", + Typeflag: tar.TypeDir, + Mode: tarDirMode, + }); err != nil { + return err + } } - if _, err := io.Copy(tw, f); err != nil { - _ = f.Close() - return err + for _, entry := range entries { + if err := tw.WriteHeader(&tar.Header{ + Name: entry.name, + Typeflag: tar.TypeReg, + Mode: tarFileMode, + Size: int64(len(entry.data)), + }); err != nil { + return err + } + if len(entry.data) == 0 { + continue + } + if _, err := tw.Write(entry.data); err != nil { + return err + } } - return f.Close() + return nil } diff --git a/driver/docker-container/tar_test.go b/driver/docker-container/tar_test.go index f9029898628b..dc6a136fdc99 100644 --- a/driver/docker-container/tar_test.go +++ b/driver/docker-container/tar_test.go @@ -3,7 +3,6 @@ package docker import ( "archive/tar" "io" - "os" "testing" "github.com/stretchr/testify/require" @@ -14,27 +13,25 @@ type tarEntry struct { body []byte } -func TestTarDirectoryConfigFiles(t *testing.T) { +func TestTarConfigFiles(t *testing.T) { files := map[string][]byte{ "buildkitd.toml": []byte("debug = true\n"), "certs/example.com/ca.pem": []byte("certificate"), } - srcPath, err := writeConfigFiles(files) - require.NoError(t, err) - defer os.RemoveAll(srcPath) - rc, err := tarDirectory(srcPath) + rc, err := tarConfigFiles(files) require.NoError(t, err) defer rc.Close() - entries := readTarEntries(t, rc) expected := []string{ "buildkit/", - "buildkit/buildkitd.toml", "buildkit/certs/", "buildkit/certs/example.com/", + "buildkit/buildkitd.toml", "buildkit/certs/example.com/ca.pem", } + entries, names := readTarEntries(t, rc) + require.Equal(t, expected, names) require.Len(t, entries, len(expected)) for _, name := range expected { entry, ok := entries[name] @@ -52,8 +49,23 @@ func TestTarDirectoryConfigFiles(t *testing.T) { require.Equal(t, files["certs/example.com/ca.pem"], entries["buildkit/certs/example.com/ca.pem"].body) } -func TestTarDirectoryEmpty(t *testing.T) { - rc, err := tarDirectory(t.TempDir()) +func TestTarConfigFilesSnapshotsContent(t *testing.T) { + files := map[string][]byte{ + "buildkitd.toml": []byte("debug = true\n"), + } + + rc, err := tarConfigFiles(files) + require.NoError(t, err) + defer rc.Close() + + files["buildkitd.toml"][0] = '#' + + entries, _ := readTarEntries(t, rc) + require.Equal(t, []byte("debug = true\n"), entries["buildkit/buildkitd.toml"].body) +} + +func TestTarConfigFilesEmpty(t *testing.T) { + rc, err := tarConfigFiles(nil) require.NoError(t, err) defer rc.Close() @@ -62,24 +74,88 @@ func TestTarDirectoryEmpty(t *testing.T) { require.ErrorIs(t, err, io.EOF) } -func readTarEntries(t *testing.T, r io.Reader) map[string]tarEntry { +func TestTarConfigFilesRejectsInvalidPaths(t *testing.T) { + for _, tc := range []struct { + name string + path string + }{ + {name: "empty", path: ""}, + {name: "dot", path: "."}, + {name: "leading-dot", path: "./buildkitd.toml"}, + {name: "parent", path: "../buildkitd.toml"}, + {name: "absolute", path: "/buildkitd.toml"}, + {name: "trailing-slash", path: "certs/"}, + {name: "parent-element", path: "certs/../ca.pem"}, + {name: "dot-element", path: "certs/./ca.pem"}, + {name: "empty-element", path: "certs//ca.pem"}, + {name: "backslash", path: `certs\ca.pem`}, + {name: "nul", path: "certs/ca\x00.pem"}, + } { + t.Run(tc.name, func(t *testing.T) { + rc, err := tarConfigFiles(map[string][]byte{ + tc.path: []byte("invalid"), + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid config file path") + require.Nil(t, rc) + }) + } +} + +func TestTarConfigFilesRejectsFileDirectoryConflict(t *testing.T) { + rc, err := tarConfigFiles(map[string][]byte{ + "certs": []byte("file"), + "certs/ca.pem": []byte("ca"), + }) + require.Error(t, err) + require.Contains(t, err.Error(), "conflicts with directory") + require.Nil(t, rc) +} + +func TestConfigTarEntriesRejectsInvalidRoot(t *testing.T) { + for _, tc := range []struct { + name string + root string + }{ + {name: "empty", root: ""}, + {name: "dot", root: "."}, + {name: "parent", root: "../buildkit"}, + {name: "absolute", root: "/buildkit"}, + {name: "nested", root: "etc/buildkit"}, + {name: "backslash", root: `etc\buildkit`}, + {name: "nul", root: "build\x00kit"}, + } { + t.Run(tc.name, func(t *testing.T) { + entries, dirs, err := configTarEntries(tc.root, map[string][]byte{ + "buildkitd.toml": []byte("debug = true\n"), + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid config archive root") + require.Nil(t, entries) + require.Nil(t, dirs) + }) + } +} + +func readTarEntries(t *testing.T, r io.Reader) (map[string]tarEntry, []string) { t.Helper() tr := tar.NewReader(r) entries := map[string]tarEntry{} + var names []string for { hdr, err := tr.Next() if err == io.EOF { break } require.NoError(t, err) + names = append(names, hdr.Name) body, err := io.ReadAll(tr) require.NoError(t, err) - hdrCopy := *hdr entries[hdr.Name] = tarEntry{ - header: &hdrCopy, + header: new(*hdr), body: body, } } - return entries + return entries, names } diff --git a/tests/create.go b/tests/create.go index 55606756b49e..6aefc68d3f6a 100644 --- a/tests/create.go +++ b/tests/create.go @@ -1,9 +1,14 @@ package tests import ( + "archive/tar" + "bytes" "fmt" + "io" "os" + "path" "path/filepath" + "strconv" "strings" "testing" @@ -24,6 +29,7 @@ func createCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) { var createTests = []func(t *testing.T, sb integration.Sandbox){ testCreateMemoryLimit, testCreateRestartAlways, + testCreateCopiesBuildkitdConfigFiles, testCreateRemoteContainer, testCreateWithProvenanceGHA, testCreateCustomImageWithDefaultPolicy, @@ -67,6 +73,44 @@ func testCreateRestartAlways(t *testing.T, sb integration.Sandbox) { builderName = strings.TrimSpace(out) } +func testCreateCopiesBuildkitdConfigFiles(t *testing.T, sb integration.Sandbox) { + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") + } + + const caContent = "ca certificate\n" + + var builderName string + t.Cleanup(func() { + if builderName == "" { + return + } + out, err := rmCmd(sb, withArgs(builderName)) + require.NoError(t, err, out) + }) + + dir := t.TempDir() + caPath := filepath.Join(dir, "ca.pem") + require.NoError(t, os.WriteFile(caPath, []byte(caContent), 0o644)) + + buildkitdConfPath := filepath.Join(dir, "buildkitd.toml") + buildkitdConf := fmt.Sprintf(`[registry."example.com"] +ca=[%s] +`, strconv.Quote(caPath)) + require.NoError(t, os.WriteFile(buildkitdConfPath, []byte(buildkitdConf), 0o644)) + + out, err := createCmd(sb, withArgs("--driver", "docker-container", "--buildkitd-config="+buildkitdConfPath)) + require.NoError(t, err, out) + builderName = strings.TrimSpace(out) + + out, err = inspectCmd(sb, withArgs(builderName, "--bootstrap")) + require.NoError(t, err, out) + + container := fmt.Sprintf("%s0", driver.BuilderName(builderName)) + require.Equal(t, []byte(caContent), dockerCpFile(t, sb, container, "/etc/buildkit/certs/example.com/ca.pem")) + require.Contains(t, string(dockerCpFile(t, sb, container, "/etc/buildkit/buildkitd.toml")), "/etc/buildkit/certs/example.com/ca.pem") +} + func testCreateRemoteContainer(t *testing.T, sb integration.Sandbox) { if !isDockerWorker(sb) { t.Skip("only testing with docker workers") @@ -177,3 +221,32 @@ func testCreateWithProvenanceGHA(t *testing.T, sb integration.Sandbox) { out, err = inspectCmd(sb, withArgs(builderName, "--bootstrap")) require.NoError(t, err, out) } + +func dockerCpFile(t *testing.T, sb integration.Sandbox, container, src string) []byte { + t.Helper() + + var stdout, stderr bytes.Buffer + cmd := dockerCmd(sb, withArgs("cp", container+":"+src, "-")) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + require.NoError(t, cmd.Run(), stderr.String()) + + tr := tar.NewReader(&stdout) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + if hdr.FileInfo().IsDir() { + continue + } + body, err := io.ReadAll(tr) + require.NoError(t, err) + if path.Base(hdr.Name) == path.Base(src) { + return body + } + } + require.Failf(t, "file not found in docker cp output", "expected %s", src) + return nil +}