diff --git a/go.mod b/go.mod index 4136ef30..9413c0da 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/in-toto/in-toto-golang v0.5.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/moby/buildkit v0.21.0 - github.com/moby/go-archive v0.0.0-20250404171912-21f3f3385ab7 + github.com/moby/go-archive v0.1.0 github.com/moby/sys/atomicwriter v0.1.0 github.com/moby/sys/mountinfo v0.7.2 github.com/moby/sys/signal v0.7.1 diff --git a/go.sum b/go.sum index 283616b8..c4514757 100644 --- a/go.sum +++ b/go.sum @@ -283,8 +283,8 @@ github.com/moby/buildkit v0.21.0 h1:+z4vVqgt0spLrOSxi4DLedRbIh2gbNVlZ5q4rsnNp60= github.com/moby/buildkit v0.21.0/go.mod h1:mBq0D44uCyz2PdX8T/qym5LBbkBO3GGv0wqgX9ABYYw= 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.0.0-20250404171912-21f3f3385ab7 h1:CWAY9uG9JhmLmnM7T64+bV0C9IraDrvxEwXq1HJ7hhk= -github.com/moby/go-archive v0.0.0-20250404171912-21f3f3385ab7/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= 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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= diff --git a/vendor/github.com/moby/go-archive/.gitattributes b/vendor/github.com/moby/go-archive/.gitattributes new file mode 100644 index 00000000..4bb139d9 --- /dev/null +++ b/vendor/github.com/moby/go-archive/.gitattributes @@ -0,0 +1,2 @@ +*.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 new file mode 100644 index 00000000..3f2bc474 --- /dev/null +++ b/vendor/github.com/moby/go-archive/.gitignore @@ -0,0 +1 @@ +/coverage.txt diff --git a/vendor/github.com/moby/go-archive/.golangci.yml b/vendor/github.com/moby/go-archive/.golangci.yml new file mode 100644 index 00000000..21439e5c --- /dev/null +++ b/vendor/github.com/moby/go-archive/.golangci.yml @@ -0,0 +1,33 @@ +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 + - unconvert + - unparam + exclusions: + generated: disable + presets: + - comments + - std-error-handling + settings: + 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 index c207e8e7..7a105aef 100644 --- a/vendor/github.com/moby/go-archive/archive.go +++ b/vendor/github.com/moby/go-archive/archive.go @@ -3,32 +3,24 @@ package archive import ( "archive/tar" - "bufio" - "bytes" - "compress/bzip2" - "compress/gzip" "context" - "encoding/binary" "errors" "fmt" "io" "os" - "os/exec" "path/filepath" "runtime" - "runtime/debug" - "strconv" "strings" - "sync" - "sync/atomic" "syscall" "time" "github.com/containerd/log" - "github.com/klauspost/compress/zstd" "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 @@ -45,7 +37,9 @@ const ImpliedDirectoryMode = 0o755 type ( // Compression is the state represents if compressed or not. - Compression int + // + // Deprecated: use [compression.Compression]. + Compression = compression.Compression // WhiteoutFormat is the format of whiteouts unpacked WhiteoutFormat int @@ -58,7 +52,7 @@ type ( TarOptions struct { IncludeFiles []string ExcludePatterns []string - Compression Compression + Compression compression.Compression NoLchown bool IDMap user.IdentityMapping ChownOpts *ChownOpts @@ -102,11 +96,11 @@ func NewDefaultArchiver() *Archiver { type breakoutError error const ( - Uncompressed Compression = 0 // Uncompressed represents the uncompressed. - Bzip2 Compression = 1 // Bzip2 is bzip2 compression algorithm. - Gzip Compression = 2 // Gzip is gzip compression algorithm. - Xz Compression = 3 // Xz is xz compression algorithm. - Zstd Compression = 4 // Zstd is zstd compression algorithm. + Uncompressed = compression.None // Deprecated: use [compression.None]. + Bzip2 = compression.Bzip2 // Deprecated: use [compression.Bzip2]. + Gzip = compression.Gzip // Deprecated: use [compression.Gzip]. + Xz = compression.Xz // Deprecated: use [compression.Xz]. + Zstd = compression.Zstd // Deprecated: use [compression.Zstd]. ) const ( @@ -122,7 +116,7 @@ func IsArchivePath(path string) bool { return false } defer file.Close() - rdr, err := DecompressStream(file) + rdr, err := compression.DecompressStream(file) if err != nil { return false } @@ -132,242 +126,25 @@ func IsArchivePath(path string) bool { return err == nil } -const ( - zstdMagicSkippableStart = 0x184D2A50 - zstdMagicSkippableMask = 0xFFFFFFF0 -) - -var ( - bzip2Magic = []byte{0x42, 0x5A, 0x68} - gzipMagic = []byte{0x1F, 0x8B, 0x08} - xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} - zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} -) - -type matcher = func([]byte) bool - -func magicNumberMatcher(m []byte) matcher { - return func(source []byte) bool { - return bytes.HasPrefix(source, m) - } -} - -// zstdMatcher detects zstd compression algorithm. -// Zstandard compressed data is made of one or more frames. -// There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames. -// See https://datatracker.ietf.org/doc/html/rfc8878#section-3 for more details. -func zstdMatcher() matcher { - return func(source []byte) bool { - if bytes.HasPrefix(source, zstdMagic) { - // Zstandard frame - return true - } - // skippable frame - if len(source) < 8 { - return false - } - // magic number from 0x184D2A50 to 0x184D2A5F. - if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart { - return true - } - return false - } -} - // DetectCompression detects the compression algorithm of the source. -func DetectCompression(source []byte) Compression { - compressionMap := map[Compression]matcher{ - Bzip2: magicNumberMatcher(bzip2Magic), - Gzip: magicNumberMatcher(gzipMagic), - Xz: magicNumberMatcher(xzMagic), - Zstd: zstdMatcher(), - } - for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} { - fn := compressionMap[compression] - if fn(source) { - return compression - } - } - return Uncompressed -} - -func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) { - args := []string{"xz", "-d", "-c", "-q"} - - return cmdStream(exec.CommandContext(ctx, args[0], args[1:]...), archive) -} - -func gzDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { - if noPigzEnv := os.Getenv("MOBY_DISABLE_PIGZ"); noPigzEnv != "" { - noPigz, err := strconv.ParseBool(noPigzEnv) - if err != nil { - log.G(ctx).WithError(err).Warn("invalid value in MOBY_DISABLE_PIGZ env var") - } - if noPigz { - log.G(ctx).Debugf("Use of pigz is disabled due to MOBY_DISABLE_PIGZ=%s", noPigzEnv) - return gzip.NewReader(buf) - } - } - - unpigzPath, err := exec.LookPath("unpigz") - if err != nil { - log.G(ctx).Debugf("unpigz binary not found, falling back to go gzip library") - return gzip.NewReader(buf) - } - - log.G(ctx).Debugf("Using %s to decompress", unpigzPath) - - return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) -} - -type readCloserWrapper struct { - io.Reader - closer func() error - closed atomic.Bool -} - -func (r *readCloserWrapper) Close() error { - if !r.closed.CompareAndSwap(false, true) { - log.G(context.TODO()).Error("subsequent attempt to close readCloserWrapper") - if log.GetLevel() >= log.DebugLevel { - log.G(context.TODO()).Errorf("stack trace: %s", string(debug.Stack())) - } - - return nil - } - if r.closer != nil { - return r.closer() - } - return nil -} - -var bufioReader32KPool = &sync.Pool{ - New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, -} - -type bufferedReader struct { - buf *bufio.Reader -} - -func newBufferedReader(r io.Reader) *bufferedReader { - buf := bufioReader32KPool.Get().(*bufio.Reader) - buf.Reset(r) - return &bufferedReader{buf} -} - -func (r *bufferedReader) Read(p []byte) (int, error) { - if r.buf == nil { - return 0, io.EOF - } - n, err := r.buf.Read(p) - if err == io.EOF { - r.buf.Reset(nil) - bufioReader32KPool.Put(r.buf) - r.buf = nil - } - return n, err -} - -func (r *bufferedReader) Peek(n int) ([]byte, error) { - if r.buf == nil { - return nil, io.EOF - } - return r.buf.Peek(n) +// +// Deprecated: use [compression.Detect]. +func DetectCompression(source []byte) compression.Compression { + return compression.Detect(source) } // DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. +// +// Deprecated: use [compression.DecompressStream]. func DecompressStream(archive io.Reader) (io.ReadCloser, error) { - buf := newBufferedReader(archive) - bs, err := buf.Peek(10) - if err != nil && err != io.EOF { - // Note: we'll ignore any io.EOF error because there are some odd - // cases where the layer.tar file will be empty (zero bytes) and - // that results in an io.EOF from the Peek() call. So, in those - // cases we'll just treat it as a non-compressed stream and - // that means just create an empty layer. - // See Issue 18170 - return nil, err - } - - compression := DetectCompression(bs) - switch compression { - case Uncompressed: - return &readCloserWrapper{ - Reader: buf, - }, nil - case Gzip: - ctx, cancel := context.WithCancel(context.Background()) - - gzReader, err := gzDecompress(ctx, buf) - if err != nil { - cancel() - return nil, err - } - return &readCloserWrapper{ - Reader: gzReader, - closer: func() error { - cancel() - return gzReader.Close() - }, - }, nil - case Bzip2: - bz2Reader := bzip2.NewReader(buf) - return &readCloserWrapper{ - Reader: bz2Reader, - }, nil - case Xz: - ctx, cancel := context.WithCancel(context.Background()) - - xzReader, err := xzDecompress(ctx, buf) - if err != nil { - cancel() - return nil, err - } - - return &readCloserWrapper{ - Reader: xzReader, - closer: func() error { - cancel() - return xzReader.Close() - }, - }, nil - case Zstd: - zstdReader, err := zstd.NewReader(buf) - if err != nil { - return nil, err - } - return &readCloserWrapper{ - Reader: zstdReader, - closer: func() error { - zstdReader.Close() - return nil - }, - }, nil - default: - return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) - } + return compression.DecompressStream(archive) } -type nopWriteCloser struct { - io.Writer -} - -func (nopWriteCloser) Close() error { return nil } - // CompressStream compresses the dest with specified compression algorithm. -func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { - switch compression { - case Uncompressed: - return nopWriteCloser{dest}, nil - case Gzip: - return gzip.NewWriter(dest), nil - case Bzip2, Xz: - // archive/bzip2 does not support writing, and there is no xz support at all - // However, this is not a problem as docker only currently generates gzipped tars - return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) - default: - return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) - } +// +// Deprecated: use [compression.CompressStream]. +func CompressStream(dest io.Writer, comp compression.Compression) (io.WriteCloser, error) { + return compression.CompressStream(dest, comp) } // TarModifierFunc is a function that can be passed to ReplaceFileTarWrapper to @@ -416,7 +193,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi var originalHeader *tar.Header for { originalHeader, err = tarReader.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { @@ -458,90 +235,11 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi return pipeReader } -// Extension returns the extension of a file that uses the specified compression algorithm. -func (compression *Compression) Extension() string { - switch *compression { - case Uncompressed: - return "tar" - case Bzip2: - return "tar.bz2" - case Gzip: - return "tar.gz" - case Xz: - return "tar.xz" - case Zstd: - return "tar.zst" - } - return "" -} - -// assert that we implement [tar.FileInfoNames]. -// -// TODO(thaJeztah): disabled to allow compiling on < go1.23. un-comment once we drop support for older versions of go. -// 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() interface{} { - // 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 -} - -// sysStat, if non-nil, populates hdr from system-dependent fields of fi. -var sysStat func(fi os.FileInfo, hdr *tar.Header) error - // 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. +// Deprecated: use [tarheader.FileInfoHeaderNoLookups]. func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link) - if err != nil { - return nil, err - } - if sysStat != nil { - return hdr, sysStat(fi, hdr) - } - return hdr, nil + return tarheader.FileInfoHeaderNoLookups(fi, link) } // FileInfoHeader creates a populated Header from fi. @@ -552,7 +250,7 @@ func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { // 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 := FileInfoHeaderNoLookups(fi, link) + hdr, err := tarheader.FileInfoHeaderNoLookups(fi, link) if err != nil { return nil, err } @@ -768,7 +466,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o 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(path); !(err == nil && fi.IsDir()) { + if fi, err := os.Lstat(path); err != nil || !fi.IsDir() { if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { return err } @@ -908,8 +606,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. -func Tar(path string, compression Compression) (io.ReadCloser, error) { - return TarWithOptions(path, &TarOptions{Compression: compression}) +func Tar(path string, comp compression.Compression) (io.ReadCloser, error) { + return TarWithOptions(path, &TarOptions{Compression: comp}) } // TarWithOptions creates an archive from the directory at `path`, only including files whose relative @@ -945,7 +643,7 @@ func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) { pipeReader, pipeWriter := io.Pipe() - compressWriter, err := CompressStream(pipeWriter, options.Compression) + compressWriter, err := compression.CompressStream(pipeWriter, options.Compression) if err != nil { return nil, err } @@ -1031,7 +729,8 @@ func (t *Tarballer) Do() { ) walkRoot := getWalkRoot(t.srcPath, include) - filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error { + // 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 @@ -1135,7 +834,7 @@ func (t *Tarballer) Do() { 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 err == io.ErrClosedPipe { + if errors.Is(err, io.ErrClosedPipe) { return err } } @@ -1155,7 +854,7 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err loop: for { hdr, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { // end of tar archive break } @@ -1217,7 +916,7 @@ loop: continue } - if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { + if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { if err := os.RemoveAll(path); err != nil { return err } @@ -1309,7 +1008,7 @@ func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) e // Handler for teasing out the automatic decompression func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { if tarArchive == nil { - return fmt.Errorf("Empty archive") + return errors.New("empty archive") } dest = filepath.Clean(dest) if options == nil { @@ -1321,7 +1020,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp r := tarArchive if decompress { - decompressedArchive, err := DecompressStream(tarArchive) + decompressedArchive, err := compression.DecompressStream(tarArchive) if err != nil { return err } @@ -1335,15 +1034,14 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp // 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 := TarWithOptions(src, &TarOptions{Compression: Uncompressed}) + archive, err := Tar(src, compression.None) if err != nil { return err } defer archive.Close() - options := &TarOptions{ + return archiver.Untar(archive, dst, &TarOptions{ IDMap: archiver.IDMapping, - } - return archiver.Untar(archive, dst, options) + }) } // UntarPath untar a file from path to a destination, src is the source tar file path. @@ -1353,10 +1051,9 @@ func (archiver *Archiver) UntarPath(src, dst string) error { return err } defer archive.Close() - options := &TarOptions{ + return archiver.Untar(archive, dst, &TarOptions{ IDMap: archiver.IDMapping, - } - return archiver.Untar(archive, dst, options) + }) } // CopyWithTar creates a tar archive of filesystem path `src`, and @@ -1393,7 +1090,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { } if srcSt.IsDir() { - return fmt.Errorf("Can't copy a directory") + return errors.New("can't copy a directory") } // Clean up the trailing slash. This must be done in an operating @@ -1421,7 +1118,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { } defer srcF.Close() - hdr, err := FileInfoHeaderNoLookups(srcSt, "") + hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") if err != nil { return err } @@ -1470,43 +1167,3 @@ func remapIDs(idMapping user.IdentityMapping, hdr *tar.Header) error { hdr.Uid, hdr.Gid = uid, gid return err } - -// cmdStream executes a command, and returns its stdout as a stream. -// If the command fails to run or doesn't complete successfully, an error -// will be returned, including anything written on stderr. -func cmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) { - cmd.Stdin = input - pipeR, pipeW := io.Pipe() - cmd.Stdout = pipeW - var errBuf bytes.Buffer - cmd.Stderr = &errBuf - - // Run the command and return the pipe - if err := cmd.Start(); err != nil { - return nil, err - } - - // Ensure the command has exited before we clean anything up - done := make(chan struct{}) - - // Copy stdout to the returned pipe - go func() { - if err := cmd.Wait(); err != nil { - pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errBuf.String())) - } else { - pipeW.Close() - } - close(done) - }() - - return &readCloserWrapper{ - Reader: pipeR, - closer: func() error { - // Close pipeR, and then wait for the command to complete before returning. We have to close pipeR first, as - // cmd.Wait waits for any non-file stdout/stderr/stdin to close. - err := pipeR.Close() - <-done - return err - }, - }, nil -} diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go index 715ac4c6..3a9f5b0b 100644 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ b/vendor/github.com/moby/go-archive/archive_unix.go @@ -7,17 +7,12 @@ import ( "errors" "os" "path/filepath" - "runtime" "strings" "syscall" "golang.org/x/sys/unix" ) -func init() { - sysStat = statUnix -} - // 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 { @@ -38,40 +33,6 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm // noop for unix as golang APIs provide perm bits correctly } -// statUnix populates hdr from system-dependent fields of fi without performing -// any OS lookups. -func statUnix(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 { - hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert - hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert - } - - return nil -} - func getInodeFromStat(stat interface{}) (uint64, error) { s, ok := stat.(*syscall.Stat_t) if !ok { diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go index 108ee5d1..0e3e316a 100644 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ b/vendor/github.com/moby/go-archive/archive_windows.go @@ -41,11 +41,6 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm | 0o111 } -func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) { - // do nothing. no notion of Rdev, Nlink in stat on Windows - return -} - func getInodeFromStat(stat interface{}) (uint64, error) { // do nothing. no notion of Inode in stat on Windows return 0, nil diff --git a/vendor/github.com/moby/go-archive/changes.go b/vendor/github.com/moby/go-archive/changes.go index 036a1b09..02a0372c 100644 --- a/vendor/github.com/moby/go-archive/changes.go +++ b/vendor/github.com/moby/go-archive/changes.go @@ -75,7 +75,7 @@ func sameFsTime(a, b time.Time) bool { // 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 changes(layers, rw, aufsDeletedFile, aufsMetadataSkip) + return collectChanges(layers, rw, aufsDeletedFile, aufsMetadataSkip) } func aufsMetadataSkip(path string) (skip bool, err error) { @@ -103,7 +103,7 @@ type ( deleteChange func(string, string, os.FileInfo) (string, error) ) -func changes(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) { +func collectChanges(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) { var ( changes []Change changedDirs = make(map[string]struct{}) diff --git a/vendor/github.com/moby/go-archive/changes_linux.go b/vendor/github.com/moby/go-archive/changes_linux.go index 9a041b09..8289fe17 100644 --- a/vendor/github.com/moby/go-archive/changes_linux.go +++ b/vendor/github.com/moby/go-archive/changes_linux.go @@ -132,14 +132,7 @@ func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) { ix1 := 0 ix2 := 0 - for { - if ix1 >= len(names1) { - break - } - if ix2 >= len(names2) { - break - } - + for ix1 < len(names1) && ix2 < len(names2) { ni1 := names1[ix1] ni2 := names2[ix2] diff --git a/vendor/github.com/moby/go-archive/compression/compression.go b/vendor/github.com/moby/go-archive/compression/compression.go new file mode 100644 index 00000000..e298cefb --- /dev/null +++ b/vendor/github.com/moby/go-archive/compression/compression.go @@ -0,0 +1,263 @@ +package compression + +import ( + "bufio" + "bytes" + "compress/bzip2" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "sync" + + "github.com/containerd/log" + "github.com/klauspost/compress/zstd" +) + +// Compression is the state represents if compressed or not. +type Compression int + +const ( + None Compression = 0 // None represents the uncompressed. + Bzip2 Compression = 1 // Bzip2 is bzip2 compression algorithm. + Gzip Compression = 2 // Gzip is gzip compression algorithm. + Xz Compression = 3 // Xz is xz compression algorithm. + Zstd Compression = 4 // Zstd is zstd compression algorithm. +) + +// Extension returns the extension of a file that uses the specified compression algorithm. +func (c *Compression) Extension() string { + switch *c { + case None: + return "tar" + case Bzip2: + return "tar.bz2" + case Gzip: + return "tar.gz" + case Xz: + return "tar.xz" + case Zstd: + return "tar.zst" + default: + return "" + } +} + +type readCloserWrapper struct { + io.Reader + closer func() error +} + +func (r *readCloserWrapper) Close() error { + if r.closer != nil { + return r.closer() + } + return nil +} + +type nopWriteCloser struct { + io.Writer +} + +func (nopWriteCloser) Close() error { return nil } + +var bufioReader32KPool = &sync.Pool{ + New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, +} + +type bufferedReader struct { + buf *bufio.Reader +} + +func newBufferedReader(r io.Reader) *bufferedReader { + buf := bufioReader32KPool.Get().(*bufio.Reader) + buf.Reset(r) + return &bufferedReader{buf} +} + +func (r *bufferedReader) Read(p []byte) (int, error) { + if r.buf == nil { + return 0, io.EOF + } + n, err := r.buf.Read(p) + if errors.Is(err, io.EOF) { + r.buf.Reset(nil) + bufioReader32KPool.Put(r.buf) + r.buf = nil + } + return n, err +} + +func (r *bufferedReader) Peek(n int) ([]byte, error) { + if r.buf == nil { + return nil, io.EOF + } + return r.buf.Peek(n) +} + +// DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. +func DecompressStream(archive io.Reader) (io.ReadCloser, error) { + buf := newBufferedReader(archive) + bs, err := buf.Peek(10) + if err != nil && !errors.Is(err, io.EOF) { + // Note: we'll ignore any io.EOF error because there are some odd + // cases where the layer.tar file will be empty (zero bytes) and + // that results in an io.EOF from the Peek() call. So, in those + // cases we'll just treat it as a non-compressed stream and + // that means just create an empty layer. + // See Issue 18170 + return nil, err + } + + switch compression := Detect(bs); compression { + case None: + return &readCloserWrapper{ + Reader: buf, + }, nil + case Gzip: + ctx, cancel := context.WithCancel(context.Background()) + gzReader, err := gzipDecompress(ctx, buf) + if err != nil { + cancel() + return nil, err + } + + return &readCloserWrapper{ + Reader: gzReader, + closer: func() error { + cancel() + return gzReader.Close() + }, + }, nil + case Bzip2: + bz2Reader := bzip2.NewReader(buf) + return &readCloserWrapper{ + Reader: bz2Reader, + }, nil + case Xz: + ctx, cancel := context.WithCancel(context.Background()) + + xzReader, err := xzDecompress(ctx, buf) + if err != nil { + cancel() + return nil, err + } + + return &readCloserWrapper{ + Reader: xzReader, + closer: func() error { + cancel() + return xzReader.Close() + }, + }, nil + case Zstd: + zstdReader, err := zstd.NewReader(buf) + if err != nil { + return nil, err + } + return &readCloserWrapper{ + Reader: zstdReader, + closer: func() error { + zstdReader.Close() + return nil + }, + }, nil + + default: + return nil, fmt.Errorf("unsupported compression format (%d)", compression) + } +} + +// CompressStream compresses the dest with specified compression algorithm. +func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { + switch compression { + case None: + return nopWriteCloser{dest}, nil + case Gzip: + return gzip.NewWriter(dest), nil + case Bzip2: + // archive/bzip2 does not support writing. + return nil, errors.New("unsupported compression format: tar.bz2") + case Xz: + // there is no xz support at all + // However, this is not a problem as docker only currently generates gzipped tars + return nil, errors.New("unsupported compression format: tar.xz") + default: + return nil, fmt.Errorf("unsupported compression format (%d)", compression) + } +} + +func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) { + args := []string{"xz", "-d", "-c", "-q"} + + return cmdStream(exec.CommandContext(ctx, args[0], args[1:]...), archive) +} + +func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { + if noPigzEnv := os.Getenv("MOBY_DISABLE_PIGZ"); noPigzEnv != "" { + noPigz, err := strconv.ParseBool(noPigzEnv) + if err != nil { + log.G(ctx).WithError(err).Warn("invalid value in MOBY_DISABLE_PIGZ env var") + } + if noPigz { + log.G(ctx).Debugf("Use of pigz is disabled due to MOBY_DISABLE_PIGZ=%s", noPigzEnv) + return gzip.NewReader(buf) + } + } + + unpigzPath, err := exec.LookPath("unpigz") + if err != nil { + log.G(ctx).Debugf("unpigz binary not found, falling back to go gzip library") + return gzip.NewReader(buf) + } + + log.G(ctx).Debugf("Using %s to decompress", unpigzPath) + + return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) +} + +// cmdStream executes a command, and returns its stdout as a stream. +// If the command fails to run or doesn't complete successfully, an error +// will be returned, including anything written on stderr. +func cmdStream(cmd *exec.Cmd, in io.Reader) (io.ReadCloser, error) { + reader, writer := io.Pipe() + + cmd.Stdin = in + cmd.Stdout = writer + + var errBuf bytes.Buffer + cmd.Stderr = &errBuf + + // Run the command and return the pipe + if err := cmd.Start(); err != nil { + return nil, err + } + + // Ensure the command has exited before we clean anything up + done := make(chan struct{}) + + // Copy stdout to the returned pipe + go func() { + if err := cmd.Wait(); err != nil { + _ = writer.CloseWithError(fmt.Errorf("%w: %s", err, errBuf.String())) + } else { + _ = writer.Close() + } + close(done) + }() + + return &readCloserWrapper{ + Reader: reader, + closer: func() error { + // Close pipeR, and then wait for the command to complete before returning. We have to close pipeR first, as + // cmd.Wait waits for any non-file stdout/stderr/stdin to close. + err := reader.Close() + <-done + return err + }, + }, nil +} diff --git a/vendor/github.com/moby/go-archive/compression/compression_detect.go b/vendor/github.com/moby/go-archive/compression/compression_detect.go new file mode 100644 index 00000000..85eda927 --- /dev/null +++ b/vendor/github.com/moby/go-archive/compression/compression_detect.go @@ -0,0 +1,65 @@ +package compression + +import ( + "bytes" + "encoding/binary" +) + +const ( + zstdMagicSkippableStart = 0x184D2A50 + zstdMagicSkippableMask = 0xFFFFFFF0 +) + +var ( + bzip2Magic = []byte{0x42, 0x5A, 0x68} + gzipMagic = []byte{0x1F, 0x8B, 0x08} + xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} + zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} +) + +type matcher = func([]byte) bool + +// Detect detects the compression algorithm of the source. +func Detect(source []byte) Compression { + compressionMap := map[Compression]matcher{ + Bzip2: magicNumberMatcher(bzip2Magic), + Gzip: magicNumberMatcher(gzipMagic), + Xz: magicNumberMatcher(xzMagic), + Zstd: zstdMatcher(), + } + for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} { + fn := compressionMap[compression] + if fn(source) { + return compression + } + } + return None +} + +func magicNumberMatcher(m []byte) matcher { + return func(source []byte) bool { + return bytes.HasPrefix(source, m) + } +} + +// zstdMatcher detects zstd compression algorithm. +// Zstandard compressed data is made of one or more frames. +// There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames. +// See https://datatracker.ietf.org/doc/html/rfc8878#section-3 for more details. +func zstdMatcher() matcher { + return func(source []byte) bool { + if bytes.HasPrefix(source, zstdMagic) { + // Zstandard frame + return true + } + // skippable frame + if len(source) < 8 { + return false + } + // magic number from 0x184D2A50 to 0x184D2A5F. + if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart { + return true + } + return false + } +} diff --git a/vendor/github.com/moby/go-archive/copy.go b/vendor/github.com/moby/go-archive/copy.go index cae0173d..77d038c4 100644 --- a/vendor/github.com/moby/go-archive/copy.go +++ b/vendor/github.com/moby/go-archive/copy.go @@ -128,7 +128,6 @@ func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, _ func TarResourceRebaseOpts(sourceBase string, rebaseName string) *TarOptions { filter := []string{sourceBase} return &TarOptions{ - Compression: Uncompressed, IncludeFiles: filter, IncludeSourceDir: true, RebaseNames: map[string]string{ @@ -335,7 +334,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read for { hdr, err := srcTar.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { // Signals end of archive. rebasedTar.Close() w.Close() diff --git a/vendor/github.com/moby/go-archive/dev_freebsd.go b/vendor/github.com/moby/go-archive/dev_freebsd.go index aa8e2915..b3068fce 100644 --- a/vendor/github.com/moby/go-archive/dev_freebsd.go +++ b/vendor/github.com/moby/go-archive/dev_freebsd.go @@ -4,4 +4,6 @@ package archive import "golang.org/x/sys/unix" -var mknod = unix.Mknod +func mknod(path string, mode uint32, dev uint64) error { + return unix.Mknod(path, mode, dev) +} diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go index d5a394cd..96db972d 100644 --- a/vendor/github.com/moby/go-archive/diff.go +++ b/vendor/github.com/moby/go-archive/diff.go @@ -3,6 +3,7 @@ package archive import ( "archive/tar" "context" + "errors" "fmt" "io" "os" @@ -11,6 +12,8 @@ import ( "strings" "github.com/containerd/log" + + "github.com/moby/go-archive/compression" ) // UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be @@ -35,7 +38,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, // Iterate through the files in the archive. for { hdr, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { // end of tar archive break } @@ -149,7 +152,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, // 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(path); err == nil { - if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { + if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { if err := os.RemoveAll(path); err != nil { return 0, err } @@ -165,7 +168,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, linkBasename := filepath.Base(hdr.Linkname) srcHdr = aufsHardlinks[linkBasename] if srcHdr == nil { - return 0, fmt.Errorf("Invalid aufs hardlink") + return 0, errors.New("invalid aufs hardlink") } tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) if err != nil { @@ -221,18 +224,18 @@ func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) ( // IsEmpty checks if the tar archive is empty (doesn't contain any entries). func IsEmpty(rd io.Reader) (bool, error) { - decompRd, err := DecompressStream(rd) + decompRd, err := compression.DecompressStream(rd) if err != nil { - return true, fmt.Errorf("failed to decompress archive: %v", err) + return true, fmt.Errorf("failed to decompress archive: %w", err) } defer decompRd.Close() tarReader := tar.NewReader(decompRd) if _, err := tarReader.Next(); err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { return true, nil } - return false, fmt.Errorf("failed to read next archive header: %v", err) + return false, fmt.Errorf("failed to read next archive header: %w", err) } return false, nil @@ -247,7 +250,7 @@ func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decomp defer restore() if decompress { - decompLayer, err := DecompressStream(layer) + decompLayer, err := compression.DecompressStream(layer) if err != nil { return 0, err } diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader.go b/vendor/github.com/moby/go-archive/tarheader/tarheader.go new file mode 100644 index 00000000..03732a4f --- /dev/null +++ b/vendor/github.com/moby/go-archive/tarheader/tarheader.go @@ -0,0 +1,67 @@ +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() interface{} { + // 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 new file mode 100644 index 00000000..9c3311c6 --- /dev/null +++ b/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go @@ -0,0 +1,46 @@ +//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 { + hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert + hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert + } + + 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 new file mode 100644 index 00000000..5d4483ce --- /dev/null +++ b/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go @@ -0,0 +1,12 @@ +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/modules.txt b/vendor/modules.txt index ebca10d4..66849124 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -566,9 +566,11 @@ 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.0.0-20250404171912-21f3f3385ab7 +# github.com/moby/go-archive v0.1.0 ## explicit; go 1.23.0 github.com/moby/go-archive +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