diff options
Diffstat (limited to 'pkg/sentry/fsimpl/kernfs')
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/BUILD | 143 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/dentry_refs.go | 112 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/fstree.go | 55 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go | 823 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/kernfs_test.go | 343 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/slot_list.go | 193 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/static_directory_refs.go | 112 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go | 112 |
8 files changed, 1407 insertions, 486 deletions
diff --git a/pkg/sentry/fsimpl/kernfs/BUILD b/pkg/sentry/fsimpl/kernfs/BUILD deleted file mode 100644 index 4de1e6922..000000000 --- a/pkg/sentry/fsimpl/kernfs/BUILD +++ /dev/null @@ -1,143 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -licenses(["notice"]) - -go_template_instance( - name = "fstree", - out = "fstree.go", - package = "kernfs", - prefix = "generic", - template = "//pkg/sentry/vfs/genericfstree:generic_fstree", - types = { - "Dentry": "Dentry", - }, -) - -go_template_instance( - name = "slot_list", - out = "slot_list.go", - package = "kernfs", - prefix = "slot", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*slot", - "Linker": "*slot", - }, -) - -go_template_instance( - name = "dentry_refs", - out = "dentry_refs.go", - package = "kernfs", - prefix = "Dentry", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "Dentry", - }, -) - -go_template_instance( - name = "static_directory_refs", - out = "static_directory_refs.go", - package = "kernfs", - prefix = "StaticDirectory", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "StaticDirectory", - }, -) - -go_template_instance( - name = "dir_refs", - out = "dir_refs.go", - package = "kernfs_test", - prefix = "dir", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "dir", - }, -) - -go_template_instance( - name = "readonly_dir_refs", - out = "readonly_dir_refs.go", - package = "kernfs_test", - prefix = "readonlyDir", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "readonlyDir", - }, -) - -go_template_instance( - name = "synthetic_directory_refs", - out = "synthetic_directory_refs.go", - package = "kernfs", - prefix = "syntheticDirectory", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "syntheticDirectory", - }, -) - -go_library( - name = "kernfs", - srcs = [ - "dentry_refs.go", - "dynamic_bytes_file.go", - "fd_impl_util.go", - "filesystem.go", - "fstree.go", - "inode_impl_util.go", - "kernfs.go", - "slot_list.go", - "static_directory_refs.go", - "symlink.go", - "synthetic_directory.go", - "synthetic_directory_refs.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/fspath", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/fs/lock", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/syserror", - "//pkg/usermem", - ], -) - -go_test( - name = "kernfs_test", - size = "small", - srcs = [ - "dir_refs.go", - "kernfs_test.go", - "readonly_dir_refs.go", - ], - deps = [ - ":kernfs", - "//pkg/abi/linux", - "//pkg/context", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/contexttest", - "//pkg/sentry/fsimpl/testutil", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/syserror", - "//pkg/usermem", - "@com_github_google_go_cmp//cmp:go_default_library", - ], -) diff --git a/pkg/sentry/fsimpl/kernfs/dentry_refs.go b/pkg/sentry/fsimpl/kernfs/dentry_refs.go new file mode 100644 index 000000000..c2304939b --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/dentry_refs.go @@ -0,0 +1,112 @@ +package kernfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// ownerType is used to customize logging. Note that we use a pointer to T so +// that we do not copy the entire object when passed as a format parameter. +var DentryownerType *Dentry + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// Note that the number of references is actually refCount + 1 so that a default +// zero-value Refs object contains one reference. +// +// +stateify savable +type DentryRefs struct { + // refCount is composed of two fields: + // + // [32-bit speculative references]:[32-bit real references] + // + // Speculative references are used for TryIncRef, to avoid a CompareAndSwap + // loop. See IncRef, DecRef and TryIncRef for details of how these fields are + // used. + refCount int64 +} + +// EnableLeakCheck enables reference leak checking on r. +func (r *DentryRefs) EnableLeakCheck() { + if refsvfs2.LeakCheckEnabled() { + refsvfs2.Register(r, fmt.Sprintf("%T", DentryownerType)) + } +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *DentryRefs) LeakMessage() string { + return fmt.Sprintf("%T %p: reference count of %d instead of 0", DentryownerType, r, r.ReadRefs()) +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *DentryRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *DentryRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %T", r, DentryownerType)) + } +} + +// TryIncRef implements refs.RefCounter.TryIncRef. +// +// To do this safely without a loop, a speculative reference is first acquired +// on the object. This allows multiple concurrent TryIncRef calls to distinguish +// other TryIncRef calls from genuine references held. +// +//go:nosplit +func (r *DentryRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + v := atomic.AddInt64(&r.refCount, speculativeRef) + if int32(v) < 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + atomic.AddInt64(&r.refCount, -speculativeRef+1) + return true +} + +// DecRef implements refs.RefCounter.DecRef. +// +// Note that speculative references are counted here. Since they were added +// prior to real references reaching zero, they will successfully convert to +// real references. In other words, we see speculative references only in the +// following case: +// +// A: TryIncRef [speculative increase => sees non-negative references] +// B: DecRef [real decrease] +// A: TryIncRef [transform speculative to real] +// +//go:nosplit +func (r *DentryRefs) DecRef(destroy func()) { + switch v := atomic.AddInt64(&r.refCount, -1); { + case v < -1: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %T", r, DentryownerType)) + + case v == -1: + if refsvfs2.LeakCheckEnabled() { + refsvfs2.Unregister(r, fmt.Sprintf("%T", DentryownerType)) + } + + if destroy != nil { + destroy() + } + } +} + +func (r *DentryRefs) afterLoad() { + if refsvfs2.LeakCheckEnabled() && r.ReadRefs() > 0 { + r.EnableLeakCheck() + } +} diff --git a/pkg/sentry/fsimpl/kernfs/fstree.go b/pkg/sentry/fsimpl/kernfs/fstree.go new file mode 100644 index 000000000..9dc52dabc --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/fstree.go @@ -0,0 +1,55 @@ +package kernfs + +import ( + "gvisor.dev/gvisor/pkg/fspath" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is +// either d2's parent or an ancestor of d2's parent. +func genericIsAncestorDentry(d, d2 *Dentry) bool { + for d2 != nil { + if d2.parent == d { + return true + } + if d2.parent == d2 { + return false + } + d2 = d2.parent + } + return false +} + +// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d. +func genericParentOrSelf(d *Dentry) *Dentry { + if d.parent != nil { + return d.parent + } + return d +} + +// PrependPath is a generic implementation of FilesystemImpl.PrependPath(). +func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *Dentry, b *fspath.Builder) error { + for { + if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() { + return vfs.PrependPathAtVFSRootError{} + } + if mnt != nil && &d.vfsd == mnt.Root() { + return nil + } + if d.parent == nil { + return vfs.PrependPathAtNonMountRootError{} + } + b.PrependComponent(d.name) + d = d.parent + } +} + +// DebugPathname returns a pathname to d relative to its filesystem root. +// DebugPathname does not correspond to any Linux function; it's used to +// generate dentry pathnames for debugging. +func genericDebugPathname(d *Dentry) string { + var b fspath.Builder + _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b) + return b.String() +} diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go b/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go new file mode 100644 index 000000000..4a00f2b37 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go @@ -0,0 +1,823 @@ +// automatically generated by stateify. + +package kernfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (r *DentryRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.DentryRefs" +} + +func (r *DentryRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *DentryRefs) beforeSave() {} + +func (r *DentryRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +func (r *DentryRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (f *DynamicBytesFile) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.DynamicBytesFile" +} + +func (f *DynamicBytesFile) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoStatFS", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "locks", + "data", + } +} + +func (f *DynamicBytesFile) beforeSave() {} + +func (f *DynamicBytesFile) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.InodeAttrs) + stateSinkObject.Save(1, &f.InodeNoStatFS) + stateSinkObject.Save(2, &f.InodeNoopRefCount) + stateSinkObject.Save(3, &f.InodeNotDirectory) + stateSinkObject.Save(4, &f.InodeNotSymlink) + stateSinkObject.Save(5, &f.locks) + stateSinkObject.Save(6, &f.data) +} + +func (f *DynamicBytesFile) afterLoad() {} + +func (f *DynamicBytesFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.InodeAttrs) + stateSourceObject.Load(1, &f.InodeNoStatFS) + stateSourceObject.Load(2, &f.InodeNoopRefCount) + stateSourceObject.Load(3, &f.InodeNotDirectory) + stateSourceObject.Load(4, &f.InodeNotSymlink) + stateSourceObject.Load(5, &f.locks) + stateSourceObject.Load(6, &f.data) +} + +func (fd *DynamicBytesFD) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.DynamicBytesFD" +} + +func (fd *DynamicBytesFD) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + "DynamicBytesFileDescriptionImpl", + "LockFD", + "vfsfd", + "inode", + } +} + +func (fd *DynamicBytesFD) beforeSave() {} + +func (fd *DynamicBytesFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(1, &fd.DynamicBytesFileDescriptionImpl) + stateSinkObject.Save(2, &fd.LockFD) + stateSinkObject.Save(3, &fd.vfsfd) + stateSinkObject.Save(4, &fd.inode) +} + +func (fd *DynamicBytesFD) afterLoad() {} + +func (fd *DynamicBytesFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(1, &fd.DynamicBytesFileDescriptionImpl) + stateSourceObject.Load(2, &fd.LockFD) + stateSourceObject.Load(3, &fd.vfsfd) + stateSourceObject.Load(4, &fd.inode) +} + +func (s *SeekEndConfig) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.SeekEndConfig" +} + +func (s *SeekEndConfig) StateFields() []string { + return nil +} + +func (g *GenericDirectoryFDOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.GenericDirectoryFDOptions" +} + +func (g *GenericDirectoryFDOptions) StateFields() []string { + return []string{ + "SeekEnd", + } +} + +func (g *GenericDirectoryFDOptions) beforeSave() {} + +func (g *GenericDirectoryFDOptions) StateSave(stateSinkObject state.Sink) { + g.beforeSave() + stateSinkObject.Save(0, &g.SeekEnd) +} + +func (g *GenericDirectoryFDOptions) afterLoad() {} + +func (g *GenericDirectoryFDOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &g.SeekEnd) +} + +func (fd *GenericDirectoryFD) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.GenericDirectoryFD" +} + +func (fd *GenericDirectoryFD) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + "DirectoryFileDescriptionDefaultImpl", + "LockFD", + "seekEnd", + "vfsfd", + "children", + "off", + } +} + +func (fd *GenericDirectoryFD) beforeSave() {} + +func (fd *GenericDirectoryFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.LockFD) + stateSinkObject.Save(3, &fd.seekEnd) + stateSinkObject.Save(4, &fd.vfsfd) + stateSinkObject.Save(5, &fd.children) + stateSinkObject.Save(6, &fd.off) +} + +func (fd *GenericDirectoryFD) afterLoad() {} + +func (fd *GenericDirectoryFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.LockFD) + stateSourceObject.Load(3, &fd.seekEnd) + stateSourceObject.Load(4, &fd.vfsfd) + stateSourceObject.Load(5, &fd.children) + stateSourceObject.Load(6, &fd.off) +} + +func (i *InodeNoopRefCount) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNoopRefCount" +} + +func (i *InodeNoopRefCount) StateFields() []string { + return []string{ + "InodeTemporary", + } +} + +func (i *InodeNoopRefCount) beforeSave() {} + +func (i *InodeNoopRefCount) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeTemporary) +} + +func (i *InodeNoopRefCount) afterLoad() {} + +func (i *InodeNoopRefCount) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeTemporary) +} + +func (i *InodeDirectoryNoNewChildren) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeDirectoryNoNewChildren" +} + +func (i *InodeDirectoryNoNewChildren) StateFields() []string { + return []string{} +} + +func (i *InodeDirectoryNoNewChildren) beforeSave() {} + +func (i *InodeDirectoryNoNewChildren) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeDirectoryNoNewChildren) afterLoad() {} + +func (i *InodeDirectoryNoNewChildren) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeNotDirectory) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNotDirectory" +} + +func (i *InodeNotDirectory) StateFields() []string { + return []string{ + "InodeAlwaysValid", + } +} + +func (i *InodeNotDirectory) beforeSave() {} + +func (i *InodeNotDirectory) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeAlwaysValid) +} + +func (i *InodeNotDirectory) afterLoad() {} + +func (i *InodeNotDirectory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeAlwaysValid) +} + +func (i *InodeNotSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNotSymlink" +} + +func (i *InodeNotSymlink) StateFields() []string { + return []string{} +} + +func (i *InodeNotSymlink) beforeSave() {} + +func (i *InodeNotSymlink) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeNotSymlink) afterLoad() {} + +func (i *InodeNotSymlink) StateLoad(stateSourceObject state.Source) { +} + +func (a *InodeAttrs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeAttrs" +} + +func (a *InodeAttrs) StateFields() []string { + return []string{ + "devMajor", + "devMinor", + "ino", + "mode", + "uid", + "gid", + "nlink", + "blockSize", + "atime", + "mtime", + "ctime", + } +} + +func (a *InodeAttrs) beforeSave() {} + +func (a *InodeAttrs) StateSave(stateSinkObject state.Sink) { + a.beforeSave() + stateSinkObject.Save(0, &a.devMajor) + stateSinkObject.Save(1, &a.devMinor) + stateSinkObject.Save(2, &a.ino) + stateSinkObject.Save(3, &a.mode) + stateSinkObject.Save(4, &a.uid) + stateSinkObject.Save(5, &a.gid) + stateSinkObject.Save(6, &a.nlink) + stateSinkObject.Save(7, &a.blockSize) + stateSinkObject.Save(8, &a.atime) + stateSinkObject.Save(9, &a.mtime) + stateSinkObject.Save(10, &a.ctime) +} + +func (a *InodeAttrs) afterLoad() {} + +func (a *InodeAttrs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &a.devMajor) + stateSourceObject.Load(1, &a.devMinor) + stateSourceObject.Load(2, &a.ino) + stateSourceObject.Load(3, &a.mode) + stateSourceObject.Load(4, &a.uid) + stateSourceObject.Load(5, &a.gid) + stateSourceObject.Load(6, &a.nlink) + stateSourceObject.Load(7, &a.blockSize) + stateSourceObject.Load(8, &a.atime) + stateSourceObject.Load(9, &a.mtime) + stateSourceObject.Load(10, &a.ctime) +} + +func (s *slot) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.slot" +} + +func (s *slot) StateFields() []string { + return []string{ + "name", + "inode", + "static", + "slotEntry", + } +} + +func (s *slot) beforeSave() {} + +func (s *slot) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.name) + stateSinkObject.Save(1, &s.inode) + stateSinkObject.Save(2, &s.static) + stateSinkObject.Save(3, &s.slotEntry) +} + +func (s *slot) afterLoad() {} + +func (s *slot) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.name) + stateSourceObject.Load(1, &s.inode) + stateSourceObject.Load(2, &s.static) + stateSourceObject.Load(3, &s.slotEntry) +} + +func (o *OrderedChildrenOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.OrderedChildrenOptions" +} + +func (o *OrderedChildrenOptions) StateFields() []string { + return []string{ + "Writable", + } +} + +func (o *OrderedChildrenOptions) beforeSave() {} + +func (o *OrderedChildrenOptions) StateSave(stateSinkObject state.Sink) { + o.beforeSave() + stateSinkObject.Save(0, &o.Writable) +} + +func (o *OrderedChildrenOptions) afterLoad() {} + +func (o *OrderedChildrenOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &o.Writable) +} + +func (o *OrderedChildren) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.OrderedChildren" +} + +func (o *OrderedChildren) StateFields() []string { + return []string{ + "writable", + "order", + "set", + } +} + +func (o *OrderedChildren) beforeSave() {} + +func (o *OrderedChildren) StateSave(stateSinkObject state.Sink) { + o.beforeSave() + stateSinkObject.Save(0, &o.writable) + stateSinkObject.Save(1, &o.order) + stateSinkObject.Save(2, &o.set) +} + +func (o *OrderedChildren) afterLoad() {} + +func (o *OrderedChildren) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &o.writable) + stateSourceObject.Load(1, &o.order) + stateSourceObject.Load(2, &o.set) +} + +func (r *renameAcrossDifferentImplementationsError) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.renameAcrossDifferentImplementationsError" +} + +func (r *renameAcrossDifferentImplementationsError) StateFields() []string { + return []string{} +} + +func (r *renameAcrossDifferentImplementationsError) beforeSave() {} + +func (r *renameAcrossDifferentImplementationsError) StateSave(stateSinkObject state.Sink) { + r.beforeSave() +} + +func (r *renameAcrossDifferentImplementationsError) afterLoad() {} + +func (r *renameAcrossDifferentImplementationsError) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeSymlink" +} + +func (i *InodeSymlink) StateFields() []string { + return []string{ + "InodeNotDirectory", + } +} + +func (i *InodeSymlink) beforeSave() {} + +func (i *InodeSymlink) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeNotDirectory) +} + +func (i *InodeSymlink) afterLoad() {} + +func (i *InodeSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeNotDirectory) +} + +func (s *StaticDirectory) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.StaticDirectory" +} + +func (s *StaticDirectory) StateFields() []string { + return []string{ + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNoStatFS", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + "StaticDirectoryRefs", + "locks", + "fdOpts", + } +} + +func (s *StaticDirectory) beforeSave() {} + +func (s *StaticDirectory) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.InodeAlwaysValid) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeDirectoryNoNewChildren) + stateSinkObject.Save(3, &s.InodeNoStatFS) + stateSinkObject.Save(4, &s.InodeNotSymlink) + stateSinkObject.Save(5, &s.InodeTemporary) + stateSinkObject.Save(6, &s.OrderedChildren) + stateSinkObject.Save(7, &s.StaticDirectoryRefs) + stateSinkObject.Save(8, &s.locks) + stateSinkObject.Save(9, &s.fdOpts) +} + +func (s *StaticDirectory) afterLoad() {} + +func (s *StaticDirectory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.InodeAlwaysValid) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeDirectoryNoNewChildren) + stateSourceObject.Load(3, &s.InodeNoStatFS) + stateSourceObject.Load(4, &s.InodeNotSymlink) + stateSourceObject.Load(5, &s.InodeTemporary) + stateSourceObject.Load(6, &s.OrderedChildren) + stateSourceObject.Load(7, &s.StaticDirectoryRefs) + stateSourceObject.Load(8, &s.locks) + stateSourceObject.Load(9, &s.fdOpts) +} + +func (i *InodeAlwaysValid) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeAlwaysValid" +} + +func (i *InodeAlwaysValid) StateFields() []string { + return []string{} +} + +func (i *InodeAlwaysValid) beforeSave() {} + +func (i *InodeAlwaysValid) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeAlwaysValid) afterLoad() {} + +func (i *InodeAlwaysValid) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeTemporary) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeTemporary" +} + +func (i *InodeTemporary) StateFields() []string { + return []string{} +} + +func (i *InodeTemporary) beforeSave() {} + +func (i *InodeTemporary) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeTemporary) afterLoad() {} + +func (i *InodeTemporary) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeNoStatFS) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNoStatFS" +} + +func (i *InodeNoStatFS) StateFields() []string { + return []string{} +} + +func (i *InodeNoStatFS) beforeSave() {} + +func (i *InodeNoStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeNoStatFS) afterLoad() {} + +func (i *InodeNoStatFS) StateLoad(stateSourceObject state.Source) { +} + +func (fs *Filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.Filesystem" +} + +func (fs *Filesystem) StateFields() []string { + return []string{ + "vfsfs", + "droppedDentries", + "nextInoMinusOne", + } +} + +func (fs *Filesystem) beforeSave() {} + +func (fs *Filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.vfsfs) + stateSinkObject.Save(1, &fs.droppedDentries) + stateSinkObject.Save(2, &fs.nextInoMinusOne) +} + +func (fs *Filesystem) afterLoad() {} + +func (fs *Filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.vfsfs) + stateSourceObject.Load(1, &fs.droppedDentries) + stateSourceObject.Load(2, &fs.nextInoMinusOne) +} + +func (d *Dentry) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.Dentry" +} + +func (d *Dentry) StateFields() []string { + return []string{ + "vfsd", + "DentryRefs", + "fs", + "flags", + "parent", + "name", + "children", + "inode", + } +} + +func (d *Dentry) beforeSave() {} + +func (d *Dentry) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.vfsd) + stateSinkObject.Save(1, &d.DentryRefs) + stateSinkObject.Save(2, &d.fs) + stateSinkObject.Save(3, &d.flags) + stateSinkObject.Save(4, &d.parent) + stateSinkObject.Save(5, &d.name) + stateSinkObject.Save(6, &d.children) + stateSinkObject.Save(7, &d.inode) +} + +func (d *Dentry) afterLoad() {} + +func (d *Dentry) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.vfsd) + stateSourceObject.Load(1, &d.DentryRefs) + stateSourceObject.Load(2, &d.fs) + stateSourceObject.Load(3, &d.flags) + stateSourceObject.Load(4, &d.parent) + stateSourceObject.Load(5, &d.name) + stateSourceObject.Load(6, &d.children) + stateSourceObject.Load(7, &d.inode) +} + +func (l *slotList) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.slotList" +} + +func (l *slotList) StateFields() []string { + return []string{ + "head", + "tail", + } +} + +func (l *slotList) beforeSave() {} + +func (l *slotList) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.head) + stateSinkObject.Save(1, &l.tail) +} + +func (l *slotList) afterLoad() {} + +func (l *slotList) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.head) + stateSourceObject.Load(1, &l.tail) +} + +func (e *slotEntry) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.slotEntry" +} + +func (e *slotEntry) StateFields() []string { + return []string{ + "next", + "prev", + } +} + +func (e *slotEntry) beforeSave() {} + +func (e *slotEntry) StateSave(stateSinkObject state.Sink) { + e.beforeSave() + stateSinkObject.Save(0, &e.next) + stateSinkObject.Save(1, &e.prev) +} + +func (e *slotEntry) afterLoad() {} + +func (e *slotEntry) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &e.next) + stateSourceObject.Load(1, &e.prev) +} + +func (r *StaticDirectoryRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.StaticDirectoryRefs" +} + +func (r *StaticDirectoryRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *StaticDirectoryRefs) beforeSave() {} + +func (r *StaticDirectoryRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +func (r *StaticDirectoryRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (s *StaticSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.StaticSymlink" +} + +func (s *StaticSymlink) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "InodeNoStatFS", + "target", + } +} + +func (s *StaticSymlink) beforeSave() {} + +func (s *StaticSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.InodeAttrs) + stateSinkObject.Save(1, &s.InodeNoopRefCount) + stateSinkObject.Save(2, &s.InodeSymlink) + stateSinkObject.Save(3, &s.InodeNoStatFS) + stateSinkObject.Save(4, &s.target) +} + +func (s *StaticSymlink) afterLoad() {} + +func (s *StaticSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.InodeAttrs) + stateSourceObject.Load(1, &s.InodeNoopRefCount) + stateSourceObject.Load(2, &s.InodeSymlink) + stateSourceObject.Load(3, &s.InodeNoStatFS) + stateSourceObject.Load(4, &s.target) +} + +func (dir *syntheticDirectory) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.syntheticDirectory" +} + +func (dir *syntheticDirectory) StateFields() []string { + return []string{ + "InodeAlwaysValid", + "InodeAttrs", + "InodeNoStatFS", + "InodeNotSymlink", + "OrderedChildren", + "syntheticDirectoryRefs", + "locks", + } +} + +func (dir *syntheticDirectory) beforeSave() {} + +func (dir *syntheticDirectory) StateSave(stateSinkObject state.Sink) { + dir.beforeSave() + stateSinkObject.Save(0, &dir.InodeAlwaysValid) + stateSinkObject.Save(1, &dir.InodeAttrs) + stateSinkObject.Save(2, &dir.InodeNoStatFS) + stateSinkObject.Save(3, &dir.InodeNotSymlink) + stateSinkObject.Save(4, &dir.OrderedChildren) + stateSinkObject.Save(5, &dir.syntheticDirectoryRefs) + stateSinkObject.Save(6, &dir.locks) +} + +func (dir *syntheticDirectory) afterLoad() {} + +func (dir *syntheticDirectory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &dir.InodeAlwaysValid) + stateSourceObject.Load(1, &dir.InodeAttrs) + stateSourceObject.Load(2, &dir.InodeNoStatFS) + stateSourceObject.Load(3, &dir.InodeNotSymlink) + stateSourceObject.Load(4, &dir.OrderedChildren) + stateSourceObject.Load(5, &dir.syntheticDirectoryRefs) + stateSourceObject.Load(6, &dir.locks) +} + +func (r *syntheticDirectoryRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.syntheticDirectoryRefs" +} + +func (r *syntheticDirectoryRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *syntheticDirectoryRefs) beforeSave() {} + +func (r *syntheticDirectoryRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +func (r *syntheticDirectoryRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func init() { + state.Register((*DentryRefs)(nil)) + state.Register((*DynamicBytesFile)(nil)) + state.Register((*DynamicBytesFD)(nil)) + state.Register((*SeekEndConfig)(nil)) + state.Register((*GenericDirectoryFDOptions)(nil)) + state.Register((*GenericDirectoryFD)(nil)) + state.Register((*InodeNoopRefCount)(nil)) + state.Register((*InodeDirectoryNoNewChildren)(nil)) + state.Register((*InodeNotDirectory)(nil)) + state.Register((*InodeNotSymlink)(nil)) + state.Register((*InodeAttrs)(nil)) + state.Register((*slot)(nil)) + state.Register((*OrderedChildrenOptions)(nil)) + state.Register((*OrderedChildren)(nil)) + state.Register((*renameAcrossDifferentImplementationsError)(nil)) + state.Register((*InodeSymlink)(nil)) + state.Register((*StaticDirectory)(nil)) + state.Register((*InodeAlwaysValid)(nil)) + state.Register((*InodeTemporary)(nil)) + state.Register((*InodeNoStatFS)(nil)) + state.Register((*Filesystem)(nil)) + state.Register((*Dentry)(nil)) + state.Register((*slotList)(nil)) + state.Register((*slotEntry)(nil)) + state.Register((*StaticDirectoryRefs)(nil)) + state.Register((*StaticSymlink)(nil)) + state.Register((*syntheticDirectory)(nil)) + state.Register((*syntheticDirectoryRefs)(nil)) +} diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_test.go b/pkg/sentry/fsimpl/kernfs/kernfs_test.go deleted file mode 100644 index 2418eec44..000000000 --- a/pkg/sentry/fsimpl/kernfs/kernfs_test.go +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright 2019 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 kernfs_test - -import ( - "bytes" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserror" - "gvisor.dev/gvisor/pkg/usermem" -) - -const defaultMode linux.FileMode = 01777 -const staticFileContent = "This is sample content for a static test file." - -// RootDentryFn is a generator function for creating the root dentry of a test -// filesystem. See newTestSystem. -type RootDentryFn func(context.Context, *auth.Credentials, *filesystem) kernfs.Inode - -// newTestSystem sets up a minimal environment for running a test, including an -// instance of a test filesystem. Tests can control the contents of the -// filesystem by providing an appropriate rootFn, which should return a -// pre-populated root dentry. -func newTestSystem(t *testing.T, rootFn RootDentryFn) *testutil.System { - ctx := contexttest.Context(t) - creds := auth.CredentialsFromContext(ctx) - v := &vfs.VirtualFilesystem{} - if err := v.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - v.MustRegisterFilesystemType("testfs", &fsType{rootFn: rootFn}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.MountOptions{}) - if err != nil { - t.Fatalf("Failed to create testfs root mount: %v", err) - } - return testutil.NewSystem(ctx, t, v, mns) -} - -type fsType struct { - rootFn RootDentryFn -} - -type filesystem struct { - kernfs.Filesystem -} - -type file struct { - kernfs.DynamicBytesFile - content string -} - -func (fs *filesystem) newFile(ctx context.Context, creds *auth.Credentials, content string) kernfs.Inode { - f := &file{} - f.content = content - f.DynamicBytesFile.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), f, 0777) - return f -} - -func (f *file) Generate(ctx context.Context, buf *bytes.Buffer) error { - fmt.Fprintf(buf, "%s", f.content) - return nil -} - -type attrs struct { - kernfs.InodeAttrs -} - -func (*attrs) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.SetStatOptions) error { - return syserror.EPERM -} - -type readonlyDir struct { - readonlyDirRefs - attrs - kernfs.InodeAlwaysValid - kernfs.InodeDirectoryNoNewChildren - kernfs.InodeNoStatFS - kernfs.InodeNotSymlink - kernfs.InodeTemporary - kernfs.OrderedChildren - - locks vfs.FileLocks -} - -func (fs *filesystem) newReadonlyDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { - dir := &readonlyDir{} - dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode) - dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) - dir.EnableLeakCheck() - dir.IncLinks(dir.OrderedChildren.Populate(contents)) - return dir -} - -func (d *readonlyDir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{ - SeekEnd: kernfs.SeekEndStaticEntries, - }) - if err != nil { - return nil, err - } - return fd.VFSFileDescription(), nil -} - -func (d *readonlyDir) DecRef(ctx context.Context) { - d.readonlyDirRefs.DecRef(func() { d.Destroy(ctx) }) -} - -type dir struct { - dirRefs - attrs - kernfs.InodeAlwaysValid - kernfs.InodeNotSymlink - kernfs.InodeNoStatFS - kernfs.InodeTemporary - kernfs.OrderedChildren - - locks vfs.FileLocks - - fs *filesystem -} - -func (fs *filesystem) newDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { - dir := &dir{} - dir.fs = fs - dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode) - dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{Writable: true}) - dir.EnableLeakCheck() - - dir.IncLinks(dir.OrderedChildren.Populate(contents)) - return dir -} - -func (d *dir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{ - SeekEnd: kernfs.SeekEndStaticEntries, - }) - if err != nil { - return nil, err - } - return fd.VFSFileDescription(), nil -} - -func (d *dir) DecRef(ctx context.Context) { - d.dirRefs.DecRef(func() { d.Destroy(ctx) }) -} - -func (d *dir) NewDir(ctx context.Context, name string, opts vfs.MkdirOptions) (kernfs.Inode, error) { - creds := auth.CredentialsFromContext(ctx) - dir := d.fs.newDir(ctx, creds, opts.Mode, nil) - if err := d.OrderedChildren.Insert(name, dir); err != nil { - dir.DecRef(ctx) - return nil, err - } - d.TouchCMtime(ctx) - d.IncLinks(1) - return dir, nil -} - -func (d *dir) NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (kernfs.Inode, error) { - creds := auth.CredentialsFromContext(ctx) - f := d.fs.newFile(ctx, creds, "") - if err := d.OrderedChildren.Insert(name, f); err != nil { - f.DecRef(ctx) - return nil, err - } - d.TouchCMtime(ctx) - return f, nil -} - -func (*dir) NewLink(context.Context, string, kernfs.Inode) (kernfs.Inode, error) { - return nil, syserror.EPERM -} - -func (*dir) NewSymlink(context.Context, string, string) (kernfs.Inode, error) { - return nil, syserror.EPERM -} - -func (*dir) NewNode(context.Context, string, vfs.MknodOptions) (kernfs.Inode, error) { - return nil, syserror.EPERM -} - -func (fsType) Name() string { - return "kernfs" -} - -func (fsType) Release(ctx context.Context) {} - -func (fst fsType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opt vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { - fs := &filesystem{} - fs.VFSFilesystem().Init(vfsObj, &fst, fs) - root := fst.rootFn(ctx, creds, fs) - var d kernfs.Dentry - d.Init(&fs.Filesystem, root) - return fs.VFSFilesystem(), d.VFSDentry(), nil -} - -// -------------------- Remainder of the file are test cases -------------------- - -func TestBasic(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "file1": fs.newFile(ctx, creds, staticFileContent), - }) - }) - defer sys.Destroy() - sys.GetDentryOrDie(sys.PathOpAtRoot("file1")).DecRef(sys.Ctx) -} - -func TestMkdirGetDentry(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir1": fs.newDir(ctx, creds, 0755, nil), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("dir1/a new directory") - if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, pop, &vfs.MkdirOptions{Mode: 0755}); err != nil { - t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err) - } - sys.GetDentryOrDie(pop).DecRef(sys.Ctx) -} - -func TestReadStaticFile(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "file1": fs.newFile(ctx, creds, staticFileContent), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("file1") - fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if err != nil { - t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) - } - defer fd.DecRef(sys.Ctx) - - content, err := sys.ReadToEnd(fd) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - if diff := cmp.Diff(staticFileContent, content); diff != "" { - t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff) - } -} - -func TestCreateNewFileInStaticDir(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir1": fs.newDir(ctx, creds, 0755, nil), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("dir1/newfile") - opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode} - fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, opts) - if err != nil { - t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err) - } - - // Close the file. The file should persist. - fd.DecRef(sys.Ctx) - - fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if err != nil { - t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err) - } - fd.DecRef(sys.Ctx) -} - -func TestDirFDReadWrite(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, nil) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("/") - fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if err != nil { - t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) - } - defer fd.DecRef(sys.Ctx) - - // Read/Write should fail for directory FDs. - if _, err := fd.Read(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); err != syserror.EISDIR { - t.Fatalf("Read for directory FD failed with unexpected error: %v", err) - } - if _, err := fd.Write(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); err != syserror.EBADF { - t.Fatalf("Write for directory FD failed with unexpected error: %v", err) - } -} - -func TestDirFDIterDirents(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - // Fill root with nodes backed by various inode implementations. - "dir1": fs.newReadonlyDir(ctx, creds, 0755, nil), - "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir3": fs.newDir(ctx, creds, 0755, nil), - }), - "file1": fs.newFile(ctx, creds, staticFileContent), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("/") - sys.AssertAllDirentTypes(sys.ListDirents(pop), map[string]testutil.DirentType{ - "dir1": linux.DT_DIR, - "dir2": linux.DT_DIR, - "file1": linux.DT_REG, - }) -} diff --git a/pkg/sentry/fsimpl/kernfs/slot_list.go b/pkg/sentry/fsimpl/kernfs/slot_list.go new file mode 100644 index 000000000..c6cd74660 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/slot_list.go @@ -0,0 +1,193 @@ +package kernfs + +// ElementMapper provides an identity mapping by default. +// +// This can be replaced to provide a struct that maps elements to linker +// objects, if they are not the same. An ElementMapper is not typically +// required if: Linker is left as is, Element is left as is, or Linker and +// Element are the same type. +type slotElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (slotElementMapper) linkerFor(elem *slot) *slot { return elem } + +// List is an intrusive list. Entries can be added to or removed from the list +// in O(1) time and with no additional memory allocations. +// +// The zero value for List is an empty list ready to use. +// +// To iterate over a list (where l is a List): +// for e := l.Front(); e != nil; e = e.Next() { +// // do something with e. +// } +// +// +stateify savable +type slotList struct { + head *slot + tail *slot +} + +// Reset resets list l to the empty state. +func (l *slotList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +func (l *slotList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +func (l *slotList) Front() *slot { + return l.head +} + +// Back returns the last element of list l or nil. +func (l *slotList) Back() *slot { + return l.tail +} + +// Len returns the number of elements in the list. +// +// NOTE: This is an O(n) operation. +func (l *slotList) Len() (count int) { + for e := l.Front(); e != nil; e = (slotElementMapper{}.linkerFor(e)).Next() { + count++ + } + return count +} + +// PushFront inserts the element e at the front of list l. +func (l *slotList) PushFront(e *slot) { + linker := slotElementMapper{}.linkerFor(e) + linker.SetNext(l.head) + linker.SetPrev(nil) + if l.head != nil { + slotElementMapper{}.linkerFor(l.head).SetPrev(e) + } else { + l.tail = e + } + + l.head = e +} + +// PushBack inserts the element e at the back of list l. +func (l *slotList) PushBack(e *slot) { + linker := slotElementMapper{}.linkerFor(e) + linker.SetNext(nil) + linker.SetPrev(l.tail) + if l.tail != nil { + slotElementMapper{}.linkerFor(l.tail).SetNext(e) + } else { + l.head = e + } + + l.tail = e +} + +// PushBackList inserts list m at the end of list l, emptying m. +func (l *slotList) PushBackList(m *slotList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + slotElementMapper{}.linkerFor(l.tail).SetNext(m.head) + slotElementMapper{}.linkerFor(m.head).SetPrev(l.tail) + + l.tail = m.tail + } + m.head = nil + m.tail = nil +} + +// InsertAfter inserts e after b. +func (l *slotList) InsertAfter(b, e *slot) { + bLinker := slotElementMapper{}.linkerFor(b) + eLinker := slotElementMapper{}.linkerFor(e) + + a := bLinker.Next() + + eLinker.SetNext(a) + eLinker.SetPrev(b) + bLinker.SetNext(e) + + if a != nil { + slotElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +func (l *slotList) InsertBefore(a, e *slot) { + aLinker := slotElementMapper{}.linkerFor(a) + eLinker := slotElementMapper{}.linkerFor(e) + + b := aLinker.Prev() + eLinker.SetNext(a) + eLinker.SetPrev(b) + aLinker.SetPrev(e) + + if b != nil { + slotElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +func (l *slotList) Remove(e *slot) { + linker := slotElementMapper{}.linkerFor(e) + prev := linker.Prev() + next := linker.Next() + + if prev != nil { + slotElementMapper{}.linkerFor(prev).SetNext(next) + } else if l.head == e { + l.head = next + } + + if next != nil { + slotElementMapper{}.linkerFor(next).SetPrev(prev) + } else if l.tail == e { + l.tail = prev + } + + linker.SetNext(nil) + linker.SetPrev(nil) +} + +// Entry is a default implementation of Linker. Users can add anonymous fields +// of this type to their structs to make them automatically implement the +// methods needed by List. +// +// +stateify savable +type slotEntry struct { + next *slot + prev *slot +} + +// Next returns the entry that follows e in the list. +func (e *slotEntry) Next() *slot { + return e.next +} + +// Prev returns the entry that precedes e in the list. +func (e *slotEntry) Prev() *slot { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +func (e *slotEntry) SetNext(elem *slot) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +func (e *slotEntry) SetPrev(elem *slot) { + e.prev = elem +} diff --git a/pkg/sentry/fsimpl/kernfs/static_directory_refs.go b/pkg/sentry/fsimpl/kernfs/static_directory_refs.go new file mode 100644 index 000000000..9472a96b9 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/static_directory_refs.go @@ -0,0 +1,112 @@ +package kernfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// ownerType is used to customize logging. Note that we use a pointer to T so +// that we do not copy the entire object when passed as a format parameter. +var StaticDirectoryownerType *StaticDirectory + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// Note that the number of references is actually refCount + 1 so that a default +// zero-value Refs object contains one reference. +// +// +stateify savable +type StaticDirectoryRefs struct { + // refCount is composed of two fields: + // + // [32-bit speculative references]:[32-bit real references] + // + // Speculative references are used for TryIncRef, to avoid a CompareAndSwap + // loop. See IncRef, DecRef and TryIncRef for details of how these fields are + // used. + refCount int64 +} + +// EnableLeakCheck enables reference leak checking on r. +func (r *StaticDirectoryRefs) EnableLeakCheck() { + if refsvfs2.LeakCheckEnabled() { + refsvfs2.Register(r, fmt.Sprintf("%T", StaticDirectoryownerType)) + } +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *StaticDirectoryRefs) LeakMessage() string { + return fmt.Sprintf("%T %p: reference count of %d instead of 0", StaticDirectoryownerType, r, r.ReadRefs()) +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *StaticDirectoryRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *StaticDirectoryRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %T", r, StaticDirectoryownerType)) + } +} + +// TryIncRef implements refs.RefCounter.TryIncRef. +// +// To do this safely without a loop, a speculative reference is first acquired +// on the object. This allows multiple concurrent TryIncRef calls to distinguish +// other TryIncRef calls from genuine references held. +// +//go:nosplit +func (r *StaticDirectoryRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + v := atomic.AddInt64(&r.refCount, speculativeRef) + if int32(v) < 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + atomic.AddInt64(&r.refCount, -speculativeRef+1) + return true +} + +// DecRef implements refs.RefCounter.DecRef. +// +// Note that speculative references are counted here. Since they were added +// prior to real references reaching zero, they will successfully convert to +// real references. In other words, we see speculative references only in the +// following case: +// +// A: TryIncRef [speculative increase => sees non-negative references] +// B: DecRef [real decrease] +// A: TryIncRef [transform speculative to real] +// +//go:nosplit +func (r *StaticDirectoryRefs) DecRef(destroy func()) { + switch v := atomic.AddInt64(&r.refCount, -1); { + case v < -1: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %T", r, StaticDirectoryownerType)) + + case v == -1: + if refsvfs2.LeakCheckEnabled() { + refsvfs2.Unregister(r, fmt.Sprintf("%T", StaticDirectoryownerType)) + } + + if destroy != nil { + destroy() + } + } +} + +func (r *StaticDirectoryRefs) afterLoad() { + if refsvfs2.LeakCheckEnabled() && r.ReadRefs() > 0 { + r.EnableLeakCheck() + } +} diff --git a/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go b/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go new file mode 100644 index 000000000..7c4fde369 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go @@ -0,0 +1,112 @@ +package kernfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// ownerType is used to customize logging. Note that we use a pointer to T so +// that we do not copy the entire object when passed as a format parameter. +var syntheticDirectoryownerType *syntheticDirectory + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// Note that the number of references is actually refCount + 1 so that a default +// zero-value Refs object contains one reference. +// +// +stateify savable +type syntheticDirectoryRefs struct { + // refCount is composed of two fields: + // + // [32-bit speculative references]:[32-bit real references] + // + // Speculative references are used for TryIncRef, to avoid a CompareAndSwap + // loop. See IncRef, DecRef and TryIncRef for details of how these fields are + // used. + refCount int64 +} + +// EnableLeakCheck enables reference leak checking on r. +func (r *syntheticDirectoryRefs) EnableLeakCheck() { + if refsvfs2.LeakCheckEnabled() { + refsvfs2.Register(r, fmt.Sprintf("%T", syntheticDirectoryownerType)) + } +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *syntheticDirectoryRefs) LeakMessage() string { + return fmt.Sprintf("%T %p: reference count of %d instead of 0", syntheticDirectoryownerType, r, r.ReadRefs()) +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *syntheticDirectoryRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *syntheticDirectoryRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %T", r, syntheticDirectoryownerType)) + } +} + +// TryIncRef implements refs.RefCounter.TryIncRef. +// +// To do this safely without a loop, a speculative reference is first acquired +// on the object. This allows multiple concurrent TryIncRef calls to distinguish +// other TryIncRef calls from genuine references held. +// +//go:nosplit +func (r *syntheticDirectoryRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + v := atomic.AddInt64(&r.refCount, speculativeRef) + if int32(v) < 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + atomic.AddInt64(&r.refCount, -speculativeRef+1) + return true +} + +// DecRef implements refs.RefCounter.DecRef. +// +// Note that speculative references are counted here. Since they were added +// prior to real references reaching zero, they will successfully convert to +// real references. In other words, we see speculative references only in the +// following case: +// +// A: TryIncRef [speculative increase => sees non-negative references] +// B: DecRef [real decrease] +// A: TryIncRef [transform speculative to real] +// +//go:nosplit +func (r *syntheticDirectoryRefs) DecRef(destroy func()) { + switch v := atomic.AddInt64(&r.refCount, -1); { + case v < -1: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %T", r, syntheticDirectoryownerType)) + + case v == -1: + if refsvfs2.LeakCheckEnabled() { + refsvfs2.Unregister(r, fmt.Sprintf("%T", syntheticDirectoryownerType)) + } + + if destroy != nil { + destroy() + } + } +} + +func (r *syntheticDirectoryRefs) afterLoad() { + if refsvfs2.LeakCheckEnabled() && r.ReadRefs() > 0 { + r.EnableLeakCheck() + } +} |