summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/dirent_refs_test.go
diff options
context:
space:
mode:
authorGoogler <noreply@google.com>2018-04-27 10:37:02 -0700
committerAdin Scannell <ascannell@google.com>2018-04-28 01:44:26 -0400
commitd02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch)
tree54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/sentry/fs/dirent_refs_test.go
parentf70210e742919f40aa2f0934a22f1c9ba6dada62 (diff)
Check in gVisor.
PiperOrigin-RevId: 194583126 Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/sentry/fs/dirent_refs_test.go')
-rw-r--r--pkg/sentry/fs/dirent_refs_test.go417
1 files changed, 417 insertions, 0 deletions
diff --git a/pkg/sentry/fs/dirent_refs_test.go b/pkg/sentry/fs/dirent_refs_test.go
new file mode 100644
index 000000000..8ce9ba02d
--- /dev/null
+++ b/pkg/sentry/fs/dirent_refs_test.go
@@ -0,0 +1,417 @@
+// Copyright 2018 Google Inc.
+//
+// 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 fs
+
+import (
+ "syscall"
+ "testing"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
+)
+
+func newMockDirInode(ctx context.Context, cache *DirentCache) *Inode {
+ return NewMockInode(ctx, NewMockMountSource(cache), StableAttr{Type: Directory})
+}
+
+func TestWalkPositive(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ root := NewDirent(newMockDirInode(ctx, nil), "root")
+
+ if got := root.TestReadRefs(); got != 0 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 0)
+ }
+
+ name := "d"
+ d, err := root.walk(ctx, root, name, false)
+ if err != nil {
+ t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
+ }
+
+ if got := root.TestReadRefs(); got != 1 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 1)
+ }
+
+ if got := d.TestReadRefs(); got != 0 {
+ t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 0)
+ }
+
+ d.DecRef()
+
+ if got := root.TestReadRefs(); got != 0 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 0)
+ }
+
+ if got := d.TestReadRefs(); got != -1 {
+ t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, -1)
+ }
+
+ root.flush()
+
+ if got := len(root.children); got != 0 {
+ t.Fatalf("root has %d children, want %d", got, 0)
+ }
+}
+
+func TestWalkNegative(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ root := NewDirent(NewEmptyDir(ctx, nil), "root")
+ mn := root.Inode.InodeOperations.(*mockInodeOperationsLookupNegative)
+
+ if got := root.TestReadRefs(); got != 0 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 0)
+ }
+
+ name := "d"
+ for i := 0; i < 100; i++ {
+ _, err := root.walk(ctx, root, name, false)
+ if err != syscall.ENOENT {
+ t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, syscall.ENOENT)
+ }
+ }
+
+ if got := root.TestReadRefs(); got != 0 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 1)
+ }
+
+ if got := len(root.children); got != 1 {
+ t.Fatalf("root has %d children, want %d", got, 1)
+ }
+
+ w, ok := root.children[name]
+ if !ok {
+ t.Fatalf("root wants child at %q", name)
+ }
+
+ child := w.Get()
+ if child == nil {
+ t.Fatalf("root wants to resolve weak reference")
+ }
+
+ if !child.(*Dirent).IsNegative() {
+ t.Fatalf("root found positive child at %q, want negative", name)
+ }
+
+ if got := child.(*Dirent).TestReadRefs(); got != 1 {
+ t.Fatalf("child has a ref count of %d, want %d", got, 1)
+ }
+
+ child.DecRef()
+
+ if got := child.(*Dirent).TestReadRefs(); got != 0 {
+ t.Fatalf("child has a ref count of %d, want %d", got, 0)
+ }
+
+ if got := len(root.children); got != 1 {
+ t.Fatalf("root has %d children, want %d", got, 1)
+ }
+
+ root.DecRef()
+
+ if got := root.TestReadRefs(); got != -1 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 0)
+ }
+
+ AsyncBarrier()
+
+ if got := mn.releaseCalled; got != true {
+ t.Fatalf("root.Close was called %v, want true", got)
+ }
+}
+
+type mockInodeOperationsLookupNegative struct {
+ *MockInodeOperations
+ releaseCalled bool
+}
+
+func NewEmptyDir(ctx context.Context, cache *DirentCache) *Inode {
+ m := NewMockMountSource(cache)
+ return NewInode(&mockInodeOperationsLookupNegative{
+ MockInodeOperations: NewMockInodeOperations(ctx),
+ }, m, StableAttr{Type: Directory})
+}
+
+func (m *mockInodeOperationsLookupNegative) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) {
+ return NewNegativeDirent(p), nil
+}
+
+func (m *mockInodeOperationsLookupNegative) Release(context.Context) {
+ m.releaseCalled = true
+}
+
+func TestHashNegativeToPositive(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ root := NewDirent(NewEmptyDir(ctx, nil), "root")
+
+ name := "d"
+ _, err := root.walk(ctx, root, name, false)
+ if err != syscall.ENOENT {
+ t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, syscall.ENOENT)
+ }
+
+ if got := root.exists(ctx, root, name); got != false {
+ t.Fatalf("got %q exists, want does not exist", name)
+ }
+
+ f, err := root.Create(ctx, root, name, FileFlags{}, FilePermissions{})
+ if err != nil {
+ t.Fatalf("root.Create(%q, _), got error %v, want nil", name, err)
+ }
+ d := f.Dirent
+
+ if d.IsNegative() {
+ t.Fatalf("got negative Dirent, want positive")
+ }
+
+ if got := d.TestReadRefs(); got != 0 {
+ t.Fatalf("child %q has a ref count of %d, want %d", name, got, 0)
+ }
+
+ if got := root.TestReadRefs(); got != 1 {
+ t.Fatalf("root has a ref count of %d, want %d", got, 1)
+ }
+
+ if got := len(root.children); got != 1 {
+ t.Fatalf("got %d children, want %d", got, 1)
+ }
+
+ w, ok := root.children[name]
+ if !ok {
+ t.Fatalf("failed to find weak reference to %q", name)
+ }
+
+ child := w.Get()
+ if child == nil {
+ t.Fatalf("want to resolve weak reference")
+ }
+
+ if child.(*Dirent) != d {
+ t.Fatalf("got foreign child")
+ }
+}
+
+func TestRevalidate(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ for _, test := range []struct {
+ // desc is the test's description.
+ desc string
+
+ // Whether to make negative Dirents.
+ makeNegative bool
+ }{
+ {
+ desc: "Revalidate negative Dirent",
+ makeNegative: true,
+ },
+ {
+ desc: "Revalidate positive Dirent",
+ makeNegative: false,
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ root := NewDirent(NewMockInodeRevalidate(ctx, test.makeNegative), "root")
+
+ name := "d"
+ d1, err := root.walk(ctx, root, name, false)
+ if !test.makeNegative && err != nil {
+ t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
+ }
+ d2, err := root.walk(ctx, root, name, false)
+ if !test.makeNegative && err != nil {
+ t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
+ }
+ if !test.makeNegative && d1 == d2 {
+ t.Fatalf("revalidating walk got same *Dirent, want different")
+ }
+ if got := len(root.children); got != 1 {
+ t.Errorf("revalidating walk got %d children, want %d", got, 1)
+ }
+ })
+ }
+}
+
+type MockInodeOperationsRevalidate struct {
+ *MockInodeOperations
+ makeNegative bool
+}
+
+func NewMockInodeRevalidate(ctx context.Context, makeNegative bool) *Inode {
+ mn := NewMockInodeOperations(ctx)
+ m := NewMockMountSource(nil)
+ m.MountSourceOperations.(*MockMountSourceOps).revalidate = true
+ return NewInode(&MockInodeOperationsRevalidate{MockInodeOperations: mn, makeNegative: makeNegative}, m, StableAttr{Type: Directory})
+}
+
+func (m *MockInodeOperationsRevalidate) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) {
+ if !m.makeNegative {
+ return m.MockInodeOperations.Lookup(ctx, dir, p)
+ }
+ return NewNegativeDirent(p), nil
+}
+
+func TestCreateExtraRefs(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ for _, test := range []struct {
+ // desc is the test's description.
+ desc string
+
+ // root is the Dirent to create from.
+ root *Dirent
+
+ // expected references on walked Dirent.
+ refs int64
+ }{
+ {
+ desc: "Create caching",
+ root: NewDirent(NewEmptyDir(ctx, NewDirentCache(1)), "root"),
+ refs: 1,
+ },
+ {
+ desc: "Create not caching",
+ root: NewDirent(NewEmptyDir(ctx, nil), "root"),
+ refs: 0,
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ name := "d"
+ f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{})
+ if err != nil {
+ t.Fatalf("root.Create(root, %q) failed: %v", name, err)
+ }
+ d := f.Dirent
+
+ if got := d.TestReadRefs(); got != test.refs {
+ t.Errorf("dirent has a ref count of %d, want %d", got, test.refs)
+ }
+ })
+ }
+}
+
+func TestRemoveExtraRefs(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ for _, test := range []struct {
+ // desc is the test's description.
+ desc string
+
+ // root is the Dirent to make and remove from.
+ root *Dirent
+ }{
+ {
+ desc: "Remove caching",
+ root: NewDirent(NewEmptyDir(ctx, NewDirentCache(1)), "root"),
+ },
+ {
+ desc: "Remove not caching",
+ root: NewDirent(NewEmptyDir(ctx, nil), "root"),
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ name := "d"
+ f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{})
+ if err != nil {
+ t.Fatalf("root.Create(%q, _) failed: %v", name, err)
+ }
+ d := f.Dirent
+
+ if err := test.root.Remove(contexttest.Context(t), test.root, name); err != nil {
+ t.Fatalf("root.Remove(root, %q) failed: %v", name, err)
+ }
+
+ if got := d.TestReadRefs(); got != 0 {
+ t.Fatalf("dirent has a ref count of %d, want %d", got, 0)
+ }
+
+ d.DecRef()
+
+ test.root.flush()
+
+ if got := len(test.root.children); got != 0 {
+ t.Errorf("root has %d children, want %d", got, 0)
+ }
+ })
+ }
+}
+
+func TestRenameExtraRefs(t *testing.T) {
+ // refs == 0 -> one reference.
+ // refs == -1 -> has been destroyed.
+
+ ctx := contexttest.Context(t)
+ for _, test := range []struct {
+ // desc is the test's description.
+ desc string
+
+ // cache of extra Dirent references, may be nil.
+ cache *DirentCache
+ }{
+ {
+ desc: "Rename no caching",
+ cache: nil,
+ },
+ {
+ desc: "Rename caching",
+ cache: NewDirentCache(5),
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ dirAttr := StableAttr{Type: Directory}
+
+ oldParent := NewDirent(NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "old_parent")
+ newParent := NewDirent(NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "new_parent")
+
+ renamed, err := oldParent.Walk(ctx, oldParent, "old_child")
+ if err != nil {
+ t.Fatalf("Walk(oldParent, %q) got error %v, want nil", "old_child", err)
+ }
+ replaced, err := newParent.Walk(ctx, oldParent, "new_child")
+ if err != nil {
+ t.Fatalf("Walk(newParent, %q) got error %v, want nil", "new_child", err)
+ }
+
+ if err := Rename(contexttest.RootContext(t), oldParent /*root */, oldParent, "old_child", newParent, "new_child"); err != nil {
+ t.Fatalf("Rename got error %v, want nil", err)
+ }
+
+ oldParent.flush()
+ newParent.flush()
+
+ // Expect to have only active references.
+ if got := renamed.TestReadRefs(); got != 0 {
+ t.Errorf("renamed has ref count %d, want only active references %d", got, 0)
+ }
+ if got := replaced.TestReadRefs(); got != 0 {
+ t.Errorf("replaced has ref count %d, want only active references %d", got, 0)
+ }
+ })
+ }
+}