diff options
Diffstat (limited to 'pkg/sentry/fs/gofer')
-rw-r--r-- | pkg/sentry/fs/gofer/BUILD | 67 | ||||
-rw-r--r-- | pkg/sentry/fs/gofer/gofer_state_autogen.go | 255 | ||||
-rw-r--r-- | pkg/sentry/fs/gofer/gofer_test.go | 310 |
3 files changed, 255 insertions, 377 deletions
diff --git a/pkg/sentry/fs/gofer/BUILD b/pkg/sentry/fs/gofer/BUILD deleted file mode 100644 index fea135eea..000000000 --- a/pkg/sentry/fs/gofer/BUILD +++ /dev/null @@ -1,67 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "gofer", - srcs = [ - "attr.go", - "cache_policy.go", - "context_file.go", - "device.go", - "fifo.go", - "file.go", - "file_state.go", - "fs.go", - "handles.go", - "inode.go", - "inode_state.go", - "path.go", - "session.go", - "session_state.go", - "socket.go", - "util.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/fd", - "//pkg/log", - "//pkg/metric", - "//pkg/p9", - "//pkg/refs", - "//pkg/safemem", - "//pkg/secio", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fdpipe", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/host", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sync", - "//pkg/syserr", - "//pkg/syserror", - "//pkg/unet", - "//pkg/usermem", - "//pkg/waiter", - ], -) - -go_test( - name = "gofer_test", - size = "small", - srcs = ["gofer_test.go"], - library = ":gofer", - deps = [ - "//pkg/context", - "//pkg/p9", - "//pkg/p9/p9test", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - ], -) diff --git a/pkg/sentry/fs/gofer/gofer_state_autogen.go b/pkg/sentry/fs/gofer/gofer_state_autogen.go new file mode 100644 index 000000000..1f7360ec8 --- /dev/null +++ b/pkg/sentry/fs/gofer/gofer_state_autogen.go @@ -0,0 +1,255 @@ +// automatically generated by stateify. + +package gofer + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (x *fifo) StateTypeName() string { + return "pkg/sentry/fs/gofer.fifo" +} + +func (x *fifo) StateFields() []string { + return []string{ + "InodeOperations", + "fileIops", + } +} + +func (x *fifo) beforeSave() {} + +func (x *fifo) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.InodeOperations) + m.Save(1, &x.fileIops) +} + +func (x *fifo) afterLoad() {} + +func (x *fifo) StateLoad(m state.Source) { + m.Load(0, &x.InodeOperations) + m.Load(1, &x.fileIops) +} + +func (x *fileOperations) StateTypeName() string { + return "pkg/sentry/fs/gofer.fileOperations" +} + +func (x *fileOperations) StateFields() []string { + return []string{ + "inodeOperations", + "dirCursor", + "flags", + } +} + +func (x *fileOperations) beforeSave() {} + +func (x *fileOperations) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.inodeOperations) + m.Save(1, &x.dirCursor) + m.Save(2, &x.flags) +} + +func (x *fileOperations) StateLoad(m state.Source) { + m.LoadWait(0, &x.inodeOperations) + m.Load(1, &x.dirCursor) + m.LoadWait(2, &x.flags) + m.AfterLoad(x.afterLoad) +} + +func (x *filesystem) StateTypeName() string { + return "pkg/sentry/fs/gofer.filesystem" +} + +func (x *filesystem) StateFields() []string { + return []string{} +} + +func (x *filesystem) beforeSave() {} + +func (x *filesystem) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *filesystem) afterLoad() {} + +func (x *filesystem) StateLoad(m state.Source) { +} + +func (x *inodeOperations) StateTypeName() string { + return "pkg/sentry/fs/gofer.inodeOperations" +} + +func (x *inodeOperations) StateFields() []string { + return []string{ + "fileState", + "cachingInodeOps", + } +} + +func (x *inodeOperations) beforeSave() {} + +func (x *inodeOperations) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.fileState) + m.Save(1, &x.cachingInodeOps) +} + +func (x *inodeOperations) afterLoad() {} + +func (x *inodeOperations) StateLoad(m state.Source) { + m.LoadWait(0, &x.fileState) + m.Load(1, &x.cachingInodeOps) +} + +func (x *inodeFileState) StateTypeName() string { + return "pkg/sentry/fs/gofer.inodeFileState" +} + +func (x *inodeFileState) StateFields() []string { + return []string{ + "s", + "sattr", + "loading", + "savedUAttr", + "hostMappable", + } +} + +func (x *inodeFileState) StateSave(m state.Sink) { + x.beforeSave() + var loading struct{} = x.saveLoading() + m.SaveValue(2, loading) + m.Save(0, &x.s) + m.Save(1, &x.sattr) + m.Save(3, &x.savedUAttr) + m.Save(4, &x.hostMappable) +} + +func (x *inodeFileState) StateLoad(m state.Source) { + m.LoadWait(0, &x.s) + m.LoadWait(1, &x.sattr) + m.Load(3, &x.savedUAttr) + m.Load(4, &x.hostMappable) + m.LoadValue(2, new(struct{}), func(y interface{}) { x.loadLoading(y.(struct{})) }) + m.AfterLoad(x.afterLoad) +} + +func (x *overrideInfo) StateTypeName() string { + return "pkg/sentry/fs/gofer.overrideInfo" +} + +func (x *overrideInfo) StateFields() []string { + return []string{ + "dirent", + "endpoint", + "inode", + } +} + +func (x *overrideInfo) beforeSave() {} + +func (x *overrideInfo) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.dirent) + m.Save(1, &x.endpoint) + m.Save(2, &x.inode) +} + +func (x *overrideInfo) afterLoad() {} + +func (x *overrideInfo) StateLoad(m state.Source) { + m.Load(0, &x.dirent) + m.Load(1, &x.endpoint) + m.Load(2, &x.inode) +} + +func (x *overrideMaps) StateTypeName() string { + return "pkg/sentry/fs/gofer.overrideMaps" +} + +func (x *overrideMaps) StateFields() []string { + return []string{ + "pathMap", + } +} + +func (x *overrideMaps) beforeSave() {} + +func (x *overrideMaps) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.pathMap) +} + +func (x *overrideMaps) afterLoad() {} + +func (x *overrideMaps) StateLoad(m state.Source) { + m.Load(0, &x.pathMap) +} + +func (x *session) StateTypeName() string { + return "pkg/sentry/fs/gofer.session" +} + +func (x *session) StateFields() []string { + return []string{ + "AtomicRefCount", + "msize", + "version", + "cachePolicy", + "aname", + "superBlockFlags", + "limitHostFDTranslation", + "overlayfsStaleRead", + "connID", + "inodeMappings", + "mounter", + "overrides", + } +} + +func (x *session) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.AtomicRefCount) + m.Save(1, &x.msize) + m.Save(2, &x.version) + m.Save(3, &x.cachePolicy) + m.Save(4, &x.aname) + m.Save(5, &x.superBlockFlags) + m.Save(6, &x.limitHostFDTranslation) + m.Save(7, &x.overlayfsStaleRead) + m.Save(8, &x.connID) + m.Save(9, &x.inodeMappings) + m.Save(10, &x.mounter) + m.Save(11, &x.overrides) +} + +func (x *session) StateLoad(m state.Source) { + m.Load(0, &x.AtomicRefCount) + m.LoadWait(1, &x.msize) + m.LoadWait(2, &x.version) + m.LoadWait(3, &x.cachePolicy) + m.LoadWait(4, &x.aname) + m.LoadWait(5, &x.superBlockFlags) + m.Load(6, &x.limitHostFDTranslation) + m.Load(7, &x.overlayfsStaleRead) + m.LoadWait(8, &x.connID) + m.LoadWait(9, &x.inodeMappings) + m.LoadWait(10, &x.mounter) + m.LoadWait(11, &x.overrides) + m.AfterLoad(x.afterLoad) +} + +func init() { + state.Register((*fifo)(nil)) + state.Register((*fileOperations)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*inodeOperations)(nil)) + state.Register((*inodeFileState)(nil)) + state.Register((*overrideInfo)(nil)) + state.Register((*overrideMaps)(nil)) + state.Register((*session)(nil)) +} diff --git a/pkg/sentry/fs/gofer/gofer_test.go b/pkg/sentry/fs/gofer/gofer_test.go deleted file mode 100644 index 326fed954..000000000 --- a/pkg/sentry/fs/gofer/gofer_test.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright 2018 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 ( - "fmt" - "syscall" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/p9/p9test" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// rootTest runs a test with a p9 mock and an fs.InodeOperations created from -// the attached root directory. The root file will be closed and client -// disconnected, but additional files must be closed manually. -func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context, *p9test.Harness, *p9test.Mock, *fs.Inode)) { - t.Run(name, func(t *testing.T) { - h, c := p9test.NewHarness(t) - defer h.Finish() - - // Create a new root. Note that we pass an empty, but non-nil - // map here. This allows tests to extend the root children - // dynamically. - root := h.NewDirectory(map[string]p9test.Generator{})(nil) - - // Return this as the root. - h.Attacher.EXPECT().Attach().Return(root, nil).Times(1) - - // ... and open via the client. - rootFile, err := c.Attach("/") - if err != nil { - t.Fatalf("unable to attach: %v", err) - } - defer rootFile.Close() - - // Wrap an a session. - s := &session{ - mounter: fs.RootOwner, - cachePolicy: cp, - client: c, - } - - // ... and an INode, with only the mode being explicitly valid for now. - ctx := contexttest.Context(t) - sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{ - file: rootFile, - }, root.QID, p9.AttrMaskAll(), root.Attr) - m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{}) - rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr) - - // Ensure that the cache is fully invalidated, so that any - // close actions actually take place before the full harness is - // torn down. - defer func() { - m.FlushDirentRefs() - - // Wait for all resources to be released, otherwise the - // operations may fail after we close the rootFile. - fs.AsyncBarrier() - }() - - // Execute the test. - fn(ctx, h, root, rootInode) - }) -} - -func TestLookup(t *testing.T) { - type lookupTest struct { - // Name of the test. - name string - - // Expected return value. - want error - } - - tests := []lookupTest{ - { - name: "mock Walk passes (function succeeds)", - want: nil, - }, - { - name: "mock Walk fails (function fails)", - want: syscall.ENOENT, - }, - } - - const file = "file" // The walked target file. - - for _, test := range tests { - rootTest(t, test.name, cacheNone, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) { - // Setup the appropriate result. - rootFile.WalkCallback = func() error { - return test.want - } - if test.want == nil { - // Set the contents of the root. We expect a - // normal file generator for ppp above. This is - // overriden by setting WalkErr in the mock. - rootFile.AddChild(file, h.NewFile()) - } - - // Call function. - dirent, err := rootInode.Lookup(ctx, file) - - // Unwrap the InodeOperations. - var newInodeOperations fs.InodeOperations - if dirent != nil { - if dirent.IsNegative() { - err = syscall.ENOENT - } else { - newInodeOperations = dirent.Inode.InodeOperations - } - } - - // Check return values. - if err != test.want { - t.Errorf("Lookup got err %v, want %v", err, test.want) - } - if err == nil && newInodeOperations == nil { - t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil") - } - }) - } -} - -func TestRevalidation(t *testing.T) { - type revalidationTest struct { - cachePolicy cachePolicy - - // Whether dirent should be reloaded before any modifications. - preModificationWantReload bool - - // Whether dirent should be reloaded after updating an unstable - // attribute on the remote fs. - postModificationWantReload bool - - // Whether dirent unstable attributes should be updated after - // updating an attribute on the remote fs. - postModificationWantUpdatedAttrs bool - - // Whether dirent should be reloaded after the remote has - // removed the file. - postRemovalWantReload bool - } - - tests := []revalidationTest{ - { - // Policy cacheNone causes Revalidate to always return - // true. - cachePolicy: cacheNone, - preModificationWantReload: true, - postModificationWantReload: true, - postModificationWantUpdatedAttrs: true, - postRemovalWantReload: true, - }, - { - // Policy cacheAll causes Revalidate to always return - // false. - cachePolicy: cacheAll, - preModificationWantReload: false, - postModificationWantReload: false, - postModificationWantUpdatedAttrs: false, - postRemovalWantReload: false, - }, - { - // Policy cacheAllWritethrough causes Revalidate to - // always return false. - cachePolicy: cacheAllWritethrough, - preModificationWantReload: false, - postModificationWantReload: false, - postModificationWantUpdatedAttrs: false, - postRemovalWantReload: false, - }, - { - // Policy cacheRemoteRevalidating causes Revalidate to - // return update cached unstable attrs, and returns - // true only when the remote inode itself has been - // removed or replaced. - cachePolicy: cacheRemoteRevalidating, - preModificationWantReload: false, - postModificationWantReload: false, - postModificationWantUpdatedAttrs: true, - postRemovalWantReload: true, - }, - } - - const file = "file" // The file walked below. - - for _, test := range tests { - name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy) - rootTest(t, name, test.cachePolicy, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) { - // Wrap in a dirent object. - rootDir := fs.NewDirent(ctx, rootInode, "root") - - // Create a mock file a child of the root. We save when - // this is generated, so that when the time changed, we - // can update the original entry. - var origMocks []*p9test.Mock - rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock { - // Regular a regular file that has a consistent - // path number. This might be used by - // validation so we don't change it. - m := h.NewMock(parent, 0, p9.Attr{ - Mode: p9.ModeRegular, - }) - origMocks = append(origMocks, m) - return m - }) - - // Do the walk. - dirent, err := rootDir.Walk(ctx, rootDir, file) - if err != nil { - t.Fatalf("Lookup failed: %v", err) - } - - // We must release the dirent, of the test will fail - // with a reference leak. This is tracked by p9test. - defer dirent.DecRef(ctx) - - // Walk again. Depending on the cache policy, we may - // get a new dirent. - newDirent, err := rootDir.Walk(ctx, rootDir, file) - if err != nil { - t.Fatalf("Lookup failed: %v", err) - } - if test.preModificationWantReload && dirent == newDirent { - t.Errorf("Lookup with cachePolicy=%s got old dirent %+v, wanted a new dirent", test.cachePolicy, dirent) - } - if !test.preModificationWantReload && dirent != newDirent { - t.Errorf("Lookup with cachePolicy=%s got new dirent %+v, wanted old dirent %+v", test.cachePolicy, newDirent, dirent) - } - newDirent.DecRef(ctx) // See above. - - // Modify the underlying mocked file's modification - // time for the next walk that occurs. - nowSeconds := time.Now().Unix() - rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock { - // Ensure that the path is the same as above, - // but we change only the modification time of - // the file. - return h.NewMock(parent, 0, p9.Attr{ - Mode: p9.ModeRegular, - MTimeSeconds: uint64(nowSeconds), - }) - }) - - // We also modify the original time, so that GetAttr - // behaves as expected for the caching case. - for _, m := range origMocks { - m.Attr.MTimeSeconds = uint64(nowSeconds) - } - - // Walk again. Depending on the cache policy, we may - // get a new dirent. - newDirent, err = rootDir.Walk(ctx, rootDir, file) - if err != nil { - t.Fatalf("Lookup failed: %v", err) - } - if test.postModificationWantReload && dirent == newDirent { - t.Errorf("Lookup with cachePolicy=%s got old dirent, wanted a new dirent", test.cachePolicy) - } - if !test.postModificationWantReload && dirent != newDirent { - t.Errorf("Lookup with cachePolicy=%s got new dirent, wanted old dirent", test.cachePolicy) - } - uattrs, err := newDirent.Inode.UnstableAttr(ctx) - if err != nil { - t.Fatalf("Error getting unstable attrs: %v", err) - } - gotModTimeSeconds := uattrs.ModificationTime.Seconds() - if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds { - t.Fatalf("Lookup with cachePolicy=%s got new modification time %v, wanted %v", test.cachePolicy, gotModTimeSeconds, nowSeconds) - } - newDirent.DecRef(ctx) // See above. - - // Remove the file from the remote fs, subsequent walks - // should now fail to find anything. - rootFile.RemoveChild(file) - - // Walk again. Depending on the cache policy, we may - // get ENOENT. - newDirent, err = rootDir.Walk(ctx, rootDir, file) - if test.postRemovalWantReload && err == nil { - t.Errorf("Lookup with cachePolicy=%s got nil error, wanted ENOENT", test.cachePolicy) - } - if !test.postRemovalWantReload && (err != nil || dirent != newDirent) { - t.Errorf("Lookup with cachePolicy=%s got new dirent and error %v, wanted old dirent and nil error", test.cachePolicy, err) - } - if err == nil { - newDirent.DecRef(ctx) // See above. - } - }) - } -} |