diff options
Diffstat (limited to 'pkg/sentry/vfs')
-rw-r--r-- | pkg/sentry/vfs/BUILD | 136 | ||||
-rw-r--r-- | pkg/sentry/vfs/README.md | 186 | ||||
-rw-r--r-- | pkg/sentry/vfs/epoll_interest_list.go | 193 | ||||
-rw-r--r-- | pkg/sentry/vfs/event_list.go | 193 | ||||
-rw-r--r-- | pkg/sentry/vfs/file_description_impl_util_test.go | 224 | ||||
-rw-r--r-- | pkg/sentry/vfs/file_description_refs.go | 118 | ||||
-rw-r--r-- | pkg/sentry/vfs/filesystem_refs.go | 118 | ||||
-rw-r--r-- | pkg/sentry/vfs/g3doc/inotify.md | 210 | ||||
-rw-r--r-- | pkg/sentry/vfs/genericfstree/BUILD | 16 | ||||
-rw-r--r-- | pkg/sentry/vfs/genericfstree/genericfstree.go | 83 | ||||
-rw-r--r-- | pkg/sentry/vfs/memxattr/BUILD | 15 | ||||
-rw-r--r-- | pkg/sentry/vfs/memxattr/memxattr_state_autogen.go | 34 | ||||
-rw-r--r-- | pkg/sentry/vfs/mount_namespace_refs.go | 118 | ||||
-rw-r--r-- | pkg/sentry/vfs/mount_test.go | 458 | ||||
-rw-r--r-- | pkg/sentry/vfs/vfs_state_autogen.go | 1888 | ||||
-rw-r--r-- | pkg/sentry/vfs/vfs_unsafe_state_autogen.go | 40 |
16 files changed, 2702 insertions, 1328 deletions
diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD deleted file mode 100644 index c855608db..000000000 --- a/pkg/sentry/vfs/BUILD +++ /dev/null @@ -1,136 +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 = "epoll_interest_list", - out = "epoll_interest_list.go", - package = "vfs", - prefix = "epollInterest", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*epollInterest", - "Linker": "*epollInterest", - }, -) - -go_template_instance( - name = "event_list", - out = "event_list.go", - package = "vfs", - prefix = "event", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*Event", - "Linker": "*Event", - }, -) - -go_template_instance( - name = "file_description_refs", - out = "file_description_refs.go", - package = "vfs", - prefix = "FileDescription", - template = "//pkg/refs_vfs2:refs_template", - types = { - "T": "FileDescription", - }, -) - -go_template_instance( - name = "mount_namespace_refs", - out = "mount_namespace_refs.go", - package = "vfs", - prefix = "MountNamespace", - template = "//pkg/refs_vfs2:refs_template", - types = { - "T": "MountNamespace", - }, -) - -go_template_instance( - name = "filesystem_refs", - out = "filesystem_refs.go", - package = "vfs", - prefix = "Filesystem", - template = "//pkg/refs_vfs2:refs_template", - types = { - "T": "Filesystem", - }, -) - -go_library( - name = "vfs", - srcs = [ - "anonfs.go", - "context.go", - "debug.go", - "dentry.go", - "device.go", - "epoll.go", - "epoll_interest_list.go", - "event_list.go", - "file_description.go", - "file_description_impl_util.go", - "file_description_refs.go", - "filesystem.go", - "filesystem_impl_util.go", - "filesystem_refs.go", - "filesystem_type.go", - "inotify.go", - "lock.go", - "mount.go", - "mount_namespace_refs.go", - "mount_unsafe.go", - "options.go", - "pathname.go", - "permissions.go", - "resolving_path.go", - "vfs.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/fd", - "//pkg/fdnotifier", - "//pkg/fspath", - "//pkg/gohacks", - "//pkg/log", - "//pkg/refs", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/lock", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/limits", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/uniqueid", - "//pkg/sync", - "//pkg/syserror", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "vfs_test", - size = "small", - srcs = [ - "file_description_impl_util_test.go", - "mount_test.go", - ], - library = ":vfs", - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/sentry/contexttest", - "//pkg/sync", - "//pkg/syserror", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/vfs/README.md b/pkg/sentry/vfs/README.md deleted file mode 100644 index 5aad31b78..000000000 --- a/pkg/sentry/vfs/README.md +++ /dev/null @@ -1,186 +0,0 @@ -# The gVisor Virtual Filesystem - -THIS PACKAGE IS CURRENTLY EXPERIMENTAL AND NOT READY OR ENABLED FOR PRODUCTION -USE. For the filesystem implementation currently used by gVisor, see the `fs` -package. - -## Implementation Notes - -### Reference Counting - -Filesystem, Dentry, Mount, MountNamespace, and FileDescription are all -reference-counted. Mount and MountNamespace are exclusively VFS-managed; when -their reference count reaches zero, VFS releases their resources. Filesystem and -FileDescription management is shared between VFS and filesystem implementations; -when their reference count reaches zero, VFS notifies the implementation by -calling `FilesystemImpl.Release()` or `FileDescriptionImpl.Release()` -respectively and then releases VFS-owned resources. Dentries are exclusively -managed by filesystem implementations; reference count changes are abstracted -through DentryImpl, which should release resources when reference count reaches -zero. - -Filesystem references are held by: - -- Mount: Each referenced Mount holds a reference on the mounted Filesystem. - -Dentry references are held by: - -- FileDescription: Each referenced FileDescription holds a reference on the - Dentry through which it was opened, via `FileDescription.vd.dentry`. - -- Mount: Each referenced Mount holds a reference on its mount point and on the - mounted filesystem root. The mount point is mutable (`mount(MS_MOVE)`). - -Mount references are held by: - -- FileDescription: Each referenced FileDescription holds a reference on the - Mount on which it was opened, via `FileDescription.vd.mount`. - -- Mount: Each referenced Mount holds a reference on its parent, which is the - mount containing its mount point. - -- VirtualFilesystem: A reference is held on each Mount that has been connected - to a mount point, but not yet umounted. - -MountNamespace and FileDescription references are held by users of VFS. The -expectation is that each `kernel.Task` holds a reference on its corresponding -MountNamespace, and each file descriptor holds a reference on its represented -FileDescription. - -Notes: - -- Dentries do not hold a reference on their owning Filesystem. Instead, all - uses of a Dentry occur in the context of a Mount, which holds a reference on - the relevant Filesystem (see e.g. the VirtualDentry type). As a corollary, - when releasing references on both a Dentry and its corresponding Mount, the - Dentry's reference must be released first (because releasing the Mount's - reference may release the last reference on the Filesystem, whose state may - be required to release the Dentry reference). - -### The Inheritance Pattern - -Filesystem, Dentry, and FileDescription are all concepts featuring both state -that must be shared between VFS and filesystem implementations, and operations -that are implementation-defined. To facilitate this, each of these three -concepts follows the same pattern, shown below for Dentry: - -```go -// Dentry represents a node in a filesystem tree. -type Dentry struct { - // VFS-required dentry state. - parent *Dentry - // ... - - // impl is the DentryImpl associated with this Dentry. impl is immutable. - // This should be the last field in Dentry. - impl DentryImpl -} - -// Init must be called before first use of d. -func (d *Dentry) Init(impl DentryImpl) { - d.impl = impl -} - -// Impl returns the DentryImpl associated with d. -func (d *Dentry) Impl() DentryImpl { - return d.impl -} - -// DentryImpl contains implementation-specific details of a Dentry. -// Implementations of DentryImpl should contain their associated Dentry by -// value as their first field. -type DentryImpl interface { - // VFS-required implementation-defined dentry operations. - IncRef() - // ... -} -``` - -This construction, which is essentially a type-safe analogue to Linux's -`container_of` pattern, has the following properties: - -- VFS works almost exclusively with pointers to Dentry rather than DentryImpl - interface objects, such as in the type of `Dentry.parent`. This avoids - interface method calls (which are somewhat expensive to perform, and defeat - inlining and escape analysis), reduces the size of VFS types (since an - interface object is two pointers in size), and allows pointers to be loaded - and stored atomically using `sync/atomic`. Implementation-defined behavior - is accessed via `Dentry.impl` when required. - -- Filesystem implementations can access the implementation-defined state - associated with objects of VFS types by type-asserting or type-switching - (e.g. `Dentry.Impl().(*myDentry)`). Type assertions to a concrete type - require only an equality comparison of the interface object's type pointer - to a static constant, and are consequently very fast. - -- Filesystem implementations can access the VFS state associated with objects - of implementation-defined types directly. - -- VFS and implementation-defined state for a given type occupy the same - object, minimizing memory allocations and maximizing memory locality. `impl` - is the last field in `Dentry`, and `Dentry` is the first field in - `DentryImpl` implementations, for similar reasons: this tends to cause - fetching of the `Dentry.impl` interface object to also fetch `DentryImpl` - fields, either because they are in the same cache line or via next-line - prefetching. - -## Future Work - -- Most `mount(2)` features, and unmounting, are incomplete. - -- VFS1 filesystems are not directly compatible with VFS2. It may be possible - to implement shims that implement `vfs.FilesystemImpl` for - `fs.MountNamespace`, `vfs.DentryImpl` for `fs.Dirent`, and - `vfs.FileDescriptionImpl` for `fs.File`, which may be adequate for - filesystems that are not performance-critical (e.g. sysfs); however, it is - not clear that this will be less effort than simply porting the filesystems - in question. Practically speaking, the following filesystems will probably - need to be ported or made compatible through a shim to evaluate filesystem - performance on realistic workloads: - - - devfs/procfs/sysfs, which will realistically be necessary to execute - most applications. (Note that procfs and sysfs do not support hard - links, so they do not require the complexity of separate inode objects. - Also note that Linux's /dev is actually a variant of tmpfs called - devtmpfs.) - - - tmpfs. This should be relatively straightforward: copy/paste memfs, - store regular file contents in pgalloc-allocated memory instead of - `[]byte`, and add support for file timestamps. (In fact, it probably - makes more sense to convert memfs to tmpfs and not keep the former.) - - - A remote filesystem, either lisafs (if it is ready by the time that - other benchmarking prerequisites are) or v9fs (aka 9P, aka gofers). - - - epoll files. - - Filesystems that will need to be ported before switching to VFS2, but can - probably be skipped for early testing: - - - overlayfs, which is needed for (at least) synthetic mount points. - - - Support for host ttys. - - - timerfd files. - - Filesystems that can be probably dropped: - - - ashmem, which is far too incomplete to use. - - - binder, which is similarly far too incomplete to use. - -- Save/restore. For instance, it is unclear if the current implementation of - the `state` package supports the inheritance pattern described above. - -- Many features that were previously implemented by VFS must now be - implemented by individual filesystems (though, in most cases, this should - consist of calls to hooks or libraries provided by `vfs` or other packages). - This includes, but is not necessarily limited to: - - - Block and character device special files - - - Inotify - - - File locking - - - `O_ASYNC` diff --git a/pkg/sentry/vfs/epoll_interest_list.go b/pkg/sentry/vfs/epoll_interest_list.go new file mode 100644 index 000000000..ad8ae496a --- /dev/null +++ b/pkg/sentry/vfs/epoll_interest_list.go @@ -0,0 +1,193 @@ +package vfs + +// 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 epollInterestElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (epollInterestElementMapper) linkerFor(elem *epollInterest) *epollInterest { 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 epollInterestList struct { + head *epollInterest + tail *epollInterest +} + +// Reset resets list l to the empty state. +func (l *epollInterestList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +func (l *epollInterestList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +func (l *epollInterestList) Front() *epollInterest { + return l.head +} + +// Back returns the last element of list l or nil. +func (l *epollInterestList) Back() *epollInterest { + return l.tail +} + +// Len returns the number of elements in the list. +// +// NOTE: This is an O(n) operation. +func (l *epollInterestList) Len() (count int) { + for e := l.Front(); e != nil; e = (epollInterestElementMapper{}.linkerFor(e)).Next() { + count++ + } + return count +} + +// PushFront inserts the element e at the front of list l. +func (l *epollInterestList) PushFront(e *epollInterest) { + linker := epollInterestElementMapper{}.linkerFor(e) + linker.SetNext(l.head) + linker.SetPrev(nil) + if l.head != nil { + epollInterestElementMapper{}.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 *epollInterestList) PushBack(e *epollInterest) { + linker := epollInterestElementMapper{}.linkerFor(e) + linker.SetNext(nil) + linker.SetPrev(l.tail) + if l.tail != nil { + epollInterestElementMapper{}.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 *epollInterestList) PushBackList(m *epollInterestList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + epollInterestElementMapper{}.linkerFor(l.tail).SetNext(m.head) + epollInterestElementMapper{}.linkerFor(m.head).SetPrev(l.tail) + + l.tail = m.tail + } + m.head = nil + m.tail = nil +} + +// InsertAfter inserts e after b. +func (l *epollInterestList) InsertAfter(b, e *epollInterest) { + bLinker := epollInterestElementMapper{}.linkerFor(b) + eLinker := epollInterestElementMapper{}.linkerFor(e) + + a := bLinker.Next() + + eLinker.SetNext(a) + eLinker.SetPrev(b) + bLinker.SetNext(e) + + if a != nil { + epollInterestElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +func (l *epollInterestList) InsertBefore(a, e *epollInterest) { + aLinker := epollInterestElementMapper{}.linkerFor(a) + eLinker := epollInterestElementMapper{}.linkerFor(e) + + b := aLinker.Prev() + eLinker.SetNext(a) + eLinker.SetPrev(b) + aLinker.SetPrev(e) + + if b != nil { + epollInterestElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +func (l *epollInterestList) Remove(e *epollInterest) { + linker := epollInterestElementMapper{}.linkerFor(e) + prev := linker.Prev() + next := linker.Next() + + if prev != nil { + epollInterestElementMapper{}.linkerFor(prev).SetNext(next) + } else if l.head == e { + l.head = next + } + + if next != nil { + epollInterestElementMapper{}.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 epollInterestEntry struct { + next *epollInterest + prev *epollInterest +} + +// Next returns the entry that follows e in the list. +func (e *epollInterestEntry) Next() *epollInterest { + return e.next +} + +// Prev returns the entry that precedes e in the list. +func (e *epollInterestEntry) Prev() *epollInterest { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +func (e *epollInterestEntry) SetNext(elem *epollInterest) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +func (e *epollInterestEntry) SetPrev(elem *epollInterest) { + e.prev = elem +} diff --git a/pkg/sentry/vfs/event_list.go b/pkg/sentry/vfs/event_list.go new file mode 100644 index 000000000..ebfb272fd --- /dev/null +++ b/pkg/sentry/vfs/event_list.go @@ -0,0 +1,193 @@ +package vfs + +// 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 eventElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (eventElementMapper) linkerFor(elem *Event) *Event { 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 eventList struct { + head *Event + tail *Event +} + +// Reset resets list l to the empty state. +func (l *eventList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +func (l *eventList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +func (l *eventList) Front() *Event { + return l.head +} + +// Back returns the last element of list l or nil. +func (l *eventList) Back() *Event { + return l.tail +} + +// Len returns the number of elements in the list. +// +// NOTE: This is an O(n) operation. +func (l *eventList) Len() (count int) { + for e := l.Front(); e != nil; e = (eventElementMapper{}.linkerFor(e)).Next() { + count++ + } + return count +} + +// PushFront inserts the element e at the front of list l. +func (l *eventList) PushFront(e *Event) { + linker := eventElementMapper{}.linkerFor(e) + linker.SetNext(l.head) + linker.SetPrev(nil) + if l.head != nil { + eventElementMapper{}.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 *eventList) PushBack(e *Event) { + linker := eventElementMapper{}.linkerFor(e) + linker.SetNext(nil) + linker.SetPrev(l.tail) + if l.tail != nil { + eventElementMapper{}.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 *eventList) PushBackList(m *eventList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + eventElementMapper{}.linkerFor(l.tail).SetNext(m.head) + eventElementMapper{}.linkerFor(m.head).SetPrev(l.tail) + + l.tail = m.tail + } + m.head = nil + m.tail = nil +} + +// InsertAfter inserts e after b. +func (l *eventList) InsertAfter(b, e *Event) { + bLinker := eventElementMapper{}.linkerFor(b) + eLinker := eventElementMapper{}.linkerFor(e) + + a := bLinker.Next() + + eLinker.SetNext(a) + eLinker.SetPrev(b) + bLinker.SetNext(e) + + if a != nil { + eventElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +func (l *eventList) InsertBefore(a, e *Event) { + aLinker := eventElementMapper{}.linkerFor(a) + eLinker := eventElementMapper{}.linkerFor(e) + + b := aLinker.Prev() + eLinker.SetNext(a) + eLinker.SetPrev(b) + aLinker.SetPrev(e) + + if b != nil { + eventElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +func (l *eventList) Remove(e *Event) { + linker := eventElementMapper{}.linkerFor(e) + prev := linker.Prev() + next := linker.Next() + + if prev != nil { + eventElementMapper{}.linkerFor(prev).SetNext(next) + } else if l.head == e { + l.head = next + } + + if next != nil { + eventElementMapper{}.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 eventEntry struct { + next *Event + prev *Event +} + +// Next returns the entry that follows e in the list. +func (e *eventEntry) Next() *Event { + return e.next +} + +// Prev returns the entry that precedes e in the list. +func (e *eventEntry) Prev() *Event { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +func (e *eventEntry) SetNext(elem *Event) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +func (e *eventEntry) SetPrev(elem *Event) { + e.prev = elem +} diff --git a/pkg/sentry/vfs/file_description_impl_util_test.go b/pkg/sentry/vfs/file_description_impl_util_test.go deleted file mode 100644 index 1cd607c0a..000000000 --- a/pkg/sentry/vfs/file_description_impl_util_test.go +++ /dev/null @@ -1,224 +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 vfs - -import ( - "bytes" - "fmt" - "io" - "sync/atomic" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/syserror" - "gvisor.dev/gvisor/pkg/usermem" -) - -// fileDescription is the common fd struct which a filesystem implementation -// embeds in all of its file description implementations as required. -type fileDescription struct { - vfsfd FileDescription - FileDescriptionDefaultImpl - NoLockFD -} - -// genCount contains the number of times its DynamicBytesSource.Generate() -// implementation has been called. -type genCount struct { - count uint64 // accessed using atomic memory ops -} - -// Generate implements DynamicBytesSource.Generate. -func (g *genCount) Generate(ctx context.Context, buf *bytes.Buffer) error { - fmt.Fprintf(buf, "%d", atomic.AddUint64(&g.count, 1)) - return nil -} - -type storeData struct { - data string -} - -var _ WritableDynamicBytesSource = (*storeData)(nil) - -// Generate implements DynamicBytesSource. -func (d *storeData) Generate(ctx context.Context, buf *bytes.Buffer) error { - buf.WriteString(d.data) - return nil -} - -// Generate implements WritableDynamicBytesSource. -func (d *storeData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { - buf := make([]byte, src.NumBytes()) - n, err := src.CopyIn(ctx, buf) - if err != nil { - return 0, err - } - - d.data = string(buf[:n]) - return 0, nil -} - -// testFD is a read-only FileDescriptionImpl representing a regular file. -type testFD struct { - fileDescription - DynamicBytesFileDescriptionImpl - - data DynamicBytesSource -} - -func newTestFD(ctx context.Context, vfsObj *VirtualFilesystem, statusFlags uint32, data DynamicBytesSource) *FileDescription { - vd := vfsObj.NewAnonVirtualDentry("genCountFD") - defer vd.DecRef(ctx) - var fd testFD - fd.vfsfd.Init(&fd, statusFlags, vd.Mount(), vd.Dentry(), &FileDescriptionOptions{}) - fd.DynamicBytesFileDescriptionImpl.SetDataSource(data) - return &fd.vfsfd -} - -// Release implements FileDescriptionImpl.Release. -func (fd *testFD) Release(context.Context) { -} - -// SetStatusFlags implements FileDescriptionImpl.SetStatusFlags. -// Stat implements FileDescriptionImpl.Stat. -func (fd *testFD) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) { - // Note that Statx.Mask == 0 in the return value. - return linux.Statx{}, nil -} - -// SetStat implements FileDescriptionImpl.SetStat. -func (fd *testFD) SetStat(ctx context.Context, opts SetStatOptions) error { - return syserror.EPERM -} - -func TestGenCountFD(t *testing.T) { - ctx := contexttest.Context(t) - - vfsObj := &VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - fd := newTestFD(ctx, vfsObj, linux.O_RDWR, &genCount{}) - defer fd.DecRef(ctx) - - // The first read causes Generate to be called to fill the FD's buffer. - buf := make([]byte, 2) - ioseq := usermem.BytesIOSequence(buf) - n, err := fd.Read(ctx, ioseq, ReadOptions{}) - if n != 1 || (err != nil && err != io.EOF) { - t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err) - } - if want := byte('1'); buf[0] != want { - t.Errorf("first Read: got byte %c, wanted %c", buf[0], want) - } - - // A second read without seeking is still at EOF. - n, err = fd.Read(ctx, ioseq, ReadOptions{}) - if n != 0 || err != io.EOF { - t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err) - } - - // Seeking to the beginning of the file causes it to be regenerated. - n, err = fd.Seek(ctx, 0, linux.SEEK_SET) - if n != 0 || err != nil { - t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err) - } - n, err = fd.Read(ctx, ioseq, ReadOptions{}) - if n != 1 || (err != nil && err != io.EOF) { - t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err) - } - if want := byte('2'); buf[0] != want { - t.Errorf("Read after Seek: got byte %c, wanted %c", buf[0], want) - } - - // PRead at the beginning of the file also causes it to be regenerated. - n, err = fd.PRead(ctx, ioseq, 0, ReadOptions{}) - if n != 1 || (err != nil && err != io.EOF) { - t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err) - } - if want := byte('3'); buf[0] != want { - t.Errorf("PRead: got byte %c, wanted %c", buf[0], want) - } - - // Write and PWrite fails. - if _, err := fd.Write(ctx, ioseq, WriteOptions{}); err != syserror.EIO { - t.Errorf("Write: got err %v, wanted %v", err, syserror.EIO) - } - if _, err := fd.PWrite(ctx, ioseq, 0, WriteOptions{}); err != syserror.EIO { - t.Errorf("Write: got err %v, wanted %v", err, syserror.EIO) - } -} - -func TestWritable(t *testing.T) { - ctx := contexttest.Context(t) - - vfsObj := &VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - fd := newTestFD(ctx, vfsObj, linux.O_RDWR, &storeData{data: "init"}) - defer fd.DecRef(ctx) - - buf := make([]byte, 10) - ioseq := usermem.BytesIOSequence(buf) - if n, err := fd.Read(ctx, ioseq, ReadOptions{}); n != 4 && err != io.EOF { - t.Fatalf("Read: got (%v, %v), wanted (4, EOF)", n, err) - } - if want := "init"; want == string(buf) { - t.Fatalf("Read: got %v, wanted %v", string(buf), want) - } - - // Test PWrite. - want := "write" - writeIOSeq := usermem.BytesIOSequence([]byte(want)) - if n, err := fd.PWrite(ctx, writeIOSeq, 0, WriteOptions{}); int(n) != len(want) && err != nil { - t.Errorf("PWrite: got err (%v, %v), wanted (%v, nil)", n, err, len(want)) - } - if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF { - t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want)) - } - if want == string(buf) { - t.Fatalf("PRead: got %v, wanted %v", string(buf), want) - } - - // Test Seek to 0 followed by Write. - want = "write2" - writeIOSeq = usermem.BytesIOSequence([]byte(want)) - if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 && err != nil { - t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err) - } - if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); int(n) != len(want) && err != nil { - t.Errorf("Write: got err (%v, %v), wanted (%v, nil)", n, err, len(want)) - } - if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF { - t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want)) - } - if want == string(buf) { - t.Fatalf("PRead: got %v, wanted %v", string(buf), want) - } - - // Test failure if offset != 0. - if n, err := fd.Seek(ctx, 1, linux.SEEK_SET); n != 0 && err != nil { - t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err) - } - if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); n != 0 && err != syserror.EINVAL { - t.Errorf("Write: got err (%v, %v), wanted (0, EINVAL)", n, err) - } - if n, err := fd.PWrite(ctx, writeIOSeq, 2, WriteOptions{}); n != 0 && err != syserror.EINVAL { - t.Errorf("PWrite: got err (%v, %v), wanted (0, EINVAL)", n, err) - } -} diff --git a/pkg/sentry/vfs/file_description_refs.go b/pkg/sentry/vfs/file_description_refs.go new file mode 100644 index 000000000..bdd7e6554 --- /dev/null +++ b/pkg/sentry/vfs/file_description_refs.go @@ -0,0 +1,118 @@ +package vfs + +import ( + "fmt" + "runtime" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/log" + refs_vfs1 "gvisor.dev/gvisor/pkg/refs" +) + +// 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 FileDescriptionownerType *FileDescription + +// 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. +// +// TODO(gvisor.dev/issue/1486): Store stack traces when leak check is enabled in +// a map with 16-bit hashes, and store the hash in the top 16 bits of refCount. +// This will allow us to add stack trace information to the leak messages +// without growing the size of Refs. +// +// +stateify savable +type FileDescriptionRefs 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 +} + +func (r *FileDescriptionRefs) finalize() { + var note string + switch refs_vfs1.GetLeakMode() { + case refs_vfs1.NoLeakChecking: + return + case refs_vfs1.UninitializedLeakChecking: + note = "(Leak checker uninitialized): " + } + if n := r.ReadRefs(); n != 0 { + log.Warningf("%sRefs %p owned by %T garbage collected with ref count of %d (want 0)", note, r, FileDescriptionownerType, n) + } +} + +// EnableLeakCheck checks for reference leaks when Refs gets garbage collected. +func (r *FileDescriptionRefs) EnableLeakCheck() { + if refs_vfs1.GetLeakMode() != refs_vfs1.NoLeakChecking { + runtime.SetFinalizer(r, (*FileDescriptionRefs).finalize) + } +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *FileDescriptionRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *FileDescriptionRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive ref count %p owned by %T", r, FileDescriptionownerType)) + } +} + +// 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 *FileDescriptionRefs) 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 *FileDescriptionRefs) 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, FileDescriptionownerType)) + + case v == -1: + + if destroy != nil { + destroy() + } + } +} diff --git a/pkg/sentry/vfs/filesystem_refs.go b/pkg/sentry/vfs/filesystem_refs.go new file mode 100644 index 000000000..38a9a986f --- /dev/null +++ b/pkg/sentry/vfs/filesystem_refs.go @@ -0,0 +1,118 @@ +package vfs + +import ( + "fmt" + "runtime" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/log" + refs_vfs1 "gvisor.dev/gvisor/pkg/refs" +) + +// 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 FilesystemownerType *Filesystem + +// 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. +// +// TODO(gvisor.dev/issue/1486): Store stack traces when leak check is enabled in +// a map with 16-bit hashes, and store the hash in the top 16 bits of refCount. +// This will allow us to add stack trace information to the leak messages +// without growing the size of Refs. +// +// +stateify savable +type FilesystemRefs 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 +} + +func (r *FilesystemRefs) finalize() { + var note string + switch refs_vfs1.GetLeakMode() { + case refs_vfs1.NoLeakChecking: + return + case refs_vfs1.UninitializedLeakChecking: + note = "(Leak checker uninitialized): " + } + if n := r.ReadRefs(); n != 0 { + log.Warningf("%sRefs %p owned by %T garbage collected with ref count of %d (want 0)", note, r, FilesystemownerType, n) + } +} + +// EnableLeakCheck checks for reference leaks when Refs gets garbage collected. +func (r *FilesystemRefs) EnableLeakCheck() { + if refs_vfs1.GetLeakMode() != refs_vfs1.NoLeakChecking { + runtime.SetFinalizer(r, (*FilesystemRefs).finalize) + } +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *FilesystemRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *FilesystemRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive ref count %p owned by %T", r, FilesystemownerType)) + } +} + +// 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 *FilesystemRefs) 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 *FilesystemRefs) 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, FilesystemownerType)) + + case v == -1: + + if destroy != nil { + destroy() + } + } +} diff --git a/pkg/sentry/vfs/g3doc/inotify.md b/pkg/sentry/vfs/g3doc/inotify.md deleted file mode 100644 index 833db213f..000000000 --- a/pkg/sentry/vfs/g3doc/inotify.md +++ /dev/null @@ -1,210 +0,0 @@ -# Inotify - -Inotify is a mechanism for monitoring filesystem events in Linux--see -inotify(7). An inotify instance can be used to monitor files and directories for -modifications, creation/deletion, etc. The inotify API consists of system calls -that create inotify instances (inotify_init/inotify_init1) and add/remove -watches on files to an instance (inotify_add_watch/inotify_rm_watch). Events are -generated from various places in the sentry, including the syscall layer, the -vfs layer, the process fd table, and within each filesystem implementation. This -document outlines the implementation details of inotify in VFS2. - -## Inotify Objects - -Inotify data structures are implemented in the vfs package. - -### vfs.Inotify - -Inotify instances are represented by vfs.Inotify objects, which implement -vfs.FileDescriptionImpl. As in Linux, inotify fds are backed by a -pseudo-filesystem (anonfs). Each inotify instance receives events from a set of -vfs.Watch objects, which can be modified with inotify_add_watch(2) and -inotify_rm_watch(2). An application can retrieve events by reading the inotify -fd. - -### vfs.Watches - -The set of all watches held on a single file (i.e., the watch target) is stored -in vfs.Watches. Each watch will belong to a different inotify instance (an -instance can only have one watch on any watch target). The watches are stored in -a map indexed by their vfs.Inotify owner’s id. Hard links and file descriptions -to a single file will all share the same vfs.Watches (with the exception of the -gofer filesystem, described in a later section). Activity on the target causes -its vfs.Watches to generate notifications on its watches’ inotify instances. - -### vfs.Watch - -A single watch, owned by one inotify instance and applied to one watch target. -Both the vfs.Inotify owner and vfs.Watches on the target will hold a vfs.Watch, -which leads to some complicated locking behavior (see Lock Ordering). Whenever a -watch is notified of an event on its target, it will queue events to its inotify -instance for delivery to the user. - -### vfs.Event - -vfs.Event is a simple struct encapsulating all the fields for an inotify event. -It is generated by vfs.Watches and forwarded to the watches' owners. It is -serialized to the user during read(2) syscalls on the associated fs.Inotify's -fd. - -## Lock Ordering - -There are three locks related to the inotify implementation: - -Inotify.mu: the inotify instance lock. Inotify.evMu: the inotify event queue -lock. Watches.mu: the watch set lock, used to protect the collection of watches -on a target. - -The correct lock ordering for inotify code is: - -Inotify.mu -> Watches.mu -> Inotify.evMu. - -Note that we use a distinct lock to protect the inotify event queue. If we -simply used Inotify.mu, we could simultaneously have locks being acquired in the -order of Inotify.mu -> Watches.mu and Watches.mu -> Inotify.mu, which would -cause deadlocks. For instance, adding a watch to an inotify instance would -require locking Inotify.mu, and then adding the same watch to the target would -cause Watches.mu to be held. At the same time, generating an event on the target -would require Watches.mu to be held before iterating through each watch, and -then notifying the owner of each watch would cause Inotify.mu to be held. - -See the vfs package comment to understand how inotify locks fit into the overall -ordering of filesystem locks. - -## Watch Targets in Different Filesystem Implementations - -In Linux, watches reside on inodes at the virtual filesystem layer. As a result, -all hard links and file descriptions on a single file will all share the same -watch set. In VFS2, there is no common inode structure across filesystem types -(some may not even have inodes), so we have to plumb inotify support through -each specific filesystem implementation. Some of the technical considerations -are outlined below. - -### Tmpfs - -For filesystems with inodes, like tmpfs, the design is quite similar to that of -Linux, where watches reside on the inode. - -### Pseudo-filesystems - -Technically, because inotify is implemented at the vfs layer in Linux, -pseudo-filesystems on top of kernfs support inotify passively. However, watches -can only track explicit filesystem operations like read/write, open/close, -mknod, etc., so watches on a target like /proc/self/fd will not generate events -every time a new fd is added or removed. As of this writing, we leave inotify -unimplemented in kernfs and anonfs; it does not seem particularly useful. - -### Gofer Filesystem (fsimpl/gofer) - -The gofer filesystem has several traits that make it difficult to support -inotify: - -* **There are no inodes.** A file is represented as a dentry that holds an - unopened p9 file (and possibly an open FID), through which the Sentry - interacts with the gofer. - * *Solution:* Because there is no inode structure stored in the sandbox, - inotify watches must be held on the dentry. For the purposes of inotify, - we assume that every dentry corresponds to a unique inode, which may - cause unexpected behavior in the presence of hard links, where multiple - dentries should share the same set of watches. Indeed, it is impossible - for us to be absolutely sure whether dentries correspond to the same - file or not, due to the following point: -* **The Sentry cannot always be aware of hard links on the remote - filesystem.** There is no way for us to confirm whether two files on the - remote filesystem are actually links to the same inode. QIDs and inodes are - not always 1:1. The assumption that dentries and inodes are 1:1 is - inevitably broken if there are remote hard links that we cannot detect. - * *Solution:* this is an issue with gofer fs in general, not only inotify, - and we will have to live with it. -* **Dentries can be cached, and then evicted.** Dentry lifetime does not - correspond to file lifetime. Because gofer fs is not entirely in-memory, the - absence of a dentry does not mean that the corresponding file does not - exist, nor does a dentry reaching zero references mean that the - corresponding file no longer exists. When a dentry reaches zero references, - it will be cached, in case the file at that path is needed again in the - future. However, the dentry may be evicted from the cache, which will cause - a new dentry to be created next time the same file path is used. The - existing watches will be lost. - * *Solution:* When a dentry reaches zero references, do not cache it if it - has any watches, so we can avoid eviction/destruction. Note that if the - dentry was deleted or invalidated (d.vfsd.IsDead()), we should still - destroy it along with its watches. Additionally, when a dentry’s last - watch is removed, we cache it if it also has zero references. This way, - the dentry can eventually be evicted from memory if it is no longer - needed. -* **Dentries can be invalidated.** Another issue with dentry lifetime is that - the remote file at the file path represented may change from underneath the - dentry. In this case, the next time that the dentry is used, it will be - invalidated and a new dentry will replace it. In this case, it is not clear - what should be done with the watches on the old dentry. - * *Solution:* Silently destroy the watches when invalidation occurs. We - have no way of knowing exactly what happened, when it happens. Inotify - instances on NFS files in Linux probably behave in a similar fashion, - since inotify is implemented at the vfs layer and is not aware of the - complexities of remote file systems. - * An alternative would be to issue some kind of event upon invalidation, - e.g. a delete event, but this has several issues: - * We cannot discern whether the remote file was invalidated because it was - moved, deleted, etc. This information is crucial, because these cases - should result in different events. Furthermore, the watches should only - be destroyed if the file has been deleted. - * Moreover, the mechanism for detecting whether the underlying file has - changed is to check whether a new QID is given by the gofer. This may - result in false positives, e.g. suppose that the server closed and - re-opened the same file, which may result in a new QID. - * Finally, the time of the event may be completely different from the time - of the file modification, since a dentry is not immediately notified - when the underlying file has changed. It would be quite unexpected to - receive the notification when invalidation was triggered, i.e. the next - time the file was accessed within the sandbox, because then the - read/write/etc. operation on the file would not result in the expected - event. - * Another point in favor of the first solution: inotify in Linux can - already be lossy on local filesystems (one of the sacrifices made so - that filesystem performance isn’t killed), and it is lossy on NFS for - similar reasons to gofer fs. Therefore, it is better for inotify to be - silent than to emit incorrect notifications. -* **There may be external users of the remote filesystem.** We can only track - operations performed on the file within the sandbox. This is sufficient - under InteropModeExclusive, but whenever there are external users, the set - of actions we are aware of is incomplete. - * *Solution:* We could either return an error or just issue a warning when - inotify is used without InteropModeExclusive. Although faulty, VFS1 - allows it when the filesystem is shared, and Linux does the same for - remote filesystems (as mentioned above, inotify sits at the vfs level). - -## Dentry Interface - -For events that must be generated above the vfs layer, we provide the following -DentryImpl methods to allow interactions with targets on any FilesystemImpl: - -* **InotifyWithParent()** generates events on the dentry’s watches as well as - its parent’s. -* **Watches()** retrieves the watch set of the target represented by the - dentry. This is used to access and modify watches on a target. -* **OnZeroWatches()** performs cleanup tasks after the last watch is removed - from a dentry. This is needed by gofer fs, which must allow a watched dentry - to be cached once it has no more watches. Most implementations can just do - nothing. Note that OnZeroWatches() must be called after all inotify locks - are released to preserve lock ordering, since it may acquire - FilesystemImpl-specific locks. - -## IN_EXCL_UNLINK - -There are several options that can be set for a watch, specified as part of the -mask in inotify_add_watch(2). In particular, IN_EXCL_UNLINK requires some -additional support in each filesystem. - -A watch with IN_EXCL_UNLINK will not generate events for its target if it -corresponds to a path that was unlinked. For instance, if an fd is opened on -“foo/bar” and “foo/bar” is subsequently unlinked, any reads/writes/etc. on the -fd will be ignored by watches on “foo” or “foo/bar” with IN_EXCL_UNLINK. This -requires each DentryImpl to keep track of whether it has been unlinked, in order -to determine whether events should be sent to watches with IN_EXCL_UNLINK. - -## IN_ONESHOT - -One-shot watches expire after generating a single event. When an event occurs, -all one-shot watches on the target that successfully generated an event are -removed. Lock ordering can cause the management of one-shot watches to be quite -expensive; see Watches.Notify() for more information. diff --git a/pkg/sentry/vfs/genericfstree/BUILD b/pkg/sentry/vfs/genericfstree/BUILD deleted file mode 100644 index d8fd92677..000000000 --- a/pkg/sentry/vfs/genericfstree/BUILD +++ /dev/null @@ -1,16 +0,0 @@ -load("//tools/go_generics:defs.bzl", "go_template") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -go_template( - name = "generic_fstree", - srcs = [ - "genericfstree.go", - ], - types = [ - "Dentry", - ], -) diff --git a/pkg/sentry/vfs/genericfstree/genericfstree.go b/pkg/sentry/vfs/genericfstree/genericfstree.go deleted file mode 100644 index 2d27d9d35..000000000 --- a/pkg/sentry/vfs/genericfstree/genericfstree.go +++ /dev/null @@ -1,83 +0,0 @@ -// 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 genericfstree provides tools for implementing vfs.FilesystemImpls -// where a single statically-determined lock or set of locks is sufficient to -// ensure that a Dentry's name and parent are contextually immutable. -// -// Clients using this package must use the go_template_instance rule in -// tools/go_generics/defs.bzl to create an instantiation of this template -// package, providing types to use in place of Dentry. -package genericfstree - -import ( - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Dentry is a required type parameter that is a struct with the given fields. -// -// +stateify savable -type Dentry struct { - // vfsd is the embedded vfs.Dentry corresponding to this vfs.DentryImpl. - vfsd vfs.Dentry - - // parent is the parent of this Dentry in the filesystem's tree. If this - // Dentry is a filesystem root, parent is nil. - parent *Dentry - - // name is the name of this Dentry in its parent. If this Dentry is a - // filesystem root, name is unspecified. - name string -} - -// 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 IsAncestorDentry(d, d2 *Dentry) bool { - for d2 != nil { // Stop at root, where d2.parent == 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 ParentOrSelf(d *Dentry) *Dentry { - if d.parent != nil { - return d.parent - } - return d -} - -// PrependPath is a generic implementation of FilesystemImpl.PrependPath(). -func PrependPath(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 &d.vfsd == mnt.Root() { - return nil - } - if d.parent == nil { - return vfs.PrependPathAtNonMountRootError{} - } - b.PrependComponent(d.name) - d = d.parent - } -} diff --git a/pkg/sentry/vfs/memxattr/BUILD b/pkg/sentry/vfs/memxattr/BUILD deleted file mode 100644 index d8c4d27b9..000000000 --- a/pkg/sentry/vfs/memxattr/BUILD +++ /dev/null @@ -1,15 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "memxattr", - srcs = ["xattr.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/syserror", - ], -) diff --git a/pkg/sentry/vfs/memxattr/memxattr_state_autogen.go b/pkg/sentry/vfs/memxattr/memxattr_state_autogen.go new file mode 100644 index 000000000..105af5cb5 --- /dev/null +++ b/pkg/sentry/vfs/memxattr/memxattr_state_autogen.go @@ -0,0 +1,34 @@ +// automatically generated by stateify. + +package memxattr + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (x *SimpleExtendedAttributes) StateTypeName() string { + return "pkg/sentry/vfs/memxattr.SimpleExtendedAttributes" +} + +func (x *SimpleExtendedAttributes) StateFields() []string { + return []string{ + "xattrs", + } +} + +func (x *SimpleExtendedAttributes) beforeSave() {} + +func (x *SimpleExtendedAttributes) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.xattrs) +} + +func (x *SimpleExtendedAttributes) afterLoad() {} + +func (x *SimpleExtendedAttributes) StateLoad(m state.Source) { + m.Load(0, &x.xattrs) +} + +func init() { + state.Register((*SimpleExtendedAttributes)(nil)) +} diff --git a/pkg/sentry/vfs/mount_namespace_refs.go b/pkg/sentry/vfs/mount_namespace_refs.go new file mode 100644 index 000000000..63285fb8e --- /dev/null +++ b/pkg/sentry/vfs/mount_namespace_refs.go @@ -0,0 +1,118 @@ +package vfs + +import ( + "fmt" + "runtime" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/log" + refs_vfs1 "gvisor.dev/gvisor/pkg/refs" +) + +// 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 MountNamespaceownerType *MountNamespace + +// 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. +// +// TODO(gvisor.dev/issue/1486): Store stack traces when leak check is enabled in +// a map with 16-bit hashes, and store the hash in the top 16 bits of refCount. +// This will allow us to add stack trace information to the leak messages +// without growing the size of Refs. +// +// +stateify savable +type MountNamespaceRefs 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 +} + +func (r *MountNamespaceRefs) finalize() { + var note string + switch refs_vfs1.GetLeakMode() { + case refs_vfs1.NoLeakChecking: + return + case refs_vfs1.UninitializedLeakChecking: + note = "(Leak checker uninitialized): " + } + if n := r.ReadRefs(); n != 0 { + log.Warningf("%sRefs %p owned by %T garbage collected with ref count of %d (want 0)", note, r, MountNamespaceownerType, n) + } +} + +// EnableLeakCheck checks for reference leaks when Refs gets garbage collected. +func (r *MountNamespaceRefs) EnableLeakCheck() { + if refs_vfs1.GetLeakMode() != refs_vfs1.NoLeakChecking { + runtime.SetFinalizer(r, (*MountNamespaceRefs).finalize) + } +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *MountNamespaceRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *MountNamespaceRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive ref count %p owned by %T", r, MountNamespaceownerType)) + } +} + +// 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 *MountNamespaceRefs) 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 *MountNamespaceRefs) 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, MountNamespaceownerType)) + + case v == -1: + + if destroy != nil { + destroy() + } + } +} diff --git a/pkg/sentry/vfs/mount_test.go b/pkg/sentry/vfs/mount_test.go deleted file mode 100644 index cb8c56bd3..000000000 --- a/pkg/sentry/vfs/mount_test.go +++ /dev/null @@ -1,458 +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 vfs - -import ( - "fmt" - "runtime" - "testing" - - "gvisor.dev/gvisor/pkg/sync" -) - -func TestMountTableLookupEmpty(t *testing.T) { - var mt mountTable - mt.Init() - - parent := &Mount{} - point := &Dentry{} - if m := mt.Lookup(parent, point); m != nil { - t.Errorf("empty mountTable lookup: got %p, wanted nil", m) - } -} - -func TestMountTableInsertLookup(t *testing.T) { - var mt mountTable - mt.Init() - - mount := &Mount{} - mount.setKey(VirtualDentry{&Mount{}, &Dentry{}}) - mt.Insert(mount) - - if m := mt.Lookup(mount.parent(), mount.point()); m != mount { - t.Errorf("mountTable positive lookup: got %p, wanted %p", m, mount) - } - - otherParent := &Mount{} - if m := mt.Lookup(otherParent, mount.point()); m != nil { - t.Errorf("mountTable lookup with wrong mount parent: got %p, wanted nil", m) - } - otherPoint := &Dentry{} - if m := mt.Lookup(mount.parent(), otherPoint); m != nil { - t.Errorf("mountTable lookup with wrong mount point: got %p, wanted nil", m) - } -} - -// TODO(gvisor.dev/issue/1035): concurrent lookup/insertion/removal. - -// must be powers of 2 -var benchNumMounts = []int{1 << 2, 1 << 5, 1 << 8} - -// For all of the following: -// -// - BenchmarkMountTableFoo tests usage pattern "Foo" for mountTable. -// -// - BenchmarkMountMapFoo tests usage pattern "Foo" for a -// sync.RWMutex-protected map. (Mutator benchmarks do not use a RWMutex, since -// mountTable also requires external synchronization between mutators.) -// -// - BenchmarkMountSyncMapFoo tests usage pattern "Foo" for a sync.Map. -// -// ParallelLookup is by far the most common and performance-sensitive operation -// for this application. NegativeLookup is also important, but less so (only -// relevant with multiple mount namespaces and significant differences in -// mounts between them). Insertion and removal are benchmarked for -// completeness. -const enableComparativeBenchmarks = false - -func newBenchMount() *Mount { - mount := &Mount{} - mount.loadKey(VirtualDentry{&Mount{}, &Dentry{}}) - return mount -} - -func BenchmarkMountTableParallelLookup(b *testing.B) { - for numG, maxG := 1, runtime.GOMAXPROCS(0); numG >= 0 && numG <= maxG; numG *= 2 { - for _, numMounts := range benchNumMounts { - desc := fmt.Sprintf("%dx%d", numG, numMounts) - b.Run(desc, func(b *testing.B) { - var mt mountTable - mt.Init() - keys := make([]VirtualDentry, 0, numMounts) - for i := 0; i < numMounts; i++ { - mount := newBenchMount() - mt.Insert(mount) - keys = append(keys, mount.saveKey()) - } - - var ready sync.WaitGroup - begin := make(chan struct{}) - var end sync.WaitGroup - for g := 0; g < numG; g++ { - ready.Add(1) - end.Add(1) - go func() { - defer end.Done() - ready.Done() - <-begin - for i := 0; i < b.N; i++ { - k := keys[i&(numMounts-1)] - m := mt.Lookup(k.mount, k.dentry) - if m == nil { - b.Fatalf("lookup failed") - } - if parent := m.parent(); parent != k.mount { - b.Fatalf("lookup returned mount with parent %p, wanted %p", parent, k.mount) - } - if point := m.point(); point != k.dentry { - b.Fatalf("lookup returned mount with point %p, wanted %p", point, k.dentry) - } - } - }() - } - - ready.Wait() - b.ResetTimer() - close(begin) - end.Wait() - }) - } - } -} - -func BenchmarkMountMapParallelLookup(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - for numG, maxG := 1, runtime.GOMAXPROCS(0); numG >= 0 && numG <= maxG; numG *= 2 { - for _, numMounts := range benchNumMounts { - desc := fmt.Sprintf("%dx%d", numG, numMounts) - b.Run(desc, func(b *testing.B) { - var mu sync.RWMutex - ms := make(map[VirtualDentry]*Mount) - keys := make([]VirtualDentry, 0, numMounts) - for i := 0; i < numMounts; i++ { - mount := newBenchMount() - key := mount.saveKey() - ms[key] = mount - keys = append(keys, key) - } - - var ready sync.WaitGroup - begin := make(chan struct{}) - var end sync.WaitGroup - for g := 0; g < numG; g++ { - ready.Add(1) - end.Add(1) - go func() { - defer end.Done() - ready.Done() - <-begin - for i := 0; i < b.N; i++ { - k := keys[i&(numMounts-1)] - mu.RLock() - m := ms[k] - mu.RUnlock() - if m == nil { - b.Fatalf("lookup failed") - } - if parent := m.parent(); parent != k.mount { - b.Fatalf("lookup returned mount with parent %p, wanted %p", parent, k.mount) - } - if point := m.point(); point != k.dentry { - b.Fatalf("lookup returned mount with point %p, wanted %p", point, k.dentry) - } - } - }() - } - - ready.Wait() - b.ResetTimer() - close(begin) - end.Wait() - }) - } - } -} - -func BenchmarkMountSyncMapParallelLookup(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - for numG, maxG := 1, runtime.GOMAXPROCS(0); numG >= 0 && numG <= maxG; numG *= 2 { - for _, numMounts := range benchNumMounts { - desc := fmt.Sprintf("%dx%d", numG, numMounts) - b.Run(desc, func(b *testing.B) { - var ms sync.Map - keys := make([]VirtualDentry, 0, numMounts) - for i := 0; i < numMounts; i++ { - mount := newBenchMount() - key := mount.getKey() - ms.Store(key, mount) - keys = append(keys, key) - } - - var ready sync.WaitGroup - begin := make(chan struct{}) - var end sync.WaitGroup - for g := 0; g < numG; g++ { - ready.Add(1) - end.Add(1) - go func() { - defer end.Done() - ready.Done() - <-begin - for i := 0; i < b.N; i++ { - k := keys[i&(numMounts-1)] - mi, ok := ms.Load(k) - if !ok { - b.Fatalf("lookup failed") - } - m := mi.(*Mount) - if parent := m.parent(); parent != k.mount { - b.Fatalf("lookup returned mount with parent %p, wanted %p", parent, k.mount) - } - if point := m.point(); point != k.dentry { - b.Fatalf("lookup returned mount with point %p, wanted %p", point, k.dentry) - } - } - }() - } - - ready.Wait() - b.ResetTimer() - close(begin) - end.Wait() - }) - } - } -} - -func BenchmarkMountTableNegativeLookup(b *testing.B) { - for _, numMounts := range benchNumMounts { - desc := fmt.Sprintf("%d", numMounts) - b.Run(desc, func(b *testing.B) { - var mt mountTable - mt.Init() - for i := 0; i < numMounts; i++ { - mt.Insert(newBenchMount()) - } - negkeys := make([]VirtualDentry, 0, numMounts) - for i := 0; i < numMounts; i++ { - negkeys = append(negkeys, VirtualDentry{ - mount: &Mount{}, - dentry: &Dentry{}, - }) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - k := negkeys[i&(numMounts-1)] - m := mt.Lookup(k.mount, k.dentry) - if m != nil { - b.Fatalf("lookup got %p, wanted nil", m) - } - } - }) - } -} - -func BenchmarkMountMapNegativeLookup(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - for _, numMounts := range benchNumMounts { - desc := fmt.Sprintf("%d", numMounts) - b.Run(desc, func(b *testing.B) { - var mu sync.RWMutex - ms := make(map[VirtualDentry]*Mount) - for i := 0; i < numMounts; i++ { - mount := newBenchMount() - ms[mount.getKey()] = mount - } - negkeys := make([]VirtualDentry, 0, numMounts) - for i := 0; i < numMounts; i++ { - negkeys = append(negkeys, VirtualDentry{ - mount: &Mount{}, - dentry: &Dentry{}, - }) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - k := negkeys[i&(numMounts-1)] - mu.RLock() - m := ms[k] - mu.RUnlock() - if m != nil { - b.Fatalf("lookup got %p, wanted nil", m) - } - } - }) - } -} - -func BenchmarkMountSyncMapNegativeLookup(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - for _, numMounts := range benchNumMounts { - desc := fmt.Sprintf("%d", numMounts) - b.Run(desc, func(b *testing.B) { - var ms sync.Map - for i := 0; i < numMounts; i++ { - mount := newBenchMount() - ms.Store(mount.saveKey(), mount) - } - negkeys := make([]VirtualDentry, 0, numMounts) - for i := 0; i < numMounts; i++ { - negkeys = append(negkeys, VirtualDentry{ - mount: &Mount{}, - dentry: &Dentry{}, - }) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - k := negkeys[i&(numMounts-1)] - m, _ := ms.Load(k) - if m != nil { - b.Fatalf("lookup got %p, wanted nil", m) - } - } - }) - } -} - -func BenchmarkMountTableInsert(b *testing.B) { - // Preallocate Mounts so that allocation time isn't included in the - // benchmark. - mounts := make([]*Mount, 0, b.N) - for i := 0; i < b.N; i++ { - mounts = append(mounts, newBenchMount()) - } - - var mt mountTable - mt.Init() - b.ResetTimer() - for i := range mounts { - mt.Insert(mounts[i]) - } -} - -func BenchmarkMountMapInsert(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - // Preallocate Mounts so that allocation time isn't included in the - // benchmark. - mounts := make([]*Mount, 0, b.N) - for i := 0; i < b.N; i++ { - mounts = append(mounts, newBenchMount()) - } - - ms := make(map[VirtualDentry]*Mount) - b.ResetTimer() - for i := range mounts { - mount := mounts[i] - ms[mount.saveKey()] = mount - } -} - -func BenchmarkMountSyncMapInsert(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - // Preallocate Mounts so that allocation time isn't included in the - // benchmark. - mounts := make([]*Mount, 0, b.N) - for i := 0; i < b.N; i++ { - mounts = append(mounts, newBenchMount()) - } - - var ms sync.Map - b.ResetTimer() - for i := range mounts { - mount := mounts[i] - ms.Store(mount.saveKey(), mount) - } -} - -func BenchmarkMountTableRemove(b *testing.B) { - mounts := make([]*Mount, 0, b.N) - for i := 0; i < b.N; i++ { - mounts = append(mounts, newBenchMount()) - } - var mt mountTable - mt.Init() - for i := range mounts { - mt.Insert(mounts[i]) - } - - b.ResetTimer() - for i := range mounts { - mt.Remove(mounts[i]) - } -} - -func BenchmarkMountMapRemove(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - mounts := make([]*Mount, 0, b.N) - for i := 0; i < b.N; i++ { - mounts = append(mounts, newBenchMount()) - } - ms := make(map[VirtualDentry]*Mount) - for i := range mounts { - mount := mounts[i] - ms[mount.saveKey()] = mount - } - - b.ResetTimer() - for i := range mounts { - mount := mounts[i] - delete(ms, mount.saveKey()) - } -} - -func BenchmarkMountSyncMapRemove(b *testing.B) { - if !enableComparativeBenchmarks { - b.Skipf("comparative benchmarks are disabled") - } - - mounts := make([]*Mount, 0, b.N) - for i := 0; i < b.N; i++ { - mounts = append(mounts, newBenchMount()) - } - var ms sync.Map - for i := range mounts { - mount := mounts[i] - ms.Store(mount.saveKey(), mount) - } - - b.ResetTimer() - for i := range mounts { - mount := mounts[i] - ms.Delete(mount.saveKey()) - } -} diff --git a/pkg/sentry/vfs/vfs_state_autogen.go b/pkg/sentry/vfs/vfs_state_autogen.go new file mode 100644 index 000000000..3c35d1577 --- /dev/null +++ b/pkg/sentry/vfs/vfs_state_autogen.go @@ -0,0 +1,1888 @@ +// automatically generated by stateify. + +package vfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (x *anonFilesystemType) StateTypeName() string { + return "pkg/sentry/vfs.anonFilesystemType" +} + +func (x *anonFilesystemType) StateFields() []string { + return []string{} +} + +func (x *anonFilesystemType) beforeSave() {} + +func (x *anonFilesystemType) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *anonFilesystemType) afterLoad() {} + +func (x *anonFilesystemType) StateLoad(m state.Source) { +} + +func (x *anonFilesystem) StateTypeName() string { + return "pkg/sentry/vfs.anonFilesystem" +} + +func (x *anonFilesystem) StateFields() []string { + return []string{ + "vfsfs", + "devMinor", + } +} + +func (x *anonFilesystem) beforeSave() {} + +func (x *anonFilesystem) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.vfsfs) + m.Save(1, &x.devMinor) +} + +func (x *anonFilesystem) afterLoad() {} + +func (x *anonFilesystem) StateLoad(m state.Source) { + m.Load(0, &x.vfsfs) + m.Load(1, &x.devMinor) +} + +func (x *anonDentry) StateTypeName() string { + return "pkg/sentry/vfs.anonDentry" +} + +func (x *anonDentry) StateFields() []string { + return []string{ + "vfsd", + "name", + } +} + +func (x *anonDentry) beforeSave() {} + +func (x *anonDentry) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.vfsd) + m.Save(1, &x.name) +} + +func (x *anonDentry) afterLoad() {} + +func (x *anonDentry) StateLoad(m state.Source) { + m.Load(0, &x.vfsd) + m.Load(1, &x.name) +} + +func (x *Dentry) StateTypeName() string { + return "pkg/sentry/vfs.Dentry" +} + +func (x *Dentry) StateFields() []string { + return []string{ + "dead", + "mounts", + "impl", + } +} + +func (x *Dentry) beforeSave() {} + +func (x *Dentry) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.dead) + m.Save(1, &x.mounts) + m.Save(2, &x.impl) +} + +func (x *Dentry) afterLoad() {} + +func (x *Dentry) StateLoad(m state.Source) { + m.Load(0, &x.dead) + m.Load(1, &x.mounts) + m.Load(2, &x.impl) +} + +func (x *DeviceKind) StateTypeName() string { + return "pkg/sentry/vfs.DeviceKind" +} + +func (x *DeviceKind) StateFields() []string { + return nil +} + +func (x *devTuple) StateTypeName() string { + return "pkg/sentry/vfs.devTuple" +} + +func (x *devTuple) StateFields() []string { + return []string{ + "kind", + "major", + "minor", + } +} + +func (x *devTuple) beforeSave() {} + +func (x *devTuple) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.kind) + m.Save(1, &x.major) + m.Save(2, &x.minor) +} + +func (x *devTuple) afterLoad() {} + +func (x *devTuple) StateLoad(m state.Source) { + m.Load(0, &x.kind) + m.Load(1, &x.major) + m.Load(2, &x.minor) +} + +func (x *registeredDevice) StateTypeName() string { + return "pkg/sentry/vfs.registeredDevice" +} + +func (x *registeredDevice) StateFields() []string { + return []string{ + "dev", + "opts", + } +} + +func (x *registeredDevice) beforeSave() {} + +func (x *registeredDevice) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.dev) + m.Save(1, &x.opts) +} + +func (x *registeredDevice) afterLoad() {} + +func (x *registeredDevice) StateLoad(m state.Source) { + m.Load(0, &x.dev) + m.Load(1, &x.opts) +} + +func (x *RegisterDeviceOptions) StateTypeName() string { + return "pkg/sentry/vfs.RegisterDeviceOptions" +} + +func (x *RegisterDeviceOptions) StateFields() []string { + return []string{ + "GroupName", + } +} + +func (x *RegisterDeviceOptions) beforeSave() {} + +func (x *RegisterDeviceOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.GroupName) +} + +func (x *RegisterDeviceOptions) afterLoad() {} + +func (x *RegisterDeviceOptions) StateLoad(m state.Source) { + m.Load(0, &x.GroupName) +} + +func (x *EpollInstance) StateTypeName() string { + return "pkg/sentry/vfs.EpollInstance" +} + +func (x *EpollInstance) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "NoLockFD", + "q", + "interest", + "ready", + } +} + +func (x *EpollInstance) beforeSave() {} + +func (x *EpollInstance) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.vfsfd) + m.Save(1, &x.FileDescriptionDefaultImpl) + m.Save(2, &x.DentryMetadataFileDescriptionImpl) + m.Save(3, &x.NoLockFD) + m.Save(4, &x.q) + m.Save(5, &x.interest) + m.Save(6, &x.ready) +} + +func (x *EpollInstance) afterLoad() {} + +func (x *EpollInstance) StateLoad(m state.Source) { + m.Load(0, &x.vfsfd) + m.Load(1, &x.FileDescriptionDefaultImpl) + m.Load(2, &x.DentryMetadataFileDescriptionImpl) + m.Load(3, &x.NoLockFD) + m.Load(4, &x.q) + m.Load(5, &x.interest) + m.Load(6, &x.ready) +} + +func (x *epollInterestKey) StateTypeName() string { + return "pkg/sentry/vfs.epollInterestKey" +} + +func (x *epollInterestKey) StateFields() []string { + return []string{ + "file", + "num", + } +} + +func (x *epollInterestKey) beforeSave() {} + +func (x *epollInterestKey) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.file) + m.Save(1, &x.num) +} + +func (x *epollInterestKey) afterLoad() {} + +func (x *epollInterestKey) StateLoad(m state.Source) { + m.Load(0, &x.file) + m.Load(1, &x.num) +} + +func (x *epollInterest) StateTypeName() string { + return "pkg/sentry/vfs.epollInterest" +} + +func (x *epollInterest) StateFields() []string { + return []string{ + "epoll", + "key", + "waiter", + "mask", + "ready", + "epollInterestEntry", + "userData", + } +} + +func (x *epollInterest) beforeSave() {} + +func (x *epollInterest) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.epoll) + m.Save(1, &x.key) + m.Save(2, &x.waiter) + m.Save(3, &x.mask) + m.Save(4, &x.ready) + m.Save(5, &x.epollInterestEntry) + m.Save(6, &x.userData) +} + +func (x *epollInterest) afterLoad() {} + +func (x *epollInterest) StateLoad(m state.Source) { + m.Load(0, &x.epoll) + m.Load(1, &x.key) + m.Load(2, &x.waiter) + m.Load(3, &x.mask) + m.Load(4, &x.ready) + m.Load(5, &x.epollInterestEntry) + m.Load(6, &x.userData) +} + +func (x *epollInterestList) StateTypeName() string { + return "pkg/sentry/vfs.epollInterestList" +} + +func (x *epollInterestList) StateFields() []string { + return []string{ + "head", + "tail", + } +} + +func (x *epollInterestList) beforeSave() {} + +func (x *epollInterestList) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.head) + m.Save(1, &x.tail) +} + +func (x *epollInterestList) afterLoad() {} + +func (x *epollInterestList) StateLoad(m state.Source) { + m.Load(0, &x.head) + m.Load(1, &x.tail) +} + +func (x *epollInterestEntry) StateTypeName() string { + return "pkg/sentry/vfs.epollInterestEntry" +} + +func (x *epollInterestEntry) StateFields() []string { + return []string{ + "next", + "prev", + } +} + +func (x *epollInterestEntry) beforeSave() {} + +func (x *epollInterestEntry) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.next) + m.Save(1, &x.prev) +} + +func (x *epollInterestEntry) afterLoad() {} + +func (x *epollInterestEntry) StateLoad(m state.Source) { + m.Load(0, &x.next) + m.Load(1, &x.prev) +} + +func (x *eventList) StateTypeName() string { + return "pkg/sentry/vfs.eventList" +} + +func (x *eventList) StateFields() []string { + return []string{ + "head", + "tail", + } +} + +func (x *eventList) beforeSave() {} + +func (x *eventList) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.head) + m.Save(1, &x.tail) +} + +func (x *eventList) afterLoad() {} + +func (x *eventList) StateLoad(m state.Source) { + m.Load(0, &x.head) + m.Load(1, &x.tail) +} + +func (x *eventEntry) StateTypeName() string { + return "pkg/sentry/vfs.eventEntry" +} + +func (x *eventEntry) StateFields() []string { + return []string{ + "next", + "prev", + } +} + +func (x *eventEntry) beforeSave() {} + +func (x *eventEntry) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.next) + m.Save(1, &x.prev) +} + +func (x *eventEntry) afterLoad() {} + +func (x *eventEntry) StateLoad(m state.Source) { + m.Load(0, &x.next) + m.Load(1, &x.prev) +} + +func (x *FileDescription) StateTypeName() string { + return "pkg/sentry/vfs.FileDescription" +} + +func (x *FileDescription) StateFields() []string { + return []string{ + "FileDescriptionRefs", + "statusFlags", + "asyncHandler", + "epolls", + "vd", + "opts", + "readable", + "writable", + "usedLockBSD", + "impl", + } +} + +func (x *FileDescription) beforeSave() {} + +func (x *FileDescription) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.FileDescriptionRefs) + m.Save(1, &x.statusFlags) + m.Save(2, &x.asyncHandler) + m.Save(3, &x.epolls) + m.Save(4, &x.vd) + m.Save(5, &x.opts) + m.Save(6, &x.readable) + m.Save(7, &x.writable) + m.Save(8, &x.usedLockBSD) + m.Save(9, &x.impl) +} + +func (x *FileDescription) afterLoad() {} + +func (x *FileDescription) StateLoad(m state.Source) { + m.Load(0, &x.FileDescriptionRefs) + m.Load(1, &x.statusFlags) + m.Load(2, &x.asyncHandler) + m.Load(3, &x.epolls) + m.Load(4, &x.vd) + m.Load(5, &x.opts) + m.Load(6, &x.readable) + m.Load(7, &x.writable) + m.Load(8, &x.usedLockBSD) + m.Load(9, &x.impl) +} + +func (x *FileDescriptionOptions) StateTypeName() string { + return "pkg/sentry/vfs.FileDescriptionOptions" +} + +func (x *FileDescriptionOptions) StateFields() []string { + return []string{ + "AllowDirectIO", + "DenyPRead", + "DenyPWrite", + "UseDentryMetadata", + } +} + +func (x *FileDescriptionOptions) beforeSave() {} + +func (x *FileDescriptionOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.AllowDirectIO) + m.Save(1, &x.DenyPRead) + m.Save(2, &x.DenyPWrite) + m.Save(3, &x.UseDentryMetadata) +} + +func (x *FileDescriptionOptions) afterLoad() {} + +func (x *FileDescriptionOptions) StateLoad(m state.Source) { + m.Load(0, &x.AllowDirectIO) + m.Load(1, &x.DenyPRead) + m.Load(2, &x.DenyPWrite) + m.Load(3, &x.UseDentryMetadata) +} + +func (x *Dirent) StateTypeName() string { + return "pkg/sentry/vfs.Dirent" +} + +func (x *Dirent) StateFields() []string { + return []string{ + "Name", + "Type", + "Ino", + "NextOff", + } +} + +func (x *Dirent) beforeSave() {} + +func (x *Dirent) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Name) + m.Save(1, &x.Type) + m.Save(2, &x.Ino) + m.Save(3, &x.NextOff) +} + +func (x *Dirent) afterLoad() {} + +func (x *Dirent) StateLoad(m state.Source) { + m.Load(0, &x.Name) + m.Load(1, &x.Type) + m.Load(2, &x.Ino) + m.Load(3, &x.NextOff) +} + +func (x *FileDescriptionDefaultImpl) StateTypeName() string { + return "pkg/sentry/vfs.FileDescriptionDefaultImpl" +} + +func (x *FileDescriptionDefaultImpl) StateFields() []string { + return []string{} +} + +func (x *FileDescriptionDefaultImpl) beforeSave() {} + +func (x *FileDescriptionDefaultImpl) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *FileDescriptionDefaultImpl) afterLoad() {} + +func (x *FileDescriptionDefaultImpl) StateLoad(m state.Source) { +} + +func (x *DirectoryFileDescriptionDefaultImpl) StateTypeName() string { + return "pkg/sentry/vfs.DirectoryFileDescriptionDefaultImpl" +} + +func (x *DirectoryFileDescriptionDefaultImpl) StateFields() []string { + return []string{} +} + +func (x *DirectoryFileDescriptionDefaultImpl) beforeSave() {} + +func (x *DirectoryFileDescriptionDefaultImpl) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *DirectoryFileDescriptionDefaultImpl) afterLoad() {} + +func (x *DirectoryFileDescriptionDefaultImpl) StateLoad(m state.Source) { +} + +func (x *DentryMetadataFileDescriptionImpl) StateTypeName() string { + return "pkg/sentry/vfs.DentryMetadataFileDescriptionImpl" +} + +func (x *DentryMetadataFileDescriptionImpl) StateFields() []string { + return []string{} +} + +func (x *DentryMetadataFileDescriptionImpl) beforeSave() {} + +func (x *DentryMetadataFileDescriptionImpl) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *DentryMetadataFileDescriptionImpl) afterLoad() {} + +func (x *DentryMetadataFileDescriptionImpl) StateLoad(m state.Source) { +} + +func (x *StaticData) StateTypeName() string { + return "pkg/sentry/vfs.StaticData" +} + +func (x *StaticData) StateFields() []string { + return []string{ + "Data", + } +} + +func (x *StaticData) beforeSave() {} + +func (x *StaticData) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Data) +} + +func (x *StaticData) afterLoad() {} + +func (x *StaticData) StateLoad(m state.Source) { + m.Load(0, &x.Data) +} + +func (x *DynamicBytesFileDescriptionImpl) StateTypeName() string { + return "pkg/sentry/vfs.DynamicBytesFileDescriptionImpl" +} + +func (x *DynamicBytesFileDescriptionImpl) StateFields() []string { + return []string{ + "data", + "buf", + "off", + "lastRead", + } +} + +func (x *DynamicBytesFileDescriptionImpl) beforeSave() {} + +func (x *DynamicBytesFileDescriptionImpl) StateSave(m state.Sink) { + x.beforeSave() + var buf []byte = x.saveBuf() + m.SaveValue(1, buf) + m.Save(0, &x.data) + m.Save(2, &x.off) + m.Save(3, &x.lastRead) +} + +func (x *DynamicBytesFileDescriptionImpl) afterLoad() {} + +func (x *DynamicBytesFileDescriptionImpl) StateLoad(m state.Source) { + m.Load(0, &x.data) + m.Load(2, &x.off) + m.Load(3, &x.lastRead) + m.LoadValue(1, new([]byte), func(y interface{}) { x.loadBuf(y.([]byte)) }) +} + +func (x *LockFD) StateTypeName() string { + return "pkg/sentry/vfs.LockFD" +} + +func (x *LockFD) StateFields() []string { + return []string{ + "locks", + } +} + +func (x *LockFD) beforeSave() {} + +func (x *LockFD) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.locks) +} + +func (x *LockFD) afterLoad() {} + +func (x *LockFD) StateLoad(m state.Source) { + m.Load(0, &x.locks) +} + +func (x *NoLockFD) StateTypeName() string { + return "pkg/sentry/vfs.NoLockFD" +} + +func (x *NoLockFD) StateFields() []string { + return []string{} +} + +func (x *NoLockFD) beforeSave() {} + +func (x *NoLockFD) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *NoLockFD) afterLoad() {} + +func (x *NoLockFD) StateLoad(m state.Source) { +} + +func (x *FileDescriptionRefs) StateTypeName() string { + return "pkg/sentry/vfs.FileDescriptionRefs" +} + +func (x *FileDescriptionRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (x *FileDescriptionRefs) beforeSave() {} + +func (x *FileDescriptionRefs) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.refCount) +} + +func (x *FileDescriptionRefs) afterLoad() {} + +func (x *FileDescriptionRefs) StateLoad(m state.Source) { + m.Load(0, &x.refCount) +} + +func (x *Filesystem) StateTypeName() string { + return "pkg/sentry/vfs.Filesystem" +} + +func (x *Filesystem) StateFields() []string { + return []string{ + "FilesystemRefs", + "vfs", + "fsType", + "impl", + } +} + +func (x *Filesystem) beforeSave() {} + +func (x *Filesystem) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.FilesystemRefs) + m.Save(1, &x.vfs) + m.Save(2, &x.fsType) + m.Save(3, &x.impl) +} + +func (x *Filesystem) afterLoad() {} + +func (x *Filesystem) StateLoad(m state.Source) { + m.Load(0, &x.FilesystemRefs) + m.Load(1, &x.vfs) + m.Load(2, &x.fsType) + m.Load(3, &x.impl) +} + +func (x *PrependPathAtVFSRootError) StateTypeName() string { + return "pkg/sentry/vfs.PrependPathAtVFSRootError" +} + +func (x *PrependPathAtVFSRootError) StateFields() []string { + return []string{} +} + +func (x *PrependPathAtVFSRootError) beforeSave() {} + +func (x *PrependPathAtVFSRootError) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *PrependPathAtVFSRootError) afterLoad() {} + +func (x *PrependPathAtVFSRootError) StateLoad(m state.Source) { +} + +func (x *PrependPathAtNonMountRootError) StateTypeName() string { + return "pkg/sentry/vfs.PrependPathAtNonMountRootError" +} + +func (x *PrependPathAtNonMountRootError) StateFields() []string { + return []string{} +} + +func (x *PrependPathAtNonMountRootError) beforeSave() {} + +func (x *PrependPathAtNonMountRootError) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *PrependPathAtNonMountRootError) afterLoad() {} + +func (x *PrependPathAtNonMountRootError) StateLoad(m state.Source) { +} + +func (x *PrependPathSyntheticError) StateTypeName() string { + return "pkg/sentry/vfs.PrependPathSyntheticError" +} + +func (x *PrependPathSyntheticError) StateFields() []string { + return []string{} +} + +func (x *PrependPathSyntheticError) beforeSave() {} + +func (x *PrependPathSyntheticError) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *PrependPathSyntheticError) afterLoad() {} + +func (x *PrependPathSyntheticError) StateLoad(m state.Source) { +} + +func (x *FilesystemRefs) StateTypeName() string { + return "pkg/sentry/vfs.FilesystemRefs" +} + +func (x *FilesystemRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (x *FilesystemRefs) beforeSave() {} + +func (x *FilesystemRefs) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.refCount) +} + +func (x *FilesystemRefs) afterLoad() {} + +func (x *FilesystemRefs) StateLoad(m state.Source) { + m.Load(0, &x.refCount) +} + +func (x *registeredFilesystemType) StateTypeName() string { + return "pkg/sentry/vfs.registeredFilesystemType" +} + +func (x *registeredFilesystemType) StateFields() []string { + return []string{ + "fsType", + "opts", + } +} + +func (x *registeredFilesystemType) beforeSave() {} + +func (x *registeredFilesystemType) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.fsType) + m.Save(1, &x.opts) +} + +func (x *registeredFilesystemType) afterLoad() {} + +func (x *registeredFilesystemType) StateLoad(m state.Source) { + m.Load(0, &x.fsType) + m.Load(1, &x.opts) +} + +func (x *RegisterFilesystemTypeOptions) StateTypeName() string { + return "pkg/sentry/vfs.RegisterFilesystemTypeOptions" +} + +func (x *RegisterFilesystemTypeOptions) StateFields() []string { + return []string{ + "AllowUserMount", + "AllowUserList", + "RequiresDevice", + } +} + +func (x *RegisterFilesystemTypeOptions) beforeSave() {} + +func (x *RegisterFilesystemTypeOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.AllowUserMount) + m.Save(1, &x.AllowUserList) + m.Save(2, &x.RequiresDevice) +} + +func (x *RegisterFilesystemTypeOptions) afterLoad() {} + +func (x *RegisterFilesystemTypeOptions) StateLoad(m state.Source) { + m.Load(0, &x.AllowUserMount) + m.Load(1, &x.AllowUserList) + m.Load(2, &x.RequiresDevice) +} + +func (x *EventType) StateTypeName() string { + return "pkg/sentry/vfs.EventType" +} + +func (x *EventType) StateFields() []string { + return nil +} + +func (x *Inotify) StateTypeName() string { + return "pkg/sentry/vfs.Inotify" +} + +func (x *Inotify) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "NoLockFD", + "id", + "events", + "scratch", + "nextWatchMinusOne", + "watches", + } +} + +func (x *Inotify) beforeSave() {} + +func (x *Inotify) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.vfsfd) + m.Save(1, &x.FileDescriptionDefaultImpl) + m.Save(2, &x.DentryMetadataFileDescriptionImpl) + m.Save(3, &x.NoLockFD) + m.Save(4, &x.id) + m.Save(5, &x.events) + m.Save(6, &x.scratch) + m.Save(7, &x.nextWatchMinusOne) + m.Save(8, &x.watches) +} + +func (x *Inotify) afterLoad() {} + +func (x *Inotify) StateLoad(m state.Source) { + m.Load(0, &x.vfsfd) + m.Load(1, &x.FileDescriptionDefaultImpl) + m.Load(2, &x.DentryMetadataFileDescriptionImpl) + m.Load(3, &x.NoLockFD) + m.Load(4, &x.id) + m.Load(5, &x.events) + m.Load(6, &x.scratch) + m.Load(7, &x.nextWatchMinusOne) + m.Load(8, &x.watches) +} + +func (x *Watches) StateTypeName() string { + return "pkg/sentry/vfs.Watches" +} + +func (x *Watches) StateFields() []string { + return []string{ + "ws", + } +} + +func (x *Watches) beforeSave() {} + +func (x *Watches) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.ws) +} + +func (x *Watches) afterLoad() {} + +func (x *Watches) StateLoad(m state.Source) { + m.Load(0, &x.ws) +} + +func (x *Watch) StateTypeName() string { + return "pkg/sentry/vfs.Watch" +} + +func (x *Watch) StateFields() []string { + return []string{ + "owner", + "wd", + "target", + "mask", + "expired", + } +} + +func (x *Watch) beforeSave() {} + +func (x *Watch) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.owner) + m.Save(1, &x.wd) + m.Save(2, &x.target) + m.Save(3, &x.mask) + m.Save(4, &x.expired) +} + +func (x *Watch) afterLoad() {} + +func (x *Watch) StateLoad(m state.Source) { + m.Load(0, &x.owner) + m.Load(1, &x.wd) + m.Load(2, &x.target) + m.Load(3, &x.mask) + m.Load(4, &x.expired) +} + +func (x *Event) StateTypeName() string { + return "pkg/sentry/vfs.Event" +} + +func (x *Event) StateFields() []string { + return []string{ + "eventEntry", + "wd", + "mask", + "cookie", + "len", + "name", + } +} + +func (x *Event) beforeSave() {} + +func (x *Event) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.eventEntry) + m.Save(1, &x.wd) + m.Save(2, &x.mask) + m.Save(3, &x.cookie) + m.Save(4, &x.len) + m.Save(5, &x.name) +} + +func (x *Event) afterLoad() {} + +func (x *Event) StateLoad(m state.Source) { + m.Load(0, &x.eventEntry) + m.Load(1, &x.wd) + m.Load(2, &x.mask) + m.Load(3, &x.cookie) + m.Load(4, &x.len) + m.Load(5, &x.name) +} + +func (x *FileLocks) StateTypeName() string { + return "pkg/sentry/vfs.FileLocks" +} + +func (x *FileLocks) StateFields() []string { + return []string{ + "bsd", + "posix", + } +} + +func (x *FileLocks) beforeSave() {} + +func (x *FileLocks) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.bsd) + m.Save(1, &x.posix) +} + +func (x *FileLocks) afterLoad() {} + +func (x *FileLocks) StateLoad(m state.Source) { + m.Load(0, &x.bsd) + m.Load(1, &x.posix) +} + +func (x *Mount) StateTypeName() string { + return "pkg/sentry/vfs.Mount" +} + +func (x *Mount) StateFields() []string { + return []string{ + "vfs", + "fs", + "root", + "ID", + "Flags", + "key", + "ns", + "refs", + "children", + "umounted", + "writers", + } +} + +func (x *Mount) beforeSave() {} + +func (x *Mount) StateSave(m state.Sink) { + x.beforeSave() + var key VirtualDentry = x.saveKey() + m.SaveValue(5, key) + m.Save(0, &x.vfs) + m.Save(1, &x.fs) + m.Save(2, &x.root) + m.Save(3, &x.ID) + m.Save(4, &x.Flags) + m.Save(6, &x.ns) + m.Save(7, &x.refs) + m.Save(8, &x.children) + m.Save(9, &x.umounted) + m.Save(10, &x.writers) +} + +func (x *Mount) afterLoad() {} + +func (x *Mount) StateLoad(m state.Source) { + m.Load(0, &x.vfs) + m.Load(1, &x.fs) + m.Load(2, &x.root) + m.Load(3, &x.ID) + m.Load(4, &x.Flags) + m.Load(6, &x.ns) + m.Load(7, &x.refs) + m.Load(8, &x.children) + m.Load(9, &x.umounted) + m.Load(10, &x.writers) + m.LoadValue(5, new(VirtualDentry), func(y interface{}) { x.loadKey(y.(VirtualDentry)) }) +} + +func (x *MountNamespace) StateTypeName() string { + return "pkg/sentry/vfs.MountNamespace" +} + +func (x *MountNamespace) StateFields() []string { + return []string{ + "MountNamespaceRefs", + "Owner", + "root", + "mountpoints", + } +} + +func (x *MountNamespace) beforeSave() {} + +func (x *MountNamespace) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.MountNamespaceRefs) + m.Save(1, &x.Owner) + m.Save(2, &x.root) + m.Save(3, &x.mountpoints) +} + +func (x *MountNamespace) afterLoad() {} + +func (x *MountNamespace) StateLoad(m state.Source) { + m.Load(0, &x.MountNamespaceRefs) + m.Load(1, &x.Owner) + m.Load(2, &x.root) + m.Load(3, &x.mountpoints) +} + +func (x *umountRecursiveOptions) StateTypeName() string { + return "pkg/sentry/vfs.umountRecursiveOptions" +} + +func (x *umountRecursiveOptions) StateFields() []string { + return []string{ + "eager", + "disconnectHierarchy", + } +} + +func (x *umountRecursiveOptions) beforeSave() {} + +func (x *umountRecursiveOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.eager) + m.Save(1, &x.disconnectHierarchy) +} + +func (x *umountRecursiveOptions) afterLoad() {} + +func (x *umountRecursiveOptions) StateLoad(m state.Source) { + m.Load(0, &x.eager) + m.Load(1, &x.disconnectHierarchy) +} + +func (x *MountNamespaceRefs) StateTypeName() string { + return "pkg/sentry/vfs.MountNamespaceRefs" +} + +func (x *MountNamespaceRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (x *MountNamespaceRefs) beforeSave() {} + +func (x *MountNamespaceRefs) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.refCount) +} + +func (x *MountNamespaceRefs) afterLoad() {} + +func (x *MountNamespaceRefs) StateLoad(m state.Source) { + m.Load(0, &x.refCount) +} + +func (x *GetDentryOptions) StateTypeName() string { + return "pkg/sentry/vfs.GetDentryOptions" +} + +func (x *GetDentryOptions) StateFields() []string { + return []string{ + "CheckSearchable", + } +} + +func (x *GetDentryOptions) beforeSave() {} + +func (x *GetDentryOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.CheckSearchable) +} + +func (x *GetDentryOptions) afterLoad() {} + +func (x *GetDentryOptions) StateLoad(m state.Source) { + m.Load(0, &x.CheckSearchable) +} + +func (x *MkdirOptions) StateTypeName() string { + return "pkg/sentry/vfs.MkdirOptions" +} + +func (x *MkdirOptions) StateFields() []string { + return []string{ + "Mode", + "ForSyntheticMountpoint", + } +} + +func (x *MkdirOptions) beforeSave() {} + +func (x *MkdirOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Mode) + m.Save(1, &x.ForSyntheticMountpoint) +} + +func (x *MkdirOptions) afterLoad() {} + +func (x *MkdirOptions) StateLoad(m state.Source) { + m.Load(0, &x.Mode) + m.Load(1, &x.ForSyntheticMountpoint) +} + +func (x *MknodOptions) StateTypeName() string { + return "pkg/sentry/vfs.MknodOptions" +} + +func (x *MknodOptions) StateFields() []string { + return []string{ + "Mode", + "DevMajor", + "DevMinor", + "Endpoint", + } +} + +func (x *MknodOptions) beforeSave() {} + +func (x *MknodOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Mode) + m.Save(1, &x.DevMajor) + m.Save(2, &x.DevMinor) + m.Save(3, &x.Endpoint) +} + +func (x *MknodOptions) afterLoad() {} + +func (x *MknodOptions) StateLoad(m state.Source) { + m.Load(0, &x.Mode) + m.Load(1, &x.DevMajor) + m.Load(2, &x.DevMinor) + m.Load(3, &x.Endpoint) +} + +func (x *MountFlags) StateTypeName() string { + return "pkg/sentry/vfs.MountFlags" +} + +func (x *MountFlags) StateFields() []string { + return []string{ + "NoExec", + "NoATime", + "NoDev", + "NoSUID", + } +} + +func (x *MountFlags) beforeSave() {} + +func (x *MountFlags) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.NoExec) + m.Save(1, &x.NoATime) + m.Save(2, &x.NoDev) + m.Save(3, &x.NoSUID) +} + +func (x *MountFlags) afterLoad() {} + +func (x *MountFlags) StateLoad(m state.Source) { + m.Load(0, &x.NoExec) + m.Load(1, &x.NoATime) + m.Load(2, &x.NoDev) + m.Load(3, &x.NoSUID) +} + +func (x *MountOptions) StateTypeName() string { + return "pkg/sentry/vfs.MountOptions" +} + +func (x *MountOptions) StateFields() []string { + return []string{ + "Flags", + "ReadOnly", + "GetFilesystemOptions", + "InternalMount", + } +} + +func (x *MountOptions) beforeSave() {} + +func (x *MountOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Flags) + m.Save(1, &x.ReadOnly) + m.Save(2, &x.GetFilesystemOptions) + m.Save(3, &x.InternalMount) +} + +func (x *MountOptions) afterLoad() {} + +func (x *MountOptions) StateLoad(m state.Source) { + m.Load(0, &x.Flags) + m.Load(1, &x.ReadOnly) + m.Load(2, &x.GetFilesystemOptions) + m.Load(3, &x.InternalMount) +} + +func (x *OpenOptions) StateTypeName() string { + return "pkg/sentry/vfs.OpenOptions" +} + +func (x *OpenOptions) StateFields() []string { + return []string{ + "Flags", + "Mode", + "FileExec", + } +} + +func (x *OpenOptions) beforeSave() {} + +func (x *OpenOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Flags) + m.Save(1, &x.Mode) + m.Save(2, &x.FileExec) +} + +func (x *OpenOptions) afterLoad() {} + +func (x *OpenOptions) StateLoad(m state.Source) { + m.Load(0, &x.Flags) + m.Load(1, &x.Mode) + m.Load(2, &x.FileExec) +} + +func (x *ReadOptions) StateTypeName() string { + return "pkg/sentry/vfs.ReadOptions" +} + +func (x *ReadOptions) StateFields() []string { + return []string{ + "Flags", + } +} + +func (x *ReadOptions) beforeSave() {} + +func (x *ReadOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Flags) +} + +func (x *ReadOptions) afterLoad() {} + +func (x *ReadOptions) StateLoad(m state.Source) { + m.Load(0, &x.Flags) +} + +func (x *RenameOptions) StateTypeName() string { + return "pkg/sentry/vfs.RenameOptions" +} + +func (x *RenameOptions) StateFields() []string { + return []string{ + "Flags", + "MustBeDir", + } +} + +func (x *RenameOptions) beforeSave() {} + +func (x *RenameOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Flags) + m.Save(1, &x.MustBeDir) +} + +func (x *RenameOptions) afterLoad() {} + +func (x *RenameOptions) StateLoad(m state.Source) { + m.Load(0, &x.Flags) + m.Load(1, &x.MustBeDir) +} + +func (x *SetStatOptions) StateTypeName() string { + return "pkg/sentry/vfs.SetStatOptions" +} + +func (x *SetStatOptions) StateFields() []string { + return []string{ + "Stat", + "NeedWritePerm", + } +} + +func (x *SetStatOptions) beforeSave() {} + +func (x *SetStatOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Stat) + m.Save(1, &x.NeedWritePerm) +} + +func (x *SetStatOptions) afterLoad() {} + +func (x *SetStatOptions) StateLoad(m state.Source) { + m.Load(0, &x.Stat) + m.Load(1, &x.NeedWritePerm) +} + +func (x *BoundEndpointOptions) StateTypeName() string { + return "pkg/sentry/vfs.BoundEndpointOptions" +} + +func (x *BoundEndpointOptions) StateFields() []string { + return []string{ + "Addr", + } +} + +func (x *BoundEndpointOptions) beforeSave() {} + +func (x *BoundEndpointOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Addr) +} + +func (x *BoundEndpointOptions) afterLoad() {} + +func (x *BoundEndpointOptions) StateLoad(m state.Source) { + m.Load(0, &x.Addr) +} + +func (x *GetXattrOptions) StateTypeName() string { + return "pkg/sentry/vfs.GetXattrOptions" +} + +func (x *GetXattrOptions) StateFields() []string { + return []string{ + "Name", + "Size", + } +} + +func (x *GetXattrOptions) beforeSave() {} + +func (x *GetXattrOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Name) + m.Save(1, &x.Size) +} + +func (x *GetXattrOptions) afterLoad() {} + +func (x *GetXattrOptions) StateLoad(m state.Source) { + m.Load(0, &x.Name) + m.Load(1, &x.Size) +} + +func (x *SetXattrOptions) StateTypeName() string { + return "pkg/sentry/vfs.SetXattrOptions" +} + +func (x *SetXattrOptions) StateFields() []string { + return []string{ + "Name", + "Value", + "Flags", + } +} + +func (x *SetXattrOptions) beforeSave() {} + +func (x *SetXattrOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Name) + m.Save(1, &x.Value) + m.Save(2, &x.Flags) +} + +func (x *SetXattrOptions) afterLoad() {} + +func (x *SetXattrOptions) StateLoad(m state.Source) { + m.Load(0, &x.Name) + m.Load(1, &x.Value) + m.Load(2, &x.Flags) +} + +func (x *StatOptions) StateTypeName() string { + return "pkg/sentry/vfs.StatOptions" +} + +func (x *StatOptions) StateFields() []string { + return []string{ + "Mask", + "Sync", + } +} + +func (x *StatOptions) beforeSave() {} + +func (x *StatOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Mask) + m.Save(1, &x.Sync) +} + +func (x *StatOptions) afterLoad() {} + +func (x *StatOptions) StateLoad(m state.Source) { + m.Load(0, &x.Mask) + m.Load(1, &x.Sync) +} + +func (x *UmountOptions) StateTypeName() string { + return "pkg/sentry/vfs.UmountOptions" +} + +func (x *UmountOptions) StateFields() []string { + return []string{ + "Flags", + } +} + +func (x *UmountOptions) beforeSave() {} + +func (x *UmountOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Flags) +} + +func (x *UmountOptions) afterLoad() {} + +func (x *UmountOptions) StateLoad(m state.Source) { + m.Load(0, &x.Flags) +} + +func (x *WriteOptions) StateTypeName() string { + return "pkg/sentry/vfs.WriteOptions" +} + +func (x *WriteOptions) StateFields() []string { + return []string{ + "Flags", + } +} + +func (x *WriteOptions) beforeSave() {} + +func (x *WriteOptions) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Flags) +} + +func (x *WriteOptions) afterLoad() {} + +func (x *WriteOptions) StateLoad(m state.Source) { + m.Load(0, &x.Flags) +} + +func (x *AccessTypes) StateTypeName() string { + return "pkg/sentry/vfs.AccessTypes" +} + +func (x *AccessTypes) StateFields() []string { + return nil +} + +func (x *ResolvingPath) StateTypeName() string { + return "pkg/sentry/vfs.ResolvingPath" +} + +func (x *ResolvingPath) StateFields() []string { + return []string{ + "vfs", + "root", + "mount", + "start", + "pit", + "flags", + "mustBeDir", + "mustBeDirOrig", + "symlinks", + "symlinksOrig", + "curPart", + "numOrigParts", + "creds", + "nextMount", + "nextStart", + "absSymlinkTarget", + "parts", + "origParts", + } +} + +func (x *ResolvingPath) beforeSave() {} + +func (x *ResolvingPath) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.vfs) + m.Save(1, &x.root) + m.Save(2, &x.mount) + m.Save(3, &x.start) + m.Save(4, &x.pit) + m.Save(5, &x.flags) + m.Save(6, &x.mustBeDir) + m.Save(7, &x.mustBeDirOrig) + m.Save(8, &x.symlinks) + m.Save(9, &x.symlinksOrig) + m.Save(10, &x.curPart) + m.Save(11, &x.numOrigParts) + m.Save(12, &x.creds) + m.Save(13, &x.nextMount) + m.Save(14, &x.nextStart) + m.Save(15, &x.absSymlinkTarget) + m.Save(16, &x.parts) + m.Save(17, &x.origParts) +} + +func (x *ResolvingPath) afterLoad() {} + +func (x *ResolvingPath) StateLoad(m state.Source) { + m.Load(0, &x.vfs) + m.Load(1, &x.root) + m.Load(2, &x.mount) + m.Load(3, &x.start) + m.Load(4, &x.pit) + m.Load(5, &x.flags) + m.Load(6, &x.mustBeDir) + m.Load(7, &x.mustBeDirOrig) + m.Load(8, &x.symlinks) + m.Load(9, &x.symlinksOrig) + m.Load(10, &x.curPart) + m.Load(11, &x.numOrigParts) + m.Load(12, &x.creds) + m.Load(13, &x.nextMount) + m.Load(14, &x.nextStart) + m.Load(15, &x.absSymlinkTarget) + m.Load(16, &x.parts) + m.Load(17, &x.origParts) +} + +func (x *resolveMountRootOrJumpError) StateTypeName() string { + return "pkg/sentry/vfs.resolveMountRootOrJumpError" +} + +func (x *resolveMountRootOrJumpError) StateFields() []string { + return []string{} +} + +func (x *resolveMountRootOrJumpError) beforeSave() {} + +func (x *resolveMountRootOrJumpError) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *resolveMountRootOrJumpError) afterLoad() {} + +func (x *resolveMountRootOrJumpError) StateLoad(m state.Source) { +} + +func (x *resolveMountPointError) StateTypeName() string { + return "pkg/sentry/vfs.resolveMountPointError" +} + +func (x *resolveMountPointError) StateFields() []string { + return []string{} +} + +func (x *resolveMountPointError) beforeSave() {} + +func (x *resolveMountPointError) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *resolveMountPointError) afterLoad() {} + +func (x *resolveMountPointError) StateLoad(m state.Source) { +} + +func (x *resolveAbsSymlinkError) StateTypeName() string { + return "pkg/sentry/vfs.resolveAbsSymlinkError" +} + +func (x *resolveAbsSymlinkError) StateFields() []string { + return []string{} +} + +func (x *resolveAbsSymlinkError) beforeSave() {} + +func (x *resolveAbsSymlinkError) StateSave(m state.Sink) { + x.beforeSave() +} + +func (x *resolveAbsSymlinkError) afterLoad() {} + +func (x *resolveAbsSymlinkError) StateLoad(m state.Source) { +} + +func (x *VirtualFilesystem) StateTypeName() string { + return "pkg/sentry/vfs.VirtualFilesystem" +} + +func (x *VirtualFilesystem) StateFields() []string { + return []string{ + "mounts", + "mountpoints", + "lastMountID", + "anonMount", + "devices", + "anonBlockDevMinorNext", + "anonBlockDevMinor", + "fsTypes", + "filesystems", + } +} + +func (x *VirtualFilesystem) beforeSave() {} + +func (x *VirtualFilesystem) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.mounts) + m.Save(1, &x.mountpoints) + m.Save(2, &x.lastMountID) + m.Save(3, &x.anonMount) + m.Save(4, &x.devices) + m.Save(5, &x.anonBlockDevMinorNext) + m.Save(6, &x.anonBlockDevMinor) + m.Save(7, &x.fsTypes) + m.Save(8, &x.filesystems) +} + +func (x *VirtualFilesystem) afterLoad() {} + +func (x *VirtualFilesystem) StateLoad(m state.Source) { + m.Load(0, &x.mounts) + m.Load(1, &x.mountpoints) + m.Load(2, &x.lastMountID) + m.Load(3, &x.anonMount) + m.Load(4, &x.devices) + m.Load(5, &x.anonBlockDevMinorNext) + m.Load(6, &x.anonBlockDevMinor) + m.Load(7, &x.fsTypes) + m.Load(8, &x.filesystems) +} + +func (x *PathOperation) StateTypeName() string { + return "pkg/sentry/vfs.PathOperation" +} + +func (x *PathOperation) StateFields() []string { + return []string{ + "Root", + "Start", + "Path", + "FollowFinalSymlink", + } +} + +func (x *PathOperation) beforeSave() {} + +func (x *PathOperation) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.Root) + m.Save(1, &x.Start) + m.Save(2, &x.Path) + m.Save(3, &x.FollowFinalSymlink) +} + +func (x *PathOperation) afterLoad() {} + +func (x *PathOperation) StateLoad(m state.Source) { + m.Load(0, &x.Root) + m.Load(1, &x.Start) + m.Load(2, &x.Path) + m.Load(3, &x.FollowFinalSymlink) +} + +func (x *VirtualDentry) StateTypeName() string { + return "pkg/sentry/vfs.VirtualDentry" +} + +func (x *VirtualDentry) StateFields() []string { + return []string{ + "mount", + "dentry", + } +} + +func (x *VirtualDentry) beforeSave() {} + +func (x *VirtualDentry) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.mount) + m.Save(1, &x.dentry) +} + +func (x *VirtualDentry) afterLoad() {} + +func (x *VirtualDentry) StateLoad(m state.Source) { + m.Load(0, &x.mount) + m.Load(1, &x.dentry) +} + +func init() { + state.Register((*anonFilesystemType)(nil)) + state.Register((*anonFilesystem)(nil)) + state.Register((*anonDentry)(nil)) + state.Register((*Dentry)(nil)) + state.Register((*DeviceKind)(nil)) + state.Register((*devTuple)(nil)) + state.Register((*registeredDevice)(nil)) + state.Register((*RegisterDeviceOptions)(nil)) + state.Register((*EpollInstance)(nil)) + state.Register((*epollInterestKey)(nil)) + state.Register((*epollInterest)(nil)) + state.Register((*epollInterestList)(nil)) + state.Register((*epollInterestEntry)(nil)) + state.Register((*eventList)(nil)) + state.Register((*eventEntry)(nil)) + state.Register((*FileDescription)(nil)) + state.Register((*FileDescriptionOptions)(nil)) + state.Register((*Dirent)(nil)) + state.Register((*FileDescriptionDefaultImpl)(nil)) + state.Register((*DirectoryFileDescriptionDefaultImpl)(nil)) + state.Register((*DentryMetadataFileDescriptionImpl)(nil)) + state.Register((*StaticData)(nil)) + state.Register((*DynamicBytesFileDescriptionImpl)(nil)) + state.Register((*LockFD)(nil)) + state.Register((*NoLockFD)(nil)) + state.Register((*FileDescriptionRefs)(nil)) + state.Register((*Filesystem)(nil)) + state.Register((*PrependPathAtVFSRootError)(nil)) + state.Register((*PrependPathAtNonMountRootError)(nil)) + state.Register((*PrependPathSyntheticError)(nil)) + state.Register((*FilesystemRefs)(nil)) + state.Register((*registeredFilesystemType)(nil)) + state.Register((*RegisterFilesystemTypeOptions)(nil)) + state.Register((*EventType)(nil)) + state.Register((*Inotify)(nil)) + state.Register((*Watches)(nil)) + state.Register((*Watch)(nil)) + state.Register((*Event)(nil)) + state.Register((*FileLocks)(nil)) + state.Register((*Mount)(nil)) + state.Register((*MountNamespace)(nil)) + state.Register((*umountRecursiveOptions)(nil)) + state.Register((*MountNamespaceRefs)(nil)) + state.Register((*GetDentryOptions)(nil)) + state.Register((*MkdirOptions)(nil)) + state.Register((*MknodOptions)(nil)) + state.Register((*MountFlags)(nil)) + state.Register((*MountOptions)(nil)) + state.Register((*OpenOptions)(nil)) + state.Register((*ReadOptions)(nil)) + state.Register((*RenameOptions)(nil)) + state.Register((*SetStatOptions)(nil)) + state.Register((*BoundEndpointOptions)(nil)) + state.Register((*GetXattrOptions)(nil)) + state.Register((*SetXattrOptions)(nil)) + state.Register((*StatOptions)(nil)) + state.Register((*UmountOptions)(nil)) + state.Register((*WriteOptions)(nil)) + state.Register((*AccessTypes)(nil)) + state.Register((*ResolvingPath)(nil)) + state.Register((*resolveMountRootOrJumpError)(nil)) + state.Register((*resolveMountPointError)(nil)) + state.Register((*resolveAbsSymlinkError)(nil)) + state.Register((*VirtualFilesystem)(nil)) + state.Register((*PathOperation)(nil)) + state.Register((*VirtualDentry)(nil)) +} diff --git a/pkg/sentry/vfs/vfs_unsafe_state_autogen.go b/pkg/sentry/vfs/vfs_unsafe_state_autogen.go new file mode 100644 index 000000000..d34d60001 --- /dev/null +++ b/pkg/sentry/vfs/vfs_unsafe_state_autogen.go @@ -0,0 +1,40 @@ +// automatically generated by stateify. + +// +build go1.12 +// +build !go1.17 + +package vfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (x *mountTable) StateTypeName() string { + return "pkg/sentry/vfs.mountTable" +} + +func (x *mountTable) StateFields() []string { + return []string{ + "seed", + "size", + } +} + +func (x *mountTable) beforeSave() {} + +func (x *mountTable) StateSave(m state.Sink) { + x.beforeSave() + m.Save(0, &x.seed) + m.Save(1, &x.size) +} + +func (x *mountTable) afterLoad() {} + +func (x *mountTable) StateLoad(m state.Source) { + m.Load(0, &x.seed) + m.Load(1, &x.size) +} + +func init() { + state.Register((*mountTable)(nil)) +} |