mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
- gofer client: Added an xattr cache on the inode struct. In case the gofer mount being used is exclusive (cachedMetadataAuthoritative() == true), we cache positive and negative xattr queries. - Subsequently, we now hold metadataMu for the entire duration of xattr operations. This is important to avoid stale updates in the xattr cache due to concurrent xattr operations. This is the same approach we take for maintaining other inode metadata in the inode struct. - Added support for setxattr/removexattr/listxattr in runsc/fsgofer and gofer client (when using directfs). Consequently the seccomp filters for sandbox and gofer processes were expanded to allow these syscalls. - Added xattr syscall tests to //test/syscalls/BUILD so they are run on BuildKite on presubmits. (Also fixed up some tests.) A note about performance: The default rootfs configuration for runsc enables rootfs overlay. In that, the gofer mount is an exclusive mount on the lower layer. fsimpl/overlay issues a lot of getxattr calls on the lower layer to check for opaque directory xattr. However, as before, the "trusted" xattr namespace is not supported. So all those calls continue to fail with EOPNOTSUPP. So this should not regress rootfs overlay performance. When using an overlay where gofer is the upper layer or when "userxattr" mount option is in effect, fsimpl/overlay will similarly issue a lot of getxattr calls using the "user" xattr namespace. This will be allowed by checkXattrPermissions(). To optimize the common case where directories are not opaque, we specially track `overlayOpaqueNegative` in `xattrCache`. This avoids a map allocation during xattrCache.addNegative(). Updates #12475 Updates #12828 PiperOrigin-RevId: 915097116
197 lines
5.9 KiB
Go
197 lines
5.9 KiB
Go
// Copyright 2020 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package gofer
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/lisafs"
|
|
"gvisor.dev/gvisor/pkg/sentry/contexttest"
|
|
"gvisor.dev/gvisor/pkg/sentry/ktime"
|
|
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
|
)
|
|
|
|
func TestDestroyIdempotent(t *testing.T) {
|
|
ctx := contexttest.Context(t)
|
|
fs := filesystem{
|
|
mf: pgalloc.MemoryFileFromContext(ctx),
|
|
inoByKey: make(map[inoKey]uint64),
|
|
inodeByKey: make(map[inoKey]*inode),
|
|
clock: ktime.RealtimeClockFromContext(ctx),
|
|
// Test relies on no dentry being held in the cache.
|
|
dentryCache: &dentryCache{maxCachedDentries: 0},
|
|
client: &lisafs.Client{},
|
|
}
|
|
|
|
parentInode := lisafs.Inode{
|
|
ControlFD: 1,
|
|
Stat: lisafs.Statx{
|
|
Mask: linux.STATX_TYPE | linux.STATX_MODE,
|
|
Mode: linux.S_IFDIR | 0666,
|
|
},
|
|
}
|
|
parent, err := fs.newLisafsDentry(ctx, &parentInode)
|
|
if err != nil {
|
|
t.Fatalf("fs.newDentry(): %v", err)
|
|
}
|
|
|
|
childInode := lisafs.Inode{
|
|
ControlFD: 2,
|
|
Stat: lisafs.Statx{
|
|
Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_SIZE,
|
|
Mode: linux.S_IFREG | 0666,
|
|
Size: 0,
|
|
},
|
|
}
|
|
|
|
child, err := fs.newLisafsDentry(ctx, &childInode)
|
|
if err != nil {
|
|
t.Fatalf("fs.newDentry(): %v", err)
|
|
}
|
|
parent.opMu.Lock()
|
|
parent.childrenMu.Lock()
|
|
parent.cacheNewChildLocked(child, "child")
|
|
parent.childrenMu.Unlock()
|
|
parent.opMu.Unlock()
|
|
|
|
fs.renameMu.Lock()
|
|
defer fs.renameMu.Unlock()
|
|
child.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
|
|
if got := child.refs.Load(); got != -1 {
|
|
t.Fatalf("child.refs=%d, want: -1", got)
|
|
}
|
|
// Parent will also be destroyed when child reference is removed.
|
|
if got := parent.refs.Load(); got != -1 {
|
|
t.Fatalf("parent.refs=%d, want: -1", got)
|
|
}
|
|
child.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
|
|
child.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
|
|
}
|
|
|
|
func TestStringFixedCache(t *testing.T) {
|
|
names := []string{"a", "b", "c"}
|
|
cache := stringFixedCache{}
|
|
|
|
cache.init(uint64(len(names)))
|
|
if inited := cache.isInited(); !inited {
|
|
t.Fatalf("cache.isInited(): %v, want: true", inited)
|
|
}
|
|
for _, s := range names {
|
|
victim := cache.add(s)
|
|
if victim != "" {
|
|
t.Fatalf("cache.add(): %v, want: \"\"", victim)
|
|
}
|
|
}
|
|
for _, s := range names {
|
|
victim := cache.add("something")
|
|
if victim != s {
|
|
t.Fatalf("cache.add(): %v, want: %v", victim, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestXattrCacheValues(t *testing.T) {
|
|
ino := &inode{}
|
|
|
|
// 1. Initial state
|
|
if _, _, found := ino.xattrCache.get("user.foo"); found {
|
|
t.Errorf("get(user.foo) got found=true, want false")
|
|
}
|
|
|
|
// 2. Cache present xattr
|
|
ino.xattrCache.add("user.foo", "bar")
|
|
val, negative, found := ino.xattrCache.get("user.foo")
|
|
if !found || negative || val != "bar" {
|
|
t.Errorf("get(user.foo) = (%q, %v, %v), want (%q, false, true)", val, negative, found, "bar")
|
|
}
|
|
|
|
// 3. Cache negative xattr
|
|
ino.xattrCache.addNegative("user.neg")
|
|
val, negative, found = ino.xattrCache.get("user.neg")
|
|
if !found || !negative || val != "" {
|
|
t.Errorf("get(user.neg) = (%q, %v, %v), want (%q, true, true)", val, negative, found, "")
|
|
}
|
|
|
|
// Overwrite negative xattr with present xattr
|
|
ino.xattrCache.add("user.neg", "baz")
|
|
val, negative, found = ino.xattrCache.get("user.neg")
|
|
if !found || negative || val != "baz" {
|
|
t.Errorf("get(user.neg) = (%q, %v, %v), want (%q, false, true)", val, negative, found, "baz")
|
|
}
|
|
|
|
// 4. Overlay opaque xattr
|
|
ino.xattrCache.addNegative(xattrOverlayOpaque)
|
|
val, negative, found = ino.xattrCache.get(xattrOverlayOpaque)
|
|
if !found || !negative || val != "" {
|
|
t.Errorf("get(xattrOverlayOpaque) = (%q, %v, %v), want (%q, true, true)", val, negative, found, "")
|
|
}
|
|
|
|
// Overwrite overlay opaque xattr with present xattr
|
|
ino.xattrCache.add(xattrOverlayOpaque, "y")
|
|
val, negative, found = ino.xattrCache.get(xattrOverlayOpaque)
|
|
if !found || negative || val != "y" {
|
|
t.Errorf("get(xattrOverlayOpaque) = (%q, %v, %v), want (%q, false, true)", val, negative, found, "y")
|
|
}
|
|
}
|
|
|
|
func TestXattrCacheList(t *testing.T) {
|
|
ino := &inode{}
|
|
|
|
// 1. Initial state
|
|
if _, found := ino.xattrCache.getList(); found {
|
|
t.Errorf("getList() got found=true, want false")
|
|
}
|
|
|
|
// 2. Cache empty list
|
|
ino.xattrCache.setList([]string{})
|
|
list, found := ino.xattrCache.getList()
|
|
if !found || len(list) != 0 {
|
|
t.Errorf("getList() = (%v, %v), want ([], true)", list, found)
|
|
}
|
|
|
|
// 3. Cache non-empty list
|
|
initialList := []string{"user.foo", "user.bar"}
|
|
ino.xattrCache.setList(initialList)
|
|
list, found = ino.xattrCache.getList()
|
|
if !found || !slices.Equal(list, initialList) {
|
|
t.Errorf("getList() = (%v, %v), want (%v, true)", list, found, initialList)
|
|
}
|
|
|
|
// 4. Cache new xattr updates list
|
|
ino.xattrCache.add("user.baz", "val")
|
|
wantList := []string{"user.foo", "user.bar", "user.baz"}
|
|
list, found = ino.xattrCache.getList()
|
|
if !found || !slices.Equal(list, wantList) {
|
|
t.Errorf("getList() = (%v, %v), want (%v, true)", list, found, wantList)
|
|
}
|
|
|
|
// 5. Cache existing xattr does not duplicate in list
|
|
ino.xattrCache.add("user.foo", "val2")
|
|
list, found = ino.xattrCache.getList()
|
|
if !found || !slices.Equal(list, wantList) {
|
|
t.Errorf("getList() = (%v, %v), want (%v, true)", list, found, wantList)
|
|
}
|
|
|
|
// 6. Cache negative xattr removes from list
|
|
ino.xattrCache.addNegative("user.bar")
|
|
wantList = []string{"user.foo", "user.baz"}
|
|
list, found = ino.xattrCache.getList()
|
|
if !found || !slices.Equal(list, wantList) {
|
|
t.Errorf("getList() = (%v, %v), want (%v, true)", list, found, wantList)
|
|
}
|
|
}
|