summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/vfs
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/vfs')
-rw-r--r--pkg/sentry/vfs/BUILD100
-rw-r--r--pkg/sentry/vfs/README.md195
-rw-r--r--pkg/sentry/vfs/epoll_interest_list.go193
-rw-r--r--pkg/sentry/vfs/event_list.go193
-rw-r--r--pkg/sentry/vfs/file_description_impl_util_test.go224
-rw-r--r--pkg/sentry/vfs/g3doc/inotify.md210
-rw-r--r--pkg/sentry/vfs/genericfstree/BUILD16
-rw-r--r--pkg/sentry/vfs/genericfstree/genericfstree.go81
-rw-r--r--pkg/sentry/vfs/memxattr/BUILD15
-rw-r--r--pkg/sentry/vfs/memxattr/memxattr_state_autogen.go34
-rw-r--r--pkg/sentry/vfs/mount_test.go458
-rw-r--r--pkg/sentry/vfs/vfs_state_autogen.go568
-rw-r--r--pkg/sentry/vfs/vfs_unsafe_state_autogen.go40
13 files changed, 1028 insertions, 1299 deletions
diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD
deleted file mode 100644
index 642769e7c..000000000
--- a/pkg/sentry/vfs/BUILD
+++ /dev/null
@@ -1,100 +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_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",
- "filesystem.go",
- "filesystem_impl_util.go",
- "filesystem_type.go",
- "inotify.go",
- "lock.go",
- "mount.go",
- "mount_unsafe.go",
- "options.go",
- "pathname.go",
- "permissions.go",
- "resolving_path.go",
- "vfs.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/context",
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/fspath",
- "//pkg/gohacks",
- "//pkg/log",
- "//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 4b9faf2ea..000000000
--- a/pkg/sentry/vfs/README.md
+++ /dev/null
@@ -1,195 +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`
-
-- Reference counts in the `vfs` package do not use the `refs` package since
- `refs.AtomicRefCount` adds 64 bytes of overhead to each 8-byte reference
- count, resulting in considerable cache bloat. 24 bytes of this overhead is
- for weak reference support, which have poor performance and will not be used
- by VFS2. The remaining 40 bytes is to store a descriptive string and stack
- trace for reference leak checking; we can support reference leak checking
- without incurring this space overhead by including the applicable
- information directly in finalizers for applicable types.
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/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 8882fa84a..000000000
--- a/pkg/sentry/vfs/genericfstree/genericfstree.go
+++ /dev/null
@@ -1,81 +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.
-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_test.go b/pkg/sentry/vfs/mount_test.go
deleted file mode 100644
index 3335e4057..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.storeKey(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.storeKey(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.loadKey())
- }
-
- 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.loadKey()
- 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.loadKey()
- 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.loadKey()] = 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.loadKey(), 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.loadKey()] = 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.loadKey(), 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.loadKey()] = mount
- }
-
- b.ResetTimer()
- for i := range mounts {
- mount := mounts[i]
- delete(ms, mount.loadKey())
- }
-}
-
-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.loadKey(), mount)
- }
-
- b.ResetTimer()
- for i := range mounts {
- mount := mounts[i]
- ms.Delete(mount.loadKey())
- }
-}
diff --git a/pkg/sentry/vfs/vfs_state_autogen.go b/pkg/sentry/vfs/vfs_state_autogen.go
new file mode 100644
index 000000000..7bd988336
--- /dev/null
+++ b/pkg/sentry/vfs/vfs_state_autogen.go
@@ -0,0 +1,568 @@
+// automatically generated by stateify.
+
+package vfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+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 *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 *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 *Filesystem) StateTypeName() string {
+ return "pkg/sentry/vfs.Filesystem"
+}
+
+func (x *Filesystem) StateFields() []string {
+ return []string{
+ "refs",
+ "vfs",
+ "fsType",
+ "impl",
+ }
+}
+
+func (x *Filesystem) beforeSave() {}
+
+func (x *Filesystem) StateSave(m state.Sink) {
+ x.beforeSave()
+ m.Save(0, &x.refs)
+ 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.refs)
+ m.Load(1, &x.vfs)
+ m.Load(2, &x.fsType)
+ m.Load(3, &x.impl)
+}
+
+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 *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 *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()
+ 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(5, &x.key)
+ 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(5, &x.key)
+ 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)
+}
+
+func (x *MountNamespace) StateTypeName() string {
+ return "pkg/sentry/vfs.MountNamespace"
+}
+
+func (x *MountNamespace) StateFields() []string {
+ return []string{
+ "Owner",
+ "root",
+ "refs",
+ "mountpoints",
+ }
+}
+
+func (x *MountNamespace) beforeSave() {}
+
+func (x *MountNamespace) StateSave(m state.Sink) {
+ x.beforeSave()
+ m.Save(0, &x.Owner)
+ m.Save(1, &x.root)
+ m.Save(2, &x.refs)
+ m.Save(3, &x.mountpoints)
+}
+
+func (x *MountNamespace) afterLoad() {}
+
+func (x *MountNamespace) StateLoad(m state.Source) {
+ m.Load(0, &x.Owner)
+ m.Load(1, &x.root)
+ m.Load(2, &x.refs)
+ m.Load(3, &x.mountpoints)
+}
+
+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 *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((*Dentry)(nil))
+ state.Register((*registeredDevice)(nil))
+ state.Register((*RegisterDeviceOptions)(nil))
+ state.Register((*epollInterestList)(nil))
+ state.Register((*epollInterestEntry)(nil))
+ state.Register((*eventList)(nil))
+ state.Register((*eventEntry)(nil))
+ state.Register((*Filesystem)(nil))
+ state.Register((*registeredFilesystemType)(nil))
+ state.Register((*Inotify)(nil))
+ state.Register((*Watches)(nil))
+ state.Register((*Watch)(nil))
+ state.Register((*Event)(nil))
+ state.Register((*Mount)(nil))
+ state.Register((*MountNamespace)(nil))
+ state.Register((*VirtualFilesystem)(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))
+}