Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fs/layer/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ func (l *layer) RootNode(baseInode uint32) (fusefs.InodeEmbedder, error) {
if l.r == nil {
return nil, fmt.Errorf("layer hasn't been verified yet")
}
return newNode(l.desc.Digest, l.r, l.blob, baseInode, l.resolver.overlayOpaqueType, l.passThrough)
return newNode(l.desc.Digest, l.r, l.blob, baseInode, l.resolver.overlayOpaqueType, l.passThrough, l.resolver.rootDir)
}

func (l *layer) ReadAt(p []byte, offset int64, opts ...remote.Option) (int, error) {
Expand Down
33 changes: 28 additions & 5 deletions fs/layer/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ var opaqueXattrs = map[OverlayOpaqueType][]string{
OverlayOpaqueUser: {"user.overlay.opaque"},
}

func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseInode uint32, opaque OverlayOpaqueType, pth passThroughConfig) (fusefs.InodeEmbedder, error) {
func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseInode uint32, opaque OverlayOpaqueType,
pth passThroughConfig, statfsBase string) (fusefs.InodeEmbedder, error) {
rootID := r.Metadata().RootID()
rootAttr, err := r.Metadata().GetAttr(rootID)
if err != nil {
Expand All @@ -93,6 +94,7 @@ func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseIno
rootID: rootID,
opaqueXattrs: opq,
passThrough: pth,
statfsBase: statfsBase,
}
ffs.s = ffs.newState(layerDgst, blob)
return &node{
Expand All @@ -111,6 +113,7 @@ type fs struct {
rootID uint32
opaqueXattrs []string
passThrough passThroughConfig
statfsBase string
}

func (fs *fs) inodeOfState() uint64 {
Expand Down Expand Up @@ -434,7 +437,7 @@ func (n *node) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
var _ = (fusefs.NodeStatfser)((*node)(nil))

func (n *node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
defaultStatfs(out)
n.fs.fillStatfs(out)
return 0
}

Expand Down Expand Up @@ -507,7 +510,7 @@ func (w *whiteout) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.A
var _ = (fusefs.NodeStatfser)((*whiteout)(nil))

func (w *whiteout) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
defaultStatfs(out)
w.fs.fillStatfs(out)
return 0
}

Expand Down Expand Up @@ -573,7 +576,7 @@ func (s *state) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.Attr
var _ = (fusefs.NodeStatfser)((*state)(nil))

func (s *state) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
defaultStatfs(out)
s.fs.fillStatfs(out)
return 0
}

Expand Down Expand Up @@ -635,7 +638,7 @@ func (sf *statFile) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.
var _ = (fusefs.NodeStatfser)((*statFile)(nil))

func (sf *statFile) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
defaultStatfs(out)
sf.fs.fillStatfs(out)
return 0
}

Expand Down Expand Up @@ -837,3 +840,23 @@ func defaultStatfs(stat *fuse.StatfsOut) {
stat.Padding = 0
stat.Spare = [6]uint32{}
}

func (fs *fs) fillStatfs(out *fuse.StatfsOut) {
if fs.statfsBase != "" {
var s unix.Statfs_t
if err := unix.Statfs(fs.statfsBase, &s); err == nil {
out.Blocks = s.Blocks
out.Bfree = s.Bfree
out.Bavail = s.Bavail
out.Files = s.Files
out.Ffree = s.Ffree
out.Bsize = uint32(s.Bsize)
out.Frsize = uint32(s.Frsize)
out.NameLen = 255
out.Padding = 0
out.Spare = [6]uint32{}
return
}
}
defaultStatfs(out)
}
61 changes: 60 additions & 1 deletion fs/layer/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func TestSuiteLayer(t *TestRunner, store metadata.Store) {
testPrefetch(t, store, lc)
testNodeRead(t, store, lc)
testNodes(t, store, lc)
testStatfs(t, store, lc)
}
}

Expand Down Expand Up @@ -814,14 +815,72 @@ func getRootNode(t TestingT, r metadata.Reader, opaque OverlayOpaqueType, tocDgs
if err != nil {
t.Fatalf("failed to verify reader: %v", err)
}
rootNode, err := newNode(testStateLayerDigest, rr, &testBlobState{10, 5}, 100, opaque, lc.passThroughConfig)
rootNode, err := newNode(testStateLayerDigest, rr, &testBlobState{10, 5}, 100, opaque, lc.passThroughConfig, "")
if err != nil {
t.Fatalf("failed to get root node: %v", err)
}
fusefs.NewNodeFS(rootNode, &fusefs.Options{}) // initializes root node
return rootNode.(*node)
}

func getRootNodeWithStatfsBase(t TestingT, r metadata.Reader, opaque OverlayOpaqueType, tocDgst digest.Digest, cc cache.BlobCache, lc layerConfig, statfsBase string) *node {
vr, err := reader.NewReader(r, cc, digest.FromString(""))
if err != nil {
t.Fatalf("failed to create reader: %v", err)
}
rr, err := vr.VerifyTOC(tocDgst)
if err != nil {
t.Fatalf("failed to verify reader: %v", err)
}
rootNode, err := newNode(testStateLayerDigest, rr, &testBlobState{10, 5}, 100, opaque, lc.passThroughConfig, statfsBase)
if err != nil {
t.Fatalf("failed to get root node: %v", err)
}
fusefs.NewNodeFS(rootNode, &fusefs.Options{})
return rootNode.(*node)
}

func testStatfs(t *TestRunner, factory metadata.Store, lc layerConfig) {
// Build a minimal layer
sr, tocDgst, err := tutil.BuildEStargz(
[]tutil.TarEntry{tutil.File("foo.txt", sampleData1)},
tutil.WithEStargzOptions(estargz.WithCompression(tutil.ZstdCompressionWithLevel(zstd.SpeedFastest)())),
)
if err != nil {
t.Fatalf("failed to build eStargz: %v", err)
}
r, err := factory(sr, metadata.WithDecompressors(tutil.ZstdCompressionWithLevel(zstd.SpeedFastest)()))
if err != nil {
t.Fatalf("failed to create metadata reader: %v", err)
}
defer r.Close()
cc := cache.NewMemoryCache()

// Case1: empty statfsBase -> zero stats
rootEmpty := getRootNode(t, r, OverlayOpaqueAll, tocDgst, cc, lc)
var out fuse.StatfsOut
if errno := rootEmpty.Operations().(fusefs.NodeStatfser).Statfs(context.Background(), &out); errno != 0 {
t.Fatalf("Statfs failed: %v", errno)
}
if out.Blocks != 0 || out.Files != 0 {
t.Fatalf("expected zero stats for empty base, got Blocks=%d Files=%d", out.Blocks, out.Files)
}

// Case2: valid statfsBase -> non-zero stats
dir, err := os.MkdirTemp("", "statfsbase")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
rootWith := getRootNodeWithStatfsBase(t, r, OverlayOpaqueAll, tocDgst, cc, lc, dir)
var out2 fuse.StatfsOut
if errno := rootWith.Operations().(fusefs.NodeStatfser).Statfs(context.Background(), &out2); errno != 0 {
t.Fatalf("Statfs with base failed: %v", errno)
}
if out2.Blocks == 0 || out2.Bsize == 0 {
t.Fatalf("expected non-zero stats for valid base, got Blocks=%d Bsize=%d", out2.Blocks, out2.Bsize)
}
}

type testBlobState struct {
size int64
fetchedSize int64
Expand Down
Loading