From 01b40b15dd3eb2d2a9eecd9d816257e73d48fc18 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 15 Jan 2026 12:55:15 +0100 Subject: [PATCH] vendor: github.com/containerd/stargz-snapshotter v0.18.1 full diff: https://github.com/containerd/stargz-snapshotter/compare/v0.17.0...v0.18.1 full diff: https://github.com/containerd/stargz-snapshotter/compare/estargz/v0.17.0...estargz/v0.18.1 Signed-off-by: Sebastiaan van Stijn --- go.mod | 6 +- go.sum | 12 +- .../stargz-snapshotter/cache/cache.go | 36 +++- .../stargz-snapshotter/estargz/build.go | 111 ++++++++++--- .../stargz-snapshotter/estargz/estargz.go | 9 + .../stargz-snapshotter/estargz/testutil.go | 155 +++++++++++------- .../stargz-snapshotter/fs/config/config.go | 3 + .../containerd/stargz-snapshotter/fs/fs.go | 25 +-- .../stargz-snapshotter/fs/layer/layer.go | 11 +- .../stargz-snapshotter/fs/layer/testutil.go | 97 +++++++---- .../stargz-snapshotter/fs/reader/reader.go | 9 +- .../stargz-snapshotter/fs/reader/testutil.go | 70 +++++--- .../stargz-snapshotter/snapshot/snapshot.go | 29 +++- .../stargz-snapshotter/util/testutil/util.go | 7 +- .../github.com/hanwen/go-fuse/v2/fs/README.md | 12 +- vendor/github.com/hanwen/go-fuse/v2/fs/api.go | 112 ++++++++++--- .../github.com/hanwen/go-fuse/v2/fs/bridge.go | 10 +- .../github.com/hanwen/go-fuse/v2/fs/inode.go | 7 +- .../hanwen/go-fuse/v2/fs/loopback.go | 14 +- .../github.com/hanwen/go-fuse/v2/fuse/api.go | 99 ++++++----- .../hanwen/go-fuse/v2/fuse/constants.go | 4 + .../hanwen/go-fuse/v2/fuse/context.go | 4 +- .../hanwen/go-fuse/v2/fuse/mount_darwin.go | 4 + .../hanwen/go-fuse/v2/fuse/mount_freebsd.go | 4 + .../hanwen/go-fuse/v2/fuse/mount_linux.go | 21 +++ .../hanwen/go-fuse/v2/fuse/print.go | 9 +- .../hanwen/go-fuse/v2/fuse/print_linux.go | 1 + .../hanwen/go-fuse/v2/fuse/protocol-server.go | 5 - .../hanwen/go-fuse/v2/fuse/request.go | 10 ++ .../hanwen/go-fuse/v2/fuse/server.go | 9 +- .../hanwen/go-fuse/v2/fuse/types.go | 6 +- .../v2/internal/openat/openat_linux.go | 7 +- vendor/modules.txt | 8 +- 33 files changed, 638 insertions(+), 288 deletions(-) diff --git a/go.mod b/go.mod index 8c2868859ac8..263dcc734eb6 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( github.com/containerd/log v0.1.0 github.com/containerd/nydus-snapshotter v0.15.10 github.com/containerd/platforms v1.0.0-rc.2 - github.com/containerd/stargz-snapshotter v0.17.0 - github.com/containerd/stargz-snapshotter/estargz v0.17.0 + github.com/containerd/stargz-snapshotter v0.18.1 + github.com/containerd/stargz-snapshotter/estargz v0.18.1 github.com/containerd/typeurl/v2 v2.2.3 github.com/containernetworking/plugins v1.9.0 github.com/coreos/go-systemd/v22 v22.6.0 @@ -190,7 +190,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/hanwen/go-fuse/v2 v2.8.0 // indirect + github.com/hanwen/go-fuse/v2 v2.9.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/in-toto/attestation v1.1.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/go.sum b/go.sum index dd3f291c05bc..030ec1093b9c 100644 --- a/go.sum +++ b/go.sum @@ -161,10 +161,10 @@ github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6a github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= -github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc= -github.com/containerd/stargz-snapshotter v0.17.0/go.mod h1:ySEul1ck7jCE4jqsuFCo8FFLrHU20UWQeI9g7mdsanI= -github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE= -github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM= +github.com/containerd/stargz-snapshotter v0.18.1 h1:eIkwsafohSWas5YmhxoumrI7elmb2EZJcW8eu7goyOY= +github.com/containerd/stargz-snapshotter v0.18.1/go.mod h1:HPC+XHGIxkjWfAONMvXepQyOs8iGApP2e5A3fOv2TCU= +github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= +github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= @@ -346,8 +346,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/hanwen/go-fuse/v2 v2.8.0 h1:wV8rG7rmCz8XHSOwBZhG5YcVqcYjkzivjmbaMafPlAs= -github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI= +github.com/hanwen/go-fuse/v2 v2.9.0 h1:0AOGUkHtbOVeyGLr0tXupiid1Vg7QB7M6YUcdmVdC58= +github.com/hanwen/go-fuse/v2 v2.9.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= diff --git a/vendor/github.com/containerd/stargz-snapshotter/cache/cache.go b/vendor/github.com/containerd/stargz-snapshotter/cache/cache.go index 13c5ad35a8b0..d4f798b9e11c 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/cache/cache.go +++ b/vendor/github.com/containerd/stargz-snapshotter/cache/cache.go @@ -27,6 +27,7 @@ import ( "github.com/containerd/stargz-snapshotter/util/cacheutil" "github.com/containerd/stargz-snapshotter/util/namedmutex" + "golang.org/x/sys/unix" ) const ( @@ -61,6 +62,9 @@ type DirectoryCacheConfig struct { // Direct forcefully enables direct mode for all operation in cache. // Thus operation won't use on-memory caches. Direct bool + + // FadvDontNeed forcefully clean fscache pagecache for saving memory. + FadvDontNeed bool } // TODO: contents validation. @@ -173,6 +177,7 @@ func NewDirectoryCache(directory string, config DirectoryCacheConfig) (BlobCache wipDirectory: wipdir, bufPool: bufPool, direct: config.Direct, + fadvDontNeed: config.FadvDontNeed, } dc.syncAdd = config.SyncAdd return dc, nil @@ -188,8 +193,9 @@ type directoryCache struct { bufPool *sync.Pool - syncAdd bool - direct bool + syncAdd bool + direct bool + fadvDontNeed bool closed bool closedMu sync.Mutex @@ -244,6 +250,12 @@ func (dc *directoryCache) Get(key string, opts ...Option) (Reader, error) { return &reader{ ReaderAt: file, closeFunc: func() error { + if dc.fadvDontNeed { + if err := dropFilePageCache(file); err != nil { + fmt.Printf("Warning: failed to drop page cache: %v\n", err) + } + } + // In passthough model, close will be toke over by go-fuse // If "passThrough" option is specified, "direct" option also will // be specified, so adding this branch here is enough @@ -301,6 +313,13 @@ func (dc *directoryCache) Add(key string, opts ...Option) (Writer, error) { errs = append(errs, fmt.Errorf("failed to create cache directory %q: %w", c, err)) return errors.Join(errs...) } + + if dc.fadvDontNeed { + if err := dropFilePageCache(wip); err != nil { + fmt.Printf("Warning: failed to drop page cache: %v\n", err) + } + } + return os.Rename(wip.Name(), c) }, abortFunc: func() error { @@ -463,3 +482,16 @@ func (w *writeCloser) Close() error { return w.closeFunc() } func nopWriteCloser(w io.Writer) io.WriteCloser { return &writeCloser{w, func() error { return nil }} } + +func dropFilePageCache(file *os.File) error { + if file == nil { + return nil + } + + fd := file.Fd() + err := unix.Fadvise(int(fd), 0, 0, unix.FADV_DONTNEED) + if err != nil { + return fmt.Errorf("posix_fadvise failed, ret=%d", err) + } + return nil +} diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go index 8b804b7dde4e..a9e1b72ba781 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go @@ -35,6 +35,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "github.com/containerd/stargz-snapshotter/estargz/errorutil" "github.com/klauspost/compress/zstd" @@ -42,6 +43,8 @@ import ( "golang.org/x/sync/errgroup" ) +type GzipHelperFunc func(io.Reader) (io.ReadCloser, error) + type options struct { chunkSize int compressionLevel int @@ -50,6 +53,7 @@ type options struct { compression Compression ctx context.Context minChunkSize int + gzipHelperFunc GzipHelperFunc } type Option func(o *options) error @@ -127,11 +131,25 @@ func WithMinChunkSize(minChunkSize int) Option { } } +// WithGzipHelperFunc option specifies a custom function to decompress gzip-compressed layers. +// When a gzip-compressed layer is detected, this function will be used instead of the +// Go standard library gzip decompression for better performance. +// The function should take an io.Reader as input and return an io.ReadCloser. +// If nil, the Go standard library gzip.NewReader will be used. +func WithGzipHelperFunc(gzipHelperFunc GzipHelperFunc) Option { + return func(o *options) error { + o.gzipHelperFunc = gzipHelperFunc + return nil + } +} + // Blob is an eStargz blob. type Blob struct { io.ReadCloser - diffID digest.Digester - tocDigest digest.Digest + diffID digest.Digester + tocDigest digest.Digest + readCompleted *atomic.Bool + uncompressedSize *atomic.Int64 } // DiffID returns the digest of uncompressed blob. @@ -145,6 +163,19 @@ func (b *Blob) TOCDigest() digest.Digest { return b.tocDigest } +// UncompressedSize returns the size of uncompressed blob. +// UncompressedSize should only be called after the blob has been fully read. +func (b *Blob) UncompressedSize() (int64, error) { + switch { + case b.uncompressedSize == nil || b.readCompleted == nil: + return -1, fmt.Errorf("readCompleted or uncompressedSize is not initialized") + case !b.readCompleted.Load(): + return -1, fmt.Errorf("called UncompressedSize before the blob has been fully read") + default: + return b.uncompressedSize.Load(), nil + } +} + // Build builds an eStargz blob which is an extended version of stargz, from a blob (gzip, zstd // or plain tar) passed through the argument. If there are some prioritized files are listed in // the option, these files are grouped as "prioritized" and can be used for runtime optimization @@ -186,7 +217,7 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) { rErr = fmt.Errorf("error from context %q: %w", cErr, rErr) } }() - tarBlob, err := decompressBlob(tarBlob, layerFiles) + tarBlob, err := decompressBlob(tarBlob, layerFiles, opts.gzipHelperFunc) if err != nil { return nil, err } @@ -252,17 +283,28 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) { } diffID := digest.Canonical.Digester() pr, pw := io.Pipe() + readCompleted := new(atomic.Bool) + uncompressedSize := new(atomic.Int64) go func() { - r, err := opts.compression.Reader(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw)) + var size int64 + var decompressFunc func(io.Reader) (io.ReadCloser, error) + if _, ok := opts.compression.(*gzipCompression); ok && opts.gzipHelperFunc != nil { + decompressFunc = opts.gzipHelperFunc + } else { + decompressFunc = opts.compression.Reader + } + decompressR, err := decompressFunc(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw)) if err != nil { pw.CloseWithError(err) return } - defer r.Close() - if _, err := io.Copy(diffID.Hash(), r); err != nil { + defer decompressR.Close() + if size, err = io.Copy(diffID.Hash(), decompressR); err != nil { pw.CloseWithError(err) return } + uncompressedSize.Store(size) + readCompleted.Store(true) pw.Close() }() return &Blob{ @@ -270,8 +312,10 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) { Reader: pr, closeFunc: layerFiles.CleanupAll, }, - tocDigest: tocDgst, - diffID: diffID, + tocDigest: tocDgst, + diffID: diffID, + readCompleted: readCompleted, + uncompressedSize: uncompressedSize, }, nil } @@ -366,8 +410,9 @@ func sortEntries(in io.ReaderAt, prioritized []string, missedPrioritized *[]stri // Sort the tar file respecting to the prioritized files list. sorted := &tarFile{} + picked := make(map[string]struct{}) for _, l := range prioritized { - if err := moveRec(l, intar, sorted); err != nil { + if err := moveRec(l, intar, sorted, picked); err != nil { if errors.Is(err, errNotFound) && missedPrioritized != nil { *missedPrioritized = append(*missedPrioritized, l) continue // allow not found @@ -395,8 +440,8 @@ func sortEntries(in io.ReaderAt, prioritized []string, missedPrioritized *[]stri }) } - // Dump all entry and concatinate them. - return append(sorted.dump(), intar.dump()...), nil + // Dump prioritized entries followed by the rest entries while skipping picked ones. + return append(sorted.dump(nil), intar.dump(picked)...), nil } // readerFromEntries returns a reader of tar archive that contains entries passed @@ -458,36 +503,42 @@ func importTar(in io.ReaderAt) (*tarFile, error) { return tf, nil } -func moveRec(name string, in *tarFile, out *tarFile) error { +func moveRec(name string, in *tarFile, out *tarFile, picked map[string]struct{}) error { name = cleanEntryName(name) if name == "" { // root directory. stop recursion. if e, ok := in.get(name); ok { // entry of the root directory exists. we should move it as well. // this case will occur if tar entries are prefixed with "./", "/", etc. - out.add(e) - in.remove(name) + if _, done := picked[name]; !done { + out.add(e) + picked[name] = struct{}{} + } } return nil } _, okIn := in.get(name) _, okOut := out.get(name) - if !okIn && !okOut { + _, okPicked := picked[name] + if !okIn && !okOut && !okPicked { return fmt.Errorf("file: %q: %w", name, errNotFound) } parent, _ := path.Split(strings.TrimSuffix(name, "/")) - if err := moveRec(parent, in, out); err != nil { + if err := moveRec(parent, in, out, picked); err != nil { return err } if e, ok := in.get(name); ok && e.header.Typeflag == tar.TypeLink { - if err := moveRec(e.header.Linkname, in, out); err != nil { + if err := moveRec(e.header.Linkname, in, out, picked); err != nil { return err } } + if _, done := picked[name]; done { + return nil + } if e, ok := in.get(name); ok { out.add(e) - in.remove(name) + picked[name] = struct{}{} } return nil } @@ -533,8 +584,18 @@ func (f *tarFile) get(name string) (e *entry, ok bool) { return } -func (f *tarFile) dump() []*entry { - return f.stream +func (f *tarFile) dump(skip map[string]struct{}) []*entry { + if len(skip) == 0 { + return f.stream + } + var out []*entry + for _, e := range f.stream { + if _, ok := skip[cleanEntryName(e.header.Name)]; ok { + continue + } + out = append(out, e) + } + return out } type readCloser struct { @@ -649,7 +710,7 @@ func (cr *countReadSeeker) currentPos() int64 { return *cr.cPos } -func decompressBlob(org *io.SectionReader, tmp *tempFiles) (*io.SectionReader, error) { +func decompressBlob(org *io.SectionReader, tmp *tempFiles, gzipHelperFunc GzipHelperFunc) (*io.SectionReader, error) { if org.Size() < 4 { return org, nil } @@ -660,7 +721,13 @@ func decompressBlob(org *io.SectionReader, tmp *tempFiles) (*io.SectionReader, e var dR io.Reader if bytes.Equal([]byte{0x1F, 0x8B, 0x08}, src[:3]) { // gzip - dgR, err := gzip.NewReader(io.NewSectionReader(org, 0, org.Size())) + var dgR io.ReadCloser + var err error + if gzipHelperFunc != nil { + dgR, err = gzipHelperFunc(io.NewSectionReader(org, 0, org.Size())) + } else { + dgR, err = gzip.NewReader(io.NewSectionReader(org, 0, org.Size())) + } if err != nil { return nil, err } diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go index f4d55465584e..ff91a37add50 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go @@ -307,6 +307,15 @@ func (r *Reader) initFields() error { } } + if len(r.m) == 0 { + r.m[""] = &TOCEntry{ + Name: "", + Type: "dir", + Mode: 0755, + NumLink: 1, + } + } + return nil } diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go index a8dcdb868e6b..ff165e090e38 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go @@ -38,7 +38,6 @@ import ( "reflect" "sort" "strings" - "testing" "time" "github.com/containerd/stargz-snapshotter/estargz/errorutil" @@ -49,16 +48,48 @@ import ( // TestingController is Compression with some helper methods necessary for testing. type TestingController interface { Compression - TestStreams(t *testing.T, b []byte, streams []int64) - DiffIDOf(*testing.T, []byte) string + TestStreams(t TestingT, b []byte, streams []int64) + DiffIDOf(TestingT, []byte) string String() string } +// TestingT is the minimal set of testing.T required to run the +// tests defined in CompressionTestSuite. This interface exists to prevent +// leaking the testing package from being exposed outside tests. +type TestingT interface { + Errorf(format string, args ...any) + FailNow() + Failed() bool + Fatal(args ...any) + Fatalf(format string, args ...any) + Logf(format string, args ...any) + Parallel() +} + +// Runner allows running subtests of TestingT. This exists instead of adding +// a Run method to TestingT interface because the Run implementation of +// testing.T would not satisfy the interface. +type Runner func(t TestingT, name string, fn func(t TestingT)) + +type TestRunner struct { + TestingT + Runner Runner +} + +func (r *TestRunner) Run(name string, run func(*TestRunner)) { + r.Runner(r.TestingT, name, func(t TestingT) { + run(&TestRunner{TestingT: t, Runner: r.Runner}) + }) +} + // CompressionTestSuite tests this pkg with controllers can build valid eStargz blobs and parse them. -func CompressionTestSuite(t *testing.T, controllers ...TestingControllerFactory) { - t.Run("testBuild", func(t *testing.T) { t.Parallel(); testBuild(t, controllers...) }) - t.Run("testDigestAndVerify", func(t *testing.T) { t.Parallel(); testDigestAndVerify(t, controllers...) }) - t.Run("testWriteAndOpen", func(t *testing.T) { t.Parallel(); testWriteAndOpen(t, controllers...) }) +func CompressionTestSuite(t *TestRunner, controllers ...TestingControllerFactory) { + t.Run("testBuild", func(t *TestRunner) { t.Parallel(); testBuild(t, controllers...) }) + t.Run("testDigestAndVerify", func(t *TestRunner) { + t.Parallel() + testDigestAndVerify(t, controllers...) + }) + t.Run("testWriteAndOpen", func(t *TestRunner) { t.Parallel(); testWriteAndOpen(t, controllers...) }) } type TestingControllerFactory func() TestingController @@ -79,7 +110,7 @@ var allowedPrefix = [4]string{"", "./", "/", "../"} // testBuild tests the resulting stargz blob built by this pkg has the same // contents as the normal stargz blob. -func testBuild(t *testing.T, controllers ...TestingControllerFactory) { +func testBuild(t *TestRunner, controllers ...TestingControllerFactory) { tests := []struct { name string chunkSize int @@ -165,7 +196,7 @@ func testBuild(t *testing.T, controllers ...TestingControllerFactory) { prefix := prefix for _, minChunkSize := range tt.minChunkSize { minChunkSize := minChunkSize - t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *testing.T) { + t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *TestRunner) { tarBlob := buildTar(t, tt.in, prefix, srcTarFormat) // Test divideEntries() entries, err := sortEntries(tarBlob, nil, nil) // identical order @@ -265,7 +296,7 @@ func testBuild(t *testing.T, controllers ...TestingControllerFactory) { } } -func isSameTarGz(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool { +func isSameTarGz(t TestingT, cla TestingController, a []byte, clb TestingController, b []byte) bool { aGz, err := cla.Reader(bytes.NewReader(a)) if err != nil { t.Fatalf("failed to read A") @@ -325,7 +356,7 @@ func isSameTarGz(t *testing.T, cla TestingController, a []byte, clb TestingContr return true } -func isSameVersion(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool { +func isSameVersion(t TestingT, cla TestingController, a []byte, clb TestingController, b []byte) bool { aJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(a), 0, int64(len(a))), cla) if err != nil { t.Fatalf("failed to parse A: %v", err) @@ -339,7 +370,7 @@ func isSameVersion(t *testing.T, cla TestingController, a []byte, clb TestingCon return aJTOC.Version == bJTOC.Version } -func isSameEntries(t *testing.T, a, b *Reader) bool { +func isSameEntries(t TestingT, a, b *Reader) bool { aroot, ok := a.Lookup("") if !ok { t.Fatalf("failed to get root of A") @@ -353,7 +384,7 @@ func isSameEntries(t *testing.T, a, b *Reader) bool { return contains(t, aEntry, bEntry) && contains(t, bEntry, aEntry) } -func compressBlob(t *testing.T, src *io.SectionReader, srcCompression int) *io.SectionReader { +func compressBlob(t TestingT, src *io.SectionReader, srcCompression int) *io.SectionReader { buf := new(bytes.Buffer) var w io.WriteCloser var err error @@ -387,7 +418,7 @@ type stargzEntry struct { // contains checks if all child entries in "b" are also contained in "a". // This function also checks if the files/chunks contain the same contents among "a" and "b". -func contains(t *testing.T, a, b stargzEntry) bool { +func contains(t TestingT, a, b stargzEntry) bool { ae, ar := a.e, a.r be, br := b.e, b.r t.Logf("Comparing: %q vs %q", ae.Name, be.Name) @@ -498,7 +529,7 @@ func equalEntry(a, b *TOCEntry) bool { a.Digest == b.Digest } -func readOffset(t *testing.T, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) { +func readOffset(t TestingT, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) { ce, ok := e.r.ChunkEntryForOffset(e.e.Name, offset) if !ok { return nil, 0, false @@ -517,7 +548,7 @@ func readOffset(t *testing.T, r *io.SectionReader, offset int64, e stargzEntry) return data[:n], offset + ce.ChunkSize, true } -func dumpTOCJSON(t *testing.T, tocJSON *JTOC) string { +func dumpTOCJSON(t TestingT, tocJSON *JTOC) string { jtocData, err := json.Marshal(*tocJSON) if err != nil { t.Fatalf("failed to marshal TOC JSON: %v", err) @@ -531,20 +562,19 @@ func dumpTOCJSON(t *testing.T, tocJSON *JTOC) string { const chunkSize = 3 -// type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, compressionLevel int) -type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) +type check func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) // testDigestAndVerify runs specified checks against sample stargz blobs. -func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) { +func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory) { tests := []struct { name string - tarInit func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) + tarInit func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) checks []check minChunkSize []int }{ { name: "no-regfile", - tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( dir("test/"), ) @@ -559,7 +589,7 @@ func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) }, { name: "small-files", - tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( regDigest(t, "baz.txt", "", dgstMap), regDigest(t, "foo.txt", "a", dgstMap), @@ -583,7 +613,7 @@ func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) }, { name: "big-files", - tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), regDigest(t, "foo.txt", "a", dgstMap), @@ -607,7 +637,7 @@ func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) { name: "with-non-regfiles", minChunkSize: []int{0, 64000}, - tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), regDigest(t, "foo.txt", "a", dgstMap), @@ -654,7 +684,7 @@ func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) srcTarFormat := srcTarFormat for _, minChunkSize := range tt.minChunkSize { minChunkSize := minChunkSize - t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *testing.T) { + t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *TestRunner) { // Get original tar file and chunk digests dgstMap := make(map[string]digest.Digest) tarBlob := buildTar(t, tt.tarInit(t, dgstMap), prefix, srcTarFormat) @@ -690,7 +720,7 @@ func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) // checkStargzTOC checks the TOC JSON of the passed stargz has the expected // digest and contains valid chunks. It walks all entries in the stargz and // checks all chunk digests stored to the TOC JSON match the actual contents. -func checkStargzTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { +func checkStargzTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { sgz, err := Open( io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), WithDecompressors(controller), @@ -801,7 +831,7 @@ func checkStargzTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstM // checkVerifyTOC checks the verification works for the TOC JSON of the passed // stargz. It walks all entries in the stargz and checks the verifications for // all chunks work. -func checkVerifyTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { +func checkVerifyTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { sgz, err := Open( io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), WithDecompressors(controller), @@ -882,9 +912,9 @@ func checkVerifyTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstM // checkVerifyInvalidTOCEntryFail checks if misconfigured TOC JSON can be // detected during the verification and the verification returns an error. func checkVerifyInvalidTOCEntryFail(filename string) check { - return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { funcs := map[string]rewriteFunc{ - "lost digest in a entry": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) { + "lost digest in a entry": func(t TestingT, toc *JTOC, sgz *io.SectionReader) { var found bool for _, e := range toc.Entries { if cleanEntryName(e.Name) == filename { @@ -902,7 +932,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check { t.Fatalf("rewrite target not found") } }, - "duplicated entry offset": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) { + "duplicated entry offset": func(t TestingT, toc *JTOC, sgz *io.SectionReader) { var ( sampleEntry *TOCEntry targetEntry *TOCEntry @@ -929,7 +959,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check { } for name, rFunc := range funcs { - t.Run(name, func(t *testing.T) { + t.Run(name, func(t *TestRunner) { newSgz, newTocDigest := rewriteTOCJSON(t, io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), rFunc, controller) buf := new(bytes.Buffer) if _, err := io.Copy(buf, newSgz); err != nil { @@ -958,7 +988,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check { // checkVerifyInvalidStargzFail checks if the verification detects that the // given stargz file doesn't match to the expected digest and returns error. func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check { - return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { cl := newController() rc, err := Build(invalid, WithChunkSize(chunkSize), WithCompression(cl)) if err != nil { @@ -990,7 +1020,7 @@ func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check { // checkVerifyBrokenContentFail checks if the verifier detects broken contents // that doesn't match to the expected digest and returns error. func checkVerifyBrokenContentFail(filename string) check { - return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { // Parse stargz file sgz, err := Open( io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), @@ -1047,9 +1077,9 @@ func chunkID(name string, offset, size int64) string { return fmt.Sprintf("%s-%d-%d", cleanEntryName(name), offset, size) } -type rewriteFunc func(t *testing.T, toc *JTOC, sgz *io.SectionReader) +type rewriteFunc func(t TestingT, toc *JTOC, sgz *io.SectionReader) -func rewriteTOCJSON(t *testing.T, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) { +func rewriteTOCJSON(t TestingT, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) { decodedJTOC, jtocOffset, err := parseStargz(sgz, controller) if err != nil { t.Fatalf("failed to extract TOC JSON: %v", err) @@ -1120,7 +1150,7 @@ func parseStargz(sgz *io.SectionReader, controller TestingController) (decodedJT return decodedJTOC, tocOffset, nil } -func testWriteAndOpen(t *testing.T, controllers ...TestingControllerFactory) { +func testWriteAndOpen(t *TestRunner, controllers ...TestingControllerFactory) { const content = "Some contents" invalidUtf8 := "\xff\xfe\xfd" @@ -1464,7 +1494,7 @@ func testWriteAndOpen(t *testing.T, controllers ...TestingControllerFactory) { for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { srcTarFormat := srcTarFormat for _, lossless := range []bool{true, false} { - t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *testing.T) { + t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *TestRunner) { var tr io.Reader = buildTar(t, tt.in, prefix, srcTarFormat) origTarDgstr := digest.Canonical.Digester() tr = io.TeeReader(tr, origTarDgstr.Hash()) @@ -1530,6 +1560,9 @@ func testWriteAndOpen(t *testing.T, controllers ...TestingControllerFactory) { if err != nil { t.Fatalf("stargz.Open: %v", err) } + if _, ok := r.Lookup(""); !ok { + t.Fatalf("failed to lookup rootdir: %v", err) + } wantTOCVersion := 1 if tt.wantTOCVersion > 0 { wantTOCVersion = tt.wantTOCVersion @@ -1628,7 +1661,7 @@ func digestFor(content string) string { type numTOCEntries int -func (n numTOCEntries) check(t *testing.T, r *Reader) { +func (n numTOCEntries) check(t TestingT, r *Reader) { if r.toc == nil { t.Fatal("nil TOC") } @@ -1648,15 +1681,15 @@ func (n numTOCEntries) check(t *testing.T, r *Reader) { func checks(s ...stargzCheck) []stargzCheck { return s } type stargzCheck interface { - check(t *testing.T, r *Reader) + check(t TestingT, r *Reader) } -type stargzCheckFn func(*testing.T, *Reader) +type stargzCheckFn func(TestingT, *Reader) -func (f stargzCheckFn) check(t *testing.T, r *Reader) { f(t, r) } +func (f stargzCheckFn) check(t TestingT, r *Reader) { f(t, r) } func maxDepth(max int) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { e, ok := r.Lookup("") if !ok { t.Fatal("root directory not found") @@ -1673,7 +1706,7 @@ func maxDepth(max int) stargzCheck { }) } -func getMaxDepth(t *testing.T, e *TOCEntry, current, limit int) (max int, rErr error) { +func getMaxDepth(t TestingT, e *TOCEntry, current, limit int) (max int, rErr error) { if current > limit { return -1, fmt.Errorf("walkMaxDepth: exceeds limit: current:%d > limit:%d", current, limit) @@ -1695,7 +1728,7 @@ func getMaxDepth(t *testing.T, e *TOCEntry, current, limit int) (max int, rErr e } func hasFileLen(file string, wantLen int) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == file { if ent.Type != "reg" { @@ -1711,7 +1744,7 @@ func hasFileLen(file string, wantLen int) stargzCheck { } func hasFileXattrs(file, name, value string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == file { if ent.Type != "reg" { @@ -1738,7 +1771,7 @@ func hasFileXattrs(file, name, value string) stargzCheck { } func hasFileDigest(file string, digest string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { ent, ok := r.Lookup(file) if !ok { t.Fatalf("didn't find TOCEntry for file %q", file) @@ -1750,7 +1783,7 @@ func hasFileDigest(file string, digest string) stargzCheck { } func hasFileContentsWithPreRead(file string, offset int, want string, extra ...chunkInfo) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { extraMap := make(map[string]chunkInfo) for _, e := range extra { extraMap[e.name] = e @@ -1797,7 +1830,7 @@ func hasFileContentsWithPreRead(file string, offset int, want string, extra ...c } func hasFileContentsRange(file string, offset int, want string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { f, err := r.OpenFile(file) if err != nil { t.Fatal(err) @@ -1814,7 +1847,7 @@ func hasFileContentsRange(file string, offset int, want string) stargzCheck { } func hasChunkEntries(file string, wantChunks int) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { ent, ok := r.Lookup(file) if !ok { t.Fatalf("no file for %q", file) @@ -1858,7 +1891,7 @@ func hasChunkEntries(file string, wantChunks int) stargzCheck { } func entryHasChildren(dir string, want ...string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { want := append([]string(nil), want...) var got []string ent, ok := r.Lookup(dir) @@ -1877,7 +1910,7 @@ func entryHasChildren(dir string, want ...string) stargzCheck { } func hasDir(file string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == cleanEntryName(file) { if ent.Type != "dir" { @@ -1891,7 +1924,7 @@ func hasDir(file string) stargzCheck { } func hasDirLinkCount(file string, count int) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == cleanEntryName(file) { if ent.Type != "dir" { @@ -1909,7 +1942,7 @@ func hasDirLinkCount(file string, count int) stargzCheck { } func hasMode(file string, mode os.FileMode) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == cleanEntryName(file) { if ent.Stat().Mode() != mode { @@ -1924,7 +1957,7 @@ func hasMode(file string, mode os.FileMode) stargzCheck { } func hasSymlink(file, target string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == file { if ent.Type != "symlink" { @@ -1940,7 +1973,7 @@ func hasSymlink(file, target string) stargzCheck { } func lookupMatch(name string, want *TOCEntry) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { e, ok := r.Lookup(name) if !ok { t.Fatalf("failed to Lookup entry %q", name) @@ -1953,7 +1986,7 @@ func lookupMatch(name string, want *TOCEntry) stargzCheck { } func hasEntryOwner(entry string, owner owner) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { ent, ok := r.Lookup(strings.TrimSuffix(entry, "/")) if !ok { t.Errorf("entry %q not found", entry) @@ -1967,7 +2000,7 @@ func hasEntryOwner(entry string, owner owner) stargzCheck { } func mustSameEntry(files ...string) stargzCheck { - return stargzCheckFn(func(t *testing.T, r *Reader) { + return stargzCheckFn(func(t TestingT, r *Reader) { var first *TOCEntry for _, f := range files { if first == nil { @@ -2039,7 +2072,7 @@ func (f tarEntryFunc) appendTar(tw *tar.Writer, prefix string, format tar.Format return f(tw, prefix, format) } -func buildTar(t *testing.T, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader { +func buildTar(t TestingT, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader { format := tar.FormatUnknown for _, opt := range opts { switch v := opt.(type) { @@ -2248,7 +2281,7 @@ func noPrefetchLandmark() tarEntry { }) } -func regDigest(t *testing.T, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry { +func regDigest(t TestingT, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry { if digestMap == nil { t.Fatalf("digest map mustn't be nil") } @@ -2318,7 +2351,7 @@ func (f fileInfoOnlyMode) ModTime() time.Time { return time.Now() } func (f fileInfoOnlyMode) IsDir() bool { return os.FileMode(f).IsDir() } func (f fileInfoOnlyMode) Sys() interface{} { return nil } -func CheckGzipHasStreams(t *testing.T, b []byte, streams []int64) { +func CheckGzipHasStreams(t TestingT, b []byte, streams []int64) { if len(streams) == 0 { return // nop } @@ -2356,7 +2389,7 @@ func CheckGzipHasStreams(t *testing.T, b []byte, streams []int64) { } } -func GzipDiffIDOf(t *testing.T, b []byte) string { +func GzipDiffIDOf(t TestingT, b []byte) string { h := sha256.New() zr, err := gzip.NewReader(bytes.NewReader(b)) if err != nil { diff --git a/vendor/github.com/containerd/stargz-snapshotter/fs/config/config.go b/vendor/github.com/containerd/stargz-snapshotter/fs/config/config.go index 31a2250d2a38..9b6962f745e3 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/fs/config/config.go +++ b/vendor/github.com/containerd/stargz-snapshotter/fs/config/config.go @@ -139,6 +139,9 @@ type DirectoryCacheConfig struct { // Direct disables on-memory data cache. Default is true for saving memory usage. Direct bool `toml:"direct" default:"true" json:"direct"` + + // FadvDontNeed forcefully clean fscache pagecache for saving memory. Default is false. + FadvDontNeed bool `toml:"fadv_dontneed" json:"fadv_dontneed"` } // FuseConfig is configuration for FUSE fs. diff --git a/vendor/github.com/containerd/stargz-snapshotter/fs/fs.go b/vendor/github.com/containerd/stargz-snapshotter/fs/fs.go index b141471bfca7..c55d57002c30 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/fs/fs.go +++ b/vendor/github.com/containerd/stargz-snapshotter/fs/fs.go @@ -39,7 +39,6 @@ package fs import ( "context" "fmt" - "os/exec" "strconv" "sync" "time" @@ -71,7 +70,6 @@ const ( defaultMaxConcurrency = 2 ) -var fusermountBin = []string{"fusermount", "fusermount3"} var ( nsLock = sync.Mutex{} @@ -349,16 +347,10 @@ func (fs *filesystem) Mount(ctx context.Context, mountpoint string, labels map[s NullPermissions: true, }) mountOpts := &fuse.MountOptions{ - AllowOther: true, // allow users other than root&mounter to access fs - FsName: "stargz", // name this filesystem as "stargz" - Debug: fs.debug, - } - if isFusermountBinExist() { - log.G(ctx).Infof("fusermount detected") - mountOpts.Options = []string{"suid"} // option for fusermount; allow setuid inside container - } else { - log.G(ctx).WithError(err).Infof("%s not installed; trying direct mount", fusermountBin) - mountOpts.DirectMount = true + AllowOther: true, // allow users other than root&mounter to access fs + FsName: "stargz", // name this filesystem as "stargz" + Debug: fs.debug, + DirectMount: true, } server, err := fuse.NewServer(rawFS, mountpoint, mountOpts) if err != nil { @@ -505,12 +497,3 @@ func neighboringLayers(manifest ocispec.Manifest, target ocispec.Descriptor) (de } return } - -func isFusermountBinExist() bool { - for _, b := range fusermountBin { - if _, err := exec.LookPath(b); err == nil { - return true - } - } - return false -} diff --git a/vendor/github.com/containerd/stargz-snapshotter/fs/layer/layer.go b/vendor/github.com/containerd/stargz-snapshotter/fs/layer/layer.go index 371a1c6e78ee..0397f2e9e43c 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/fs/layer/layer.go +++ b/vendor/github.com/containerd/stargz-snapshotter/fs/layer/layer.go @@ -235,11 +235,12 @@ func newCache(root string, cacheType string, cfg config.Config) (cache.BlobCache return cache.NewDirectoryCache( cachePath, cache.DirectoryCacheConfig{ - SyncAdd: dcc.SyncAdd, - DataCache: dCache, - FdCache: fCache, - BufPool: bufPool, - Direct: dcc.Direct, + SyncAdd: dcc.SyncAdd, + DataCache: dCache, + FdCache: fCache, + BufPool: bufPool, + Direct: dcc.Direct, + FadvDontNeed: dcc.FadvDontNeed, }, ) } diff --git a/vendor/github.com/containerd/stargz-snapshotter/fs/layer/testutil.go b/vendor/github.com/containerd/stargz-snapshotter/fs/layer/testutil.go index 87580dd3c19d..ac2356af74e2 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/fs/layer/testutil.go +++ b/vendor/github.com/containerd/stargz-snapshotter/fs/layer/testutil.go @@ -39,7 +39,6 @@ import ( "path/filepath" "strings" "syscall" - "testing" "time" "github.com/containerd/containerd/v2/pkg/reference" @@ -76,7 +75,33 @@ type layerConfig struct { passThroughConfig passThroughConfig } -func TestSuiteLayer(t *testing.T, store metadata.Store) { +// TestingT is the minimal set of testing.T required to run the +// tests defined in TestSuiteLayer. This interface exists to prevent +// leaking the testing package from being exposed outside tests. +type TestingT interface { + Errorf(format string, args ...any) + Fatal(args ...any) + Fatalf(format string, args ...any) + Logf(format string, args ...any) +} + +// Runner allows running subtests of TestingT. This exists instead of adding +// a Run method to TestingT interface because the Run implementation of +// testing.T would not satisfy the interface. +type Runner func(t TestingT, name string, fn func(t TestingT)) + +type TestRunner struct { + TestingT + Runner Runner +} + +func (r *TestRunner) Run(name string, run func(*TestRunner)) { + r.Runner(r.TestingT, name, func(t TestingT) { + run(&TestRunner{TestingT: t, Runner: r.Runner}) + }) +} + +func TestSuiteLayer(t *TestRunner, store metadata.Store) { for _, lc := range []layerConfig{ { name: "default", @@ -117,10 +142,14 @@ func TestSuiteLayer(t *testing.T, store metadata.Store) { var testStateLayerDigest = digest.FromString("dummy") -func testPrefetch(t *testing.T, factory metadata.Store, lc layerConfig) { - data64KB := string(tutil.RandomBytes(t, 64000)) +func testPrefetch(t *TestRunner, factory metadata.Store, lc layerConfig) { + randomData, err := tutil.RandomBytes(64000) + if err != nil { + t.Fatalf("failed rand.Read: %v", err) + } + data64KB := string(randomData) defaultPrefetchSize := int64(10000) - landmarkPosition := func(t *testing.T, l *layer) int64 { + landmarkPosition := func(t TestingT, l *layer) int64 { if l.r == nil { t.Fatalf("layer hasn't been verified yet") } @@ -140,7 +169,7 @@ func testPrefetch(t *testing.T, factory metadata.Store, lc layerConfig) { in []tutil.TarEntry wantNum int // number of chunks wanted in the cache wants []string // filenames to compare - prefetchSize func(*testing.T, *layer) int64 + prefetchSize func(TestingT, *layer) int64 prioritizedFiles []string }{ { @@ -218,7 +247,7 @@ func testPrefetch(t *testing.T, factory metadata.Store, lc layerConfig) { for _, tt := range tests { for srcCompressionName, srcCompression := range srcCompressions { cl := srcCompression() - t.Run("testPrefetch-"+tt.name+"-"+srcCompressionName+"-"+lc.name, func(t *testing.T) { + t.Run("testPrefetch-"+tt.name+"-"+srcCompressionName+"-"+lc.name, func(t *TestRunner) { chunkSize := sampleChunkSize if tt.chunkSize > 0 { chunkSize = tt.chunkSize @@ -345,7 +374,7 @@ func isDup(a, b region) bool { return b.end >= a.begin } -func newBlob(t *testing.T, sr *io.SectionReader) *sampleBlob { +func newBlob(t TestingT, sr *io.SectionReader) *sampleBlob { return &sampleBlob{ t: t, r: sr, @@ -353,7 +382,7 @@ func newBlob(t *testing.T, sr *io.SectionReader) *sampleBlob { } type sampleBlob struct { - t *testing.T + t TestingT r *io.SectionReader readCalled bool @@ -418,7 +447,7 @@ const ( lastChunkOffset1 = sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize) ) -func testNodeRead(t *testing.T, factory metadata.Store, lc layerConfig) { +func testNodeRead(t *TestRunner, factory metadata.Store, lc layerConfig) { sizeCond := map[string]int64{ "single_chunk": sampleChunkSize - sampleMiddleOffset, "multi_chunks": sampleChunkSize + sampleMiddleOffset, @@ -443,7 +472,7 @@ func testNodeRead(t *testing.T, factory metadata.Store, lc layerConfig) { for fn, filesize := range fileSizeCond { for _, srcCompression := range srcCompressions { cl := srcCompression() - t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s", sn, in, bo, fn, lc.name), func(t *testing.T) { + t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s", sn, in, bo, fn, lc.name), func(t *TestRunner) { if filesize > int64(len(sampleData1)) { t.Fatal("sample file size is larger than sample data") } @@ -498,7 +527,7 @@ func testNodeRead(t *testing.T, factory metadata.Store, lc layerConfig) { } } -func makeNodeReader(t *testing.T, contents []byte, chunkSize int, factory metadata.Store, cl tutil.Compression, lc layerConfig) (_ *file, closeFn func() error) { +func makeNodeReader(t TestingT, contents []byte, chunkSize int, factory metadata.Store, cl tutil.Compression, lc layerConfig) (_ *file, closeFn func() error) { testName := "test" sr, tocDgst, err := tutil.BuildEStargz( []tutil.TarEntry{tutil.File(testName, string(contents))}, @@ -526,16 +555,20 @@ func makeNodeReader(t *testing.T, contents []byte, chunkSize int, factory metada return f.(*file), r.Close } -func testNodes(t *testing.T, factory metadata.Store, lc layerConfig) { +func testNodes(t *TestRunner, factory metadata.Store, lc layerConfig) { for _, o := range []OverlayOpaqueType{OverlayOpaqueAll, OverlayOpaqueTrusted, OverlayOpaqueUser} { testNodesWithOpaque(t, factory, o, lc) } } -func testNodesWithOpaque(t *testing.T, factory metadata.Store, opaque OverlayOpaqueType, lc layerConfig) { - data64KB := string(tutil.RandomBytes(t, 64000)) +func testNodesWithOpaque(t *TestRunner, factory metadata.Store, opaque OverlayOpaqueType, lc layerConfig) { + randomData, err := tutil.RandomBytes(64000) + if err != nil { + t.Fatalf("failed rand.Read: %v", err) + } + data64KB := string(randomData) hasOpaque := func(entry string) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { for _, k := range opaqueXattrs[opaque] { hasNodeXattrs(entry, k, opaqueXattrValue)(t, root, cc, cr) } @@ -741,7 +774,7 @@ func testNodesWithOpaque(t *testing.T, factory metadata.Store, opaque OverlayOpa for _, tt := range tests { for _, srcCompression := range srcCompressions { cl := srcCompression() - t.Run(tt.name+"-"+lc.name, func(t *testing.T) { + t.Run(tt.name+"-"+lc.name, func(t *TestRunner) { opts := []tutil.BuildEStargzOption{ tutil.WithEStargzOptions(estargz.WithCompression(cl)), } @@ -772,7 +805,7 @@ func testNodesWithOpaque(t *testing.T, factory metadata.Store, opaque OverlayOpa } } -func getRootNode(t *testing.T, r metadata.Reader, opaque OverlayOpaqueType, tocDgst digest.Digest, cc cache.BlobCache, lc layerConfig) *node { +func getRootNode(t TestingT, r metadata.Reader, opaque OverlayOpaqueType, tocDgst digest.Digest, cc cache.BlobCache, lc layerConfig) *node { vr, err := reader.NewReader(r, cc, digest.FromString("")) if err != nil { t.Fatalf("failed to create reader: %v", err) @@ -806,10 +839,10 @@ func (tb *testBlobState) Refresh(ctx context.Context, host source.RegistryHosts, } func (tb *testBlobState) Close() error { return nil } -type check func(*testing.T, *node, cache.BlobCache, *calledReaderAt) +type check func(TestingT, *node, cache.BlobCache, *calledReaderAt) func fileNotExist(file string) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { if _, _, err := getDirentAndNode(t, root, file); err == nil { t.Errorf("Node %q exists", file) } @@ -817,7 +850,7 @@ func fileNotExist(file string) check { } func hasFileDigest(filename string, digest string) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { _, n, err := getDirentAndNode(t, root, filename) if err != nil { t.Fatalf("failed to get node %q: %v", filename, err) @@ -846,7 +879,7 @@ func hasFileDigest(filename string, digest string) check { } func hasSize(name string, size int) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { _, n, err := getDirentAndNode(t, root, name) if err != nil { t.Fatalf("failed to get node %q: %v", name, err) @@ -862,7 +895,7 @@ func hasSize(name string, size int) check { } func hasExtraMode(name string, mode os.FileMode) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { _, n, err := getDirentAndNode(t, root, name) if err != nil { t.Fatalf("failed to get node %q: %v", name, err) @@ -881,7 +914,7 @@ func hasExtraMode(name string, mode os.FileMode) check { } func hasValidWhiteout(name string) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { ent, n, err := getDirentAndNode(t, root, name) if err != nil { t.Fatalf("failed to get node %q: %v", name, err) @@ -917,7 +950,7 @@ func hasValidWhiteout(name string) check { } func hasNodeXattrs(entry, name, value string) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { _, n, err := getDirentAndNode(t, root, entry) if err != nil { t.Fatalf("failed to get node %q: %v", entry, err) @@ -958,7 +991,7 @@ func hasNodeXattrs(entry, name, value string) check { } } -func hasEntry(t *testing.T, name string, ents fusefs.DirStream) (fuse.DirEntry, bool) { +func hasEntry(t TestingT, name string, ents fusefs.DirStream) (fuse.DirEntry, bool) { for ents.HasNext() { de, errno := ents.Next() if errno != 0 { @@ -971,8 +1004,8 @@ func hasEntry(t *testing.T, name string, ents fusefs.DirStream) (fuse.DirEntry, return fuse.DirEntry{}, false } -func hasStateFile(t *testing.T, id string) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { +func hasStateFile(t TestingT, id string) check { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { // Check the state dir is hidden on OpenDir for "/" ents, errno := root.Readdir(context.Background()) @@ -1069,7 +1102,7 @@ func hasStateFile(t *testing.T, id string) check { // getDirentAndNode gets dirent and node at the specified path at once and makes // sure that the both of them exist. -func getDirentAndNode(t *testing.T, root *node, path string) (ent fuse.DirEntry, n *fusefs.Inode, err error) { +func getDirentAndNode(t TestingT, root *node, path string) (ent fuse.DirEntry, n *fusefs.Inode, err error) { dir, base := filepath.Split(filepath.Clean(path)) // get the target's parent directory. @@ -1145,7 +1178,7 @@ type chunkInfo struct { } func hasFileContentsWithPreCached(name string, off int64, contents string, extra ...chunkInfo) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { buf := readFile(t, root, name, int64(len(contents)), off) if len(buf) != len(contents) { t.Fatalf("failed to read contents %q (off:%d, want:%q) got %q", name, off, longBytesView([]byte(contents)), longBytesView(buf)) @@ -1167,7 +1200,7 @@ func hasFileContentsWithPreCached(name string, off int64, contents string, extra } func hasFileContentsOffset(name string, off int64, contents string, fromCache bool) check { - return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) { + return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { cr.called = nil // reset test buf := readFile(t, root, name, int64(len(contents)), off) if len(buf) != len(contents) { @@ -1189,7 +1222,7 @@ func hasFileContentsOffset(name string, off int64, contents string, fromCache bo } } -func readFile(t *testing.T, root *node, filename string, size, off int64) []byte { +func readFile(t TestingT, root *node, filename string, size, off int64) []byte { _, n, err := getDirentAndNode(t, root, filename) if err != nil { t.Fatalf("failed to get node %q: %v", filename, err) diff --git a/vendor/github.com/containerd/stargz-snapshotter/fs/reader/reader.go b/vendor/github.com/containerd/stargz-snapshotter/fs/reader/reader.go index 9b9290314198..6362b5b5d892 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/fs/reader/reader.go +++ b/vendor/github.com/containerd/stargz-snapshotter/fs/reader/reader.go @@ -505,10 +505,9 @@ type chunkData struct { func (sf *file) GetPassthroughFd(mergeBufferSize int64, mergeWorkerCount int) (uintptr, error) { var ( - offset int64 - firstChunkOffset int64 - totalSize int64 - hasLargeChunk bool + offset int64 + totalSize int64 + hasLargeChunk bool ) var chunks []chunkData @@ -530,7 +529,7 @@ func (sf *file) GetPassthroughFd(mergeBufferSize int64, mergeWorkerCount int) (u offset = chunkOffset + chunkSize } - id := genID(sf.id, firstChunkOffset, totalSize) + id := genID(sf.id, 0, totalSize) // cache.PassThrough() is necessary to take over files r, err := sf.gr.cache.Get(id, cache.PassThrough()) diff --git a/vendor/github.com/containerd/stargz-snapshotter/fs/reader/testutil.go b/vendor/github.com/containerd/stargz-snapshotter/fs/reader/testutil.go index 95e41b61d8fb..89d858dc0700 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/fs/reader/testutil.go +++ b/vendor/github.com/containerd/stargz-snapshotter/fs/reader/testutil.go @@ -32,7 +32,6 @@ import ( "path/filepath" "strings" "sync" - "testing" "time" "github.com/containerd/stargz-snapshotter/cache" @@ -64,7 +63,34 @@ var ( MockReadAtOutput = 4194304 ) -func TestSuiteReader(t *testing.T, store metadata.Store) { +// TestingT is the minimal set of testing.T required to run the +// tests defined in TestSuiteReader. This interface exists to prevent +// leaking the testing package from being exposed outside tests. +type TestingT interface { + Cleanup(func()) + Errorf(format string, args ...any) + Fatal(args ...any) + Fatalf(format string, args ...any) + Logf(format string, args ...any) +} + +// Runner allows running subtests of TestingT. This exists instead of adding +// a Run method to TestingT interface because the Run implementation of +// testing.T would not satisfy the interface. +type Runner func(t TestingT, name string, fn func(t TestingT)) + +type TestRunner struct { + TestingT + Runner Runner +} + +func (r *TestRunner) Run(name string, run func(*TestRunner)) { + r.Runner(r.TestingT, name, func(t TestingT) { + run(&TestRunner{TestingT: t, Runner: r.Runner}) + }) +} + +func TestSuiteReader(t *TestRunner, store metadata.Store) { testFileReadAt(t, store) testCacheVerify(t, store) testFailReader(t, store) @@ -72,7 +98,7 @@ func TestSuiteReader(t *testing.T, store metadata.Store) { testProcessBatchChunks(t) } -func testFileReadAt(t *testing.T, factory metadata.Store) { +func testFileReadAt(t *TestRunner, factory metadata.Store) { sizeCond := map[string]int64{ "single_chunk": sampleChunkSize - sampleMiddleOffset, "multi_chunks": sampleChunkSize + sampleMiddleOffset, @@ -109,7 +135,7 @@ func testFileReadAt(t *testing.T, factory metadata.Store) { for cc, cacheExcept := range cacheCond { for srcCompressionName, srcCompression := range srcCompressions { srcCompression := srcCompression() - t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s_%s", sn, in, bo, fn, cc, srcCompressionName), func(t *testing.T) { + t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s_%s", sn, in, bo, fn, cc, srcCompressionName), func(t *TestRunner) { if filesize > int64(len(sampleData1)) { t.Fatal("sample file size is larger than sample data") } @@ -199,7 +225,7 @@ func testFileReadAt(t *testing.T, factory metadata.Store) { } } -func newExceptFile(t *testing.T, fr metadata.File, except ...region) metadata.File { +func newExceptFile(t TestingT, fr metadata.File, except ...region) metadata.File { er := exceptFile{fr: fr, t: t} er.except = map[region]bool{} for _, reg := range except { @@ -211,7 +237,7 @@ func newExceptFile(t *testing.T, fr metadata.File, except ...region) metadata.Fi type exceptFile struct { fr metadata.File except map[region]bool - t *testing.T + t TestingT } func (er *exceptFile) ReadAt(p []byte, offset int64) (int, error) { @@ -225,7 +251,7 @@ func (er *exceptFile) ChunkEntryForOffset(offset int64) (off int64, size int64, return er.fr.ChunkEntryForOffset(offset) } -func makeFile(t *testing.T, contents []byte, chunkSize int, factory metadata.Store, comp tutil.Compression) (*file, func() error) { +func makeFile(t TestingT, contents []byte, chunkSize int, factory metadata.Store, comp tutil.Compression) (*file, func() error) { testName := "test" sr, dgst, err := tutil.BuildEStargz([]tutil.TarEntry{ tutil.File(testName, string(contents)), @@ -265,7 +291,7 @@ func makeFile(t *testing.T, contents []byte, chunkSize int, factory metadata.Sto return f, vr.Close } -func testCacheVerify(t *testing.T, factory metadata.Store) { +func testCacheVerify(t *TestRunner, factory metadata.Store) { for _, skipVerify := range [2]bool{true, false} { for _, invalidChunkBeforeVerify := range [2]bool{true, false} { for _, invalidChunkAfterVerify := range [2]bool{true, false} { @@ -273,7 +299,7 @@ func testCacheVerify(t *testing.T, factory metadata.Store) { srcCompression := srcCompression() name := fmt.Sprintf("test_cache_verify_%v_%v_%v_%v", skipVerify, invalidChunkBeforeVerify, invalidChunkAfterVerify, srcCompressionName) - t.Run(name, func(t *testing.T) { + t.Run(name, func(t *TestRunner) { sr, tocDgst, err := tutil.BuildEStargz([]tutil.TarEntry{ tutil.File("a", sampleData1+"a"), tutil.File("b", sampleData1+"b"), @@ -483,11 +509,11 @@ func prepareMap(mr metadata.Reader, id uint32, p string) (off2id map[int64]uint3 return off2id, id2path, nil } -func testFailReader(t *testing.T, factory metadata.Store) { +func testFailReader(t *TestRunner, factory metadata.Store) { testFileName := "test" for srcCompressionName, srcCompression := range srcCompressions { srcCompression := srcCompression() - t.Run(fmt.Sprintf("%v", srcCompressionName), func(t *testing.T) { + t.Run(fmt.Sprintf("%v", srcCompressionName), func(t *TestRunner) { for _, rs := range []bool{true, false} { for _, vs := range []bool{true, false} { stargzFile, tocDigest, err := tutil.BuildEStargz([]tutil.TarEntry{ @@ -595,8 +621,12 @@ func (bev *testChunkVerifier) verifier(id uint32, chunkDigest string) (digest.Ve return &testVerifier{bev.success}, nil } -func testPreReader(t *testing.T, factory metadata.Store) { - data64KB := string(tutil.RandomBytes(t, 64000)) +func testPreReader(t *TestRunner, factory metadata.Store) { + randomData, err := tutil.RandomBytes(64000) + if err != nil { + t.Fatalf("failed rand.Read: %v", err) + } + data64KB := string(randomData) tests := []struct { name string chunkSize int @@ -666,7 +696,7 @@ func testPreReader(t *testing.T, factory metadata.Store) { for _, tt := range tests { for srcCompresionName, srcCompression := range srcCompressions { srcCompression := srcCompression() - t.Run(tt.name+"-"+srcCompresionName, func(t *testing.T) { + t.Run(tt.name+"-"+srcCompresionName, func(t *TestRunner) { opts := []tutil.BuildEStargzOption{ tutil.WithEStargzOptions(estargz.WithCompression(srcCompression)), } @@ -705,7 +735,7 @@ func testPreReader(t *testing.T, factory metadata.Store) { } } -type check func(*testing.T, *reader, *calledReaderAt) +type check func(TestingT, *reader, *calledReaderAt) type chunkInfo struct { name string @@ -715,7 +745,7 @@ type chunkInfo struct { } func hasFileContentsOffset(name string, off int64, contents string, fromCache bool) check { - return func(t *testing.T, r *reader, cr *calledReaderAt) { + return func(t TestingT, r *reader, cr *calledReaderAt) { tid, err := lookup(r, name) if err != nil { t.Fatalf("failed to lookup %q", name) @@ -750,7 +780,7 @@ func hasFileContentsOffset(name string, off int64, contents string, fromCache bo } func hasFileContentsWithPreCached(name string, off int64, contents string, extra ...chunkInfo) check { - return func(t *testing.T, r *reader, cr *calledReaderAt) { + return func(t TestingT, r *reader, cr *calledReaderAt) { tid, err := lookup(r, name) if err != nil { t.Fatalf("failed to lookup %q", name) @@ -870,7 +900,7 @@ func (f *mockFile) ReadAt(p []byte, offset int64) (int, error) { return MockReadAtOutput, nil } -func testProcessBatchChunks(t *testing.T) { +func testProcessBatchChunks(t *TestRunner) { type testCase struct { name string setupMock func() @@ -878,7 +908,7 @@ func testProcessBatchChunks(t *testing.T) { expectErrorInHoles bool } - runTest := func(t *testing.T, tc testCase) { + runTest := func(t TestingT, tc testCase) { if tc.setupMock != nil { tc.setupMock() } @@ -1000,7 +1030,7 @@ func testProcessBatchChunks(t *testing.T) { } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { + t.Run(tc.name, func(t *TestRunner) { runTest(t, tc) }) } diff --git a/vendor/github.com/containerd/stargz-snapshotter/snapshot/snapshot.go b/vendor/github.com/containerd/stargz-snapshotter/snapshot/snapshot.go index 37a9bab29274..bbd73ad2c314 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/snapshot/snapshot.go +++ b/vendor/github.com/containerd/stargz-snapshotter/snapshot/snapshot.go @@ -455,12 +455,34 @@ func (o *snapshotter) getCleanupDirectories(ctx context.Context, t storage.Trans return nil, err } + remoteSnapshotNames := make(map[string]struct{}) + { + keyToID := make(map[string]string, len(ids)) + for id, key := range ids { + keyToID[key] = id + } + if err := storage.WalkInfo(ctx, func(ctx context.Context, info snapshots.Info) error { + if _, ok := info.Labels[remoteLabel]; ok { + if id, exists := keyToID[info.Name]; exists { + remoteSnapshotNames[id] = struct{}{} + } + } + return nil + }); err != nil { + return nil, err + } + } + cleanup := []string{} for _, d := range dirs { if !cleanupCommitted { if _, ok := ids[d]; ok { continue } + } else { + if _, ok := remoteSnapshotNames[d]; !ok { + continue + } } cleanup = append(cleanup, filepath.Join(snapshotDir, d)) @@ -760,10 +782,13 @@ func (o *snapshotter) restoreRemoteSnapshot(ctx context.Context) error { if err != nil { return err } - if err := os.Mkdir(filepath.Join(o.root, "snapshots", id), 0700); err != nil { + if err := os.Mkdir(filepath.Join(o.root, "snapshots", id), 0700); err != nil && !os.IsExist(err) { + return err + } + if err := os.Mkdir(o.upperPath(id), 0755); err != nil && !os.IsExist(err) { return err } - return os.Mkdir(o.upperPath(id), 0755) + return nil }(); err != nil { return fmt.Errorf("failed to create remote snapshot directory: %s: %w", info.Name, err) } diff --git a/vendor/github.com/containerd/stargz-snapshotter/util/testutil/util.go b/vendor/github.com/containerd/stargz-snapshotter/util/testutil/util.go index e4313481ecc3..691a77f8a354 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/util/testutil/util.go +++ b/vendor/github.com/containerd/stargz-snapshotter/util/testutil/util.go @@ -18,14 +18,13 @@ package testutil import ( "crypto/rand" - "testing" ) // RandomBytes returns the specified number of random bytes -func RandomBytes(t *testing.T, n int) []byte { +func RandomBytes(n int) ([]byte, error) { b := make([]byte, n) if _, err := rand.Read(b); err != nil { - t.Fatalf("failed rand.Read: %v", err) + return nil, err } - return b + return b, nil } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/README.md b/vendor/github.com/hanwen/go-fuse/v2/fs/README.md index 91ca60ca3fb0..80b462d165ca 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/README.md +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/README.md @@ -20,19 +20,15 @@ Decisions control of the kernel. This is useful for constructing FS trees in advance, rather than driven by LOOKUP. - * The NodeID for FS tree node must be defined on creation and are - immutable. By contrast, reusing NodeIds (eg. rsc/bazil FUSE, as - well as old go-fuse/fuse/nodefs) needs extra synchronization to - avoid races with notify and FORGET, and makes handling the inode - Generation more complicated. + * The NodeID (used for communicating with the kernel, not to be + confused with the inode number reported by `ls -i`) is generated + internally and immutable for an Inode. This avoids any races + between LOOKUP, NOTIFY and FORGET. * The mode of an Inode is defined on creation. Files cannot change type during their lifetime. This also prevents the common error of forgetting to return the filetype in Lookup/GetAttr. - * The NodeID (used for communicating with kernel) is equal to - Attr.Ino (value shown in Stat and Lstat return values.). - * No global treelock, to ensure scalability. * Support for hard links. libfuse doesn't support this in the diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/api.go b/vendor/github.com/hanwen/go-fuse/v2/fs/api.go index 73f690200c5f..3cbd51455ab9 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/api.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/api.go @@ -83,11 +83,11 @@ // File descriptor: a handle returned to opening a file. File // descriptors always refer to a single inode. // -// Dirent: a dirent maps (parent inode number, name string) tuple to +// Dentry: a dirent maps (parent inode number, name string) tuple to // child inode, thus representing a parent/child relation (or the -// absense thereof). Dirents do not have an equivalent type inside +// absense thereof). Dentries do not have an equivalent type inside // Go-FUSE, but the result of Lookup operation essentially is a -// dirent, which the kernel puts in a cache. +// dentry, which the kernel puts in a cache. // // # Kernel caching // @@ -101,11 +101,11 @@ // attribute timeout fields in fuse.AttrOut and fuse.EntryOut, which // get be populated from Getattr and Lookup // -// 3. Directory entries (parent/child relations in the FS tree): +// 3. Dentries (parent/child relations in the FS tree): // controlled with the timeout fields in fuse.EntryOut, and // invalidated with Inode.NotifyEntry and Inode.NotifyDelete. // -// Without Directory Entry timeouts, every operation on file "a/b/c" +// Without entry timeouts, every operation on file "a/b/c" // must first do lookups for "a", "a/b" and "a/b/c", which is // expensive because of context switches between the kernel and the // FUSE process. @@ -124,6 +124,16 @@ // AttrTimeout: &sec, // } // +// # Interrupts +// +// If the process accessing a FUSE file system is interrupted, the +// kernel sends an interrupt message, which cancels the context passed +// to the NodeXxxxx methods. If the file system chooses to honor this +// cancellation, the method must return [syscall.EINTR]. All unmasked +// signals generate an interrupt. In particular, the SIGURG signal +// (which the Go runtime uses for managing goroutine preemption) also +// generates an interrupt. +// // # Locking // // Locks for networked filesystems are supported through the suite of @@ -193,6 +203,15 @@ // } // } // +// The library tries to reserve fd 3, because FUSE mounts are created +// by calling "fusermount" with an inherited file descriptor, but the +// same problem may occur for other file descriptors. +// +// 1c. If the executable is on the FUSE mount. In this case, the child +// calls exec, which reads the file to execute, which triggers an OPEN +// opcode. This can be worked around by invoking the subprocess +// through a wrapper, eg `bash -c file/on/fuse-mount`. +// // 2. The Go runtime uses the epoll system call to understand which // goroutines can respond to I/O. The runtime assumes that epoll does // not block, but if files are on a FUSE filesystem, the kernel will @@ -269,16 +288,18 @@ type NodeStatfser interface { // Access should return if the caller can access the file with the // given mode. This is used for two purposes: to determine if a user -// may enter a directory, and to answer to implement the access system +// may enter a directory, and to implement the access system // call. In the latter case, the context has data about the real // UID. For example, a root-SUID binary called by user susan gets the // UID and GID for susan here. // // If not defined, a default implementation will check traditional -// unix permissions of the Getattr result agains the caller. If so, it -// is necessary to either return permissions from GetAttr/Lookup or -// set Options.DefaultPermissions in order to allow chdir into the -// FUSE mount. +// unix permissions of the Getattr result agains the caller. If access +// permissions must be obeyed precisely, the filesystem should return +// permissions from GetAttr/Lookup, and set [Options.NullPermissions]. +// Without [Options.NullPermissions], a missing permission (mode = +// 0000) is interpreted as 0755 for directories, and chdir is always +// allowed. type NodeAccesser interface { Access(ctx context.Context, mask uint32) syscall.Errno } @@ -292,7 +313,7 @@ type NodeAccesser interface { // is assumed, and the 'blocks' field is set accordingly. The 'f' // argument is provided for consistency, however, in practice the // kernel never sends a file handle, even if the Getattr call -// originated from a fstat system call. +// originated from an fstat system call. type NodeGetattrer interface { Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno } @@ -497,6 +518,28 @@ type NodeLookuper interface { Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) } +// NodeWrapChilder wraps a FS node implementation in another one. If +// defined, it is called automatically from NewInode and +// NewPersistentInode. Thus, existing file system implementations, +// even from other packages, can be customized by wrapping them. The +// following example is a loopback file system that forbids deletions. +// +// type NoDelete struct { +// *fs.LoopbackNode +// } +// func (w *NoDelete) Unlink(ctx context.Context, name string) syscall.Errno { +// return syscall.EPERM +// } +// func (w *NoDelete) WrapChild(ctx context.Context, ops fs.InodeEmbedder) fs.InodeEmbedder { +// return &NoDelete{ops.(*LoopbackNode)} +// } +// +// See also the LoopbackReuse example for a more practical +// application. +type NodeWrapChilder interface { + WrapChild(ctx context.Context, ops InodeEmbedder) InodeEmbedder +} + // OpenDir opens a directory Inode for reading its // contents. The actual reading is driven from Readdir, so // this method is just for performing sanity/permission @@ -688,6 +731,17 @@ type FileReaddirenter interface { Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno) } +// FileLookuper is a directory handle that supports lookup. If this is +// defined, FileLookuper.Lookup on the directory is called for +// READDIRPLUS calls, rather than NodeLookuper.Lookup. The name passed +// in will always be the last name produced by Readdirent. If a child +// with the given name already exists, that should be returned. In +// case of directory seeks that straddle response boundaries, +// Readdirent may be called without a subsequent Lookup call. +type FileLookuper interface { + Lookup(ctx context.Context, name string, out *fuse.EntryOut) (child *Inode, errno syscall.Errno) +} + // FileFsyncer is a directory that supports fsyncdir. type FileFsyncdirer interface { Fsyncdir(ctx context.Context, flags uint32) syscall.Errno @@ -706,47 +760,51 @@ type FileReleasedirer interface { Releasedir(ctx context.Context, releaseFlags uint32) } -// Options sets options for the entire filesystem +// Options are options for the entire filesystem. type Options struct { - // MountOptions contain the options for mounting the fuse server + // MountOptions contain the options for mounting the fuse server. fuse.MountOptions - // If set to nonnil, this defines the overall entry timeout - // for the file system. See fuse.EntryOut for more information. + // EntryTimeout, if non-nil, defines the overall entry timeout + // for the file system. See [fuse.EntryOut] for more information. EntryTimeout *time.Duration - // If set to nonnil, this defines the overall attribute - // timeout for the file system. See fuse.EntryOut for more + // AttrTimeout, if non-nil, defines the overall attribute + // timeout for the file system. See [fuse.AttrOut] for more // information. AttrTimeout *time.Duration - // If set to nonnil, this defines the overall entry timeout - // for failed lookups (fuse.ENOENT). See fuse.EntryOut for + // NegativeTimeout, if non-nil, defines the overall entry timeout + // for failed lookups (fuse.ENOENT). See [fuse.EntryOut] for // more information. NegativeTimeout *time.Duration - // Automatic inode numbers are handed out sequentially - // starting from this number. If unset, use 2^63. + // FirstAutomaticIno is start of the automatic inode numbers that are handed + // out sequentially. + // + // If unset, the default is 2^63. FirstAutomaticIno uint64 - // OnAdd is an alternative way to specify the OnAdd + // OnAdd, if non-nil, is an alternative way to specify the OnAdd // functionality of the root node. OnAdd func(ctx context.Context) - // NullPermissions if set, leaves null file permissions + // NullPermissions, if set, leaves null file permissions // alone. Otherwise, they are set to 755 (dirs) or 644 (other // files.), which is necessary for doing a chdir into the FUSE // directories. NullPermissions bool - // If nonzero, replace default (zero) UID with the given UID + // UID, if nonzero, is the default UID to use instead of the + // zero (zero) UID. UID uint32 - // If nonzero, replace default (zero) GID with the given GID + // GID, if nonzero, is the default GID to use instead of the + // zero (zero) GID. GID uint32 - // ServerCallbacks can be provided to stub out notification - // functions for testing a filesystem without mounting it. + // ServerCallbacks are optional callbacks to stub out notification functions + // for testing a filesystem without mounting it. ServerCallbacks ServerCallbacks // Logger is a sink for diagnostic messages. Diagnostic diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go index 5788033e9a04..bbac58d4c8df 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go @@ -97,6 +97,7 @@ type rawBridge struct { // // A simple incrementing counter is used as the NodeID (see `nextNodeID`). kernelNodeIds map[uint64]*Inode + // nextNodeID is the next free NodeID. Increment after copying the value. nextNodeId uint64 // nodeCountHigh records the highest number of entries we had in the @@ -1192,7 +1193,13 @@ func (b *rawBridge) readDirMaybeLookup(cancel <-chan struct{}, input *fuse.ReadI continue } - child, errno := b.lookup(ctx, n, de.Name, entryOut) + var child *Inode + if fileLookupper, ok := f.file.(FileLookuper); ok { + child, errno = fileLookupper.Lookup(ctx, de.Name, entryOut) + } else { + child, errno = b.lookup(ctx, n, de.Name, entryOut) + } + if errno != 0 { if b.options.NegativeTimeout != nil { entryOut.SetEntryTimeout(*b.options.NegativeTimeout) @@ -1200,6 +1207,7 @@ func (b *rawBridge) readDirMaybeLookup(cancel <-chan struct{}, input *fuse.ReadI // TODO: maybe simply not produce the dirent here? // test? } + // TODO: should break? } else { child, _ = b.addNewChild(n, de.Name, child, nil, 0, entryOut) child.setEntryOut(entryOut) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go b/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go index 9dfe687d6786..2a115b0b1dcb 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go @@ -366,12 +366,17 @@ func (n *Inode) ForgetPersistent() { // should be standard mode argument (eg. S_IFDIR). The inode number in // id.Ino argument is used to implement hard-links. If it is given, // and another node with the same ID is known, the new inode may be -// ignored, and the old one used instead. +// ignored, and the old one used instead. If the parent inode +// implements NodeWrapChilder, the returned Inode will have a +// different InodeEmbedder from the one passed in. func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode { return n.newInode(ctx, node, id, false) } func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode { + if wc, ok := n.ops.(NodeWrapChilder); ok { + ops = wc.WrapChild(ctx, ops) + } return n.bridge.newInode(ctx, ops, id, persistent) } diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go index d2fec090ecc4..c836ac31658c 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go @@ -30,6 +30,8 @@ type LoopbackRoot struct { // NewNode returns a new InodeEmbedder to be used to respond // to a LOOKUP/CREATE/MKDIR/MKNOD opcode. If not set, use a // LoopbackNode. + // + // Deprecated: use NodeWrapChilder instead. NewNode func(rootData *LoopbackRoot, parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder // RootNode is the root of the Loopback. This must be set if @@ -226,8 +228,8 @@ func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeE return syscall.EXDEV } - if flags&RENAME_EXCHANGE != 0 { - return n.renameExchange(name, e2.loopbackNode(), newName) + if flags != 0 { + return n.rename2(name, e2.loopbackNode(), newName, flags) } p1 := filepath.Join(n.path(), name) @@ -261,7 +263,7 @@ func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mo return ch, lf, 0, 0 } -func (n *LoopbackNode) renameExchange(name string, newParent *LoopbackNode, newName string) syscall.Errno { +func (n *LoopbackNode) rename2(name string, newParent *LoopbackNode, newName string, flags uint32) syscall.Errno { fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0) if err != nil { return ToErrno(err) @@ -269,10 +271,10 @@ func (n *LoopbackNode) renameExchange(name string, newParent *LoopbackNode, newN defer syscall.Close(fd1) p2 := newParent.path() fd2, err := syscall.Open(p2, syscall.O_DIRECTORY, 0) - defer syscall.Close(fd2) if err != nil { return ToErrno(err) } + defer syscall.Close(fd2) var st syscall.Stat_t if err := syscall.Fstat(fd1, &st); err != nil { @@ -291,7 +293,7 @@ func (n *LoopbackNode) renameExchange(name string, newParent *LoopbackNode, newN return syscall.EBUSY } - return ToErrno(renameat.Renameat(fd1, name, fd2, newName, renameat.RENAME_EXCHANGE)) + return ToErrno(renameat.Renameat(fd1, name, fd2, newName, uint(flags))) } var _ = (NodeSymlinker)((*LoopbackNode)(nil)) @@ -358,7 +360,7 @@ var _ = (NodeOpener)((*LoopbackNode)(nil)) // Symlink-safe through use of OpenSymlinkAware. func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { - flags = flags &^ syscall.O_APPEND + flags = flags &^ (syscall.O_APPEND | fuse.FMODE_EXEC) f, err := openat.OpenSymlinkAware(n.RootData.Path, n.relativePath(), int(flags), 0) if err != nil { diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go index 36209caa6022..a8d780337585 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go @@ -157,12 +157,14 @@ type ReadResult interface { type MountOptions struct { AllowOther bool - // Options are passed as -o string to fusermount. + // Options are the options passed as -o string to fusermount. Options []string - // Default is _DEFAULT_BACKGROUND_TASKS, 12. This numbers - // controls the allowed number of requests that relate to - // async I/O. Concurrency for synchronous I/O is not limited. + // MaxBackground controls the maximum number of allowed backgruond + // asynchronous I/O requests. + // + // If unset, the default is _DEFAULT_BACKGROUND_TASKS, 12. + // Concurrency for synchronous I/O is not limited. MaxBackground int // MaxWrite is the max size for read and write requests. If 0, use @@ -203,35 +205,37 @@ type MountOptions struct { // (up to MaxWrite or VM_READAHEAD_PAGES=128 kiB, whichever is less). MaxReadAhead int - // If IgnoreSecurityLabels is set, all security related xattr - // requests will return NO_DATA without passing through the - // user defined filesystem. You should only set this if you + // IgnoreSecurityLabels, if set, makes security related xattr + // requests return NO_DATA without passing through the + // user defined filesystem. You should only set this if you // file system implements extended attributes, and you are not // interested in security labels. IgnoreSecurityLabels bool // ignoring labels should be provided as a fusermount mount option. - // If RememberInodes is set, we will never forget inodes. + // RememberInodes, if set, makes go-fuse never forget inodes. // This may be useful for NFS. RememberInodes bool - // Values shown in "df -T" and friends - // First column, "Filesystem" + // FsName is the name of the filesystem, shown in "df -T" + // and friends (as the first column, "Filesystem"). FsName string - // Second column, "Type", will be shown as "fuse." + Name + // Name is the "fuse." suffix, shown in "df -T" and friends + // (as the second column, "Type") Name string - // If set, wrap the file system in a single-threaded locking wrapper. + // SingleThreaded, if set, wraps the file system in a single-threaded + // locking wrapper. SingleThreaded bool - // If set, return ENOSYS for Getxattr calls, so the kernel does not issue any - // Xattr operations at all. + // DisableXAttrs, if set, returns ENOSYS for Getxattr calls, so the kernel + // does not issue any Xattr operations at all. DisableXAttrs bool - // If set, print debugging information. + // Debug, if set, enables verbose debugging information. Debug bool - // If set, sink for debug statements. + // Logger, if set, is an alternate log sink for debug statements. // // To increase signal/noise ratio Go-FUSE uses abbreviations in its debug log // output. Here is how to read it: @@ -269,20 +273,21 @@ type MountOptions struct { // tx 11: OK, {tA=1s {M040755 SZ=0 L=1 1000:1000 B0*0 i0:1 A 0.000000 M 0.000000 C 0.000000}} Logger *log.Logger - // If set, ask kernel to forward file locks to FUSE. If using, - // you must implement the GetLk/SetLk/SetLkw methods. + // EnableLocks, if set, asks the kernel to forward file locks to FUSE + // When used, you must implement the GetLk/SetLk/SetLkw methods. EnableLocks bool - // If set, the kernel caches all Readlink return values. The - // filesystem must use content notification to force the + // EnableSymlinkCaching, if set, makes the kernel cache all Readlink return values. + // The filesystem must use content notification to force the // kernel to issue a new Readlink call. EnableSymlinkCaching bool - // If set, ask kernel not to do automatic data cache invalidation. - // The filesystem is fully responsible for invalidating data cache. + // ExplicitDataCacheControl, if set, asks the kernel not to do automatic + // data cache invalidation. The filesystem is fully responsible for + // invalidating data cache. ExplicitDataCacheControl bool - // SyncRead is off by default, which means that go-fuse enable the + // SyncRead, if set, makes go-fuse enable the // FUSE_CAP_ASYNC_READ capability. // The kernel then submits multiple concurrent reads to service // userspace requests and kernel readahead. @@ -299,14 +304,14 @@ type MountOptions struct { // for more details. SyncRead bool - // If set, fuse will first attempt to use syscall.Mount instead of + // DirectMount, if set, makes go-fuse first attempt to use syscall.Mount instead of // fusermount to mount the filesystem. This will not update /etc/mtab // but might be needed if fusermount is not available. // Also, Server.Unmount will attempt syscall.Unmount before calling // fusermount. DirectMount bool - // DirectMountStrict is like DirectMount but no fallback to fusermount is + // DirectMountStrict, if set, is like DirectMount but no fallback to fusermount is // performed. If both DirectMount and DirectMountStrict are set, // DirectMountStrict wins. DirectMountStrict bool @@ -318,34 +323,36 @@ type MountOptions struct { // by the kernel. See `man 2 mount` for details about MS_MGC_VAL. DirectMountFlags uintptr - // EnableAcls enables kernel ACL support. + // EnableAcl, if set, enables kernel ACL support. // // See the comments to FUSE_CAP_POSIX_ACL // in https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h // for details. EnableAcl bool - // Disable ReadDirPlus capability so ReadDir is used instead. Simple - // directory queries (i.e. 'ls' without '-l') can be faster with - // ReadDir, as no per-file stat calls are needed + // DisableReadDirPlus, if set, disables the ReadDirPlus capability so + // ReadDir is used instead. Simple directory queries (i.e. 'ls' without + // '-l') can be faster with ReadDir, as no per-file stat calls are needed. DisableReadDirPlus bool - // Disable splicing from files to the FUSE device. + // DisableSplice, if set, disables splicing from files to the FUSE device. DisableSplice bool - // Maximum stacking depth for passthrough files. Defaults to 1. + // MaxStackDepth is the maximum stacking depth for passthrough files. + // If unset, the default is 1. MaxStackDepth int - // Enable ID-mapped mount if the Kernel supports it. - // ID-mapped mount allows the device to be mounted on the system - // with the IDs remapped (via mount_setattr, move_mount syscalls) to - // those of the user on the local system. + // RawFileSystem, if set, enables an ID-mapped mount if the Kernel supports + // it. + // + // An ID-mapped mount allows the device to be mounted on the system with the + // IDs remapped (via mount_setattr, move_mount syscalls) to those of the + // user on the local system. // - // Enabling this flag automatically sets the "default_permissions" - // mount option. This is required by FUSE to delegate the UID/GID-based - // permission checks to the kernel. For requests that create new inodes, - // FUSE will send the mapped UID/GIDs. For all other requests, FUSE - // will send "-1". + // Enabling this flag automatically sets the "default_permissions" mount + // option. This is required by FUSE to delegate the UID/GID-based permission + // checks to the kernel. For requests that create new inodes, FUSE will send + // the mapped UID/GIDs. For all other requests, FUSE will send "-1". IDMappedMount bool } @@ -370,11 +377,13 @@ type MountOptions struct { // API call, any incoming request data it wants to reference should be // copied over. // -// If a FUSE API call is canceled (which is signaled by closing the -// `cancel` channel), the API call should return EINTR. In this case, -// the outstanding request data is not reused, so the API call may -// return EINTR without ensuring that child contexts have successfully -// completed. +// If a FS operation is interrupted, the `cancel` channel is +// closed. The fileystem can honor this request by returning EINTR. In +// this case, the outstanding request data is not reused. Interrupts +// occur if the process accessing the file system receives any signal +// that is not ignored. In particular, the Go runtime uses signals to +// manage goroutine preemption, so Go programs under load naturally +// generate interupt opcodes when they access a FUSE filesystem. type RawFileSystem interface { String() string diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go index 606044a96ce5..97c350529009 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go @@ -36,5 +36,9 @@ const ( O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC) + // FMODE_EXEC is a passed to OPEN requests if the file is + // being executed. + FMODE_EXEC = 0x20 + logicalBlockSize = 512 ) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go index fbc6a0ae7bf0..557044f6a127 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go @@ -14,8 +14,8 @@ import ( // package from Go, but it does implement the context.Context // interface. // -// When a FUSE request is canceled, the API routine should respond by -// returning the EINTR status code. +// When a FUSE request is canceled, and the file system chooses to honor +// the cancellation, the response should be EINTR. type Context struct { Caller Cancel <-chan struct{} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go index eb4587d99a9e..1e733aede1b8 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go @@ -13,6 +13,10 @@ import ( "syscall" ) +func getMaxWrite() int { + return 1 << 20 +} + func unixgramSocketpair() (l, r *os.File, err error) { fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) if err != nil { diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go index ceea4f046478..694287eca5ce 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go @@ -7,6 +7,10 @@ import ( "syscall" ) +func getMaxWrite() int { + return 1 << 20 +} + func callMountFuseFs(mountPoint string, opts *MountOptions) (devFuseFd int, err error) { bin, err := fusermountBinary() if err != nil { diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go index 9f7b1c8dfa36..689a46489399 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path" + "strconv" "strings" "syscall" ) @@ -256,3 +257,23 @@ func fusermountBinary() (string, error) { func umountBinary() (string, error) { return lookPathFallback("umount", "/bin") } + +func getMaxWrite() int { + return maxPageLimit() * syscall.Getpagesize() +} + +func maxPageLimit() (lim int) { + lim = 256 + + d, err := os.ReadFile("/proc/sys/fs/fuse/max_pages_limit") + if err != nil { + return lim + } + + newLim, err := strconv.Atoi(string(d)) + if err != nil { + return lim + } + + return newLim +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go index 8603b4fcc7d6..33f224a3e938 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go @@ -54,6 +54,8 @@ var ( {CAP_NO_EXPORT_SUPPORT, "NO_EXPORT_SUPPORT"}, {CAP_HAS_RESEND, "HAS_RESEND"}, {CAP_ALLOW_IDMAP, "ALLOW_IDMAP"}, + {CAP_OVER_IO_URING, "IO_URING"}, + {CAP_REQUEST_TIMEOUT, "REQUEST_TIMEOUT"}, }) releaseFlagNames = newFlagNames([]flagNameEntry{ {RELEASE_FLUSH, "FLUSH"}, @@ -96,6 +98,11 @@ var ( getAttrFlagNames = newFlagNames([]flagNameEntry{ {FUSE_GETATTR_FH, "FH"}, }) + renameFlagNames = newFlagNames([]flagNameEntry{ + {1, "NOREPLACE"}, + {2, "EXCHANGE"}, + {4, "WHITEOUT"}, + }) ) // flagNames associate flag bits to their names. @@ -176,7 +183,7 @@ func (in *Rename1In) string() string { } func (in *RenameIn) string() string { - return fmt.Sprintf("{i%d %x}", in.Newdir, in.Flags) + return fmt.Sprintf("{i%d %s}", in.Newdir, flagString(renameFlagNames, int64(in.Flags), "0")) } func (in *SetAttrIn) string() string { diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go index bdb6e25c7e06..2d896c5e1f5b 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go @@ -39,6 +39,7 @@ func init() { openFlagNames.set(syscall.O_DIRECT, "DIRECT") openFlagNames.set(syscall_O_NOATIME, "NOATIME") + openFlagNames.set(FMODE_EXEC, "EXEC") initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT") initFlagNames.set(CAP_EXPLICIT_INVAL_DATA, "EXPLICIT_INVAL_DATA") initFlagNames.set(CAP_MAP_ALIGNMENT, "MAP_ALIGNMENT") diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go index d5e7a1ffa234..25b2153c9c8e 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/protocol-server.go @@ -67,11 +67,6 @@ func (ms *protocolServer) handleRequest(h *operationHandler, req *request) { if req.suppressReply { return } - if req.inHeader().Opcode == _OP_INIT && ms.kernelSettings.Minor <= 22 { - // v8-v22 don't have TimeGran and further fields. - // This includes osxfuse (a.k.a. macfuse). - req.outHeader().Length = uint32(sizeOfOutHeader) + 24 - } if req.fdData != nil && ms.opts.DisableSplice { req.outPayload, req.status = req.fdData.Bytes(req.outPayload) req.fdData = nil diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go index 496655f5291c..51e1dfe65870 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go @@ -273,6 +273,16 @@ func (r *request) serializeHeader(outPayloadSize int) { } } + // The InitOut structure has 24 bytes (ie. TimeGran and + // further fields not available) in fuse version <= 22. + // https://john-millikin.com/the-fuse-protocol#FUSE_INIT + if r.inHeader().Opcode == _OP_INIT { + out := (*InitOut)(r.outData()) + if out.Minor <= 22 { + dataLength = 24 + } + } + o := r.outHeader() o.Unique = r.inHeader().Unique o.Status = int32(-r.status) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go index c54e9e5a00a7..ced3a8533484 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go @@ -21,7 +21,9 @@ import ( ) const ( - // Linux v4.20+ caps requests at 1 MiB. Older kernels at 128 kiB. + // Linux v4.20+ caps requests at 1 MiB. Older kernels at 128 + // kiB. Deprecated: current linux kernels allow tuning this + // using sysctl. MAX_KERNEL_WRITE = 1024 * 1024 // Linux kernel constant from include/uapi/linux/fuse.h @@ -175,8 +177,9 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server if o.MaxWrite == 0 { o.MaxWrite = defaultMaxWrite } - if o.MaxWrite > MAX_KERNEL_WRITE { - o.MaxWrite = MAX_KERNEL_WRITE + kernelMaxWrite := getMaxWrite() + if o.MaxWrite > kernelMaxWrite { + o.MaxWrite = kernelMaxWrite } if o.MaxStackDepth == 0 { o.MaxStackDepth = 1 diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go index 3b84248fb14e..15eea0e51c61 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go @@ -310,6 +310,8 @@ const ( CAP_NO_EXPORT_SUPPORT = (1 << 38) CAP_HAS_RESEND = (1 << 39) CAP_ALLOW_IDMAP = (1 << 40) + CAP_OVER_IO_URING = (1 << 41) + CAP_REQUEST_TIMEOUT = (1 << 42) ) type InitIn struct { @@ -340,7 +342,9 @@ type InitOut struct { Padding uint16 Flags2 uint32 MaxStackDepth uint32 - Unused [6]uint32 + RequestTimeout uint16 + + _Unused [11]uint16 } func (o *InitOut) Flags64() uint64 { diff --git a/vendor/github.com/hanwen/go-fuse/v2/internal/openat/openat_linux.go b/vendor/github.com/hanwen/go-fuse/v2/internal/openat/openat_linux.go index c26aa689e9cd..de8db304b953 100644 --- a/vendor/github.com/hanwen/go-fuse/v2/internal/openat/openat_linux.go +++ b/vendor/github.com/hanwen/go-fuse/v2/internal/openat/openat_linux.go @@ -11,5 +11,10 @@ func openatNoSymlinks(dirfd int, path string, flags int, mode uint32) (fd int, e Mode: uint64(mode), Resolve: unix.RESOLVE_NO_SYMLINKS, } - return unix.Openat2(dirfd, path, &how) + fd, err = unix.Openat2(dirfd, path, &how) + if err != nil && err == unix.ENOSYS { + flags |= unix.O_NOFOLLOW | unix.O_CLOEXEC + fd, err = unix.Openat(dirfd, path, flags, mode) + } + return fd, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index cdb831581d88..bcce780b38d4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -472,7 +472,7 @@ github.com/containerd/platforms # github.com/containerd/plugin v1.0.0 ## explicit; go 1.20 github.com/containerd/plugin -# github.com/containerd/stargz-snapshotter v0.17.0 +# github.com/containerd/stargz-snapshotter v0.18.1 ## explicit; go 1.24.0 github.com/containerd/stargz-snapshotter/cache github.com/containerd/stargz-snapshotter/fs @@ -490,8 +490,8 @@ github.com/containerd/stargz-snapshotter/task github.com/containerd/stargz-snapshotter/util/cacheutil github.com/containerd/stargz-snapshotter/util/namedmutex github.com/containerd/stargz-snapshotter/util/testutil -# github.com/containerd/stargz-snapshotter/estargz v0.17.0 -## explicit; go 1.23.0 +# github.com/containerd/stargz-snapshotter/estargz v0.18.1 +## explicit; go 1.24.0 github.com/containerd/stargz-snapshotter/estargz github.com/containerd/stargz-snapshotter/estargz/errorutil github.com/containerd/stargz-snapshotter/estargz/externaltoc @@ -735,7 +735,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2/internal/httprule github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options github.com/grpc-ecosystem/grpc-gateway/v2/runtime github.com/grpc-ecosystem/grpc-gateway/v2/utilities -# github.com/hanwen/go-fuse/v2 v2.8.0 +# github.com/hanwen/go-fuse/v2 v2.9.0 ## explicit; go 1.17 github.com/hanwen/go-fuse/v2/fs github.com/hanwen/go-fuse/v2/fuse