summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fsimpl
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fsimpl')
-rw-r--r--pkg/sentry/fsimpl/cgroupfs/BUILD60
-rw-r--r--pkg/sentry/fsimpl/cgroupfs/bitmap_test.go99
-rw-r--r--pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go766
-rw-r--r--pkg/sentry/fsimpl/cgroupfs/dir_refs.go140
-rw-r--r--pkg/sentry/fsimpl/devpts/BUILD64
-rw-r--r--pkg/sentry/fsimpl/devpts/devpts_state_autogen.go502
-rw-r--r--pkg/sentry/fsimpl/devpts/devpts_test.go90
-rw-r--r--pkg/sentry/fsimpl/devpts/root_inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/devtmpfs/BUILD37
-rw-r--r--pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go41
-rw-r--r--pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go230
-rw-r--r--pkg/sentry/fsimpl/eventfd/BUILD35
-rw-r--r--pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go57
-rw-r--r--pkg/sentry/fsimpl/eventfd/eventfd_test.go97
-rw-r--r--pkg/sentry/fsimpl/ext/BUILD0
-rw-r--r--pkg/sentry/fsimpl/fuse/BUILD90
-rw-r--r--pkg/sentry/fsimpl/fuse/connection_test.go111
-rw-r--r--pkg/sentry/fsimpl/fuse/dev_test.go320
-rw-r--r--pkg/sentry/fsimpl/fuse/fuse_state_autogen.go562
-rw-r--r--pkg/sentry/fsimpl/fuse/inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/fuse/request_list.go221
-rw-r--r--pkg/sentry/fsimpl/fuse/utils_test.go127
-rw-r--r--pkg/sentry/fsimpl/gofer/BUILD101
-rw-r--r--pkg/sentry/fsimpl/gofer/dentry_list.go221
-rw-r--r--pkg/sentry/fsimpl/gofer/fstree.go55
-rw-r--r--pkg/sentry/fsimpl/gofer/gofer_state_autogen.go652
-rw-r--r--pkg/sentry/fsimpl/gofer/gofer_test.go69
-rw-r--r--pkg/sentry/fsimpl/host/BUILD79
-rw-r--r--pkg/sentry/fsimpl/host/connected_endpoint_refs.go140
-rw-r--r--pkg/sentry/fsimpl/host/host_state_autogen.go327
-rw-r--r--pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go3
-rw-r--r--pkg/sentry/fsimpl/host/inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/kernfs/BUILD150
-rw-r--r--pkg/sentry/fsimpl/kernfs/dentry_list.go221
-rw-r--r--pkg/sentry/fsimpl/kernfs/fstree.go55
-rw-r--r--pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go965
-rw-r--r--pkg/sentry/fsimpl/kernfs/kernfs_test.go409
-rw-r--r--pkg/sentry/fsimpl/kernfs/slot_list.go221
-rw-r--r--pkg/sentry/fsimpl/kernfs/static_directory_refs.go140
-rw-r--r--pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go140
-rw-r--r--pkg/sentry/fsimpl/overlay/BUILD48
-rw-r--r--pkg/sentry/fsimpl/overlay/fstree.go55
-rw-r--r--pkg/sentry/fsimpl/overlay/overlay_state_autogen.go321
-rw-r--r--pkg/sentry/fsimpl/pipefs/BUILD21
-rw-r--r--pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go111
-rw-r--r--pkg/sentry/fsimpl/proc/BUILD133
-rw-r--r--pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/proc/proc_state_autogen.go2454
-rw-r--r--pkg/sentry/fsimpl/proc/subtasks_inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/proc/task_inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/proc/tasks_inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/proc/tasks_sys_test.go149
-rw-r--r--pkg/sentry/fsimpl/proc/tasks_test.go511
-rw-r--r--pkg/sentry/fsimpl/signalfd/BUILD19
-rw-r--r--pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go51
-rw-r--r--pkg/sentry/fsimpl/sockfs/BUILD18
-rw-r--r--pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go96
-rw-r--r--pkg/sentry/fsimpl/sys/BUILD55
-rw-r--r--pkg/sentry/fsimpl/sys/dir_refs.go140
-rw-r--r--pkg/sentry/fsimpl/sys/sys_state_autogen.go263
-rw-r--r--pkg/sentry/fsimpl/sys/sys_test.go103
-rw-r--r--pkg/sentry/fsimpl/testutil/BUILD38
-rw-r--r--pkg/sentry/fsimpl/testutil/kernel.go182
-rw-r--r--pkg/sentry/fsimpl/testutil/testutil.go288
-rw-r--r--pkg/sentry/fsimpl/timerfd/BUILD18
-rw-r--r--pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go54
-rw-r--r--pkg/sentry/fsimpl/tmpfs/BUILD129
-rw-r--r--pkg/sentry/fsimpl/tmpfs/benchmark_test.go488
-rw-r--r--pkg/sentry/fsimpl/tmpfs/dentry_list.go221
-rw-r--r--pkg/sentry/fsimpl/tmpfs/fstree.go55
-rw-r--r--pkg/sentry/fsimpl/tmpfs/inode_refs.go140
-rw-r--r--pkg/sentry/fsimpl/tmpfs/pipe_test.go239
-rw-r--r--pkg/sentry/fsimpl/tmpfs/regular_file_test.go349
-rw-r--r--pkg/sentry/fsimpl/tmpfs/stat_test.go236
-rw-r--r--pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go599
-rw-r--r--pkg/sentry/fsimpl/tmpfs/tmpfs_test.go157
-rw-r--r--pkg/sentry/fsimpl/verity/BUILD68
-rw-r--r--pkg/sentry/fsimpl/verity/dentry_list.go221
-rw-r--r--pkg/sentry/fsimpl/verity/verity_state_autogen.go316
-rw-r--r--pkg/sentry/fsimpl/verity/verity_test.go1211
81 files changed, 11646 insertions, 6628 deletions
diff --git a/pkg/sentry/fsimpl/cgroupfs/BUILD b/pkg/sentry/fsimpl/cgroupfs/BUILD
deleted file mode 100644
index 60ee5ede2..000000000
--- a/pkg/sentry/fsimpl/cgroupfs/BUILD
+++ /dev/null
@@ -1,60 +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 = "dir_refs",
- out = "dir_refs.go",
- package = "cgroupfs",
- prefix = "dir",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "dir",
- },
-)
-
-go_library(
- name = "cgroupfs",
- srcs = [
- "base.go",
- "bitmap.go",
- "cgroupfs.go",
- "cpu.go",
- "cpuacct.go",
- "cpuset.go",
- "dir_refs.go",
- "job.go",
- "memory.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/bitmap",
- "//pkg/context",
- "//pkg/coverage",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/sentry/arch",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/usage",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "cgroupfs_test",
- size = "small",
- srcs = ["bitmap_test.go"],
- library = ":cgroupfs",
- deps = ["//pkg/bitmap"],
-)
diff --git a/pkg/sentry/fsimpl/cgroupfs/bitmap_test.go b/pkg/sentry/fsimpl/cgroupfs/bitmap_test.go
deleted file mode 100644
index 5cc56de3b..000000000
--- a/pkg/sentry/fsimpl/cgroupfs/bitmap_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2021 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 cgroupfs
-
-import (
- "fmt"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/bitmap"
-)
-
-func TestFormat(t *testing.T) {
- tests := []struct {
- input []uint32
- output string
- }{
- {[]uint32{1, 2, 3, 4, 7}, "1-4,7"},
- {[]uint32{2}, "2"},
- {[]uint32{0, 1, 2}, "0-2"},
- {[]uint32{}, ""},
- {[]uint32{1, 3, 4, 5, 6, 9, 11, 13, 14, 15, 16, 17}, "1,3-6,9,11,13-17"},
- {[]uint32{2, 3, 10, 12, 13, 14, 15, 16, 20, 21, 33, 34, 47}, "2-3,10,12-16,20-21,33-34,47"},
- }
- for i, tt := range tests {
- t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
- b := bitmap.New(64)
- for _, v := range tt.input {
- b.Add(v)
- }
- s := formatBitmap(&b)
- if s != tt.output {
- t.Errorf("Expected %q, got %q", tt.output, s)
- }
- b1, err := parseBitmap(s, 64)
- if err != nil {
- t.Fatalf("Failed to parse formatted bitmap: %v", err)
- }
- if got, want := b1.ToSlice(), b.ToSlice(); !reflect.DeepEqual(got, want) {
- t.Errorf("Parsing formatted output doesn't result in the original bitmap. Got %v, want %v", got, want)
- }
- })
- }
-}
-
-func TestParse(t *testing.T) {
- tests := []struct {
- input string
- output []uint32
- shouldFail bool
- }{
- {"1", []uint32{1}, false},
- {"", []uint32{}, false},
- {"1,2,3,4", []uint32{1, 2, 3, 4}, false},
- {"1-4", []uint32{1, 2, 3, 4}, false},
- {"1,2-4", []uint32{1, 2, 3, 4}, false},
- {"1,2-3,4", []uint32{1, 2, 3, 4}, false},
- {"1-2,3,4,10,11", []uint32{1, 2, 3, 4, 10, 11}, false},
- {"1,2-4,5,16", []uint32{1, 2, 3, 4, 5, 16}, false},
- {"abc", []uint32{}, true},
- {"1,3-2,4", []uint32{}, true},
- {"1,3-3,4", []uint32{}, true},
- {"1,2,3\000,4", []uint32{1, 2, 3}, false},
- {"1,2,3\n,4", []uint32{1, 2, 3}, false},
- }
- for i, tt := range tests {
- t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
- b, err := parseBitmap(tt.input, 64)
- if tt.shouldFail {
- if err == nil {
- t.Fatalf("Expected parsing of %q to fail, but it didn't", tt.input)
- }
- return
- }
- if err != nil {
- t.Fatalf("Failed to parse bitmap: %v", err)
- return
- }
-
- got := b.ToSlice()
- if !reflect.DeepEqual(got, tt.output) {
- t.Errorf("Parsed bitmap doesn't match what we expected. Got %v, want %v", got, tt.output)
- }
-
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go b/pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go
new file mode 100644
index 000000000..47ddb54c5
--- /dev/null
+++ b/pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go
@@ -0,0 +1,766 @@
+// automatically generated by stateify.
+
+package cgroupfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (c *controllerCommon) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.controllerCommon"
+}
+
+func (c *controllerCommon) StateFields() []string {
+ return []string{
+ "ty",
+ "fs",
+ }
+}
+
+func (c *controllerCommon) beforeSave() {}
+
+// +checklocksignore
+func (c *controllerCommon) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.ty)
+ stateSinkObject.Save(1, &c.fs)
+}
+
+func (c *controllerCommon) afterLoad() {}
+
+// +checklocksignore
+func (c *controllerCommon) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.ty)
+ stateSourceObject.Load(1, &c.fs)
+}
+
+func (c *cgroupInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cgroupInode"
+}
+
+func (c *cgroupInode) StateFields() []string {
+ return []string{
+ "dir",
+ "ts",
+ }
+}
+
+func (c *cgroupInode) beforeSave() {}
+
+// +checklocksignore
+func (c *cgroupInode) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.dir)
+ stateSinkObject.Save(1, &c.ts)
+}
+
+func (c *cgroupInode) afterLoad() {}
+
+// +checklocksignore
+func (c *cgroupInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.dir)
+ stateSourceObject.Load(1, &c.ts)
+}
+
+func (d *cgroupProcsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cgroupProcsData"
+}
+
+func (d *cgroupProcsData) StateFields() []string {
+ return []string{
+ "cgroupInode",
+ }
+}
+
+func (d *cgroupProcsData) beforeSave() {}
+
+// +checklocksignore
+func (d *cgroupProcsData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.cgroupInode)
+}
+
+func (d *cgroupProcsData) afterLoad() {}
+
+// +checklocksignore
+func (d *cgroupProcsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.cgroupInode)
+}
+
+func (d *tasksData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.tasksData"
+}
+
+func (d *tasksData) StateFields() []string {
+ return []string{
+ "cgroupInode",
+ }
+}
+
+func (d *tasksData) beforeSave() {}
+
+// +checklocksignore
+func (d *tasksData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.cgroupInode)
+}
+
+func (d *tasksData) afterLoad() {}
+
+// +checklocksignore
+func (d *tasksData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.cgroupInode)
+}
+
+func (fsType *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.FilesystemType"
+}
+
+func (fsType *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fsType *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fsType *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fsType.beforeSave()
+}
+
+func (fsType *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fsType *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *InternalData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.InternalData"
+}
+
+func (i *InternalData) StateFields() []string {
+ return []string{
+ "DefaultControlValues",
+ "InitialCgroupPath",
+ }
+}
+
+func (i *InternalData) beforeSave() {}
+
+// +checklocksignore
+func (i *InternalData) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.DefaultControlValues)
+ stateSinkObject.Save(1, &i.InitialCgroupPath)
+}
+
+func (i *InternalData) afterLoad() {}
+
+// +checklocksignore
+func (i *InternalData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.DefaultControlValues)
+ stateSourceObject.Load(1, &i.InitialCgroupPath)
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ "hierarchyID",
+ "controllers",
+ "kcontrollers",
+ "numCgroups",
+ "root",
+ "effectiveRoot",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+ stateSinkObject.Save(2, &fs.hierarchyID)
+ stateSinkObject.Save(3, &fs.controllers)
+ stateSinkObject.Save(4, &fs.kcontrollers)
+ stateSinkObject.Save(5, &fs.numCgroups)
+ stateSinkObject.Save(6, &fs.root)
+ stateSinkObject.Save(7, &fs.effectiveRoot)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+ stateSourceObject.Load(2, &fs.hierarchyID)
+ stateSourceObject.Load(3, &fs.controllers)
+ stateSourceObject.Load(4, &fs.kcontrollers)
+ stateSourceObject.Load(5, &fs.numCgroups)
+ stateSourceObject.Load(6, &fs.root)
+ stateSourceObject.Load(7, &fs.effectiveRoot)
+}
+
+func (i *implStatFS) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.implStatFS"
+}
+
+func (i *implStatFS) StateFields() []string {
+ return []string{}
+}
+
+func (i *implStatFS) beforeSave() {}
+
+// +checklocksignore
+func (i *implStatFS) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *implStatFS) afterLoad() {}
+
+// +checklocksignore
+func (i *implStatFS) StateLoad(stateSourceObject state.Source) {
+}
+
+func (d *dir) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.dir"
+}
+
+func (d *dir) StateFields() []string {
+ return []string{
+ "InodeNoopRefCount",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeNotSymlink",
+ "InodeDirectoryNoNewChildren",
+ "OrderedChildren",
+ "implStatFS",
+ "locks",
+ "fs",
+ "cgi",
+ }
+}
+
+func (d *dir) beforeSave() {}
+
+// +checklocksignore
+func (d *dir) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.InodeNoopRefCount)
+ stateSinkObject.Save(1, &d.InodeAlwaysValid)
+ stateSinkObject.Save(2, &d.InodeAttrs)
+ stateSinkObject.Save(3, &d.InodeNotSymlink)
+ stateSinkObject.Save(4, &d.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(5, &d.OrderedChildren)
+ stateSinkObject.Save(6, &d.implStatFS)
+ stateSinkObject.Save(7, &d.locks)
+ stateSinkObject.Save(8, &d.fs)
+ stateSinkObject.Save(9, &d.cgi)
+}
+
+func (d *dir) afterLoad() {}
+
+// +checklocksignore
+func (d *dir) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.InodeNoopRefCount)
+ stateSourceObject.Load(1, &d.InodeAlwaysValid)
+ stateSourceObject.Load(2, &d.InodeAttrs)
+ stateSourceObject.Load(3, &d.InodeNotSymlink)
+ stateSourceObject.Load(4, &d.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(5, &d.OrderedChildren)
+ stateSourceObject.Load(6, &d.implStatFS)
+ stateSourceObject.Load(7, &d.locks)
+ stateSourceObject.Load(8, &d.fs)
+ stateSourceObject.Load(9, &d.cgi)
+}
+
+func (c *controllerFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.controllerFile"
+}
+
+func (c *controllerFile) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ }
+}
+
+func (c *controllerFile) beforeSave() {}
+
+// +checklocksignore
+func (c *controllerFile) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.DynamicBytesFile)
+}
+
+func (c *controllerFile) afterLoad() {}
+
+// +checklocksignore
+func (c *controllerFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.DynamicBytesFile)
+}
+
+func (s *staticControllerFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.staticControllerFile"
+}
+
+func (s *staticControllerFile) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "StaticData",
+ }
+}
+
+func (s *staticControllerFile) beforeSave() {}
+
+// +checklocksignore
+func (s *staticControllerFile) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.DynamicBytesFile)
+ stateSinkObject.Save(1, &s.StaticData)
+}
+
+func (s *staticControllerFile) afterLoad() {}
+
+// +checklocksignore
+func (s *staticControllerFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.DynamicBytesFile)
+ stateSourceObject.Load(1, &s.StaticData)
+}
+
+func (c *cpuController) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuController"
+}
+
+func (c *cpuController) StateFields() []string {
+ return []string{
+ "controllerCommon",
+ "cfsPeriod",
+ "cfsQuota",
+ "shares",
+ }
+}
+
+func (c *cpuController) beforeSave() {}
+
+// +checklocksignore
+func (c *cpuController) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.controllerCommon)
+ stateSinkObject.Save(1, &c.cfsPeriod)
+ stateSinkObject.Save(2, &c.cfsQuota)
+ stateSinkObject.Save(3, &c.shares)
+}
+
+func (c *cpuController) afterLoad() {}
+
+// +checklocksignore
+func (c *cpuController) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.controllerCommon)
+ stateSourceObject.Load(1, &c.cfsPeriod)
+ stateSourceObject.Load(2, &c.cfsQuota)
+ stateSourceObject.Load(3, &c.shares)
+}
+
+func (c *cpuacctController) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuacctController"
+}
+
+func (c *cpuacctController) StateFields() []string {
+ return []string{
+ "controllerCommon",
+ }
+}
+
+func (c *cpuacctController) beforeSave() {}
+
+// +checklocksignore
+func (c *cpuacctController) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.controllerCommon)
+}
+
+func (c *cpuacctController) afterLoad() {}
+
+// +checklocksignore
+func (c *cpuacctController) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.controllerCommon)
+}
+
+func (c *cpuacctCgroup) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuacctCgroup"
+}
+
+func (c *cpuacctCgroup) StateFields() []string {
+ return []string{
+ "cgroupInode",
+ }
+}
+
+func (c *cpuacctCgroup) beforeSave() {}
+
+// +checklocksignore
+func (c *cpuacctCgroup) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.cgroupInode)
+}
+
+func (c *cpuacctCgroup) afterLoad() {}
+
+// +checklocksignore
+func (c *cpuacctCgroup) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.cgroupInode)
+}
+
+func (d *cpuacctStatData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuacctStatData"
+}
+
+func (d *cpuacctStatData) StateFields() []string {
+ return []string{
+ "cpuacctCgroup",
+ }
+}
+
+func (d *cpuacctStatData) beforeSave() {}
+
+// +checklocksignore
+func (d *cpuacctStatData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctStatData) afterLoad() {}
+
+// +checklocksignore
+func (d *cpuacctStatData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctUsageData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuacctUsageData"
+}
+
+func (d *cpuacctUsageData) StateFields() []string {
+ return []string{
+ "cpuacctCgroup",
+ }
+}
+
+func (d *cpuacctUsageData) beforeSave() {}
+
+// +checklocksignore
+func (d *cpuacctUsageData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctUsageData) afterLoad() {}
+
+// +checklocksignore
+func (d *cpuacctUsageData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctUsageUserData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuacctUsageUserData"
+}
+
+func (d *cpuacctUsageUserData) StateFields() []string {
+ return []string{
+ "cpuacctCgroup",
+ }
+}
+
+func (d *cpuacctUsageUserData) beforeSave() {}
+
+// +checklocksignore
+func (d *cpuacctUsageUserData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctUsageUserData) afterLoad() {}
+
+// +checklocksignore
+func (d *cpuacctUsageUserData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctUsageSysData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpuacctUsageSysData"
+}
+
+func (d *cpuacctUsageSysData) StateFields() []string {
+ return []string{
+ "cpuacctCgroup",
+ }
+}
+
+func (d *cpuacctUsageSysData) beforeSave() {}
+
+// +checklocksignore
+func (d *cpuacctUsageSysData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.cpuacctCgroup)
+}
+
+func (d *cpuacctUsageSysData) afterLoad() {}
+
+// +checklocksignore
+func (d *cpuacctUsageSysData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.cpuacctCgroup)
+}
+
+func (c *cpusetController) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpusetController"
+}
+
+func (c *cpusetController) StateFields() []string {
+ return []string{
+ "controllerCommon",
+ "maxCpus",
+ "maxMems",
+ "cpus",
+ "mems",
+ }
+}
+
+func (c *cpusetController) beforeSave() {}
+
+// +checklocksignore
+func (c *cpusetController) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.controllerCommon)
+ stateSinkObject.Save(1, &c.maxCpus)
+ stateSinkObject.Save(2, &c.maxMems)
+ stateSinkObject.Save(3, &c.cpus)
+ stateSinkObject.Save(4, &c.mems)
+}
+
+func (c *cpusetController) afterLoad() {}
+
+// +checklocksignore
+func (c *cpusetController) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.controllerCommon)
+ stateSourceObject.Load(1, &c.maxCpus)
+ stateSourceObject.Load(2, &c.maxMems)
+ stateSourceObject.Load(3, &c.cpus)
+ stateSourceObject.Load(4, &c.mems)
+}
+
+func (d *cpusData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.cpusData"
+}
+
+func (d *cpusData) StateFields() []string {
+ return []string{
+ "c",
+ }
+}
+
+func (d *cpusData) beforeSave() {}
+
+// +checklocksignore
+func (d *cpusData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.c)
+}
+
+func (d *cpusData) afterLoad() {}
+
+// +checklocksignore
+func (d *cpusData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.c)
+}
+
+func (d *memsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.memsData"
+}
+
+func (d *memsData) StateFields() []string {
+ return []string{
+ "c",
+ }
+}
+
+func (d *memsData) beforeSave() {}
+
+// +checklocksignore
+func (d *memsData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.c)
+}
+
+func (d *memsData) afterLoad() {}
+
+// +checklocksignore
+func (d *memsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.c)
+}
+
+func (r *dirRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.dirRefs"
+}
+
+func (r *dirRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *dirRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *dirRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *dirRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (c *jobController) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.jobController"
+}
+
+func (c *jobController) StateFields() []string {
+ return []string{
+ "controllerCommon",
+ "id",
+ }
+}
+
+func (c *jobController) beforeSave() {}
+
+// +checklocksignore
+func (c *jobController) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.controllerCommon)
+ stateSinkObject.Save(1, &c.id)
+}
+
+func (c *jobController) afterLoad() {}
+
+// +checklocksignore
+func (c *jobController) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.controllerCommon)
+ stateSourceObject.Load(1, &c.id)
+}
+
+func (d *jobIDData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.jobIDData"
+}
+
+func (d *jobIDData) StateFields() []string {
+ return []string{
+ "c",
+ }
+}
+
+func (d *jobIDData) beforeSave() {}
+
+// +checklocksignore
+func (d *jobIDData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.c)
+}
+
+func (d *jobIDData) afterLoad() {}
+
+// +checklocksignore
+func (d *jobIDData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.c)
+}
+
+func (c *memoryController) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.memoryController"
+}
+
+func (c *memoryController) StateFields() []string {
+ return []string{
+ "controllerCommon",
+ "limitBytes",
+ "softLimitBytes",
+ "moveChargeAtImmigrate",
+ }
+}
+
+func (c *memoryController) beforeSave() {}
+
+// +checklocksignore
+func (c *memoryController) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.controllerCommon)
+ stateSinkObject.Save(1, &c.limitBytes)
+ stateSinkObject.Save(2, &c.softLimitBytes)
+ stateSinkObject.Save(3, &c.moveChargeAtImmigrate)
+}
+
+func (c *memoryController) afterLoad() {}
+
+// +checklocksignore
+func (c *memoryController) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.controllerCommon)
+ stateSourceObject.Load(1, &c.limitBytes)
+ stateSourceObject.Load(2, &c.softLimitBytes)
+ stateSourceObject.Load(3, &c.moveChargeAtImmigrate)
+}
+
+func (d *memoryUsageInBytesData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/cgroupfs.memoryUsageInBytesData"
+}
+
+func (d *memoryUsageInBytesData) StateFields() []string {
+ return []string{}
+}
+
+func (d *memoryUsageInBytesData) beforeSave() {}
+
+// +checklocksignore
+func (d *memoryUsageInBytesData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+}
+
+func (d *memoryUsageInBytesData) afterLoad() {}
+
+// +checklocksignore
+func (d *memoryUsageInBytesData) StateLoad(stateSourceObject state.Source) {
+}
+
+func init() {
+ state.Register((*controllerCommon)(nil))
+ state.Register((*cgroupInode)(nil))
+ state.Register((*cgroupProcsData)(nil))
+ state.Register((*tasksData)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*InternalData)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*implStatFS)(nil))
+ state.Register((*dir)(nil))
+ state.Register((*controllerFile)(nil))
+ state.Register((*staticControllerFile)(nil))
+ state.Register((*cpuController)(nil))
+ state.Register((*cpuacctController)(nil))
+ state.Register((*cpuacctCgroup)(nil))
+ state.Register((*cpuacctStatData)(nil))
+ state.Register((*cpuacctUsageData)(nil))
+ state.Register((*cpuacctUsageUserData)(nil))
+ state.Register((*cpuacctUsageSysData)(nil))
+ state.Register((*cpusetController)(nil))
+ state.Register((*cpusData)(nil))
+ state.Register((*memsData)(nil))
+ state.Register((*dirRefs)(nil))
+ state.Register((*jobController)(nil))
+ state.Register((*jobIDData)(nil))
+ state.Register((*memoryController)(nil))
+ state.Register((*memoryUsageInBytesData)(nil))
+}
diff --git a/pkg/sentry/fsimpl/cgroupfs/dir_refs.go b/pkg/sentry/fsimpl/cgroupfs/dir_refs.go
new file mode 100644
index 000000000..c29f0c9ae
--- /dev/null
+++ b/pkg/sentry/fsimpl/cgroupfs/dir_refs.go
@@ -0,0 +1,140 @@
+package cgroupfs
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const direnableLogging = false
+
+// obj 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 dirobj *dir
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type dirRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *dirRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *dirRefs) RefType() string {
+ return fmt.Sprintf("%T", dirobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *dirRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *dirRefs) LogRefs() bool {
+ return direnableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *dirRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *dirRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if direnableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *dirRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if direnableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *dirRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if direnableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *dirRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/devpts/BUILD b/pkg/sentry/fsimpl/devpts/BUILD
deleted file mode 100644
index e0b879339..000000000
--- a/pkg/sentry/fsimpl/devpts/BUILD
+++ /dev/null
@@ -1,64 +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 = "root_inode_refs",
- out = "root_inode_refs.go",
- package = "devpts",
- prefix = "rootInode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "rootInode",
- },
-)
-
-go_library(
- name = "devpts",
- srcs = [
- "devpts.go",
- "line_discipline.go",
- "master.go",
- "queue.go",
- "replica.go",
- "root_inode_refs.go",
- "terminal.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/log",
- "//pkg/marshal",
- "//pkg/marshal/primitive",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/arch",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "devpts_test",
- size = "small",
- srcs = ["devpts_test.go"],
- library = ":devpts",
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/contexttest",
- "//pkg/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fsimpl/devpts/devpts_state_autogen.go b/pkg/sentry/fsimpl/devpts/devpts_state_autogen.go
new file mode 100644
index 000000000..78e685c7e
--- /dev/null
+++ b/pkg/sentry/fsimpl/devpts/devpts_state_autogen.go
@@ -0,0 +1,502 @@
+// automatically generated by stateify.
+
+package devpts
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (fstype *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.FilesystemType"
+}
+
+func (fstype *FilesystemType) StateFields() []string {
+ return []string{
+ "initErr",
+ "fs",
+ "root",
+ }
+}
+
+func (fstype *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fstype.beforeSave()
+ stateSinkObject.Save(0, &fstype.initErr)
+ stateSinkObject.Save(1, &fstype.fs)
+ stateSinkObject.Save(2, &fstype.root)
+}
+
+func (fstype *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fstype.initErr)
+ stateSourceObject.Load(1, &fstype.fs)
+ stateSourceObject.Load(2, &fstype.root)
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+}
+
+func (i *rootInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.rootInode"
+}
+
+func (i *rootInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ "rootInodeRefs",
+ "locks",
+ "master",
+ "replicas",
+ "nextIdx",
+ }
+}
+
+func (i *rootInode) beforeSave() {}
+
+// +checklocksignore
+func (i *rootInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.implStatFS)
+ stateSinkObject.Save(1, &i.InodeAlwaysValid)
+ stateSinkObject.Save(2, &i.InodeAttrs)
+ stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(4, &i.InodeNotSymlink)
+ stateSinkObject.Save(5, &i.InodeTemporary)
+ stateSinkObject.Save(6, &i.OrderedChildren)
+ stateSinkObject.Save(7, &i.rootInodeRefs)
+ stateSinkObject.Save(8, &i.locks)
+ stateSinkObject.Save(9, &i.master)
+ stateSinkObject.Save(10, &i.replicas)
+ stateSinkObject.Save(11, &i.nextIdx)
+}
+
+func (i *rootInode) afterLoad() {}
+
+// +checklocksignore
+func (i *rootInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.implStatFS)
+ stateSourceObject.Load(1, &i.InodeAlwaysValid)
+ stateSourceObject.Load(2, &i.InodeAttrs)
+ stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(4, &i.InodeNotSymlink)
+ stateSourceObject.Load(5, &i.InodeTemporary)
+ stateSourceObject.Load(6, &i.OrderedChildren)
+ stateSourceObject.Load(7, &i.rootInodeRefs)
+ stateSourceObject.Load(8, &i.locks)
+ stateSourceObject.Load(9, &i.master)
+ stateSourceObject.Load(10, &i.replicas)
+ stateSourceObject.Load(11, &i.nextIdx)
+}
+
+func (i *implStatFS) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.implStatFS"
+}
+
+func (i *implStatFS) StateFields() []string {
+ return []string{}
+}
+
+func (i *implStatFS) beforeSave() {}
+
+// +checklocksignore
+func (i *implStatFS) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *implStatFS) afterLoad() {}
+
+// +checklocksignore
+func (i *implStatFS) StateLoad(stateSourceObject state.Source) {
+}
+
+func (l *lineDiscipline) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.lineDiscipline"
+}
+
+func (l *lineDiscipline) StateFields() []string {
+ return []string{
+ "size",
+ "inQueue",
+ "outQueue",
+ "termios",
+ "column",
+ "masterWaiter",
+ "replicaWaiter",
+ }
+}
+
+func (l *lineDiscipline) beforeSave() {}
+
+// +checklocksignore
+func (l *lineDiscipline) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.size)
+ stateSinkObject.Save(1, &l.inQueue)
+ stateSinkObject.Save(2, &l.outQueue)
+ stateSinkObject.Save(3, &l.termios)
+ stateSinkObject.Save(4, &l.column)
+ stateSinkObject.Save(5, &l.masterWaiter)
+ stateSinkObject.Save(6, &l.replicaWaiter)
+}
+
+func (l *lineDiscipline) afterLoad() {}
+
+// +checklocksignore
+func (l *lineDiscipline) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.size)
+ stateSourceObject.Load(1, &l.inQueue)
+ stateSourceObject.Load(2, &l.outQueue)
+ stateSourceObject.Load(3, &l.termios)
+ stateSourceObject.Load(4, &l.column)
+ stateSourceObject.Load(5, &l.masterWaiter)
+ stateSourceObject.Load(6, &l.replicaWaiter)
+}
+
+func (o *outputQueueTransformer) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.outputQueueTransformer"
+}
+
+func (o *outputQueueTransformer) StateFields() []string {
+ return []string{}
+}
+
+func (o *outputQueueTransformer) beforeSave() {}
+
+// +checklocksignore
+func (o *outputQueueTransformer) StateSave(stateSinkObject state.Sink) {
+ o.beforeSave()
+}
+
+func (o *outputQueueTransformer) afterLoad() {}
+
+// +checklocksignore
+func (o *outputQueueTransformer) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *inputQueueTransformer) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.inputQueueTransformer"
+}
+
+func (i *inputQueueTransformer) StateFields() []string {
+ return []string{}
+}
+
+func (i *inputQueueTransformer) beforeSave() {}
+
+// +checklocksignore
+func (i *inputQueueTransformer) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *inputQueueTransformer) afterLoad() {}
+
+// +checklocksignore
+func (i *inputQueueTransformer) StateLoad(stateSourceObject state.Source) {
+}
+
+func (mi *masterInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.masterInode"
+}
+
+func (mi *masterInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "locks",
+ "root",
+ }
+}
+
+func (mi *masterInode) beforeSave() {}
+
+// +checklocksignore
+func (mi *masterInode) StateSave(stateSinkObject state.Sink) {
+ mi.beforeSave()
+ stateSinkObject.Save(0, &mi.implStatFS)
+ stateSinkObject.Save(1, &mi.InodeAttrs)
+ stateSinkObject.Save(2, &mi.InodeNoopRefCount)
+ stateSinkObject.Save(3, &mi.InodeNotDirectory)
+ stateSinkObject.Save(4, &mi.InodeNotSymlink)
+ stateSinkObject.Save(5, &mi.locks)
+ stateSinkObject.Save(6, &mi.root)
+}
+
+func (mi *masterInode) afterLoad() {}
+
+// +checklocksignore
+func (mi *masterInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &mi.implStatFS)
+ stateSourceObject.Load(1, &mi.InodeAttrs)
+ stateSourceObject.Load(2, &mi.InodeNoopRefCount)
+ stateSourceObject.Load(3, &mi.InodeNotDirectory)
+ stateSourceObject.Load(4, &mi.InodeNotSymlink)
+ stateSourceObject.Load(5, &mi.locks)
+ stateSourceObject.Load(6, &mi.root)
+}
+
+func (mfd *masterFileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.masterFileDescription"
+}
+
+func (mfd *masterFileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ "inode",
+ "t",
+ }
+}
+
+func (mfd *masterFileDescription) beforeSave() {}
+
+// +checklocksignore
+func (mfd *masterFileDescription) StateSave(stateSinkObject state.Sink) {
+ mfd.beforeSave()
+ stateSinkObject.Save(0, &mfd.vfsfd)
+ stateSinkObject.Save(1, &mfd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &mfd.LockFD)
+ stateSinkObject.Save(3, &mfd.inode)
+ stateSinkObject.Save(4, &mfd.t)
+}
+
+func (mfd *masterFileDescription) afterLoad() {}
+
+// +checklocksignore
+func (mfd *masterFileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &mfd.vfsfd)
+ stateSourceObject.Load(1, &mfd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &mfd.LockFD)
+ stateSourceObject.Load(3, &mfd.inode)
+ stateSourceObject.Load(4, &mfd.t)
+}
+
+func (q *queue) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.queue"
+}
+
+func (q *queue) StateFields() []string {
+ return []string{
+ "readBuf",
+ "waitBuf",
+ "waitBufLen",
+ "readable",
+ "transformer",
+ }
+}
+
+func (q *queue) beforeSave() {}
+
+// +checklocksignore
+func (q *queue) StateSave(stateSinkObject state.Sink) {
+ q.beforeSave()
+ stateSinkObject.Save(0, &q.readBuf)
+ stateSinkObject.Save(1, &q.waitBuf)
+ stateSinkObject.Save(2, &q.waitBufLen)
+ stateSinkObject.Save(3, &q.readable)
+ stateSinkObject.Save(4, &q.transformer)
+}
+
+func (q *queue) afterLoad() {}
+
+// +checklocksignore
+func (q *queue) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &q.readBuf)
+ stateSourceObject.Load(1, &q.waitBuf)
+ stateSourceObject.Load(2, &q.waitBufLen)
+ stateSourceObject.Load(3, &q.readable)
+ stateSourceObject.Load(4, &q.transformer)
+}
+
+func (ri *replicaInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.replicaInode"
+}
+
+func (ri *replicaInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "locks",
+ "root",
+ "t",
+ }
+}
+
+func (ri *replicaInode) beforeSave() {}
+
+// +checklocksignore
+func (ri *replicaInode) StateSave(stateSinkObject state.Sink) {
+ ri.beforeSave()
+ stateSinkObject.Save(0, &ri.implStatFS)
+ stateSinkObject.Save(1, &ri.InodeAttrs)
+ stateSinkObject.Save(2, &ri.InodeNoopRefCount)
+ stateSinkObject.Save(3, &ri.InodeNotDirectory)
+ stateSinkObject.Save(4, &ri.InodeNotSymlink)
+ stateSinkObject.Save(5, &ri.locks)
+ stateSinkObject.Save(6, &ri.root)
+ stateSinkObject.Save(7, &ri.t)
+}
+
+func (ri *replicaInode) afterLoad() {}
+
+// +checklocksignore
+func (ri *replicaInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &ri.implStatFS)
+ stateSourceObject.Load(1, &ri.InodeAttrs)
+ stateSourceObject.Load(2, &ri.InodeNoopRefCount)
+ stateSourceObject.Load(3, &ri.InodeNotDirectory)
+ stateSourceObject.Load(4, &ri.InodeNotSymlink)
+ stateSourceObject.Load(5, &ri.locks)
+ stateSourceObject.Load(6, &ri.root)
+ stateSourceObject.Load(7, &ri.t)
+}
+
+func (rfd *replicaFileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.replicaFileDescription"
+}
+
+func (rfd *replicaFileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ "inode",
+ }
+}
+
+func (rfd *replicaFileDescription) beforeSave() {}
+
+// +checklocksignore
+func (rfd *replicaFileDescription) StateSave(stateSinkObject state.Sink) {
+ rfd.beforeSave()
+ stateSinkObject.Save(0, &rfd.vfsfd)
+ stateSinkObject.Save(1, &rfd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &rfd.LockFD)
+ stateSinkObject.Save(3, &rfd.inode)
+}
+
+func (rfd *replicaFileDescription) afterLoad() {}
+
+// +checklocksignore
+func (rfd *replicaFileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &rfd.vfsfd)
+ stateSourceObject.Load(1, &rfd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &rfd.LockFD)
+ stateSourceObject.Load(3, &rfd.inode)
+}
+
+func (r *rootInodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.rootInodeRefs"
+}
+
+func (r *rootInodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *rootInodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *rootInodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *rootInodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (tm *Terminal) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devpts.Terminal"
+}
+
+func (tm *Terminal) StateFields() []string {
+ return []string{
+ "n",
+ "ld",
+ "masterKTTY",
+ "replicaKTTY",
+ }
+}
+
+func (tm *Terminal) beforeSave() {}
+
+// +checklocksignore
+func (tm *Terminal) StateSave(stateSinkObject state.Sink) {
+ tm.beforeSave()
+ stateSinkObject.Save(0, &tm.n)
+ stateSinkObject.Save(1, &tm.ld)
+ stateSinkObject.Save(2, &tm.masterKTTY)
+ stateSinkObject.Save(3, &tm.replicaKTTY)
+}
+
+func (tm *Terminal) afterLoad() {}
+
+// +checklocksignore
+func (tm *Terminal) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &tm.n)
+ stateSourceObject.Load(1, &tm.ld)
+ stateSourceObject.Load(2, &tm.masterKTTY)
+ stateSourceObject.Load(3, &tm.replicaKTTY)
+}
+
+func init() {
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*rootInode)(nil))
+ state.Register((*implStatFS)(nil))
+ state.Register((*lineDiscipline)(nil))
+ state.Register((*outputQueueTransformer)(nil))
+ state.Register((*inputQueueTransformer)(nil))
+ state.Register((*masterInode)(nil))
+ state.Register((*masterFileDescription)(nil))
+ state.Register((*queue)(nil))
+ state.Register((*replicaInode)(nil))
+ state.Register((*replicaFileDescription)(nil))
+ state.Register((*rootInodeRefs)(nil))
+ state.Register((*Terminal)(nil))
+}
diff --git a/pkg/sentry/fsimpl/devpts/devpts_test.go b/pkg/sentry/fsimpl/devpts/devpts_test.go
deleted file mode 100644
index 1ef07d702..000000000
--- a/pkg/sentry/fsimpl/devpts/devpts_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package devpts
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestSimpleMasterToReplica(t *testing.T) {
- ld := newLineDiscipline(linux.DefaultReplicaTermios)
- ctx := contexttest.Context(t)
- inBytes := []byte("hello, tty\n")
- src := usermem.BytesIOSequence(inBytes)
- outBytes := make([]byte, 32)
- dst := usermem.BytesIOSequence(outBytes)
-
- // Write to the input queue.
- nw, err := ld.inputQueueWrite(ctx, src)
- if err != nil {
- t.Fatalf("error writing to input queue: %v", err)
- }
- if nw != int64(len(inBytes)) {
- t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes))
- }
-
- // Read from the input queue.
- nr, err := ld.inputQueueRead(ctx, dst)
- if err != nil {
- t.Fatalf("error reading from input queue: %v", err)
- }
- if nr != int64(len(inBytes)) {
- t.Fatalf("read wrong length: got %d, want %d", nr, len(inBytes))
- }
-
- outStr := string(outBytes[:nr])
- inStr := string(inBytes)
- if outStr != inStr {
- t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr)
- }
-}
-
-type callback func(*waiter.Entry, waiter.EventMask)
-
-func (cb callback) Callback(entry *waiter.Entry, mask waiter.EventMask) {
- cb(entry, mask)
-}
-
-func TestEchoDeadlock(t *testing.T) {
- ctx := contexttest.Context(t)
- termios := linux.DefaultReplicaTermios
- termios.LocalFlags |= linux.ECHO
- ld := newLineDiscipline(termios)
- outBytes := make([]byte, 32)
- dst := usermem.BytesIOSequence(outBytes)
- entry := &waiter.Entry{Callback: callback(func(*waiter.Entry, waiter.EventMask) {
- ld.inputQueueRead(ctx, dst)
- })}
- ld.masterWaiter.EventRegister(entry, waiter.ReadableEvents)
- defer ld.masterWaiter.EventUnregister(entry)
- inBytes := []byte("hello, tty\n")
- n, err := ld.inputQueueWrite(ctx, usermem.BytesIOSequence(inBytes))
- if err != nil {
- t.Fatalf("inputQueueWrite: %v", err)
- }
- if int(n) != len(inBytes) {
- t.Fatalf("read wrong length: got %d, want %d", n, len(inBytes))
- }
- outStr := string(outBytes[:n])
- inStr := string(inBytes)
- if outStr != inStr {
- t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr)
- }
-}
diff --git a/pkg/sentry/fsimpl/devpts/root_inode_refs.go b/pkg/sentry/fsimpl/devpts/root_inode_refs.go
new file mode 100644
index 000000000..e53739a90
--- /dev/null
+++ b/pkg/sentry/fsimpl/devpts/root_inode_refs.go
@@ -0,0 +1,140 @@
+package devpts
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const rootInodeenableLogging = false
+
+// obj 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 rootInodeobj *rootInode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type rootInodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *rootInodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *rootInodeRefs) RefType() string {
+ return fmt.Sprintf("%T", rootInodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *rootInodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *rootInodeRefs) LogRefs() bool {
+ return rootInodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *rootInodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *rootInodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if rootInodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *rootInodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if rootInodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *rootInodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if rootInodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *rootInodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/devtmpfs/BUILD b/pkg/sentry/fsimpl/devtmpfs/BUILD
deleted file mode 100644
index e49a04c1b..000000000
--- a/pkg/sentry/fsimpl/devtmpfs/BUILD
+++ /dev/null
@@ -1,37 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-licenses(["notice"])
-
-go_library(
- name = "devtmpfs",
- srcs = [
- "devtmpfs.go",
- "save_restore.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/fspath",
- "//pkg/sentry/fsimpl/tmpfs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- ],
-)
-
-go_test(
- name = "devtmpfs_test",
- size = "small",
- srcs = ["devtmpfs_test.go"],
- library = ":devtmpfs",
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/fspath",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/fsimpl/tmpfs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- ],
-)
diff --git a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go
new file mode 100644
index 000000000..900c7d8fe
--- /dev/null
+++ b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go
@@ -0,0 +1,41 @@
+// automatically generated by stateify.
+
+package devtmpfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (fst *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/devtmpfs.FilesystemType"
+}
+
+func (fst *FilesystemType) StateFields() []string {
+ return []string{
+ "initErr",
+ "fs",
+ "root",
+ }
+}
+
+func (fst *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fst *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fst.beforeSave()
+ stateSinkObject.Save(0, &fst.initErr)
+ stateSinkObject.Save(1, &fst.fs)
+ stateSinkObject.Save(2, &fst.root)
+}
+
+// +checklocksignore
+func (fst *FilesystemType) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fst.initErr)
+ stateSourceObject.Load(1, &fst.fs)
+ stateSourceObject.Load(2, &fst.root)
+ stateSourceObject.AfterLoad(fst.afterLoad)
+}
+
+func init() {
+ state.Register((*FilesystemType)(nil))
+}
diff --git a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go
deleted file mode 100644
index e058eda7a..000000000
--- a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go
+++ /dev/null
@@ -1,230 +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 devtmpfs
-
-import (
- "path"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-const devPath = "/dev"
-
-func setupDevtmpfs(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry, func()) {
- t.Helper()
-
- ctx := contexttest.Context(t)
- creds := auth.CredentialsFromContext(ctx)
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- t.Fatalf("VFS init: %v", err)
- }
- // Register tmpfs just so that we can have a root filesystem that isn't
- // devtmpfs.
- vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
- vfsObj.MustRegisterFilesystemType("devtmpfs", &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- // Create a test mount namespace with devtmpfs mounted at "/dev".
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "tmpfs" /* source */, "tmpfs" /* fsTypeName */, &vfs.MountOptions{})
- if err != nil {
- t.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- root := mntns.Root()
- root.IncRef()
- devpop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(devPath),
- }
- if err := vfsObj.MkdirAt(ctx, creds, &devpop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- t.Fatalf("failed to create mount point: %v", err)
- }
- if _, err := vfsObj.MountAt(ctx, creds, "devtmpfs" /* source */, &devpop, "devtmpfs" /* fsTypeName */, &vfs.MountOptions{}); err != nil {
- t.Fatalf("failed to mount devtmpfs: %v", err)
- }
-
- return ctx, creds, vfsObj, root, func() {
- root.DecRef(ctx)
- mntns.DecRef(ctx)
- }
-}
-
-func TestUserspaceInit(t *testing.T) {
- ctx, creds, vfsObj, root, cleanup := setupDevtmpfs(t)
- defer cleanup()
-
- a, err := NewAccessor(ctx, vfsObj, creds, "devtmpfs")
- if err != nil {
- t.Fatalf("failed to create devtmpfs.Accessor: %v", err)
- }
- defer a.Release(ctx)
-
- // Create "userspace-initialized" files using a devtmpfs.Accessor.
- if err := a.UserspaceInit(ctx); err != nil {
- t.Fatalf("failed to userspace-initialize devtmpfs: %v", err)
- }
-
- // Created files should be visible in the test mount namespace.
- links := []struct {
- source string
- target string
- }{
- {
- source: "fd",
- target: "/proc/self/fd",
- },
- {
- source: "stdin",
- target: "/proc/self/fd/0",
- },
- {
- source: "stdout",
- target: "/proc/self/fd/1",
- },
- {
- source: "stderr",
- target: "/proc/self/fd/2",
- },
- {
- source: "ptmx",
- target: "pts/ptmx",
- },
- }
-
- for _, link := range links {
- abspath := path.Join(devPath, link.source)
- if gotTarget, err := vfsObj.ReadlinkAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(abspath),
- }); err != nil || gotTarget != link.target {
- t.Errorf("readlink(%q): got (%q, %v), wanted (%q, nil)", abspath, gotTarget, err, link.target)
- }
- }
-
- dirs := []string{"shm", "pts"}
- for _, dir := range dirs {
- abspath := path.Join(devPath, dir)
- statx, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(abspath),
- }, &vfs.StatOptions{
- Mask: linux.STATX_MODE,
- })
- if err != nil {
- t.Errorf("stat(%q): got error %v ", abspath, err)
- continue
- }
- if want := uint16(0755) | linux.S_IFDIR; statx.Mode != want {
- t.Errorf("stat(%q): got mode %x, want %x", abspath, statx.Mode, want)
- }
- }
-}
-
-func TestCreateDeviceFile(t *testing.T) {
- ctx, creds, vfsObj, root, cleanup := setupDevtmpfs(t)
- defer cleanup()
-
- a, err := NewAccessor(ctx, vfsObj, creds, "devtmpfs")
- if err != nil {
- t.Fatalf("failed to create devtmpfs.Accessor: %v", err)
- }
- defer a.Release(ctx)
-
- devFiles := []struct {
- path string
- kind vfs.DeviceKind
- major uint32
- minor uint32
- perms uint16
- }{
- {
- path: "dummy",
- kind: vfs.CharDevice,
- major: 12,
- minor: 34,
- perms: 0600,
- },
- {
- path: "foo/bar",
- kind: vfs.BlockDevice,
- major: 13,
- minor: 35,
- perms: 0660,
- },
- {
- path: "foo/baz",
- kind: vfs.CharDevice,
- major: 12,
- minor: 40,
- perms: 0666,
- },
- {
- path: "a/b/c/d/e",
- kind: vfs.BlockDevice,
- major: 12,
- minor: 34,
- perms: 0600,
- },
- }
-
- for _, f := range devFiles {
- if err := a.CreateDeviceFile(ctx, f.path, f.kind, f.major, f.minor, f.perms); err != nil {
- t.Fatalf("failed to create device file: %v", err)
- }
- // The device special file should be visible in the test mount namespace.
- abspath := path.Join(devPath, f.path)
- stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(abspath),
- }, &vfs.StatOptions{
- Mask: linux.STATX_TYPE | linux.STATX_MODE,
- })
- if err != nil {
- t.Fatalf("failed to stat device file at %q: %v", abspath, err)
- }
- if stat.RdevMajor != f.major {
- t.Errorf("major device number: got %v, wanted %v", stat.RdevMajor, f.major)
- }
- if stat.RdevMinor != f.minor {
- t.Errorf("minor device number: got %v, wanted %v", stat.RdevMinor, f.minor)
- }
- wantMode := f.perms
- switch f.kind {
- case vfs.CharDevice:
- wantMode |= linux.S_IFCHR
- case vfs.BlockDevice:
- wantMode |= linux.S_IFBLK
- }
- if stat.Mode != wantMode {
- t.Errorf("device file mode: got %v, wanted %v", stat.Mode, wantMode)
- }
- }
-}
diff --git a/pkg/sentry/fsimpl/eventfd/BUILD b/pkg/sentry/fsimpl/eventfd/BUILD
deleted file mode 100644
index 1cb049a29..000000000
--- a/pkg/sentry/fsimpl/eventfd/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-licenses(["notice"])
-
-go_library(
- name = "eventfd",
- srcs = ["eventfd.go"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fdnotifier",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "eventfd_test",
- size = "small",
- srcs = ["eventfd_test.go"],
- library = ":eventfd",
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go b/pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go
new file mode 100644
index 000000000..93de7f32e
--- /dev/null
+++ b/pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go
@@ -0,0 +1,57 @@
+// automatically generated by stateify.
+
+package eventfd
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (efd *EventFileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/eventfd.EventFileDescription"
+}
+
+func (efd *EventFileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "DentryMetadataFileDescriptionImpl",
+ "NoLockFD",
+ "queue",
+ "val",
+ "semMode",
+ "hostfd",
+ }
+}
+
+func (efd *EventFileDescription) beforeSave() {}
+
+// +checklocksignore
+func (efd *EventFileDescription) StateSave(stateSinkObject state.Sink) {
+ efd.beforeSave()
+ stateSinkObject.Save(0, &efd.vfsfd)
+ stateSinkObject.Save(1, &efd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &efd.DentryMetadataFileDescriptionImpl)
+ stateSinkObject.Save(3, &efd.NoLockFD)
+ stateSinkObject.Save(4, &efd.queue)
+ stateSinkObject.Save(5, &efd.val)
+ stateSinkObject.Save(6, &efd.semMode)
+ stateSinkObject.Save(7, &efd.hostfd)
+}
+
+func (efd *EventFileDescription) afterLoad() {}
+
+// +checklocksignore
+func (efd *EventFileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &efd.vfsfd)
+ stateSourceObject.Load(1, &efd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &efd.DentryMetadataFileDescriptionImpl)
+ stateSourceObject.Load(3, &efd.NoLockFD)
+ stateSourceObject.Load(4, &efd.queue)
+ stateSourceObject.Load(5, &efd.val)
+ stateSourceObject.Load(6, &efd.semMode)
+ stateSourceObject.Load(7, &efd.hostfd)
+}
+
+func init() {
+ state.Register((*EventFileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/eventfd/eventfd_test.go b/pkg/sentry/fsimpl/eventfd/eventfd_test.go
deleted file mode 100644
index 85718f813..000000000
--- a/pkg/sentry/fsimpl/eventfd/eventfd_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package eventfd
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestEventFD(t *testing.T) {
- initVals := []uint64{
- 0,
- // Using a non-zero initial value verifies that writing to an
- // eventfd signals when the eventfd's counter was already
- // non-zero.
- 343,
- }
-
- for _, initVal := range initVals {
- ctx := contexttest.Context(t)
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- t.Fatalf("VFS init: %v", err)
- }
-
- // Make a new eventfd that is writable.
- eventfd, err := New(ctx, vfsObj, initVal, false, linux.O_RDWR)
- if err != nil {
- t.Fatalf("New() failed: %v", err)
- }
- defer eventfd.DecRef(ctx)
-
- // Register a callback for a write event.
- w, ch := waiter.NewChannelEntry(nil)
- eventfd.EventRegister(&w, waiter.ReadableEvents)
- defer eventfd.EventUnregister(&w)
-
- data := []byte("00000124")
- // Create and submit a write request.
- n, err := eventfd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if n != 8 {
- t.Errorf("eventfd.write wrote %d bytes, not full int64", n)
- }
-
- // Check if the callback fired due to the write event.
- select {
- case <-ch:
- default:
- t.Errorf("Didn't get notified of EventIn after write")
- }
- }
-}
-
-func TestEventFDStat(t *testing.T) {
- ctx := contexttest.Context(t)
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- t.Fatalf("VFS init: %v", err)
- }
-
- // Make a new eventfd that is writable.
- eventfd, err := New(ctx, vfsObj, 0, false, linux.O_RDWR)
- if err != nil {
- t.Fatalf("New() failed: %v", err)
- }
- defer eventfd.DecRef(ctx)
-
- statx, err := eventfd.Stat(ctx, vfs.StatOptions{
- Mask: linux.STATX_BASIC_STATS,
- })
- if err != nil {
- t.Fatalf("eventfd.Stat failed: %v", err)
- }
- if statx.Size != 0 {
- t.Errorf("eventfd size should be 0")
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/BUILD b/pkg/sentry/fsimpl/ext/BUILD
deleted file mode 100644
index e69de29bb..000000000
--- a/pkg/sentry/fsimpl/ext/BUILD
+++ /dev/null
diff --git a/pkg/sentry/fsimpl/fuse/BUILD b/pkg/sentry/fsimpl/fuse/BUILD
deleted file mode 100644
index 05c4fbeb2..000000000
--- a/pkg/sentry/fsimpl/fuse/BUILD
+++ /dev/null
@@ -1,90 +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 = "request_list",
- out = "request_list.go",
- package = "fuse",
- prefix = "request",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Request",
- "Linker": "*Request",
- },
-)
-
-go_template_instance(
- name = "inode_refs",
- out = "inode_refs.go",
- package = "fuse",
- prefix = "inode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "inode",
- },
-)
-
-go_library(
- name = "fuse",
- srcs = [
- "connection.go",
- "connection_control.go",
- "dev.go",
- "directory.go",
- "file.go",
- "fusefs.go",
- "inode_refs.go",
- "read_write.go",
- "register.go",
- "regular_file.go",
- "request_list.go",
- "request_response.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/marshal",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/fsimpl/devtmpfs",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "fuse_test",
- size = "small",
- srcs = [
- "connection_test.go",
- "dev_test.go",
- "utils_test.go",
- ],
- library = ":fuse",
- deps = [
- "//pkg/abi/linux",
- "//pkg/errors/linuxerr",
- "//pkg/hostarch",
- "//pkg/marshal",
- "//pkg/sentry/fsimpl/testutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fsimpl/fuse/connection_test.go b/pkg/sentry/fsimpl/fuse/connection_test.go
deleted file mode 100644
index 1fddd858e..000000000
--- a/pkg/sentry/fsimpl/fuse/connection_test.go
+++ /dev/null
@@ -1,111 +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 fuse
-
-import (
- "math/rand"
- "testing"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
-)
-
-// TestConnectionInitBlock tests if initialization
-// correctly blocks and unblocks the connection.
-// Since it's unfeasible to test kernelTask.Block() in unit test,
-// the code in Call() are not tested here.
-func TestConnectionInitBlock(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
-
- conn, _, err := newTestConnection(s, k, maxActiveRequestsDefault)
- if err != nil {
- t.Fatalf("newTestConnection: %v", err)
- }
-
- select {
- case <-conn.initializedChan:
- t.Fatalf("initializedChan should be blocking before SetInitialized")
- default:
- }
-
- conn.SetInitialized()
-
- select {
- case <-conn.initializedChan:
- default:
- t.Fatalf("initializedChan should not be blocking after SetInitialized")
- }
-}
-
-func TestConnectionAbort(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
- creds := auth.CredentialsFromContext(s.Ctx)
- task := kernel.TaskFromContext(s.Ctx)
-
- const numRequests uint64 = 256
-
- conn, _, err := newTestConnection(s, k, numRequests)
- if err != nil {
- t.Fatalf("newTestConnection: %v", err)
- }
-
- testObj := &testPayload{
- data: rand.Uint32(),
- }
-
- var futNormal []*futureResponse
-
- for i := 0; i < int(numRequests); i++ {
- req := conn.NewRequest(creds, uint32(i), uint64(i), 0, testObj)
- fut, err := conn.callFutureLocked(task, req)
- if err != nil {
- t.Fatalf("callFutureLocked failed: %v", err)
- }
- futNormal = append(futNormal, fut)
- }
-
- conn.Abort(s.Ctx)
-
- // Abort should unblock the initialization channel.
- // Note: no test requests are actually blocked on `conn.initializedChan`.
- select {
- case <-conn.initializedChan:
- default:
- t.Fatalf("initializedChan should not be blocking after SetInitialized")
- }
-
- // Abort will return ECONNABORTED error to unblocked requests.
- for _, fut := range futNormal {
- if fut.getResponse().hdr.Error != -int32(unix.ECONNABORTED) {
- t.Fatalf("Incorrect error code received for aborted connection: %v", fut.getResponse().hdr.Error)
- }
- }
-
- // After abort, Call() should return directly with ENOTCONN.
- req := conn.NewRequest(creds, 0, 0, 0, testObj)
- _, err = conn.Call(task, req)
- if !linuxerr.Equals(linuxerr.ENOTCONN, err) {
- t.Fatalf("Incorrect error code received for Call() after connection aborted")
- }
-
-}
diff --git a/pkg/sentry/fsimpl/fuse/dev_test.go b/pkg/sentry/fsimpl/fuse/dev_test.go
deleted file mode 100644
index 8951b5ba8..000000000
--- a/pkg/sentry/fsimpl/fuse/dev_test.go
+++ /dev/null
@@ -1,320 +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 fuse
-
-import (
- "fmt"
- "math/rand"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// echoTestOpcode is the Opcode used during testing. The server used in tests
-// will simply echo the payload back with the appropriate headers.
-const echoTestOpcode linux.FUSEOpcode = 1000
-
-// TestFUSECommunication tests that the communication layer between the Sentry and the
-// FUSE server daemon works as expected.
-func TestFUSECommunication(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
- creds := auth.CredentialsFromContext(s.Ctx)
-
- // Create test cases with different number of concurrent clients and servers.
- testCases := []struct {
- Name string
- NumClients int
- NumServers int
- MaxActiveRequests uint64
- }{
- {
- Name: "SingleClientSingleServer",
- NumClients: 1,
- NumServers: 1,
- MaxActiveRequests: maxActiveRequestsDefault,
- },
- {
- Name: "SingleClientMultipleServers",
- NumClients: 1,
- NumServers: 10,
- MaxActiveRequests: maxActiveRequestsDefault,
- },
- {
- Name: "MultipleClientsSingleServer",
- NumClients: 10,
- NumServers: 1,
- MaxActiveRequests: maxActiveRequestsDefault,
- },
- {
- Name: "MultipleClientsMultipleServers",
- NumClients: 10,
- NumServers: 10,
- MaxActiveRequests: maxActiveRequestsDefault,
- },
- {
- Name: "RequestCapacityFull",
- NumClients: 10,
- NumServers: 1,
- MaxActiveRequests: 1,
- },
- {
- Name: "RequestCapacityContinuouslyFull",
- NumClients: 100,
- NumServers: 2,
- MaxActiveRequests: 2,
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.Name, func(t *testing.T) {
- conn, fd, err := newTestConnection(s, k, testCase.MaxActiveRequests)
- if err != nil {
- t.Fatalf("newTestConnection: %v", err)
- }
-
- clientsDone := make([]chan struct{}, testCase.NumClients)
- serversDone := make([]chan struct{}, testCase.NumServers)
- serversKill := make([]chan struct{}, testCase.NumServers)
-
- // FUSE clients.
- for i := 0; i < testCase.NumClients; i++ {
- clientsDone[i] = make(chan struct{})
- go func(i int) {
- fuseClientRun(t, s, k, conn, creds, uint32(i), uint64(i), clientsDone[i])
- }(i)
- }
-
- // FUSE servers.
- for j := 0; j < testCase.NumServers; j++ {
- serversDone[j] = make(chan struct{})
- serversKill[j] = make(chan struct{}, 1) // The kill command shouldn't block.
- go func(j int) {
- fuseServerRun(t, s, k, fd, serversDone[j], serversKill[j])
- }(j)
- }
-
- // Tear down.
- //
- // Make sure all the clients are done.
- for i := 0; i < testCase.NumClients; i++ {
- <-clientsDone[i]
- }
-
- // Kill any server that is potentially waiting.
- for j := 0; j < testCase.NumServers; j++ {
- serversKill[j] <- struct{}{}
- }
-
- // Make sure all the servers are done.
- for j := 0; j < testCase.NumServers; j++ {
- <-serversDone[j]
- }
- })
- }
-}
-
-// CallTest makes a request to the server and blocks the invoking
-// goroutine until a server responds with a response. Doesn't block
-// a kernel.Task. Analogous to Connection.Call but used for testing.
-func CallTest(conn *connection, t *kernel.Task, r *Request, i uint32) (*Response, error) {
- conn.fd.mu.Lock()
-
- // Wait until we're certain that a new request can be processed.
- for conn.fd.numActiveRequests == conn.fd.fs.opts.maxActiveRequests {
- conn.fd.mu.Unlock()
- select {
- case <-conn.fd.fullQueueCh:
- }
- conn.fd.mu.Lock()
- }
-
- fut, err := conn.callFutureLocked(t, r) // No task given.
- conn.fd.mu.Unlock()
-
- if err != nil {
- return nil, err
- }
-
- // Resolve the response.
- //
- // Block without a task.
- select {
- case <-fut.ch:
- }
-
- // A response is ready. Resolve and return it.
- return fut.getResponse(), nil
-}
-
-// ReadTest is analogous to vfs.FileDescription.Read and reads from the FUSE
-// device. However, it does so by - not blocking the task that is calling - and
-// instead just waits on a channel. The behaviour is essentially the same as
-// DeviceFD.Read except it guarantees that the task is not blocked.
-func ReadTest(serverTask *kernel.Task, fd *vfs.FileDescription, inIOseq usermem.IOSequence, killServer chan struct{}) (int64, bool, error) {
- var err error
- var n, total int64
-
- dev := fd.Impl().(*DeviceFD)
-
- // Register for notifications.
- w, ch := waiter.NewChannelEntry(nil)
- dev.EventRegister(&w, waiter.ReadableEvents)
- for {
- // Issue the request and break out if it completes with anything other than
- // "would block".
- n, err = dev.Read(serverTask, inIOseq, vfs.ReadOptions{})
- total += n
- if err != linuxerr.ErrWouldBlock {
- break
- }
-
- // Wait for a notification that we should retry.
- // Emulate the blocking for when no requests are available
- select {
- case <-ch:
- case <-killServer:
- // Server killed by the main program.
- return 0, true, nil
- }
- }
-
- dev.EventUnregister(&w)
- return total, false, err
-}
-
-// fuseClientRun emulates all the actions of a normal FUSE request. It creates
-// a header, a payload, calls the server, waits for the response, and processes
-// the response.
-func fuseClientRun(t *testing.T, s *testutil.System, k *kernel.Kernel, conn *connection, creds *auth.Credentials, pid uint32, inode uint64, clientDone chan struct{}) {
- defer func() { clientDone <- struct{}{} }()
-
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- clientTask, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("fuse-client-%v", pid), tc, s.MntNs, s.Root, s.Root)
- if err != nil {
- t.Fatal(err)
- }
- testObj := &testPayload{
- data: rand.Uint32(),
- }
-
- req := conn.NewRequest(creds, pid, inode, echoTestOpcode, testObj)
-
- // Queue up a request.
- // Analogous to Call except it doesn't block on the task.
- resp, err := CallTest(conn, clientTask, req, pid)
- if err != nil {
- t.Fatalf("CallTaskNonBlock failed: %v", err)
- }
-
- if err = resp.Error(); err != nil {
- t.Fatalf("Server responded with an error: %v", err)
- }
-
- var respTestPayload testPayload
- if err := resp.UnmarshalPayload(&respTestPayload); err != nil {
- t.Fatalf("Unmarshalling payload error: %v", err)
- }
-
- if resp.hdr.Unique != req.hdr.Unique {
- t.Fatalf("got response for another request. Expected response for req %v but got response for req %v",
- req.hdr.Unique, resp.hdr.Unique)
- }
-
- if respTestPayload.data != testObj.data {
- t.Fatalf("read incorrect data. Data expected: %v, but got %v", testObj.data, respTestPayload.data)
- }
-
-}
-
-// fuseServerRun creates a task and emulates all the actions of a simple FUSE server
-// that simply reads a request and echos the same struct back as a response using the
-// appropriate headers.
-func fuseServerRun(t *testing.T, s *testutil.System, k *kernel.Kernel, fd *vfs.FileDescription, serverDone, killServer chan struct{}) {
- defer func() { serverDone <- struct{}{} }()
-
- // Create the tasks that the server will be using.
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- var readPayload testPayload
-
- serverTask, err := testutil.CreateTask(s.Ctx, "fuse-server", tc, s.MntNs, s.Root, s.Root)
- if err != nil {
- t.Fatal(err)
- }
-
- // Read the request.
- for {
- inHdrLen := uint32((*linux.FUSEHeaderIn)(nil).SizeBytes())
- payloadLen := uint32(readPayload.SizeBytes())
-
- // The raed buffer must meet some certain size criteria.
- buffSize := inHdrLen + payloadLen
- if buffSize < linux.FUSE_MIN_READ_BUFFER {
- buffSize = linux.FUSE_MIN_READ_BUFFER
- }
- inBuf := make([]byte, buffSize)
- inIOseq := usermem.BytesIOSequence(inBuf)
-
- n, serverKilled, err := ReadTest(serverTask, fd, inIOseq, killServer)
- if err != nil {
- t.Fatalf("Read failed :%v", err)
- }
-
- // Server should shut down. No new requests are going to be made.
- if serverKilled {
- break
- }
-
- if n <= 0 {
- t.Fatalf("Read read no bytes")
- }
-
- var readFUSEHeaderIn linux.FUSEHeaderIn
- readFUSEHeaderIn.UnmarshalUnsafe(inBuf[:inHdrLen])
- readPayload.UnmarshalUnsafe(inBuf[inHdrLen : inHdrLen+payloadLen])
-
- if readFUSEHeaderIn.Opcode != echoTestOpcode {
- t.Fatalf("read incorrect data. Header: %v, Payload: %v", readFUSEHeaderIn, readPayload)
- }
-
- // Write the response.
- outHdrLen := uint32((*linux.FUSEHeaderOut)(nil).SizeBytes())
- outBuf := make([]byte, outHdrLen+payloadLen)
- outHeader := linux.FUSEHeaderOut{
- Len: outHdrLen + payloadLen,
- Error: 0,
- Unique: readFUSEHeaderIn.Unique,
- }
-
- // Echo the payload back.
- outHeader.MarshalUnsafe(outBuf[:outHdrLen])
- readPayload.MarshalUnsafe(outBuf[outHdrLen:])
- outIOseq := usermem.BytesIOSequence(outBuf)
-
- _, err = fd.Write(s.Ctx, outIOseq, vfs.WriteOptions{})
- if err != nil {
- t.Fatalf("Write failed :%v", err)
- }
- }
-}
diff --git a/pkg/sentry/fsimpl/fuse/fuse_state_autogen.go b/pkg/sentry/fsimpl/fuse/fuse_state_autogen.go
new file mode 100644
index 000000000..711a6c425
--- /dev/null
+++ b/pkg/sentry/fsimpl/fuse/fuse_state_autogen.go
@@ -0,0 +1,562 @@
+// automatically generated by stateify.
+
+package fuse
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (conn *connection) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.connection"
+}
+
+func (conn *connection) StateFields() []string {
+ return []string{
+ "fd",
+ "attributeVersion",
+ "initialized",
+ "initializedChan",
+ "connected",
+ "connInitError",
+ "connInitSuccess",
+ "aborted",
+ "numWaiting",
+ "asyncNum",
+ "asyncCongestionThreshold",
+ "asyncNumMax",
+ "maxRead",
+ "maxWrite",
+ "maxPages",
+ "minor",
+ "atomicOTrunc",
+ "asyncRead",
+ "writebackCache",
+ "bigWrites",
+ "dontMask",
+ "noOpen",
+ }
+}
+
+func (conn *connection) beforeSave() {}
+
+// +checklocksignore
+func (conn *connection) StateSave(stateSinkObject state.Sink) {
+ conn.beforeSave()
+ var initializedChanValue bool
+ initializedChanValue = conn.saveInitializedChan()
+ stateSinkObject.SaveValue(3, initializedChanValue)
+ stateSinkObject.Save(0, &conn.fd)
+ stateSinkObject.Save(1, &conn.attributeVersion)
+ stateSinkObject.Save(2, &conn.initialized)
+ stateSinkObject.Save(4, &conn.connected)
+ stateSinkObject.Save(5, &conn.connInitError)
+ stateSinkObject.Save(6, &conn.connInitSuccess)
+ stateSinkObject.Save(7, &conn.aborted)
+ stateSinkObject.Save(8, &conn.numWaiting)
+ stateSinkObject.Save(9, &conn.asyncNum)
+ stateSinkObject.Save(10, &conn.asyncCongestionThreshold)
+ stateSinkObject.Save(11, &conn.asyncNumMax)
+ stateSinkObject.Save(12, &conn.maxRead)
+ stateSinkObject.Save(13, &conn.maxWrite)
+ stateSinkObject.Save(14, &conn.maxPages)
+ stateSinkObject.Save(15, &conn.minor)
+ stateSinkObject.Save(16, &conn.atomicOTrunc)
+ stateSinkObject.Save(17, &conn.asyncRead)
+ stateSinkObject.Save(18, &conn.writebackCache)
+ stateSinkObject.Save(19, &conn.bigWrites)
+ stateSinkObject.Save(20, &conn.dontMask)
+ stateSinkObject.Save(21, &conn.noOpen)
+}
+
+func (conn *connection) afterLoad() {}
+
+// +checklocksignore
+func (conn *connection) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &conn.fd)
+ stateSourceObject.Load(1, &conn.attributeVersion)
+ stateSourceObject.Load(2, &conn.initialized)
+ stateSourceObject.Load(4, &conn.connected)
+ stateSourceObject.Load(5, &conn.connInitError)
+ stateSourceObject.Load(6, &conn.connInitSuccess)
+ stateSourceObject.Load(7, &conn.aborted)
+ stateSourceObject.Load(8, &conn.numWaiting)
+ stateSourceObject.Load(9, &conn.asyncNum)
+ stateSourceObject.Load(10, &conn.asyncCongestionThreshold)
+ stateSourceObject.Load(11, &conn.asyncNumMax)
+ stateSourceObject.Load(12, &conn.maxRead)
+ stateSourceObject.Load(13, &conn.maxWrite)
+ stateSourceObject.Load(14, &conn.maxPages)
+ stateSourceObject.Load(15, &conn.minor)
+ stateSourceObject.Load(16, &conn.atomicOTrunc)
+ stateSourceObject.Load(17, &conn.asyncRead)
+ stateSourceObject.Load(18, &conn.writebackCache)
+ stateSourceObject.Load(19, &conn.bigWrites)
+ stateSourceObject.Load(20, &conn.dontMask)
+ stateSourceObject.Load(21, &conn.noOpen)
+ stateSourceObject.LoadValue(3, new(bool), func(y interface{}) { conn.loadInitializedChan(y.(bool)) })
+}
+
+func (f *fuseDevice) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.fuseDevice"
+}
+
+func (f *fuseDevice) StateFields() []string {
+ return []string{}
+}
+
+func (f *fuseDevice) beforeSave() {}
+
+// +checklocksignore
+func (f *fuseDevice) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+}
+
+func (f *fuseDevice) afterLoad() {}
+
+// +checklocksignore
+func (f *fuseDevice) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fd *DeviceFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.DeviceFD"
+}
+
+func (fd *DeviceFD) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "DentryMetadataFileDescriptionImpl",
+ "NoLockFD",
+ "nextOpID",
+ "queue",
+ "numActiveRequests",
+ "completions",
+ "writeCursor",
+ "writeBuf",
+ "writeCursorFR",
+ "waitQueue",
+ "fullQueueCh",
+ "fs",
+ }
+}
+
+func (fd *DeviceFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *DeviceFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ var fullQueueChValue int
+ fullQueueChValue = fd.saveFullQueueCh()
+ stateSinkObject.SaveValue(12, fullQueueChValue)
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.DentryMetadataFileDescriptionImpl)
+ stateSinkObject.Save(3, &fd.NoLockFD)
+ stateSinkObject.Save(4, &fd.nextOpID)
+ stateSinkObject.Save(5, &fd.queue)
+ stateSinkObject.Save(6, &fd.numActiveRequests)
+ stateSinkObject.Save(7, &fd.completions)
+ stateSinkObject.Save(8, &fd.writeCursor)
+ stateSinkObject.Save(9, &fd.writeBuf)
+ stateSinkObject.Save(10, &fd.writeCursorFR)
+ stateSinkObject.Save(11, &fd.waitQueue)
+ stateSinkObject.Save(13, &fd.fs)
+}
+
+func (fd *DeviceFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *DeviceFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.DentryMetadataFileDescriptionImpl)
+ stateSourceObject.Load(3, &fd.NoLockFD)
+ stateSourceObject.Load(4, &fd.nextOpID)
+ stateSourceObject.Load(5, &fd.queue)
+ stateSourceObject.Load(6, &fd.numActiveRequests)
+ stateSourceObject.Load(7, &fd.completions)
+ stateSourceObject.Load(8, &fd.writeCursor)
+ stateSourceObject.Load(9, &fd.writeBuf)
+ stateSourceObject.Load(10, &fd.writeCursorFR)
+ stateSourceObject.Load(11, &fd.waitQueue)
+ stateSourceObject.Load(13, &fd.fs)
+ stateSourceObject.LoadValue(12, new(int), func(y interface{}) { fd.loadFullQueueCh(y.(int)) })
+}
+
+func (fsType *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.FilesystemType"
+}
+
+func (fsType *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fsType *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fsType *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fsType.beforeSave()
+}
+
+func (fsType *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fsType *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (f *filesystemOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.filesystemOptions"
+}
+
+func (f *filesystemOptions) StateFields() []string {
+ return []string{
+ "mopts",
+ "uid",
+ "gid",
+ "rootMode",
+ "maxActiveRequests",
+ "maxRead",
+ "defaultPermissions",
+ "allowOther",
+ }
+}
+
+func (f *filesystemOptions) beforeSave() {}
+
+// +checklocksignore
+func (f *filesystemOptions) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.mopts)
+ stateSinkObject.Save(1, &f.uid)
+ stateSinkObject.Save(2, &f.gid)
+ stateSinkObject.Save(3, &f.rootMode)
+ stateSinkObject.Save(4, &f.maxActiveRequests)
+ stateSinkObject.Save(5, &f.maxRead)
+ stateSinkObject.Save(6, &f.defaultPermissions)
+ stateSinkObject.Save(7, &f.allowOther)
+}
+
+func (f *filesystemOptions) afterLoad() {}
+
+// +checklocksignore
+func (f *filesystemOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.mopts)
+ stateSourceObject.Load(1, &f.uid)
+ stateSourceObject.Load(2, &f.gid)
+ stateSourceObject.Load(3, &f.rootMode)
+ stateSourceObject.Load(4, &f.maxActiveRequests)
+ stateSourceObject.Load(5, &f.maxRead)
+ stateSourceObject.Load(6, &f.defaultPermissions)
+ stateSourceObject.Load(7, &f.allowOther)
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ "conn",
+ "opts",
+ "umounted",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+ stateSinkObject.Save(2, &fs.conn)
+ stateSinkObject.Save(3, &fs.opts)
+ stateSinkObject.Save(4, &fs.umounted)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+ stateSourceObject.Load(2, &fs.conn)
+ stateSourceObject.Load(3, &fs.opts)
+ stateSourceObject.Load(4, &fs.umounted)
+}
+
+func (i *inode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.inode"
+}
+
+func (i *inode) StateFields() []string {
+ return []string{
+ "inodeRefs",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "OrderedChildren",
+ "fs",
+ "metadataMu",
+ "nodeID",
+ "locks",
+ "size",
+ "attributeVersion",
+ "attributeTime",
+ "version",
+ "link",
+ }
+}
+
+func (i *inode) beforeSave() {}
+
+// +checklocksignore
+func (i *inode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.inodeRefs)
+ stateSinkObject.Save(1, &i.InodeAlwaysValid)
+ stateSinkObject.Save(2, &i.InodeAttrs)
+ stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(4, &i.InodeNotSymlink)
+ stateSinkObject.Save(5, &i.OrderedChildren)
+ stateSinkObject.Save(6, &i.fs)
+ stateSinkObject.Save(7, &i.metadataMu)
+ stateSinkObject.Save(8, &i.nodeID)
+ stateSinkObject.Save(9, &i.locks)
+ stateSinkObject.Save(10, &i.size)
+ stateSinkObject.Save(11, &i.attributeVersion)
+ stateSinkObject.Save(12, &i.attributeTime)
+ stateSinkObject.Save(13, &i.version)
+ stateSinkObject.Save(14, &i.link)
+}
+
+func (i *inode) afterLoad() {}
+
+// +checklocksignore
+func (i *inode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.inodeRefs)
+ stateSourceObject.Load(1, &i.InodeAlwaysValid)
+ stateSourceObject.Load(2, &i.InodeAttrs)
+ stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(4, &i.InodeNotSymlink)
+ stateSourceObject.Load(5, &i.OrderedChildren)
+ stateSourceObject.Load(6, &i.fs)
+ stateSourceObject.Load(7, &i.metadataMu)
+ stateSourceObject.Load(8, &i.nodeID)
+ stateSourceObject.Load(9, &i.locks)
+ stateSourceObject.Load(10, &i.size)
+ stateSourceObject.Load(11, &i.attributeVersion)
+ stateSourceObject.Load(12, &i.attributeTime)
+ stateSourceObject.Load(13, &i.version)
+ stateSourceObject.Load(14, &i.link)
+}
+
+func (r *inodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.inodeRefs"
+}
+
+func (r *inodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *inodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *inodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *inodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (l *requestList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.requestList"
+}
+
+func (l *requestList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *requestList) beforeSave() {}
+
+// +checklocksignore
+func (l *requestList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *requestList) afterLoad() {}
+
+// +checklocksignore
+func (l *requestList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *requestEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.requestEntry"
+}
+
+func (e *requestEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *requestEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *requestEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *requestEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *requestEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (r *Request) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.Request"
+}
+
+func (r *Request) StateFields() []string {
+ return []string{
+ "requestEntry",
+ "id",
+ "hdr",
+ "data",
+ "payload",
+ "async",
+ "noReply",
+ }
+}
+
+func (r *Request) beforeSave() {}
+
+// +checklocksignore
+func (r *Request) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.requestEntry)
+ stateSinkObject.Save(1, &r.id)
+ stateSinkObject.Save(2, &r.hdr)
+ stateSinkObject.Save(3, &r.data)
+ stateSinkObject.Save(4, &r.payload)
+ stateSinkObject.Save(5, &r.async)
+ stateSinkObject.Save(6, &r.noReply)
+}
+
+func (r *Request) afterLoad() {}
+
+// +checklocksignore
+func (r *Request) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.requestEntry)
+ stateSourceObject.Load(1, &r.id)
+ stateSourceObject.Load(2, &r.hdr)
+ stateSourceObject.Load(3, &r.data)
+ stateSourceObject.Load(4, &r.payload)
+ stateSourceObject.Load(5, &r.async)
+ stateSourceObject.Load(6, &r.noReply)
+}
+
+func (f *futureResponse) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.futureResponse"
+}
+
+func (f *futureResponse) StateFields() []string {
+ return []string{
+ "opcode",
+ "ch",
+ "hdr",
+ "data",
+ "async",
+ }
+}
+
+func (f *futureResponse) beforeSave() {}
+
+// +checklocksignore
+func (f *futureResponse) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.opcode)
+ stateSinkObject.Save(1, &f.ch)
+ stateSinkObject.Save(2, &f.hdr)
+ stateSinkObject.Save(3, &f.data)
+ stateSinkObject.Save(4, &f.async)
+}
+
+func (f *futureResponse) afterLoad() {}
+
+// +checklocksignore
+func (f *futureResponse) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.opcode)
+ stateSourceObject.Load(1, &f.ch)
+ stateSourceObject.Load(2, &f.hdr)
+ stateSourceObject.Load(3, &f.data)
+ stateSourceObject.Load(4, &f.async)
+}
+
+func (r *Response) StateTypeName() string {
+ return "pkg/sentry/fsimpl/fuse.Response"
+}
+
+func (r *Response) StateFields() []string {
+ return []string{
+ "opcode",
+ "hdr",
+ "data",
+ }
+}
+
+func (r *Response) beforeSave() {}
+
+// +checklocksignore
+func (r *Response) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.opcode)
+ stateSinkObject.Save(1, &r.hdr)
+ stateSinkObject.Save(2, &r.data)
+}
+
+func (r *Response) afterLoad() {}
+
+// +checklocksignore
+func (r *Response) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.opcode)
+ stateSourceObject.Load(1, &r.hdr)
+ stateSourceObject.Load(2, &r.data)
+}
+
+func init() {
+ state.Register((*connection)(nil))
+ state.Register((*fuseDevice)(nil))
+ state.Register((*DeviceFD)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystemOptions)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*inode)(nil))
+ state.Register((*inodeRefs)(nil))
+ state.Register((*requestList)(nil))
+ state.Register((*requestEntry)(nil))
+ state.Register((*Request)(nil))
+ state.Register((*futureResponse)(nil))
+ state.Register((*Response)(nil))
+}
diff --git a/pkg/sentry/fsimpl/fuse/inode_refs.go b/pkg/sentry/fsimpl/fuse/inode_refs.go
new file mode 100644
index 000000000..74489cf5e
--- /dev/null
+++ b/pkg/sentry/fsimpl/fuse/inode_refs.go
@@ -0,0 +1,140 @@
+package fuse
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const inodeenableLogging = false
+
+// obj 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 inodeobj *inode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type inodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *inodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *inodeRefs) RefType() string {
+ return fmt.Sprintf("%T", inodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *inodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *inodeRefs) LogRefs() bool {
+ return inodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *inodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *inodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if inodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *inodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if inodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *inodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if inodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *inodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/fuse/request_list.go b/pkg/sentry/fsimpl/fuse/request_list.go
new file mode 100644
index 000000000..060ac4a3f
--- /dev/null
+++ b/pkg/sentry/fsimpl/fuse/request_list.go
@@ -0,0 +1,221 @@
+package fuse
+
+// 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 requestElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (requestElementMapper) linkerFor(elem *Request) *Request { 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 requestList struct {
+ head *Request
+ tail *Request
+}
+
+// Reset resets list l to the empty state.
+func (l *requestList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *requestList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *requestList) Front() *Request {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *requestList) Back() *Request {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *requestList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (requestElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *requestList) PushFront(e *Request) {
+ linker := requestElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ requestElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *requestList) PushBack(e *Request) {
+ linker := requestElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ requestElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *requestList) PushBackList(m *requestList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ requestElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ requestElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *requestList) InsertAfter(b, e *Request) {
+ bLinker := requestElementMapper{}.linkerFor(b)
+ eLinker := requestElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ requestElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *requestList) InsertBefore(a, e *Request) {
+ aLinker := requestElementMapper{}.linkerFor(a)
+ eLinker := requestElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ requestElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *requestList) Remove(e *Request) {
+ linker := requestElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ requestElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ requestElementMapper{}.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 requestEntry struct {
+ next *Request
+ prev *Request
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *requestEntry) Next() *Request {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *requestEntry) Prev() *Request {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *requestEntry) SetNext(elem *Request) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *requestEntry) SetPrev(elem *Request) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/fuse/utils_test.go b/pkg/sentry/fsimpl/fuse/utils_test.go
deleted file mode 100644
index b0bab0066..000000000
--- a/pkg/sentry/fsimpl/fuse/utils_test.go
+++ /dev/null
@@ -1,127 +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 fuse
-
-import (
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/marshal"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-
- "gvisor.dev/gvisor/pkg/hostarch"
-)
-
-func setup(t *testing.T) *testutil.System {
- k, err := testutil.Boot()
- if err != nil {
- t.Fatalf("Error creating kernel: %v", err)
- }
-
- ctx := k.SupervisorContext()
- creds := auth.CredentialsFromContext(ctx)
-
- k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserList: true,
- AllowUserMount: true,
- })
-
- mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
- if err != nil {
- t.Fatalf("NewMountNamespace(): %v", err)
- }
-
- return testutil.NewSystem(ctx, t, k.VFS(), mntns)
-}
-
-// newTestConnection creates a fuse connection that the sentry can communicate with
-// and the FD for the server to communicate with.
-func newTestConnection(system *testutil.System, k *kernel.Kernel, maxActiveRequests uint64) (*connection, *vfs.FileDescription, error) {
- fuseDev := &DeviceFD{}
-
- vd := system.VFS.NewAnonVirtualDentry("fuse")
- defer vd.DecRef(system.Ctx)
- if err := fuseDev.vfsfd.Init(fuseDev, linux.O_RDWR, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{}); err != nil {
- return nil, nil, err
- }
-
- fsopts := filesystemOptions{
- maxActiveRequests: maxActiveRequests,
- }
- fs, err := newFUSEFilesystem(system.Ctx, system.VFS, &FilesystemType{}, fuseDev, 0, &fsopts)
- if err != nil {
- return nil, nil, err
- }
- return fs.conn, &fuseDev.vfsfd, nil
-}
-
-type testPayload struct {
- marshal.StubMarshallable
- data uint32
-}
-
-// SizeBytes implements marshal.Marshallable.SizeBytes.
-func (t *testPayload) SizeBytes() int {
- return 4
-}
-
-// MarshalBytes implements marshal.Marshallable.MarshalBytes.
-func (t *testPayload) MarshalBytes(dst []byte) {
- hostarch.ByteOrder.PutUint32(dst[:4], t.data)
-}
-
-// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
-func (t *testPayload) UnmarshalBytes(src []byte) {
- *t = testPayload{data: hostarch.ByteOrder.Uint32(src[:4])}
-}
-
-// Packed implements marshal.Marshallable.Packed.
-func (t *testPayload) Packed() bool {
- return true
-}
-
-// MarshalUnsafe implements marshal.Marshallable.MarshalUnsafe.
-func (t *testPayload) MarshalUnsafe(dst []byte) {
- t.MarshalBytes(dst)
-}
-
-// UnmarshalUnsafe implements marshal.Marshallable.UnmarshalUnsafe.
-func (t *testPayload) UnmarshalUnsafe(src []byte) {
- t.UnmarshalBytes(src)
-}
-
-// CopyOutN implements marshal.Marshallable.CopyOutN.
-func (t *testPayload) CopyOutN(task marshal.CopyContext, addr hostarch.Addr, limit int) (int, error) {
- panic("not implemented")
-}
-
-// CopyOut implements marshal.Marshallable.CopyOut.
-func (t *testPayload) CopyOut(task marshal.CopyContext, addr hostarch.Addr) (int, error) {
- panic("not implemented")
-}
-
-// CopyIn implements marshal.Marshallable.CopyIn.
-func (t *testPayload) CopyIn(task marshal.CopyContext, addr hostarch.Addr) (int, error) {
- panic("not implemented")
-}
-
-// WriteTo implements io.WriterTo.WriteTo.
-func (t *testPayload) WriteTo(w io.Writer) (int64, error) {
- panic("not implemented")
-}
diff --git a/pkg/sentry/fsimpl/gofer/BUILD b/pkg/sentry/fsimpl/gofer/BUILD
deleted file mode 100644
index 509dd0e1a..000000000
--- a/pkg/sentry/fsimpl/gofer/BUILD
+++ /dev/null
@@ -1,101 +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 = "dentry_list",
- out = "dentry_list.go",
- package = "gofer",
- prefix = "dentry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*dentry",
- "Linker": "*dentry",
- },
-)
-
-go_template_instance(
- name = "fstree",
- out = "fstree.go",
- package = "gofer",
- prefix = "generic",
- template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
- types = {
- "Dentry": "dentry",
- },
-)
-
-go_library(
- name = "gofer",
- srcs = [
- "dentry_list.go",
- "directory.go",
- "filesystem.go",
- "fstree.go",
- "gofer.go",
- "handle.go",
- "host_named_pipe.go",
- "p9file.go",
- "regular_file.go",
- "revalidate.go",
- "save_restore.go",
- "socket.go",
- "special_file.go",
- "symlink.go",
- "time.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/lisafs",
- "//pkg/log",
- "//pkg/marshal",
- "//pkg/marshal/primitive",
- "//pkg/metric",
- "//pkg/p9",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fsimpl/host",
- "//pkg/sentry/fsmetric",
- "//pkg/sentry/hostfd",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/pipe",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/syserr",
- "//pkg/unet",
- "//pkg/usermem",
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "gofer_test",
- srcs = ["gofer_test.go"],
- library = ":gofer",
- deps = [
- "//pkg/p9",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/pgalloc",
- ],
-)
diff --git a/pkg/sentry/fsimpl/gofer/dentry_list.go b/pkg/sentry/fsimpl/gofer/dentry_list.go
new file mode 100644
index 000000000..2e43b8e02
--- /dev/null
+++ b/pkg/sentry/fsimpl/gofer/dentry_list.go
@@ -0,0 +1,221 @@
+package gofer
+
+// 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 dentryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (dentryElementMapper) linkerFor(elem *dentry) *dentry { 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 dentryList struct {
+ head *dentry
+ tail *dentry
+}
+
+// Reset resets list l to the empty state.
+func (l *dentryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *dentryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Front() *dentry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Back() *dentry {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *dentryList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (dentryElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *dentryList) PushFront(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ dentryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *dentryList) PushBack(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *dentryList) PushBackList(m *dentryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ dentryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *dentryList) InsertAfter(b, e *dentry) {
+ bLinker := dentryElementMapper{}.linkerFor(b)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ dentryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *dentryList) InsertBefore(a, e *dentry) {
+ aLinker := dentryElementMapper{}.linkerFor(a)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ dentryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *dentryList) Remove(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ dentryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ dentryElementMapper{}.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 dentryEntry struct {
+ next *dentry
+ prev *dentry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Next() *dentry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Prev() *dentry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetNext(elem *dentry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetPrev(elem *dentry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/gofer/fstree.go b/pkg/sentry/fsimpl/gofer/fstree.go
new file mode 100644
index 000000000..6e43d4a4b
--- /dev/null
+++ b/pkg/sentry/fsimpl/gofer/fstree.go
@@ -0,0 +1,55 @@
+package gofer
+
+import (
+ "gvisor.dev/gvisor/pkg/fspath"
+ "gvisor.dev/gvisor/pkg/sentry/vfs"
+)
+
+// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is
+// either d2's parent or an ancestor of d2's parent.
+func genericIsAncestorDentry(d, d2 *dentry) bool {
+ for d2 != nil {
+ if d2.parent == d {
+ return true
+ }
+ if d2.parent == d2 {
+ return false
+ }
+ d2 = d2.parent
+ }
+ return false
+}
+
+// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d.
+func genericParentOrSelf(d *dentry) *dentry {
+ if d.parent != nil {
+ return d.parent
+ }
+ return d
+}
+
+// PrependPath is a generic implementation of FilesystemImpl.PrependPath().
+func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *dentry, b *fspath.Builder) error {
+ for {
+ if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() {
+ return vfs.PrependPathAtVFSRootError{}
+ }
+ if mnt != nil && &d.vfsd == mnt.Root() {
+ return nil
+ }
+ if d.parent == nil {
+ return vfs.PrependPathAtNonMountRootError{}
+ }
+ b.PrependComponent(d.name)
+ d = d.parent
+ }
+}
+
+// DebugPathname returns a pathname to d relative to its filesystem root.
+// DebugPathname does not correspond to any Linux function; it's used to
+// generate dentry pathnames for debugging.
+func genericDebugPathname(d *dentry) string {
+ var b fspath.Builder
+ _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b)
+ return b.String()
+}
diff --git a/pkg/sentry/fsimpl/gofer/gofer_state_autogen.go b/pkg/sentry/fsimpl/gofer/gofer_state_autogen.go
new file mode 100644
index 000000000..0e99b2c45
--- /dev/null
+++ b/pkg/sentry/fsimpl/gofer/gofer_state_autogen.go
@@ -0,0 +1,652 @@
+// automatically generated by stateify.
+
+package gofer
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *dentryList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.dentryList"
+}
+
+func (l *dentryList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *dentryList) beforeSave() {}
+
+// +checklocksignore
+func (l *dentryList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *dentryList) afterLoad() {}
+
+// +checklocksignore
+func (l *dentryList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *dentryEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.dentryEntry"
+}
+
+func (e *dentryEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *dentryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *dentryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (fd *directoryFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.directoryFD"
+}
+
+func (fd *directoryFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "DirectoryFileDescriptionDefaultImpl",
+ "off",
+ "dirents",
+ }
+}
+
+func (fd *directoryFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *directoryFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.off)
+ stateSinkObject.Save(3, &fd.dirents)
+}
+
+func (fd *directoryFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *directoryFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.off)
+ stateSourceObject.Load(3, &fd.dirents)
+}
+
+func (fstype *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.FilesystemType"
+}
+
+func (fstype *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fstype *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fstype.beforeSave()
+}
+
+func (fstype *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "vfsfs",
+ "mfp",
+ "opts",
+ "iopts",
+ "clock",
+ "devMinor",
+ "root",
+ "cachedDentries",
+ "cachedDentriesLen",
+ "syncableDentries",
+ "specialFileFDs",
+ "lastIno",
+ "savedDentryRW",
+ "released",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.vfsfs)
+ stateSinkObject.Save(1, &fs.mfp)
+ stateSinkObject.Save(2, &fs.opts)
+ stateSinkObject.Save(3, &fs.iopts)
+ stateSinkObject.Save(4, &fs.clock)
+ stateSinkObject.Save(5, &fs.devMinor)
+ stateSinkObject.Save(6, &fs.root)
+ stateSinkObject.Save(7, &fs.cachedDentries)
+ stateSinkObject.Save(8, &fs.cachedDentriesLen)
+ stateSinkObject.Save(9, &fs.syncableDentries)
+ stateSinkObject.Save(10, &fs.specialFileFDs)
+ stateSinkObject.Save(11, &fs.lastIno)
+ stateSinkObject.Save(12, &fs.savedDentryRW)
+ stateSinkObject.Save(13, &fs.released)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.vfsfs)
+ stateSourceObject.Load(1, &fs.mfp)
+ stateSourceObject.Load(2, &fs.opts)
+ stateSourceObject.Load(3, &fs.iopts)
+ stateSourceObject.Load(4, &fs.clock)
+ stateSourceObject.Load(5, &fs.devMinor)
+ stateSourceObject.Load(6, &fs.root)
+ stateSourceObject.Load(7, &fs.cachedDentries)
+ stateSourceObject.Load(8, &fs.cachedDentriesLen)
+ stateSourceObject.Load(9, &fs.syncableDentries)
+ stateSourceObject.Load(10, &fs.specialFileFDs)
+ stateSourceObject.Load(11, &fs.lastIno)
+ stateSourceObject.Load(12, &fs.savedDentryRW)
+ stateSourceObject.Load(13, &fs.released)
+}
+
+func (f *filesystemOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.filesystemOptions"
+}
+
+func (f *filesystemOptions) StateFields() []string {
+ return []string{
+ "fd",
+ "aname",
+ "interop",
+ "dfltuid",
+ "dfltgid",
+ "msize",
+ "version",
+ "maxCachedDentries",
+ "forcePageCache",
+ "limitHostFDTranslation",
+ "overlayfsStaleRead",
+ "regularFilesUseSpecialFileFD",
+ "lisaEnabled",
+ }
+}
+
+func (f *filesystemOptions) beforeSave() {}
+
+// +checklocksignore
+func (f *filesystemOptions) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.fd)
+ stateSinkObject.Save(1, &f.aname)
+ stateSinkObject.Save(2, &f.interop)
+ stateSinkObject.Save(3, &f.dfltuid)
+ stateSinkObject.Save(4, &f.dfltgid)
+ stateSinkObject.Save(5, &f.msize)
+ stateSinkObject.Save(6, &f.version)
+ stateSinkObject.Save(7, &f.maxCachedDentries)
+ stateSinkObject.Save(8, &f.forcePageCache)
+ stateSinkObject.Save(9, &f.limitHostFDTranslation)
+ stateSinkObject.Save(10, &f.overlayfsStaleRead)
+ stateSinkObject.Save(11, &f.regularFilesUseSpecialFileFD)
+ stateSinkObject.Save(12, &f.lisaEnabled)
+}
+
+func (f *filesystemOptions) afterLoad() {}
+
+// +checklocksignore
+func (f *filesystemOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.fd)
+ stateSourceObject.Load(1, &f.aname)
+ stateSourceObject.Load(2, &f.interop)
+ stateSourceObject.Load(3, &f.dfltuid)
+ stateSourceObject.Load(4, &f.dfltgid)
+ stateSourceObject.Load(5, &f.msize)
+ stateSourceObject.Load(6, &f.version)
+ stateSourceObject.Load(7, &f.maxCachedDentries)
+ stateSourceObject.Load(8, &f.forcePageCache)
+ stateSourceObject.Load(9, &f.limitHostFDTranslation)
+ stateSourceObject.Load(10, &f.overlayfsStaleRead)
+ stateSourceObject.Load(11, &f.regularFilesUseSpecialFileFD)
+ stateSourceObject.Load(12, &f.lisaEnabled)
+}
+
+func (i *InteropMode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.InteropMode"
+}
+
+func (i *InteropMode) StateFields() []string {
+ return nil
+}
+
+func (i *InternalFilesystemOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.InternalFilesystemOptions"
+}
+
+func (i *InternalFilesystemOptions) StateFields() []string {
+ return []string{
+ "UniqueID",
+ "LeakConnection",
+ "OpenSocketsByConnecting",
+ }
+}
+
+func (i *InternalFilesystemOptions) beforeSave() {}
+
+// +checklocksignore
+func (i *InternalFilesystemOptions) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.UniqueID)
+ stateSinkObject.Save(1, &i.LeakConnection)
+ stateSinkObject.Save(2, &i.OpenSocketsByConnecting)
+}
+
+func (i *InternalFilesystemOptions) afterLoad() {}
+
+// +checklocksignore
+func (i *InternalFilesystemOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.UniqueID)
+ stateSourceObject.Load(1, &i.LeakConnection)
+ stateSourceObject.Load(2, &i.OpenSocketsByConnecting)
+}
+
+func (i *inoKey) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.inoKey"
+}
+
+func (i *inoKey) StateFields() []string {
+ return []string{
+ "ino",
+ "devMinor",
+ "devMajor",
+ }
+}
+
+func (i *inoKey) beforeSave() {}
+
+// +checklocksignore
+func (i *inoKey) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.ino)
+ stateSinkObject.Save(1, &i.devMinor)
+ stateSinkObject.Save(2, &i.devMajor)
+}
+
+func (i *inoKey) afterLoad() {}
+
+// +checklocksignore
+func (i *inoKey) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.ino)
+ stateSourceObject.Load(1, &i.devMinor)
+ stateSourceObject.Load(2, &i.devMajor)
+}
+
+func (d *dentry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.dentry"
+}
+
+func (d *dentry) StateFields() []string {
+ return []string{
+ "vfsd",
+ "refs",
+ "fs",
+ "parent",
+ "name",
+ "qidPath",
+ "inoKey",
+ "deleted",
+ "cached",
+ "dentryEntry",
+ "children",
+ "syntheticChildren",
+ "dirents",
+ "ino",
+ "mode",
+ "uid",
+ "gid",
+ "blockSize",
+ "atime",
+ "mtime",
+ "ctime",
+ "btime",
+ "size",
+ "atimeDirty",
+ "mtimeDirty",
+ "nlink",
+ "mappings",
+ "cache",
+ "dirty",
+ "pf",
+ "haveTarget",
+ "target",
+ "endpoint",
+ "pipe",
+ "locks",
+ "watches",
+ }
+}
+
+// +checklocksignore
+func (d *dentry) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.vfsd)
+ stateSinkObject.Save(1, &d.refs)
+ stateSinkObject.Save(2, &d.fs)
+ stateSinkObject.Save(3, &d.parent)
+ stateSinkObject.Save(4, &d.name)
+ stateSinkObject.Save(5, &d.qidPath)
+ stateSinkObject.Save(6, &d.inoKey)
+ stateSinkObject.Save(7, &d.deleted)
+ stateSinkObject.Save(8, &d.cached)
+ stateSinkObject.Save(9, &d.dentryEntry)
+ stateSinkObject.Save(10, &d.children)
+ stateSinkObject.Save(11, &d.syntheticChildren)
+ stateSinkObject.Save(12, &d.dirents)
+ stateSinkObject.Save(13, &d.ino)
+ stateSinkObject.Save(14, &d.mode)
+ stateSinkObject.Save(15, &d.uid)
+ stateSinkObject.Save(16, &d.gid)
+ stateSinkObject.Save(17, &d.blockSize)
+ stateSinkObject.Save(18, &d.atime)
+ stateSinkObject.Save(19, &d.mtime)
+ stateSinkObject.Save(20, &d.ctime)
+ stateSinkObject.Save(21, &d.btime)
+ stateSinkObject.Save(22, &d.size)
+ stateSinkObject.Save(23, &d.atimeDirty)
+ stateSinkObject.Save(24, &d.mtimeDirty)
+ stateSinkObject.Save(25, &d.nlink)
+ stateSinkObject.Save(26, &d.mappings)
+ stateSinkObject.Save(27, &d.cache)
+ stateSinkObject.Save(28, &d.dirty)
+ stateSinkObject.Save(29, &d.pf)
+ stateSinkObject.Save(30, &d.haveTarget)
+ stateSinkObject.Save(31, &d.target)
+ stateSinkObject.Save(32, &d.endpoint)
+ stateSinkObject.Save(33, &d.pipe)
+ stateSinkObject.Save(34, &d.locks)
+ stateSinkObject.Save(35, &d.watches)
+}
+
+// +checklocksignore
+func (d *dentry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.vfsd)
+ stateSourceObject.Load(1, &d.refs)
+ stateSourceObject.Load(2, &d.fs)
+ stateSourceObject.Load(3, &d.parent)
+ stateSourceObject.Load(4, &d.name)
+ stateSourceObject.Load(5, &d.qidPath)
+ stateSourceObject.Load(6, &d.inoKey)
+ stateSourceObject.Load(7, &d.deleted)
+ stateSourceObject.Load(8, &d.cached)
+ stateSourceObject.Load(9, &d.dentryEntry)
+ stateSourceObject.Load(10, &d.children)
+ stateSourceObject.Load(11, &d.syntheticChildren)
+ stateSourceObject.Load(12, &d.dirents)
+ stateSourceObject.Load(13, &d.ino)
+ stateSourceObject.Load(14, &d.mode)
+ stateSourceObject.Load(15, &d.uid)
+ stateSourceObject.Load(16, &d.gid)
+ stateSourceObject.Load(17, &d.blockSize)
+ stateSourceObject.Load(18, &d.atime)
+ stateSourceObject.Load(19, &d.mtime)
+ stateSourceObject.Load(20, &d.ctime)
+ stateSourceObject.Load(21, &d.btime)
+ stateSourceObject.Load(22, &d.size)
+ stateSourceObject.Load(23, &d.atimeDirty)
+ stateSourceObject.Load(24, &d.mtimeDirty)
+ stateSourceObject.Load(25, &d.nlink)
+ stateSourceObject.Load(26, &d.mappings)
+ stateSourceObject.Load(27, &d.cache)
+ stateSourceObject.Load(28, &d.dirty)
+ stateSourceObject.Load(29, &d.pf)
+ stateSourceObject.Load(30, &d.haveTarget)
+ stateSourceObject.Load(31, &d.target)
+ stateSourceObject.Load(32, &d.endpoint)
+ stateSourceObject.Load(33, &d.pipe)
+ stateSourceObject.Load(34, &d.locks)
+ stateSourceObject.Load(35, &d.watches)
+ stateSourceObject.AfterLoad(d.afterLoad)
+}
+
+func (fd *fileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.fileDescription"
+}
+
+func (fd *fileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ }
+}
+
+func (fd *fileDescription) beforeSave() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.LockFD)
+}
+
+func (fd *fileDescription) afterLoad() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.LockFD)
+}
+
+func (fd *regularFileFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.regularFileFD"
+}
+
+func (fd *regularFileFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "off",
+ }
+}
+
+func (fd *regularFileFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *regularFileFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.off)
+}
+
+func (fd *regularFileFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *regularFileFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.off)
+}
+
+func (d *dentryPlatformFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.dentryPlatformFile"
+}
+
+func (d *dentryPlatformFile) StateFields() []string {
+ return []string{
+ "dentry",
+ "fdRefs",
+ "hostFileMapper",
+ }
+}
+
+func (d *dentryPlatformFile) beforeSave() {}
+
+// +checklocksignore
+func (d *dentryPlatformFile) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.dentry)
+ stateSinkObject.Save(1, &d.fdRefs)
+ stateSinkObject.Save(2, &d.hostFileMapper)
+}
+
+// +checklocksignore
+func (d *dentryPlatformFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.dentry)
+ stateSourceObject.Load(1, &d.fdRefs)
+ stateSourceObject.Load(2, &d.hostFileMapper)
+ stateSourceObject.AfterLoad(d.afterLoad)
+}
+
+func (s *savedDentryRW) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.savedDentryRW"
+}
+
+func (s *savedDentryRW) StateFields() []string {
+ return []string{
+ "read",
+ "write",
+ }
+}
+
+func (s *savedDentryRW) beforeSave() {}
+
+// +checklocksignore
+func (s *savedDentryRW) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.read)
+ stateSinkObject.Save(1, &s.write)
+}
+
+func (s *savedDentryRW) afterLoad() {}
+
+// +checklocksignore
+func (s *savedDentryRW) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.read)
+ stateSourceObject.Load(1, &s.write)
+}
+
+func (e *endpoint) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.endpoint"
+}
+
+func (e *endpoint) StateFields() []string {
+ return []string{
+ "dentry",
+ "path",
+ }
+}
+
+func (e *endpoint) beforeSave() {}
+
+// +checklocksignore
+func (e *endpoint) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.dentry)
+ stateSinkObject.Save(1, &e.path)
+}
+
+func (e *endpoint) afterLoad() {}
+
+// +checklocksignore
+func (e *endpoint) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.dentry)
+ stateSourceObject.Load(1, &e.path)
+}
+
+func (fd *specialFileFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/gofer.specialFileFD"
+}
+
+func (fd *specialFileFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "isRegularFile",
+ "seekable",
+ "queue",
+ "off",
+ "haveBuf",
+ "buf",
+ "hostFileMapper",
+ "fileRefs",
+ }
+}
+
+func (fd *specialFileFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *specialFileFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.isRegularFile)
+ stateSinkObject.Save(2, &fd.seekable)
+ stateSinkObject.Save(3, &fd.queue)
+ stateSinkObject.Save(4, &fd.off)
+ stateSinkObject.Save(5, &fd.haveBuf)
+ stateSinkObject.Save(6, &fd.buf)
+ stateSinkObject.Save(7, &fd.hostFileMapper)
+ stateSinkObject.Save(8, &fd.fileRefs)
+}
+
+// +checklocksignore
+func (fd *specialFileFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.isRegularFile)
+ stateSourceObject.Load(2, &fd.seekable)
+ stateSourceObject.Load(3, &fd.queue)
+ stateSourceObject.Load(4, &fd.off)
+ stateSourceObject.Load(5, &fd.haveBuf)
+ stateSourceObject.Load(6, &fd.buf)
+ stateSourceObject.Load(7, &fd.hostFileMapper)
+ stateSourceObject.Load(8, &fd.fileRefs)
+ stateSourceObject.AfterLoad(fd.afterLoad)
+}
+
+func init() {
+ state.Register((*dentryList)(nil))
+ state.Register((*dentryEntry)(nil))
+ state.Register((*directoryFD)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*filesystemOptions)(nil))
+ state.Register((*InteropMode)(nil))
+ state.Register((*InternalFilesystemOptions)(nil))
+ state.Register((*inoKey)(nil))
+ state.Register((*dentry)(nil))
+ state.Register((*fileDescription)(nil))
+ state.Register((*regularFileFD)(nil))
+ state.Register((*dentryPlatformFile)(nil))
+ state.Register((*savedDentryRW)(nil))
+ state.Register((*endpoint)(nil))
+ state.Register((*specialFileFD)(nil))
+}
diff --git a/pkg/sentry/fsimpl/gofer/gofer_test.go b/pkg/sentry/fsimpl/gofer/gofer_test.go
deleted file mode 100644
index d5cc73f33..000000000
--- a/pkg/sentry/fsimpl/gofer/gofer_test.go
+++ /dev/null
@@ -1,69 +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 gofer
-
-import (
- "sync/atomic"
- "testing"
-
- "gvisor.dev/gvisor/pkg/p9"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/pgalloc"
-)
-
-func TestDestroyIdempotent(t *testing.T) {
- ctx := contexttest.Context(t)
- fs := filesystem{
- mfp: pgalloc.MemoryFileProviderFromContext(ctx),
- opts: filesystemOptions{
- // Test relies on no dentry being held in the cache.
- maxCachedDentries: 0,
- },
- syncableDentries: make(map[*dentry]struct{}),
- inoByQIDPath: make(map[uint64]uint64),
- inoByKey: make(map[inoKey]uint64),
- }
-
- attr := &p9.Attr{
- Mode: p9.ModeRegular,
- }
- mask := p9.AttrMask{
- Mode: true,
- Size: true,
- }
- parent, err := fs.newDentry(ctx, p9file{}, p9.QID{}, mask, attr)
- if err != nil {
- t.Fatalf("fs.newDentry(): %v", err)
- }
-
- child, err := fs.newDentry(ctx, p9file{}, p9.QID{}, mask, attr)
- if err != nil {
- t.Fatalf("fs.newDentry(): %v", err)
- }
- parent.cacheNewChildLocked(child, "child")
-
- fs.renameMu.Lock()
- defer fs.renameMu.Unlock()
- child.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
- if got := atomic.LoadInt64(&child.refs); got != -1 {
- t.Fatalf("child.refs=%d, want: -1", got)
- }
- // Parent will also be destroyed when child reference is removed.
- if got := atomic.LoadInt64(&parent.refs); got != -1 {
- t.Fatalf("parent.refs=%d, want: -1", got)
- }
- child.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
- child.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
-}
diff --git a/pkg/sentry/fsimpl/host/BUILD b/pkg/sentry/fsimpl/host/BUILD
deleted file mode 100644
index 180a35583..000000000
--- a/pkg/sentry/fsimpl/host/BUILD
+++ /dev/null
@@ -1,79 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-licenses(["notice"])
-
-go_template_instance(
- name = "inode_refs",
- out = "inode_refs.go",
- package = "host",
- prefix = "inode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "inode",
- },
-)
-
-go_template_instance(
- name = "connected_endpoint_refs",
- out = "connected_endpoint_refs.go",
- package = "host",
- prefix = "ConnectedEndpoint",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "ConnectedEndpoint",
- },
-)
-
-go_library(
- name = "host",
- srcs = [
- "connected_endpoint_refs.go",
- "control.go",
- "host.go",
- "inode_refs.go",
- "ioctl_unsafe.go",
- "save_restore.go",
- "socket.go",
- "socket_iovec.go",
- "socket_unsafe.go",
- "tty.go",
- "util.go",
- "util_unsafe.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fdnotifier",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/marshal/primitive",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/arch",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/hostfd",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/syserr",
- "//pkg/tcpip",
- "//pkg/unet",
- "//pkg/usermem",
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fsimpl/host/connected_endpoint_refs.go b/pkg/sentry/fsimpl/host/connected_endpoint_refs.go
new file mode 100644
index 000000000..c0a87f656
--- /dev/null
+++ b/pkg/sentry/fsimpl/host/connected_endpoint_refs.go
@@ -0,0 +1,140 @@
+package host
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const ConnectedEndpointenableLogging = false
+
+// obj 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 ConnectedEndpointobj *ConnectedEndpoint
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type ConnectedEndpointRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *ConnectedEndpointRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *ConnectedEndpointRefs) RefType() string {
+ return fmt.Sprintf("%T", ConnectedEndpointobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *ConnectedEndpointRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *ConnectedEndpointRefs) LogRefs() bool {
+ return ConnectedEndpointenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *ConnectedEndpointRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *ConnectedEndpointRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if ConnectedEndpointenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *ConnectedEndpointRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if ConnectedEndpointenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *ConnectedEndpointRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if ConnectedEndpointenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *ConnectedEndpointRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/host/host_state_autogen.go b/pkg/sentry/fsimpl/host/host_state_autogen.go
new file mode 100644
index 000000000..607474165
--- /dev/null
+++ b/pkg/sentry/fsimpl/host/host_state_autogen.go
@@ -0,0 +1,327 @@
+// automatically generated by stateify.
+
+package host
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (r *ConnectedEndpointRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.ConnectedEndpointRefs"
+}
+
+func (r *ConnectedEndpointRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *ConnectedEndpointRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *ConnectedEndpointRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *ConnectedEndpointRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (v *virtualOwner) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.virtualOwner"
+}
+
+func (v *virtualOwner) StateFields() []string {
+ return []string{
+ "enabled",
+ "uid",
+ "gid",
+ "mode",
+ }
+}
+
+func (v *virtualOwner) beforeSave() {}
+
+// +checklocksignore
+func (v *virtualOwner) StateSave(stateSinkObject state.Sink) {
+ v.beforeSave()
+ stateSinkObject.Save(0, &v.enabled)
+ stateSinkObject.Save(1, &v.uid)
+ stateSinkObject.Save(2, &v.gid)
+ stateSinkObject.Save(3, &v.mode)
+}
+
+func (v *virtualOwner) afterLoad() {}
+
+// +checklocksignore
+func (v *virtualOwner) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &v.enabled)
+ stateSourceObject.Load(1, &v.uid)
+ stateSourceObject.Load(2, &v.gid)
+ stateSourceObject.Load(3, &v.mode)
+}
+
+func (i *inode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.inode"
+}
+
+func (i *inode) StateFields() []string {
+ return []string{
+ "InodeNoStatFS",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "CachedMappable",
+ "InodeTemporary",
+ "locks",
+ "inodeRefs",
+ "hostFD",
+ "ino",
+ "ftype",
+ "mayBlock",
+ "seekable",
+ "isTTY",
+ "savable",
+ "queue",
+ "virtualOwner",
+ "haveBuf",
+ "buf",
+ }
+}
+
+// +checklocksignore
+func (i *inode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeNoStatFS)
+ stateSinkObject.Save(1, &i.InodeNotDirectory)
+ stateSinkObject.Save(2, &i.InodeNotSymlink)
+ stateSinkObject.Save(3, &i.CachedMappable)
+ stateSinkObject.Save(4, &i.InodeTemporary)
+ stateSinkObject.Save(5, &i.locks)
+ stateSinkObject.Save(6, &i.inodeRefs)
+ stateSinkObject.Save(7, &i.hostFD)
+ stateSinkObject.Save(8, &i.ino)
+ stateSinkObject.Save(9, &i.ftype)
+ stateSinkObject.Save(10, &i.mayBlock)
+ stateSinkObject.Save(11, &i.seekable)
+ stateSinkObject.Save(12, &i.isTTY)
+ stateSinkObject.Save(13, &i.savable)
+ stateSinkObject.Save(14, &i.queue)
+ stateSinkObject.Save(15, &i.virtualOwner)
+ stateSinkObject.Save(16, &i.haveBuf)
+ stateSinkObject.Save(17, &i.buf)
+}
+
+// +checklocksignore
+func (i *inode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeNoStatFS)
+ stateSourceObject.Load(1, &i.InodeNotDirectory)
+ stateSourceObject.Load(2, &i.InodeNotSymlink)
+ stateSourceObject.Load(3, &i.CachedMappable)
+ stateSourceObject.Load(4, &i.InodeTemporary)
+ stateSourceObject.Load(5, &i.locks)
+ stateSourceObject.Load(6, &i.inodeRefs)
+ stateSourceObject.Load(7, &i.hostFD)
+ stateSourceObject.Load(8, &i.ino)
+ stateSourceObject.Load(9, &i.ftype)
+ stateSourceObject.Load(10, &i.mayBlock)
+ stateSourceObject.Load(11, &i.seekable)
+ stateSourceObject.Load(12, &i.isTTY)
+ stateSourceObject.Load(13, &i.savable)
+ stateSourceObject.Load(14, &i.queue)
+ stateSourceObject.Load(15, &i.virtualOwner)
+ stateSourceObject.Load(16, &i.haveBuf)
+ stateSourceObject.Load(17, &i.buf)
+ stateSourceObject.AfterLoad(i.afterLoad)
+}
+
+func (f *filesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.filesystemType"
+}
+
+func (f *filesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (f *filesystemType) beforeSave() {}
+
+// +checklocksignore
+func (f *filesystemType) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+}
+
+func (f *filesystemType) afterLoad() {}
+
+// +checklocksignore
+func (f *filesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+}
+
+func (f *fileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.fileDescription"
+}
+
+func (f *fileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ "inode",
+ "offset",
+ }
+}
+
+func (f *fileDescription) beforeSave() {}
+
+// +checklocksignore
+func (f *fileDescription) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.vfsfd)
+ stateSinkObject.Save(1, &f.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &f.LockFD)
+ stateSinkObject.Save(3, &f.inode)
+ stateSinkObject.Save(4, &f.offset)
+}
+
+func (f *fileDescription) afterLoad() {}
+
+// +checklocksignore
+func (f *fileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.vfsfd)
+ stateSourceObject.Load(1, &f.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &f.LockFD)
+ stateSourceObject.Load(3, &f.inode)
+ stateSourceObject.Load(4, &f.offset)
+}
+
+func (r *inodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.inodeRefs"
+}
+
+func (r *inodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *inodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *inodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *inodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (c *ConnectedEndpoint) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.ConnectedEndpoint"
+}
+
+func (c *ConnectedEndpoint) StateFields() []string {
+ return []string{
+ "ConnectedEndpointRefs",
+ "fd",
+ "addr",
+ "stype",
+ }
+}
+
+func (c *ConnectedEndpoint) beforeSave() {}
+
+// +checklocksignore
+func (c *ConnectedEndpoint) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.ConnectedEndpointRefs)
+ stateSinkObject.Save(1, &c.fd)
+ stateSinkObject.Save(2, &c.addr)
+ stateSinkObject.Save(3, &c.stype)
+}
+
+// +checklocksignore
+func (c *ConnectedEndpoint) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.ConnectedEndpointRefs)
+ stateSourceObject.Load(1, &c.fd)
+ stateSourceObject.Load(2, &c.addr)
+ stateSourceObject.Load(3, &c.stype)
+ stateSourceObject.AfterLoad(c.afterLoad)
+}
+
+func (t *TTYFileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/host.TTYFileDescription"
+}
+
+func (t *TTYFileDescription) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "session",
+ "fgProcessGroup",
+ "termios",
+ }
+}
+
+func (t *TTYFileDescription) beforeSave() {}
+
+// +checklocksignore
+func (t *TTYFileDescription) StateSave(stateSinkObject state.Sink) {
+ t.beforeSave()
+ stateSinkObject.Save(0, &t.fileDescription)
+ stateSinkObject.Save(1, &t.session)
+ stateSinkObject.Save(2, &t.fgProcessGroup)
+ stateSinkObject.Save(3, &t.termios)
+}
+
+func (t *TTYFileDescription) afterLoad() {}
+
+// +checklocksignore
+func (t *TTYFileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &t.fileDescription)
+ stateSourceObject.Load(1, &t.session)
+ stateSourceObject.Load(2, &t.fgProcessGroup)
+ stateSourceObject.Load(3, &t.termios)
+}
+
+func init() {
+ state.Register((*ConnectedEndpointRefs)(nil))
+ state.Register((*virtualOwner)(nil))
+ state.Register((*inode)(nil))
+ state.Register((*filesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*fileDescription)(nil))
+ state.Register((*inodeRefs)(nil))
+ state.Register((*ConnectedEndpoint)(nil))
+ state.Register((*TTYFileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go b/pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go
new file mode 100644
index 000000000..b2d8c661f
--- /dev/null
+++ b/pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package host
diff --git a/pkg/sentry/fsimpl/host/inode_refs.go b/pkg/sentry/fsimpl/host/inode_refs.go
new file mode 100644
index 000000000..112f39850
--- /dev/null
+++ b/pkg/sentry/fsimpl/host/inode_refs.go
@@ -0,0 +1,140 @@
+package host
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const inodeenableLogging = false
+
+// obj 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 inodeobj *inode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type inodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *inodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *inodeRefs) RefType() string {
+ return fmt.Sprintf("%T", inodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *inodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *inodeRefs) LogRefs() bool {
+ return inodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *inodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *inodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if inodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *inodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if inodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *inodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if inodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *inodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/kernfs/BUILD b/pkg/sentry/fsimpl/kernfs/BUILD
deleted file mode 100644
index 4b577ea43..000000000
--- a/pkg/sentry/fsimpl/kernfs/BUILD
+++ /dev/null
@@ -1,150 +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 = "dentry_list",
- out = "dentry_list.go",
- package = "kernfs",
- prefix = "dentry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Dentry",
- "Linker": "*Dentry",
- },
-)
-
-go_template_instance(
- name = "fstree",
- out = "fstree.go",
- package = "kernfs",
- prefix = "generic",
- template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
- types = {
- "Dentry": "Dentry",
- },
-)
-
-go_template_instance(
- name = "slot_list",
- out = "slot_list.go",
- package = "kernfs",
- prefix = "slot",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*slot",
- "Linker": "*slot",
- },
-)
-
-go_template_instance(
- name = "static_directory_refs",
- out = "static_directory_refs.go",
- package = "kernfs",
- prefix = "StaticDirectory",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "StaticDirectory",
- },
-)
-
-go_template_instance(
- name = "dir_refs",
- out = "dir_refs.go",
- package = "kernfs_test",
- prefix = "dir",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "dir",
- },
-)
-
-go_template_instance(
- name = "readonly_dir_refs",
- out = "readonly_dir_refs.go",
- package = "kernfs_test",
- prefix = "readonlyDir",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "readonlyDir",
- },
-)
-
-go_template_instance(
- name = "synthetic_directory_refs",
- out = "synthetic_directory_refs.go",
- package = "kernfs",
- prefix = "syntheticDirectory",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "syntheticDirectory",
- },
-)
-
-go_library(
- name = "kernfs",
- srcs = [
- "dentry_list.go",
- "dynamic_bytes_file.go",
- "fd_impl_util.go",
- "filesystem.go",
- "fstree.go",
- "inode_impl_util.go",
- "kernfs.go",
- "mmap_util.go",
- "save_restore.go",
- "slot_list.go",
- "static_directory_refs.go",
- "symlink.go",
- "synthetic_directory.go",
- "synthetic_directory_refs.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "kernfs_test",
- size = "small",
- srcs = [
- "dir_refs.go",
- "kernfs_test.go",
- "readonly_dir_refs.go",
- ],
- deps = [
- ":kernfs",
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/fsimpl/testutil",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fsimpl/kernfs/dentry_list.go b/pkg/sentry/fsimpl/kernfs/dentry_list.go
new file mode 100644
index 000000000..e73cde1f1
--- /dev/null
+++ b/pkg/sentry/fsimpl/kernfs/dentry_list.go
@@ -0,0 +1,221 @@
+package kernfs
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type dentryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (dentryElementMapper) linkerFor(elem *Dentry) *Dentry { 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 dentryList struct {
+ head *Dentry
+ tail *Dentry
+}
+
+// Reset resets list l to the empty state.
+func (l *dentryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *dentryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Front() *Dentry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Back() *Dentry {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *dentryList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (dentryElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *dentryList) PushFront(e *Dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ dentryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *dentryList) PushBack(e *Dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *dentryList) PushBackList(m *dentryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ dentryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *dentryList) InsertAfter(b, e *Dentry) {
+ bLinker := dentryElementMapper{}.linkerFor(b)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ dentryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *dentryList) InsertBefore(a, e *Dentry) {
+ aLinker := dentryElementMapper{}.linkerFor(a)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ dentryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *dentryList) Remove(e *Dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ dentryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ dentryElementMapper{}.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 dentryEntry struct {
+ next *Dentry
+ prev *Dentry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Next() *Dentry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Prev() *Dentry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetNext(elem *Dentry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetPrev(elem *Dentry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/kernfs/fstree.go b/pkg/sentry/fsimpl/kernfs/fstree.go
new file mode 100644
index 000000000..9dc52dabc
--- /dev/null
+++ b/pkg/sentry/fsimpl/kernfs/fstree.go
@@ -0,0 +1,55 @@
+package kernfs
+
+import (
+ "gvisor.dev/gvisor/pkg/fspath"
+ "gvisor.dev/gvisor/pkg/sentry/vfs"
+)
+
+// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is
+// either d2's parent or an ancestor of d2's parent.
+func genericIsAncestorDentry(d, d2 *Dentry) bool {
+ for d2 != nil {
+ if d2.parent == d {
+ return true
+ }
+ if d2.parent == d2 {
+ return false
+ }
+ d2 = d2.parent
+ }
+ return false
+}
+
+// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d.
+func genericParentOrSelf(d *Dentry) *Dentry {
+ if d.parent != nil {
+ return d.parent
+ }
+ return d
+}
+
+// PrependPath is a generic implementation of FilesystemImpl.PrependPath().
+func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *Dentry, b *fspath.Builder) error {
+ for {
+ if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() {
+ return vfs.PrependPathAtVFSRootError{}
+ }
+ if mnt != nil && &d.vfsd == mnt.Root() {
+ return nil
+ }
+ if d.parent == nil {
+ return vfs.PrependPathAtNonMountRootError{}
+ }
+ b.PrependComponent(d.name)
+ d = d.parent
+ }
+}
+
+// DebugPathname returns a pathname to d relative to its filesystem root.
+// DebugPathname does not correspond to any Linux function; it's used to
+// generate dentry pathnames for debugging.
+func genericDebugPathname(d *Dentry) string {
+ var b fspath.Builder
+ _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b)
+ return b.String()
+}
diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go b/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go
new file mode 100644
index 000000000..f8add23f8
--- /dev/null
+++ b/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go
@@ -0,0 +1,965 @@
+// automatically generated by stateify.
+
+package kernfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *dentryList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.dentryList"
+}
+
+func (l *dentryList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *dentryList) beforeSave() {}
+
+// +checklocksignore
+func (l *dentryList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *dentryList) afterLoad() {}
+
+// +checklocksignore
+func (l *dentryList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *dentryEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.dentryEntry"
+}
+
+func (e *dentryEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *dentryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *dentryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (f *DynamicBytesFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.DynamicBytesFile"
+}
+
+func (f *DynamicBytesFile) StateFields() []string {
+ return []string{
+ "InodeAttrs",
+ "InodeNoStatFS",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "locks",
+ "data",
+ }
+}
+
+func (f *DynamicBytesFile) beforeSave() {}
+
+// +checklocksignore
+func (f *DynamicBytesFile) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.InodeAttrs)
+ stateSinkObject.Save(1, &f.InodeNoStatFS)
+ stateSinkObject.Save(2, &f.InodeNoopRefCount)
+ stateSinkObject.Save(3, &f.InodeNotDirectory)
+ stateSinkObject.Save(4, &f.InodeNotSymlink)
+ stateSinkObject.Save(5, &f.locks)
+ stateSinkObject.Save(6, &f.data)
+}
+
+func (f *DynamicBytesFile) afterLoad() {}
+
+// +checklocksignore
+func (f *DynamicBytesFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.InodeAttrs)
+ stateSourceObject.Load(1, &f.InodeNoStatFS)
+ stateSourceObject.Load(2, &f.InodeNoopRefCount)
+ stateSourceObject.Load(3, &f.InodeNotDirectory)
+ stateSourceObject.Load(4, &f.InodeNotSymlink)
+ stateSourceObject.Load(5, &f.locks)
+ stateSourceObject.Load(6, &f.data)
+}
+
+func (fd *DynamicBytesFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.DynamicBytesFD"
+}
+
+func (fd *DynamicBytesFD) StateFields() []string {
+ return []string{
+ "FileDescriptionDefaultImpl",
+ "DynamicBytesFileDescriptionImpl",
+ "LockFD",
+ "vfsfd",
+ "inode",
+ }
+}
+
+func (fd *DynamicBytesFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *DynamicBytesFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(1, &fd.DynamicBytesFileDescriptionImpl)
+ stateSinkObject.Save(2, &fd.LockFD)
+ stateSinkObject.Save(3, &fd.vfsfd)
+ stateSinkObject.Save(4, &fd.inode)
+}
+
+func (fd *DynamicBytesFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *DynamicBytesFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(1, &fd.DynamicBytesFileDescriptionImpl)
+ stateSourceObject.Load(2, &fd.LockFD)
+ stateSourceObject.Load(3, &fd.vfsfd)
+ stateSourceObject.Load(4, &fd.inode)
+}
+
+func (s *SeekEndConfig) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.SeekEndConfig"
+}
+
+func (s *SeekEndConfig) StateFields() []string {
+ return nil
+}
+
+func (g *GenericDirectoryFDOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.GenericDirectoryFDOptions"
+}
+
+func (g *GenericDirectoryFDOptions) StateFields() []string {
+ return []string{
+ "SeekEnd",
+ }
+}
+
+func (g *GenericDirectoryFDOptions) beforeSave() {}
+
+// +checklocksignore
+func (g *GenericDirectoryFDOptions) StateSave(stateSinkObject state.Sink) {
+ g.beforeSave()
+ stateSinkObject.Save(0, &g.SeekEnd)
+}
+
+func (g *GenericDirectoryFDOptions) afterLoad() {}
+
+// +checklocksignore
+func (g *GenericDirectoryFDOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &g.SeekEnd)
+}
+
+func (fd *GenericDirectoryFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.GenericDirectoryFD"
+}
+
+func (fd *GenericDirectoryFD) StateFields() []string {
+ return []string{
+ "FileDescriptionDefaultImpl",
+ "DirectoryFileDescriptionDefaultImpl",
+ "LockFD",
+ "seekEnd",
+ "vfsfd",
+ "children",
+ "off",
+ }
+}
+
+func (fd *GenericDirectoryFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *GenericDirectoryFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.LockFD)
+ stateSinkObject.Save(3, &fd.seekEnd)
+ stateSinkObject.Save(4, &fd.vfsfd)
+ stateSinkObject.Save(5, &fd.children)
+ stateSinkObject.Save(6, &fd.off)
+}
+
+func (fd *GenericDirectoryFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *GenericDirectoryFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.LockFD)
+ stateSourceObject.Load(3, &fd.seekEnd)
+ stateSourceObject.Load(4, &fd.vfsfd)
+ stateSourceObject.Load(5, &fd.children)
+ stateSourceObject.Load(6, &fd.off)
+}
+
+func (i *InodeNoopRefCount) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeNoopRefCount"
+}
+
+func (i *InodeNoopRefCount) StateFields() []string {
+ return []string{
+ "InodeTemporary",
+ }
+}
+
+func (i *InodeNoopRefCount) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeNoopRefCount) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeTemporary)
+}
+
+func (i *InodeNoopRefCount) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeNoopRefCount) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeTemporary)
+}
+
+func (i *InodeDirectoryNoNewChildren) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeDirectoryNoNewChildren"
+}
+
+func (i *InodeDirectoryNoNewChildren) StateFields() []string {
+ return []string{}
+}
+
+func (i *InodeDirectoryNoNewChildren) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeDirectoryNoNewChildren) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *InodeDirectoryNoNewChildren) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeDirectoryNoNewChildren) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *InodeNotDirectory) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeNotDirectory"
+}
+
+func (i *InodeNotDirectory) StateFields() []string {
+ return []string{
+ "InodeAlwaysValid",
+ }
+}
+
+func (i *InodeNotDirectory) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeNotDirectory) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeAlwaysValid)
+}
+
+func (i *InodeNotDirectory) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeNotDirectory) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeAlwaysValid)
+}
+
+func (i *InodeNotSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeNotSymlink"
+}
+
+func (i *InodeNotSymlink) StateFields() []string {
+ return []string{}
+}
+
+func (i *InodeNotSymlink) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeNotSymlink) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *InodeNotSymlink) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeNotSymlink) StateLoad(stateSourceObject state.Source) {
+}
+
+func (a *InodeAttrs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeAttrs"
+}
+
+func (a *InodeAttrs) StateFields() []string {
+ return []string{
+ "devMajor",
+ "devMinor",
+ "ino",
+ "mode",
+ "uid",
+ "gid",
+ "nlink",
+ "blockSize",
+ "atime",
+ "mtime",
+ "ctime",
+ }
+}
+
+func (a *InodeAttrs) beforeSave() {}
+
+// +checklocksignore
+func (a *InodeAttrs) StateSave(stateSinkObject state.Sink) {
+ a.beforeSave()
+ stateSinkObject.Save(0, &a.devMajor)
+ stateSinkObject.Save(1, &a.devMinor)
+ stateSinkObject.Save(2, &a.ino)
+ stateSinkObject.Save(3, &a.mode)
+ stateSinkObject.Save(4, &a.uid)
+ stateSinkObject.Save(5, &a.gid)
+ stateSinkObject.Save(6, &a.nlink)
+ stateSinkObject.Save(7, &a.blockSize)
+ stateSinkObject.Save(8, &a.atime)
+ stateSinkObject.Save(9, &a.mtime)
+ stateSinkObject.Save(10, &a.ctime)
+}
+
+func (a *InodeAttrs) afterLoad() {}
+
+// +checklocksignore
+func (a *InodeAttrs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &a.devMajor)
+ stateSourceObject.Load(1, &a.devMinor)
+ stateSourceObject.Load(2, &a.ino)
+ stateSourceObject.Load(3, &a.mode)
+ stateSourceObject.Load(4, &a.uid)
+ stateSourceObject.Load(5, &a.gid)
+ stateSourceObject.Load(6, &a.nlink)
+ stateSourceObject.Load(7, &a.blockSize)
+ stateSourceObject.Load(8, &a.atime)
+ stateSourceObject.Load(9, &a.mtime)
+ stateSourceObject.Load(10, &a.ctime)
+}
+
+func (s *slot) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.slot"
+}
+
+func (s *slot) StateFields() []string {
+ return []string{
+ "name",
+ "inode",
+ "static",
+ "slotEntry",
+ }
+}
+
+func (s *slot) beforeSave() {}
+
+// +checklocksignore
+func (s *slot) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.name)
+ stateSinkObject.Save(1, &s.inode)
+ stateSinkObject.Save(2, &s.static)
+ stateSinkObject.Save(3, &s.slotEntry)
+}
+
+func (s *slot) afterLoad() {}
+
+// +checklocksignore
+func (s *slot) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.name)
+ stateSourceObject.Load(1, &s.inode)
+ stateSourceObject.Load(2, &s.static)
+ stateSourceObject.Load(3, &s.slotEntry)
+}
+
+func (o *OrderedChildrenOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.OrderedChildrenOptions"
+}
+
+func (o *OrderedChildrenOptions) StateFields() []string {
+ return []string{
+ "Writable",
+ }
+}
+
+func (o *OrderedChildrenOptions) beforeSave() {}
+
+// +checklocksignore
+func (o *OrderedChildrenOptions) StateSave(stateSinkObject state.Sink) {
+ o.beforeSave()
+ stateSinkObject.Save(0, &o.Writable)
+}
+
+func (o *OrderedChildrenOptions) afterLoad() {}
+
+// +checklocksignore
+func (o *OrderedChildrenOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &o.Writable)
+}
+
+func (o *OrderedChildren) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.OrderedChildren"
+}
+
+func (o *OrderedChildren) StateFields() []string {
+ return []string{
+ "writable",
+ "order",
+ "set",
+ }
+}
+
+func (o *OrderedChildren) beforeSave() {}
+
+// +checklocksignore
+func (o *OrderedChildren) StateSave(stateSinkObject state.Sink) {
+ o.beforeSave()
+ stateSinkObject.Save(0, &o.writable)
+ stateSinkObject.Save(1, &o.order)
+ stateSinkObject.Save(2, &o.set)
+}
+
+func (o *OrderedChildren) afterLoad() {}
+
+// +checklocksignore
+func (o *OrderedChildren) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &o.writable)
+ stateSourceObject.Load(1, &o.order)
+ stateSourceObject.Load(2, &o.set)
+}
+
+func (i *InodeSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeSymlink"
+}
+
+func (i *InodeSymlink) StateFields() []string {
+ return []string{
+ "InodeNotDirectory",
+ }
+}
+
+func (i *InodeSymlink) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeSymlink) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeNotDirectory)
+}
+
+func (i *InodeSymlink) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeNotDirectory)
+}
+
+func (s *StaticDirectory) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.StaticDirectory"
+}
+
+func (s *StaticDirectory) StateFields() []string {
+ return []string{
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNoStatFS",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ "StaticDirectoryRefs",
+ "locks",
+ "fdOpts",
+ }
+}
+
+func (s *StaticDirectory) beforeSave() {}
+
+// +checklocksignore
+func (s *StaticDirectory) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.InodeAlwaysValid)
+ stateSinkObject.Save(1, &s.InodeAttrs)
+ stateSinkObject.Save(2, &s.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(3, &s.InodeNoStatFS)
+ stateSinkObject.Save(4, &s.InodeNotSymlink)
+ stateSinkObject.Save(5, &s.InodeTemporary)
+ stateSinkObject.Save(6, &s.OrderedChildren)
+ stateSinkObject.Save(7, &s.StaticDirectoryRefs)
+ stateSinkObject.Save(8, &s.locks)
+ stateSinkObject.Save(9, &s.fdOpts)
+}
+
+func (s *StaticDirectory) afterLoad() {}
+
+// +checklocksignore
+func (s *StaticDirectory) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.InodeAlwaysValid)
+ stateSourceObject.Load(1, &s.InodeAttrs)
+ stateSourceObject.Load(2, &s.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(3, &s.InodeNoStatFS)
+ stateSourceObject.Load(4, &s.InodeNotSymlink)
+ stateSourceObject.Load(5, &s.InodeTemporary)
+ stateSourceObject.Load(6, &s.OrderedChildren)
+ stateSourceObject.Load(7, &s.StaticDirectoryRefs)
+ stateSourceObject.Load(8, &s.locks)
+ stateSourceObject.Load(9, &s.fdOpts)
+}
+
+func (i *InodeAlwaysValid) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeAlwaysValid"
+}
+
+func (i *InodeAlwaysValid) StateFields() []string {
+ return []string{}
+}
+
+func (i *InodeAlwaysValid) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeAlwaysValid) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *InodeAlwaysValid) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeAlwaysValid) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *InodeTemporary) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeTemporary"
+}
+
+func (i *InodeTemporary) StateFields() []string {
+ return []string{}
+}
+
+func (i *InodeTemporary) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeTemporary) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *InodeTemporary) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeTemporary) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *InodeNoStatFS) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.InodeNoStatFS"
+}
+
+func (i *InodeNoStatFS) StateFields() []string {
+ return []string{}
+}
+
+func (i *InodeNoStatFS) beforeSave() {}
+
+// +checklocksignore
+func (i *InodeNoStatFS) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *InodeNoStatFS) afterLoad() {}
+
+// +checklocksignore
+func (i *InodeNoStatFS) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *Filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.Filesystem"
+}
+
+func (fs *Filesystem) StateFields() []string {
+ return []string{
+ "vfsfs",
+ "deferredDecRefs",
+ "nextInoMinusOne",
+ "cachedDentries",
+ "cachedDentriesLen",
+ "MaxCachedDentries",
+ "root",
+ }
+}
+
+func (fs *Filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *Filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.vfsfs)
+ stateSinkObject.Save(1, &fs.deferredDecRefs)
+ stateSinkObject.Save(2, &fs.nextInoMinusOne)
+ stateSinkObject.Save(3, &fs.cachedDentries)
+ stateSinkObject.Save(4, &fs.cachedDentriesLen)
+ stateSinkObject.Save(5, &fs.MaxCachedDentries)
+ stateSinkObject.Save(6, &fs.root)
+}
+
+func (fs *Filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *Filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.vfsfs)
+ stateSourceObject.Load(1, &fs.deferredDecRefs)
+ stateSourceObject.Load(2, &fs.nextInoMinusOne)
+ stateSourceObject.Load(3, &fs.cachedDentries)
+ stateSourceObject.Load(4, &fs.cachedDentriesLen)
+ stateSourceObject.Load(5, &fs.MaxCachedDentries)
+ stateSourceObject.Load(6, &fs.root)
+}
+
+func (d *Dentry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.Dentry"
+}
+
+func (d *Dentry) StateFields() []string {
+ return []string{
+ "vfsd",
+ "refs",
+ "fs",
+ "flags",
+ "parent",
+ "name",
+ "cached",
+ "dentryEntry",
+ "children",
+ "inode",
+ }
+}
+
+func (d *Dentry) beforeSave() {}
+
+// +checklocksignore
+func (d *Dentry) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.vfsd)
+ stateSinkObject.Save(1, &d.refs)
+ stateSinkObject.Save(2, &d.fs)
+ stateSinkObject.Save(3, &d.flags)
+ stateSinkObject.Save(4, &d.parent)
+ stateSinkObject.Save(5, &d.name)
+ stateSinkObject.Save(6, &d.cached)
+ stateSinkObject.Save(7, &d.dentryEntry)
+ stateSinkObject.Save(8, &d.children)
+ stateSinkObject.Save(9, &d.inode)
+}
+
+// +checklocksignore
+func (d *Dentry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.vfsd)
+ stateSourceObject.Load(1, &d.refs)
+ stateSourceObject.Load(2, &d.fs)
+ stateSourceObject.Load(3, &d.flags)
+ stateSourceObject.Load(4, &d.parent)
+ stateSourceObject.Load(5, &d.name)
+ stateSourceObject.Load(6, &d.cached)
+ stateSourceObject.Load(7, &d.dentryEntry)
+ stateSourceObject.Load(8, &d.children)
+ stateSourceObject.Load(9, &d.inode)
+ stateSourceObject.AfterLoad(d.afterLoad)
+}
+
+func (i *inodePlatformFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.inodePlatformFile"
+}
+
+func (i *inodePlatformFile) StateFields() []string {
+ return []string{
+ "hostFD",
+ "fdRefs",
+ "fileMapper",
+ }
+}
+
+func (i *inodePlatformFile) beforeSave() {}
+
+// +checklocksignore
+func (i *inodePlatformFile) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.hostFD)
+ stateSinkObject.Save(1, &i.fdRefs)
+ stateSinkObject.Save(2, &i.fileMapper)
+}
+
+// +checklocksignore
+func (i *inodePlatformFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.hostFD)
+ stateSourceObject.Load(1, &i.fdRefs)
+ stateSourceObject.Load(2, &i.fileMapper)
+ stateSourceObject.AfterLoad(i.afterLoad)
+}
+
+func (i *CachedMappable) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.CachedMappable"
+}
+
+func (i *CachedMappable) StateFields() []string {
+ return []string{
+ "mappings",
+ "pf",
+ }
+}
+
+func (i *CachedMappable) beforeSave() {}
+
+// +checklocksignore
+func (i *CachedMappable) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.mappings)
+ stateSinkObject.Save(1, &i.pf)
+}
+
+func (i *CachedMappable) afterLoad() {}
+
+// +checklocksignore
+func (i *CachedMappable) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.mappings)
+ stateSourceObject.Load(1, &i.pf)
+}
+
+func (l *slotList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.slotList"
+}
+
+func (l *slotList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *slotList) beforeSave() {}
+
+// +checklocksignore
+func (l *slotList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *slotList) afterLoad() {}
+
+// +checklocksignore
+func (l *slotList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *slotEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.slotEntry"
+}
+
+func (e *slotEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *slotEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *slotEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *slotEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *slotEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (r *StaticDirectoryRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.StaticDirectoryRefs"
+}
+
+func (r *StaticDirectoryRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *StaticDirectoryRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *StaticDirectoryRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *StaticDirectoryRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (s *StaticSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.StaticSymlink"
+}
+
+func (s *StaticSymlink) StateFields() []string {
+ return []string{
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeSymlink",
+ "InodeNoStatFS",
+ "target",
+ }
+}
+
+func (s *StaticSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *StaticSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.InodeAttrs)
+ stateSinkObject.Save(1, &s.InodeNoopRefCount)
+ stateSinkObject.Save(2, &s.InodeSymlink)
+ stateSinkObject.Save(3, &s.InodeNoStatFS)
+ stateSinkObject.Save(4, &s.target)
+}
+
+func (s *StaticSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *StaticSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.InodeAttrs)
+ stateSourceObject.Load(1, &s.InodeNoopRefCount)
+ stateSourceObject.Load(2, &s.InodeSymlink)
+ stateSourceObject.Load(3, &s.InodeNoStatFS)
+ stateSourceObject.Load(4, &s.target)
+}
+
+func (dir *syntheticDirectory) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.syntheticDirectory"
+}
+
+func (dir *syntheticDirectory) StateFields() []string {
+ return []string{
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeNoStatFS",
+ "InodeNotSymlink",
+ "OrderedChildren",
+ "syntheticDirectoryRefs",
+ "locks",
+ }
+}
+
+func (dir *syntheticDirectory) beforeSave() {}
+
+// +checklocksignore
+func (dir *syntheticDirectory) StateSave(stateSinkObject state.Sink) {
+ dir.beforeSave()
+ stateSinkObject.Save(0, &dir.InodeAlwaysValid)
+ stateSinkObject.Save(1, &dir.InodeAttrs)
+ stateSinkObject.Save(2, &dir.InodeNoStatFS)
+ stateSinkObject.Save(3, &dir.InodeNotSymlink)
+ stateSinkObject.Save(4, &dir.OrderedChildren)
+ stateSinkObject.Save(5, &dir.syntheticDirectoryRefs)
+ stateSinkObject.Save(6, &dir.locks)
+}
+
+func (dir *syntheticDirectory) afterLoad() {}
+
+// +checklocksignore
+func (dir *syntheticDirectory) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &dir.InodeAlwaysValid)
+ stateSourceObject.Load(1, &dir.InodeAttrs)
+ stateSourceObject.Load(2, &dir.InodeNoStatFS)
+ stateSourceObject.Load(3, &dir.InodeNotSymlink)
+ stateSourceObject.Load(4, &dir.OrderedChildren)
+ stateSourceObject.Load(5, &dir.syntheticDirectoryRefs)
+ stateSourceObject.Load(6, &dir.locks)
+}
+
+func (r *syntheticDirectoryRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/kernfs.syntheticDirectoryRefs"
+}
+
+func (r *syntheticDirectoryRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *syntheticDirectoryRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *syntheticDirectoryRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *syntheticDirectoryRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func init() {
+ state.Register((*dentryList)(nil))
+ state.Register((*dentryEntry)(nil))
+ state.Register((*DynamicBytesFile)(nil))
+ state.Register((*DynamicBytesFD)(nil))
+ state.Register((*SeekEndConfig)(nil))
+ state.Register((*GenericDirectoryFDOptions)(nil))
+ state.Register((*GenericDirectoryFD)(nil))
+ state.Register((*InodeNoopRefCount)(nil))
+ state.Register((*InodeDirectoryNoNewChildren)(nil))
+ state.Register((*InodeNotDirectory)(nil))
+ state.Register((*InodeNotSymlink)(nil))
+ state.Register((*InodeAttrs)(nil))
+ state.Register((*slot)(nil))
+ state.Register((*OrderedChildrenOptions)(nil))
+ state.Register((*OrderedChildren)(nil))
+ state.Register((*InodeSymlink)(nil))
+ state.Register((*StaticDirectory)(nil))
+ state.Register((*InodeAlwaysValid)(nil))
+ state.Register((*InodeTemporary)(nil))
+ state.Register((*InodeNoStatFS)(nil))
+ state.Register((*Filesystem)(nil))
+ state.Register((*Dentry)(nil))
+ state.Register((*inodePlatformFile)(nil))
+ state.Register((*CachedMappable)(nil))
+ state.Register((*slotList)(nil))
+ state.Register((*slotEntry)(nil))
+ state.Register((*StaticDirectoryRefs)(nil))
+ state.Register((*StaticSymlink)(nil))
+ state.Register((*syntheticDirectory)(nil))
+ state.Register((*syntheticDirectoryRefs)(nil))
+}
diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_test.go b/pkg/sentry/fsimpl/kernfs/kernfs_test.go
deleted file mode 100644
index a2aba9321..000000000
--- a/pkg/sentry/fsimpl/kernfs/kernfs_test.go
+++ /dev/null
@@ -1,409 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kernfs_test
-
-import (
- "bytes"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-const defaultMode linux.FileMode = 01777
-const staticFileContent = "This is sample content for a static test file."
-
-// RootDentryFn is a generator function for creating the root dentry of a test
-// filesystem. See newTestSystem.
-type RootDentryFn func(context.Context, *auth.Credentials, *filesystem) kernfs.Inode
-
-// newTestSystem sets up a minimal environment for running a test, including an
-// instance of a test filesystem. Tests can control the contents of the
-// filesystem by providing an appropriate rootFn, which should return a
-// pre-populated root dentry.
-func newTestSystem(t *testing.T, rootFn RootDentryFn) *testutil.System {
- ctx := contexttest.Context(t)
- creds := auth.CredentialsFromContext(ctx)
- v := &vfs.VirtualFilesystem{}
- if err := v.Init(ctx); err != nil {
- t.Fatalf("VFS init: %v", err)
- }
- v.MustRegisterFilesystemType("testfs", &fsType{rootFn: rootFn}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
- mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.MountOptions{})
- if err != nil {
- t.Fatalf("Failed to create testfs root mount: %v", err)
- }
- return testutil.NewSystem(ctx, t, v, mns)
-}
-
-type fsType struct {
- rootFn RootDentryFn
-}
-
-type filesystem struct {
- kernfs.Filesystem
-}
-
-// MountOptions implements vfs.FilesystemImpl.MountOptions.
-func (fs *filesystem) MountOptions() string {
- return ""
-}
-
-type file struct {
- kernfs.DynamicBytesFile
- content string
-}
-
-func (fs *filesystem) newFile(ctx context.Context, creds *auth.Credentials, content string) kernfs.Inode {
- f := &file{}
- f.content = content
- f.DynamicBytesFile.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), f, 0777)
- return f
-}
-
-func (f *file) Generate(ctx context.Context, buf *bytes.Buffer) error {
- fmt.Fprintf(buf, "%s", f.content)
- return nil
-}
-
-type attrs struct {
- kernfs.InodeAttrs
-}
-
-func (*attrs) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.SetStatOptions) error {
- return linuxerr.EPERM
-}
-
-type readonlyDir struct {
- readonlyDirRefs
- attrs
- kernfs.InodeAlwaysValid
- kernfs.InodeDirectoryNoNewChildren
- kernfs.InodeNoStatFS
- kernfs.InodeNotSymlink
- kernfs.InodeTemporary
- kernfs.OrderedChildren
-
- locks vfs.FileLocks
-}
-
-func (fs *filesystem) newReadonlyDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode {
- dir := &readonlyDir{}
- dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode)
- dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
- dir.InitRefs()
- dir.IncLinks(dir.OrderedChildren.Populate(contents))
- return dir
-}
-
-func (d *readonlyDir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
- fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{
- SeekEnd: kernfs.SeekEndStaticEntries,
- })
- if err != nil {
- return nil, err
- }
- return fd.VFSFileDescription(), nil
-}
-
-func (d *readonlyDir) DecRef(ctx context.Context) {
- d.readonlyDirRefs.DecRef(func() { d.Destroy(ctx) })
-}
-
-type dir struct {
- dirRefs
- attrs
- kernfs.InodeAlwaysValid
- kernfs.InodeNotSymlink
- kernfs.InodeNoStatFS
- kernfs.InodeTemporary
- kernfs.OrderedChildren
-
- locks vfs.FileLocks
-
- fs *filesystem
-}
-
-func (fs *filesystem) newDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode {
- dir := &dir{}
- dir.fs = fs
- dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode)
- dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{Writable: true})
- dir.InitRefs()
-
- dir.IncLinks(dir.OrderedChildren.Populate(contents))
- return dir
-}
-
-func (d *dir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
- fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{
- SeekEnd: kernfs.SeekEndStaticEntries,
- })
- if err != nil {
- return nil, err
- }
- return fd.VFSFileDescription(), nil
-}
-
-func (d *dir) DecRef(ctx context.Context) {
- d.dirRefs.DecRef(func() { d.Destroy(ctx) })
-}
-
-func (d *dir) NewDir(ctx context.Context, name string, opts vfs.MkdirOptions) (kernfs.Inode, error) {
- creds := auth.CredentialsFromContext(ctx)
- dir := d.fs.newDir(ctx, creds, opts.Mode, nil)
- if err := d.OrderedChildren.Insert(name, dir); err != nil {
- dir.DecRef(ctx)
- return nil, err
- }
- d.TouchCMtime(ctx)
- d.IncLinks(1)
- return dir, nil
-}
-
-func (d *dir) NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (kernfs.Inode, error) {
- creds := auth.CredentialsFromContext(ctx)
- f := d.fs.newFile(ctx, creds, "")
- if err := d.OrderedChildren.Insert(name, f); err != nil {
- f.DecRef(ctx)
- return nil, err
- }
- d.TouchCMtime(ctx)
- return f, nil
-}
-
-func (*dir) NewLink(context.Context, string, kernfs.Inode) (kernfs.Inode, error) {
- return nil, linuxerr.EPERM
-}
-
-func (*dir) NewSymlink(context.Context, string, string) (kernfs.Inode, error) {
- return nil, linuxerr.EPERM
-}
-
-func (*dir) NewNode(context.Context, string, vfs.MknodOptions) (kernfs.Inode, error) {
- return nil, linuxerr.EPERM
-}
-
-func (fsType) Name() string {
- return "kernfs"
-}
-
-func (fsType) Release(ctx context.Context) {}
-
-func (fst fsType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opt vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
- fs := &filesystem{}
- fs.VFSFilesystem().Init(vfsObj, &fst, fs)
- root := fst.rootFn(ctx, creds, fs)
- var d kernfs.Dentry
- d.Init(&fs.Filesystem, root)
- return fs.VFSFilesystem(), d.VFSDentry(), nil
-}
-
-// -------------------- Remainder of the file are test cases --------------------
-
-func TestBasic(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "file1": fs.newFile(ctx, creds, staticFileContent),
- })
- })
- defer sys.Destroy()
- sys.GetDentryOrDie(sys.PathOpAtRoot("file1")).DecRef(sys.Ctx)
-}
-
-func TestMkdirGetDentry(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "dir1": fs.newDir(ctx, creds, 0755, nil),
- })
- })
- defer sys.Destroy()
-
- pop := sys.PathOpAtRoot("dir1/a new directory")
- if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
- t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err)
- }
- sys.GetDentryOrDie(pop).DecRef(sys.Ctx)
-}
-
-func TestReadStaticFile(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "file1": fs.newFile(ctx, creds, staticFileContent),
- })
- })
- defer sys.Destroy()
-
- pop := sys.PathOpAtRoot("file1")
- fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{
- Flags: linux.O_RDONLY,
- })
- if err != nil {
- t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
- }
- defer fd.DecRef(sys.Ctx)
-
- content, err := sys.ReadToEnd(fd)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
- if diff := cmp.Diff(staticFileContent, content); diff != "" {
- t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff)
- }
-}
-
-func TestCreateNewFileInStaticDir(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "dir1": fs.newDir(ctx, creds, 0755, nil),
- })
- })
- defer sys.Destroy()
-
- pop := sys.PathOpAtRoot("dir1/newfile")
- opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode}
- fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, opts)
- if err != nil {
- t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err)
- }
-
- // Close the file. The file should persist.
- fd.DecRef(sys.Ctx)
-
- fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{
- Flags: linux.O_RDONLY,
- })
- if err != nil {
- t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
- }
- fd.DecRef(sys.Ctx)
-}
-
-func TestDirFDReadWrite(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newReadonlyDir(ctx, creds, 0755, nil)
- })
- defer sys.Destroy()
-
- pop := sys.PathOpAtRoot("/")
- fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{
- Flags: linux.O_RDONLY,
- })
- if err != nil {
- t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
- }
- defer fd.DecRef(sys.Ctx)
-
- // Read/Write should fail for directory FDs.
- if _, err := fd.Read(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); !linuxerr.Equals(linuxerr.EISDIR, err) {
- t.Fatalf("Read for directory FD failed with unexpected error: %v", err)
- }
- if _, err := fd.Write(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); !linuxerr.Equals(linuxerr.EBADF, err) {
- t.Fatalf("Write for directory FD failed with unexpected error: %v", err)
- }
-}
-
-func TestDirFDIterDirents(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{
- // Fill root with nodes backed by various inode implementations.
- "dir1": fs.newReadonlyDir(ctx, creds, 0755, nil),
- "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "dir3": fs.newDir(ctx, creds, 0755, nil),
- }),
- "file1": fs.newFile(ctx, creds, staticFileContent),
- })
- })
- defer sys.Destroy()
-
- pop := sys.PathOpAtRoot("/")
- sys.AssertAllDirentTypes(sys.ListDirents(pop), map[string]testutil.DirentType{
- "dir1": linux.DT_DIR,
- "dir2": linux.DT_DIR,
- "file1": linux.DT_REG,
- })
-}
-
-func TestDirWalkDentryTree(t *testing.T) {
- sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode {
- return fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "dir1": fs.newDir(ctx, creds, 0755, nil),
- "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{
- "file1": fs.newFile(ctx, creds, staticFileContent),
- "dir3": fs.newDir(ctx, creds, 0755, nil),
- }),
- })
- })
- defer sys.Destroy()
-
- testWalk := func(from *kernfs.Dentry, getDentryPath, walkPath string, expectedErr error) {
- var d *kernfs.Dentry
- if getDentryPath != "" {
- pop := sys.PathOpAtRoot(getDentryPath)
- vd := sys.GetDentryOrDie(pop)
- defer vd.DecRef(sys.Ctx)
- d = vd.Dentry().Impl().(*kernfs.Dentry)
- }
-
- match, err := from.WalkDentryTree(sys.Ctx, sys.VFS, fspath.Parse(walkPath))
- if err == nil {
- defer match.DecRef(sys.Ctx)
- }
-
- if err != expectedErr {
- t.Fatalf("WalkDentryTree from %q to %q (with expected error: %v) unexpected error, want: %v, got: %v", from.FSLocalPath(), walkPath, expectedErr, expectedErr, err)
- }
- if expectedErr != nil {
- return
- }
-
- if d != match {
- t.Fatalf("WalkDentryTree from %q to %q (with expected error: %v) found unexpected dentry; want: %v, got: %v", from.FSLocalPath(), walkPath, expectedErr, d, match)
- }
- }
-
- rootD := sys.Root.Dentry().Impl().(*kernfs.Dentry)
-
- testWalk(rootD, "dir1", "/dir1", nil)
- testWalk(rootD, "", "/dir-non-existent", linuxerr.ENOENT)
- testWalk(rootD, "", "/dir1/child-non-existent", linuxerr.ENOENT)
- testWalk(rootD, "", "/dir2/inner-non-existent/dir3", linuxerr.ENOENT)
-
- testWalk(rootD, "dir2/dir3", "/dir2/../dir2/dir3", nil)
- testWalk(rootD, "dir2/dir3", "/dir2/././dir3", nil)
- testWalk(rootD, "dir2/dir3", "/dir2/././dir3/.././dir3", nil)
-
- pop := sys.PathOpAtRoot("dir2")
- dir2VD := sys.GetDentryOrDie(pop)
- defer dir2VD.DecRef(sys.Ctx)
- dir2D := dir2VD.Dentry().Impl().(*kernfs.Dentry)
-
- testWalk(dir2D, "dir2/dir3", "/dir3", nil)
- testWalk(dir2D, "dir2/dir3", "/../../../dir3", nil)
- testWalk(dir2D, "dir2/file1", "/file1", nil)
- testWalk(dir2D, "dir2/file1", "file1", nil)
-}
diff --git a/pkg/sentry/fsimpl/kernfs/slot_list.go b/pkg/sentry/fsimpl/kernfs/slot_list.go
new file mode 100644
index 000000000..181fe7c8f
--- /dev/null
+++ b/pkg/sentry/fsimpl/kernfs/slot_list.go
@@ -0,0 +1,221 @@
+package kernfs
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type slotElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (slotElementMapper) linkerFor(elem *slot) *slot { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type slotList struct {
+ head *slot
+ tail *slot
+}
+
+// Reset resets list l to the empty state.
+func (l *slotList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *slotList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *slotList) Front() *slot {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *slotList) Back() *slot {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *slotList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (slotElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *slotList) PushFront(e *slot) {
+ linker := slotElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ slotElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *slotList) PushBack(e *slot) {
+ linker := slotElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ slotElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *slotList) PushBackList(m *slotList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ slotElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ slotElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *slotList) InsertAfter(b, e *slot) {
+ bLinker := slotElementMapper{}.linkerFor(b)
+ eLinker := slotElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ slotElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *slotList) InsertBefore(a, e *slot) {
+ aLinker := slotElementMapper{}.linkerFor(a)
+ eLinker := slotElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ slotElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *slotList) Remove(e *slot) {
+ linker := slotElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ slotElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ slotElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else if l.tail == e {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type slotEntry struct {
+ next *slot
+ prev *slot
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *slotEntry) Next() *slot {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *slotEntry) Prev() *slot {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *slotEntry) SetNext(elem *slot) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *slotEntry) SetPrev(elem *slot) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/kernfs/static_directory_refs.go b/pkg/sentry/fsimpl/kernfs/static_directory_refs.go
new file mode 100644
index 000000000..69534a2d2
--- /dev/null
+++ b/pkg/sentry/fsimpl/kernfs/static_directory_refs.go
@@ -0,0 +1,140 @@
+package kernfs
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const StaticDirectoryenableLogging = false
+
+// obj 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 StaticDirectoryobj *StaticDirectory
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type StaticDirectoryRefs struct {
+ // refCount is composed of two fields:
+ //
+ // [32-bit speculative references]:[32-bit real references]
+ //
+ // Speculative references are used for TryIncRef, to avoid a CompareAndSwap
+ // loop. See IncRef, DecRef and TryIncRef for details of how these fields are
+ // used.
+ refCount int64
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *StaticDirectoryRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *StaticDirectoryRefs) RefType() string {
+ return fmt.Sprintf("%T", StaticDirectoryobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *StaticDirectoryRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *StaticDirectoryRefs) LogRefs() bool {
+ return StaticDirectoryenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *StaticDirectoryRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *StaticDirectoryRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if StaticDirectoryenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.TryIncRef.
+//
+// To do this safely without a loop, a speculative reference is first acquired
+// on the object. This allows multiple concurrent TryIncRef calls to distinguish
+// other TryIncRef calls from genuine references held.
+//
+//go:nosplit
+func (r *StaticDirectoryRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if StaticDirectoryenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ return true
+}
+
+// DecRef implements refs.RefCounter.DecRef.
+//
+// Note that speculative references are counted here. Since they were added
+// prior to real references reaching zero, they will successfully convert to
+// real references. In other words, we see speculative references only in the
+// following case:
+//
+// A: TryIncRef [speculative increase => sees non-negative references]
+// B: DecRef [real decrease]
+// A: TryIncRef [transform speculative to real]
+//
+//go:nosplit
+func (r *StaticDirectoryRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if StaticDirectoryenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *StaticDirectoryRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go b/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go
new file mode 100644
index 000000000..3c5fdf15e
--- /dev/null
+++ b/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go
@@ -0,0 +1,140 @@
+package kernfs
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const syntheticDirectoryenableLogging = false
+
+// obj 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 syntheticDirectoryobj *syntheticDirectory
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type syntheticDirectoryRefs struct {
+ // refCount is composed of two fields:
+ //
+ // [32-bit speculative references]:[32-bit real references]
+ //
+ // Speculative references are used for TryIncRef, to avoid a CompareAndSwap
+ // loop. See IncRef, DecRef and TryIncRef for details of how these fields are
+ // used.
+ refCount int64
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *syntheticDirectoryRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *syntheticDirectoryRefs) RefType() string {
+ return fmt.Sprintf("%T", syntheticDirectoryobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *syntheticDirectoryRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *syntheticDirectoryRefs) LogRefs() bool {
+ return syntheticDirectoryenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *syntheticDirectoryRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *syntheticDirectoryRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if syntheticDirectoryenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.TryIncRef.
+//
+// To do this safely without a loop, a speculative reference is first acquired
+// on the object. This allows multiple concurrent TryIncRef calls to distinguish
+// other TryIncRef calls from genuine references held.
+//
+//go:nosplit
+func (r *syntheticDirectoryRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if syntheticDirectoryenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ return true
+}
+
+// DecRef implements refs.RefCounter.DecRef.
+//
+// Note that speculative references are counted here. Since they were added
+// prior to real references reaching zero, they will successfully convert to
+// real references. In other words, we see speculative references only in the
+// following case:
+//
+// A: TryIncRef [speculative increase => sees non-negative references]
+// B: DecRef [real decrease]
+// A: TryIncRef [transform speculative to real]
+//
+//go:nosplit
+func (r *syntheticDirectoryRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if syntheticDirectoryenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *syntheticDirectoryRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/overlay/BUILD b/pkg/sentry/fsimpl/overlay/BUILD
deleted file mode 100644
index d16dfef9b..000000000
--- a/pkg/sentry/fsimpl/overlay/BUILD
+++ /dev/null
@@ -1,48 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-licenses(["notice"])
-
-go_template_instance(
- name = "fstree",
- out = "fstree.go",
- package = "overlay",
- prefix = "generic",
- template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
- types = {
- "Dentry": "dentry",
- },
-)
-
-go_library(
- name = "overlay",
- srcs = [
- "copy_up.go",
- "directory.go",
- "filesystem.go",
- "fstree.go",
- "overlay.go",
- "regular_file.go",
- "save_restore.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/sentry/arch",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fsimpl/overlay/fstree.go b/pkg/sentry/fsimpl/overlay/fstree.go
new file mode 100644
index 000000000..c3eb062ed
--- /dev/null
+++ b/pkg/sentry/fsimpl/overlay/fstree.go
@@ -0,0 +1,55 @@
+package overlay
+
+import (
+ "gvisor.dev/gvisor/pkg/fspath"
+ "gvisor.dev/gvisor/pkg/sentry/vfs"
+)
+
+// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is
+// either d2's parent or an ancestor of d2's parent.
+func genericIsAncestorDentry(d, d2 *dentry) bool {
+ for d2 != nil {
+ if d2.parent == d {
+ return true
+ }
+ if d2.parent == d2 {
+ return false
+ }
+ d2 = d2.parent
+ }
+ return false
+}
+
+// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d.
+func genericParentOrSelf(d *dentry) *dentry {
+ if d.parent != nil {
+ return d.parent
+ }
+ return d
+}
+
+// PrependPath is a generic implementation of FilesystemImpl.PrependPath().
+func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *dentry, b *fspath.Builder) error {
+ for {
+ if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() {
+ return vfs.PrependPathAtVFSRootError{}
+ }
+ if mnt != nil && &d.vfsd == mnt.Root() {
+ return nil
+ }
+ if d.parent == nil {
+ return vfs.PrependPathAtNonMountRootError{}
+ }
+ b.PrependComponent(d.name)
+ d = d.parent
+ }
+}
+
+// DebugPathname returns a pathname to d relative to its filesystem root.
+// DebugPathname does not correspond to any Linux function; it's used to
+// generate dentry pathnames for debugging.
+func genericDebugPathname(d *dentry) string {
+ var b fspath.Builder
+ _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b)
+ return b.String()
+}
diff --git a/pkg/sentry/fsimpl/overlay/overlay_state_autogen.go b/pkg/sentry/fsimpl/overlay/overlay_state_autogen.go
new file mode 100644
index 000000000..923a2e71a
--- /dev/null
+++ b/pkg/sentry/fsimpl/overlay/overlay_state_autogen.go
@@ -0,0 +1,321 @@
+// automatically generated by stateify.
+
+package overlay
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (fd *directoryFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.directoryFD"
+}
+
+func (fd *directoryFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "DirectoryFileDescriptionDefaultImpl",
+ "DentryMetadataFileDescriptionImpl",
+ "off",
+ "dirents",
+ }
+}
+
+func (fd *directoryFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *directoryFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.DentryMetadataFileDescriptionImpl)
+ stateSinkObject.Save(3, &fd.off)
+ stateSinkObject.Save(4, &fd.dirents)
+}
+
+func (fd *directoryFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *directoryFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.DentryMetadataFileDescriptionImpl)
+ stateSourceObject.Load(3, &fd.off)
+ stateSourceObject.Load(4, &fd.dirents)
+}
+
+func (fstype *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.FilesystemType"
+}
+
+func (fstype *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fstype *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fstype.beforeSave()
+}
+
+func (fstype *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (f *FilesystemOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.FilesystemOptions"
+}
+
+func (f *FilesystemOptions) StateFields() []string {
+ return []string{
+ "UpperRoot",
+ "LowerRoots",
+ }
+}
+
+func (f *FilesystemOptions) beforeSave() {}
+
+// +checklocksignore
+func (f *FilesystemOptions) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.UpperRoot)
+ stateSinkObject.Save(1, &f.LowerRoots)
+}
+
+func (f *FilesystemOptions) afterLoad() {}
+
+// +checklocksignore
+func (f *FilesystemOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.UpperRoot)
+ stateSourceObject.Load(1, &f.LowerRoots)
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "vfsfs",
+ "opts",
+ "creds",
+ "privateDevMinors",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.vfsfs)
+ stateSinkObject.Save(1, &fs.opts)
+ stateSinkObject.Save(2, &fs.creds)
+ stateSinkObject.Save(3, &fs.privateDevMinors)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.vfsfs)
+ stateSourceObject.Load(1, &fs.opts)
+ stateSourceObject.Load(2, &fs.creds)
+ stateSourceObject.Load(3, &fs.privateDevMinors)
+}
+
+func (l *layerDevNumber) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.layerDevNumber"
+}
+
+func (l *layerDevNumber) StateFields() []string {
+ return []string{
+ "major",
+ "minor",
+ }
+}
+
+func (l *layerDevNumber) beforeSave() {}
+
+// +checklocksignore
+func (l *layerDevNumber) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.major)
+ stateSinkObject.Save(1, &l.minor)
+}
+
+func (l *layerDevNumber) afterLoad() {}
+
+// +checklocksignore
+func (l *layerDevNumber) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.major)
+ stateSourceObject.Load(1, &l.minor)
+}
+
+func (d *dentry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.dentry"
+}
+
+func (d *dentry) StateFields() []string {
+ return []string{
+ "vfsd",
+ "refs",
+ "fs",
+ "mode",
+ "uid",
+ "gid",
+ "copiedUp",
+ "parent",
+ "name",
+ "children",
+ "dirents",
+ "upperVD",
+ "lowerVDs",
+ "inlineLowerVDs",
+ "devMajor",
+ "devMinor",
+ "ino",
+ "lowerMappings",
+ "wrappedMappable",
+ "isMappable",
+ "locks",
+ "watches",
+ }
+}
+
+func (d *dentry) beforeSave() {}
+
+// +checklocksignore
+func (d *dentry) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.vfsd)
+ stateSinkObject.Save(1, &d.refs)
+ stateSinkObject.Save(2, &d.fs)
+ stateSinkObject.Save(3, &d.mode)
+ stateSinkObject.Save(4, &d.uid)
+ stateSinkObject.Save(5, &d.gid)
+ stateSinkObject.Save(6, &d.copiedUp)
+ stateSinkObject.Save(7, &d.parent)
+ stateSinkObject.Save(8, &d.name)
+ stateSinkObject.Save(9, &d.children)
+ stateSinkObject.Save(10, &d.dirents)
+ stateSinkObject.Save(11, &d.upperVD)
+ stateSinkObject.Save(12, &d.lowerVDs)
+ stateSinkObject.Save(13, &d.inlineLowerVDs)
+ stateSinkObject.Save(14, &d.devMajor)
+ stateSinkObject.Save(15, &d.devMinor)
+ stateSinkObject.Save(16, &d.ino)
+ stateSinkObject.Save(17, &d.lowerMappings)
+ stateSinkObject.Save(18, &d.wrappedMappable)
+ stateSinkObject.Save(19, &d.isMappable)
+ stateSinkObject.Save(20, &d.locks)
+ stateSinkObject.Save(21, &d.watches)
+}
+
+// +checklocksignore
+func (d *dentry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.vfsd)
+ stateSourceObject.Load(1, &d.refs)
+ stateSourceObject.Load(2, &d.fs)
+ stateSourceObject.Load(3, &d.mode)
+ stateSourceObject.Load(4, &d.uid)
+ stateSourceObject.Load(5, &d.gid)
+ stateSourceObject.Load(6, &d.copiedUp)
+ stateSourceObject.Load(7, &d.parent)
+ stateSourceObject.Load(8, &d.name)
+ stateSourceObject.Load(9, &d.children)
+ stateSourceObject.Load(10, &d.dirents)
+ stateSourceObject.Load(11, &d.upperVD)
+ stateSourceObject.Load(12, &d.lowerVDs)
+ stateSourceObject.Load(13, &d.inlineLowerVDs)
+ stateSourceObject.Load(14, &d.devMajor)
+ stateSourceObject.Load(15, &d.devMinor)
+ stateSourceObject.Load(16, &d.ino)
+ stateSourceObject.Load(17, &d.lowerMappings)
+ stateSourceObject.Load(18, &d.wrappedMappable)
+ stateSourceObject.Load(19, &d.isMappable)
+ stateSourceObject.Load(20, &d.locks)
+ stateSourceObject.Load(21, &d.watches)
+ stateSourceObject.AfterLoad(d.afterLoad)
+}
+
+func (fd *fileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.fileDescription"
+}
+
+func (fd *fileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ }
+}
+
+func (fd *fileDescription) beforeSave() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.LockFD)
+}
+
+func (fd *fileDescription) afterLoad() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.LockFD)
+}
+
+func (fd *regularFileFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/overlay.regularFileFD"
+}
+
+func (fd *regularFileFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "copiedUp",
+ "cachedFD",
+ "cachedFlags",
+ "lowerWaiters",
+ }
+}
+
+func (fd *regularFileFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *regularFileFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.copiedUp)
+ stateSinkObject.Save(2, &fd.cachedFD)
+ stateSinkObject.Save(3, &fd.cachedFlags)
+ stateSinkObject.Save(4, &fd.lowerWaiters)
+}
+
+func (fd *regularFileFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *regularFileFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.copiedUp)
+ stateSourceObject.Load(2, &fd.cachedFD)
+ stateSourceObject.Load(3, &fd.cachedFlags)
+ stateSourceObject.Load(4, &fd.lowerWaiters)
+}
+
+func init() {
+ state.Register((*directoryFD)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*FilesystemOptions)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*layerDevNumber)(nil))
+ state.Register((*dentry)(nil))
+ state.Register((*fileDescription)(nil))
+ state.Register((*regularFileFD)(nil))
+}
diff --git a/pkg/sentry/fsimpl/pipefs/BUILD b/pkg/sentry/fsimpl/pipefs/BUILD
deleted file mode 100644
index a50510031..000000000
--- a/pkg/sentry/fsimpl/pipefs/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-licenses(["notice"])
-
-go_library(
- name = "pipefs",
- srcs = ["pipefs.go"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/pipe",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/vfs",
- ],
-)
diff --git a/pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go b/pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go
new file mode 100644
index 000000000..5f9e117c3
--- /dev/null
+++ b/pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go
@@ -0,0 +1,111 @@
+// automatically generated by stateify.
+
+package pipefs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (f *filesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/pipefs.filesystemType"
+}
+
+func (f *filesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (f *filesystemType) beforeSave() {}
+
+// +checklocksignore
+func (f *filesystemType) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+}
+
+func (f *filesystemType) afterLoad() {}
+
+// +checklocksignore
+func (f *filesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/pipefs.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+}
+
+func (i *inode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/pipefs.inode"
+}
+
+func (i *inode) StateFields() []string {
+ return []string{
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "InodeNoopRefCount",
+ "locks",
+ "pipe",
+ "ino",
+ "uid",
+ "gid",
+ "ctime",
+ }
+}
+
+func (i *inode) beforeSave() {}
+
+// +checklocksignore
+func (i *inode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeNotDirectory)
+ stateSinkObject.Save(1, &i.InodeNotSymlink)
+ stateSinkObject.Save(2, &i.InodeNoopRefCount)
+ stateSinkObject.Save(3, &i.locks)
+ stateSinkObject.Save(4, &i.pipe)
+ stateSinkObject.Save(5, &i.ino)
+ stateSinkObject.Save(6, &i.uid)
+ stateSinkObject.Save(7, &i.gid)
+ stateSinkObject.Save(8, &i.ctime)
+}
+
+func (i *inode) afterLoad() {}
+
+// +checklocksignore
+func (i *inode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeNotDirectory)
+ stateSourceObject.Load(1, &i.InodeNotSymlink)
+ stateSourceObject.Load(2, &i.InodeNoopRefCount)
+ stateSourceObject.Load(3, &i.locks)
+ stateSourceObject.Load(4, &i.pipe)
+ stateSourceObject.Load(5, &i.ino)
+ stateSourceObject.Load(6, &i.uid)
+ stateSourceObject.Load(7, &i.gid)
+ stateSourceObject.Load(8, &i.ctime)
+}
+
+func init() {
+ state.Register((*filesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*inode)(nil))
+}
diff --git a/pkg/sentry/fsimpl/proc/BUILD b/pkg/sentry/fsimpl/proc/BUILD
deleted file mode 100644
index 95cfbdc42..000000000
--- a/pkg/sentry/fsimpl/proc/BUILD
+++ /dev/null
@@ -1,133 +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 = "fd_dir_inode_refs",
- out = "fd_dir_inode_refs.go",
- package = "proc",
- prefix = "fdDirInode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "fdDirInode",
- },
-)
-
-go_template_instance(
- name = "fd_info_dir_inode_refs",
- out = "fd_info_dir_inode_refs.go",
- package = "proc",
- prefix = "fdInfoDirInode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "fdInfoDirInode",
- },
-)
-
-go_template_instance(
- name = "subtasks_inode_refs",
- out = "subtasks_inode_refs.go",
- package = "proc",
- prefix = "subtasksInode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "subtasksInode",
- },
-)
-
-go_template_instance(
- name = "task_inode_refs",
- out = "task_inode_refs.go",
- package = "proc",
- prefix = "taskInode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "taskInode",
- },
-)
-
-go_template_instance(
- name = "tasks_inode_refs",
- out = "tasks_inode_refs.go",
- package = "proc",
- prefix = "tasksInode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "tasksInode",
- },
-)
-
-go_library(
- name = "proc",
- srcs = [
- "fd_dir_inode_refs.go",
- "fd_info_dir_inode_refs.go",
- "filesystem.go",
- "subtasks.go",
- "subtasks_inode_refs.go",
- "task.go",
- "task_fds.go",
- "task_files.go",
- "task_inode_refs.go",
- "task_net.go",
- "tasks.go",
- "tasks_files.go",
- "tasks_inode_refs.go",
- "tasks_sys.go",
- "yama.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fsbridge",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/mm",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/tcpip/header",
- "//pkg/tcpip/network/ipv4",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "proc_test",
- size = "small",
- srcs = [
- "tasks_sys_test.go",
- "tasks_test.go",
- ],
- library = ":proc",
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/fsimpl/testutil",
- "//pkg/sentry/fsimpl/tmpfs",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- ],
-)
diff --git a/pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go b/pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go
new file mode 100644
index 000000000..61138f055
--- /dev/null
+++ b/pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go
@@ -0,0 +1,140 @@
+package proc
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const fdDirInodeenableLogging = false
+
+// obj 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 fdDirInodeobj *fdDirInode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type fdDirInodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *fdDirInodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *fdDirInodeRefs) RefType() string {
+ return fmt.Sprintf("%T", fdDirInodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *fdDirInodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *fdDirInodeRefs) LogRefs() bool {
+ return fdDirInodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *fdDirInodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *fdDirInodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if fdDirInodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *fdDirInodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if fdDirInodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *fdDirInodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if fdDirInodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *fdDirInodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go b/pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go
new file mode 100644
index 000000000..53fb0910a
--- /dev/null
+++ b/pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go
@@ -0,0 +1,140 @@
+package proc
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const fdInfoDirInodeenableLogging = false
+
+// obj 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 fdInfoDirInodeobj *fdInfoDirInode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type fdInfoDirInodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *fdInfoDirInodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *fdInfoDirInodeRefs) RefType() string {
+ return fmt.Sprintf("%T", fdInfoDirInodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *fdInfoDirInodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *fdInfoDirInodeRefs) LogRefs() bool {
+ return fdInfoDirInodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *fdInfoDirInodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *fdInfoDirInodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if fdInfoDirInodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *fdInfoDirInodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if fdInfoDirInodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *fdInfoDirInodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if fdInfoDirInodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *fdInfoDirInodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/proc/proc_state_autogen.go b/pkg/sentry/fsimpl/proc/proc_state_autogen.go
new file mode 100644
index 000000000..e32e7671c
--- /dev/null
+++ b/pkg/sentry/fsimpl/proc/proc_state_autogen.go
@@ -0,0 +1,2454 @@
+// automatically generated by stateify.
+
+package proc
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (r *fdDirInodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdDirInodeRefs"
+}
+
+func (r *fdDirInodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *fdDirInodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *fdDirInodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *fdDirInodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (r *fdInfoDirInodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdInfoDirInodeRefs"
+}
+
+func (r *fdInfoDirInodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *fdInfoDirInodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *fdInfoDirInodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *fdInfoDirInodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (ft *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.FilesystemType"
+}
+
+func (ft *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (ft *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (ft *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ ft.beforeSave()
+}
+
+func (ft *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (ft *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+}
+
+func (s *staticFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.staticFile"
+}
+
+func (s *staticFile) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "StaticData",
+ }
+}
+
+func (s *staticFile) beforeSave() {}
+
+// +checklocksignore
+func (s *staticFile) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.DynamicBytesFile)
+ stateSinkObject.Save(1, &s.StaticData)
+}
+
+func (s *staticFile) afterLoad() {}
+
+// +checklocksignore
+func (s *staticFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.DynamicBytesFile)
+ stateSourceObject.Load(1, &s.StaticData)
+}
+
+func (i *InternalData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.InternalData"
+}
+
+func (i *InternalData) StateFields() []string {
+ return []string{
+ "Cgroups",
+ }
+}
+
+func (i *InternalData) beforeSave() {}
+
+// +checklocksignore
+func (i *InternalData) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.Cgroups)
+}
+
+func (i *InternalData) afterLoad() {}
+
+// +checklocksignore
+func (i *InternalData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.Cgroups)
+}
+
+func (i *implStatFS) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.implStatFS"
+}
+
+func (i *implStatFS) StateFields() []string {
+ return []string{}
+}
+
+func (i *implStatFS) beforeSave() {}
+
+// +checklocksignore
+func (i *implStatFS) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *implStatFS) afterLoad() {}
+
+// +checklocksignore
+func (i *implStatFS) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *subtasksInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.subtasksInode"
+}
+
+func (i *subtasksInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ "subtasksInodeRefs",
+ "locks",
+ "fs",
+ "task",
+ "pidns",
+ "cgroupControllers",
+ }
+}
+
+func (i *subtasksInode) beforeSave() {}
+
+// +checklocksignore
+func (i *subtasksInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.implStatFS)
+ stateSinkObject.Save(1, &i.InodeAlwaysValid)
+ stateSinkObject.Save(2, &i.InodeAttrs)
+ stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(4, &i.InodeNotSymlink)
+ stateSinkObject.Save(5, &i.InodeTemporary)
+ stateSinkObject.Save(6, &i.OrderedChildren)
+ stateSinkObject.Save(7, &i.subtasksInodeRefs)
+ stateSinkObject.Save(8, &i.locks)
+ stateSinkObject.Save(9, &i.fs)
+ stateSinkObject.Save(10, &i.task)
+ stateSinkObject.Save(11, &i.pidns)
+ stateSinkObject.Save(12, &i.cgroupControllers)
+}
+
+func (i *subtasksInode) afterLoad() {}
+
+// +checklocksignore
+func (i *subtasksInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.implStatFS)
+ stateSourceObject.Load(1, &i.InodeAlwaysValid)
+ stateSourceObject.Load(2, &i.InodeAttrs)
+ stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(4, &i.InodeNotSymlink)
+ stateSourceObject.Load(5, &i.InodeTemporary)
+ stateSourceObject.Load(6, &i.OrderedChildren)
+ stateSourceObject.Load(7, &i.subtasksInodeRefs)
+ stateSourceObject.Load(8, &i.locks)
+ stateSourceObject.Load(9, &i.fs)
+ stateSourceObject.Load(10, &i.task)
+ stateSourceObject.Load(11, &i.pidns)
+ stateSourceObject.Load(12, &i.cgroupControllers)
+}
+
+func (fd *subtasksFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.subtasksFD"
+}
+
+func (fd *subtasksFD) StateFields() []string {
+ return []string{
+ "GenericDirectoryFD",
+ "task",
+ }
+}
+
+func (fd *subtasksFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *subtasksFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.GenericDirectoryFD)
+ stateSinkObject.Save(1, &fd.task)
+}
+
+func (fd *subtasksFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *subtasksFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.GenericDirectoryFD)
+ stateSourceObject.Load(1, &fd.task)
+}
+
+func (r *subtasksInodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.subtasksInodeRefs"
+}
+
+func (r *subtasksInodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *subtasksInodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *subtasksInodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *subtasksInodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (i *taskInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.taskInode"
+}
+
+func (i *taskInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ "taskInodeRefs",
+ "locks",
+ "task",
+ }
+}
+
+func (i *taskInode) beforeSave() {}
+
+// +checklocksignore
+func (i *taskInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.implStatFS)
+ stateSinkObject.Save(1, &i.InodeAttrs)
+ stateSinkObject.Save(2, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(3, &i.InodeNotSymlink)
+ stateSinkObject.Save(4, &i.InodeTemporary)
+ stateSinkObject.Save(5, &i.OrderedChildren)
+ stateSinkObject.Save(6, &i.taskInodeRefs)
+ stateSinkObject.Save(7, &i.locks)
+ stateSinkObject.Save(8, &i.task)
+}
+
+func (i *taskInode) afterLoad() {}
+
+// +checklocksignore
+func (i *taskInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.implStatFS)
+ stateSourceObject.Load(1, &i.InodeAttrs)
+ stateSourceObject.Load(2, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(3, &i.InodeNotSymlink)
+ stateSourceObject.Load(4, &i.InodeTemporary)
+ stateSourceObject.Load(5, &i.OrderedChildren)
+ stateSourceObject.Load(6, &i.taskInodeRefs)
+ stateSourceObject.Load(7, &i.locks)
+ stateSourceObject.Load(8, &i.task)
+}
+
+func (i *taskOwnedInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.taskOwnedInode"
+}
+
+func (i *taskOwnedInode) StateFields() []string {
+ return []string{
+ "Inode",
+ "owner",
+ }
+}
+
+func (i *taskOwnedInode) beforeSave() {}
+
+// +checklocksignore
+func (i *taskOwnedInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.Inode)
+ stateSinkObject.Save(1, &i.owner)
+}
+
+func (i *taskOwnedInode) afterLoad() {}
+
+// +checklocksignore
+func (i *taskOwnedInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.Inode)
+ stateSourceObject.Load(1, &i.owner)
+}
+
+func (i *fdDir) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdDir"
+}
+
+func (i *fdDir) StateFields() []string {
+ return []string{
+ "locks",
+ "fs",
+ "task",
+ "produceSymlink",
+ }
+}
+
+func (i *fdDir) beforeSave() {}
+
+// +checklocksignore
+func (i *fdDir) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.locks)
+ stateSinkObject.Save(1, &i.fs)
+ stateSinkObject.Save(2, &i.task)
+ stateSinkObject.Save(3, &i.produceSymlink)
+}
+
+func (i *fdDir) afterLoad() {}
+
+// +checklocksignore
+func (i *fdDir) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.locks)
+ stateSourceObject.Load(1, &i.fs)
+ stateSourceObject.Load(2, &i.task)
+ stateSourceObject.Load(3, &i.produceSymlink)
+}
+
+func (i *fdDirInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdDirInode"
+}
+
+func (i *fdDirInode) StateFields() []string {
+ return []string{
+ "fdDir",
+ "fdDirInodeRefs",
+ "implStatFS",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ }
+}
+
+func (i *fdDirInode) beforeSave() {}
+
+// +checklocksignore
+func (i *fdDirInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.fdDir)
+ stateSinkObject.Save(1, &i.fdDirInodeRefs)
+ stateSinkObject.Save(2, &i.implStatFS)
+ stateSinkObject.Save(3, &i.InodeAlwaysValid)
+ stateSinkObject.Save(4, &i.InodeAttrs)
+ stateSinkObject.Save(5, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(6, &i.InodeNotSymlink)
+ stateSinkObject.Save(7, &i.InodeTemporary)
+ stateSinkObject.Save(8, &i.OrderedChildren)
+}
+
+func (i *fdDirInode) afterLoad() {}
+
+// +checklocksignore
+func (i *fdDirInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.fdDir)
+ stateSourceObject.Load(1, &i.fdDirInodeRefs)
+ stateSourceObject.Load(2, &i.implStatFS)
+ stateSourceObject.Load(3, &i.InodeAlwaysValid)
+ stateSourceObject.Load(4, &i.InodeAttrs)
+ stateSourceObject.Load(5, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(6, &i.InodeNotSymlink)
+ stateSourceObject.Load(7, &i.InodeTemporary)
+ stateSourceObject.Load(8, &i.OrderedChildren)
+}
+
+func (s *fdSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdSymlink"
+}
+
+func (s *fdSymlink) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeSymlink",
+ "fs",
+ "task",
+ "fd",
+ }
+}
+
+func (s *fdSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *fdSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.implStatFS)
+ stateSinkObject.Save(1, &s.InodeAttrs)
+ stateSinkObject.Save(2, &s.InodeNoopRefCount)
+ stateSinkObject.Save(3, &s.InodeSymlink)
+ stateSinkObject.Save(4, &s.fs)
+ stateSinkObject.Save(5, &s.task)
+ stateSinkObject.Save(6, &s.fd)
+}
+
+func (s *fdSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *fdSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.implStatFS)
+ stateSourceObject.Load(1, &s.InodeAttrs)
+ stateSourceObject.Load(2, &s.InodeNoopRefCount)
+ stateSourceObject.Load(3, &s.InodeSymlink)
+ stateSourceObject.Load(4, &s.fs)
+ stateSourceObject.Load(5, &s.task)
+ stateSourceObject.Load(6, &s.fd)
+}
+
+func (i *fdInfoDirInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdInfoDirInode"
+}
+
+func (i *fdInfoDirInode) StateFields() []string {
+ return []string{
+ "fdDir",
+ "fdInfoDirInodeRefs",
+ "implStatFS",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ }
+}
+
+func (i *fdInfoDirInode) beforeSave() {}
+
+// +checklocksignore
+func (i *fdInfoDirInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.fdDir)
+ stateSinkObject.Save(1, &i.fdInfoDirInodeRefs)
+ stateSinkObject.Save(2, &i.implStatFS)
+ stateSinkObject.Save(3, &i.InodeAlwaysValid)
+ stateSinkObject.Save(4, &i.InodeAttrs)
+ stateSinkObject.Save(5, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(6, &i.InodeNotSymlink)
+ stateSinkObject.Save(7, &i.InodeTemporary)
+ stateSinkObject.Save(8, &i.OrderedChildren)
+}
+
+func (i *fdInfoDirInode) afterLoad() {}
+
+// +checklocksignore
+func (i *fdInfoDirInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.fdDir)
+ stateSourceObject.Load(1, &i.fdInfoDirInodeRefs)
+ stateSourceObject.Load(2, &i.implStatFS)
+ stateSourceObject.Load(3, &i.InodeAlwaysValid)
+ stateSourceObject.Load(4, &i.InodeAttrs)
+ stateSourceObject.Load(5, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(6, &i.InodeNotSymlink)
+ stateSourceObject.Load(7, &i.InodeTemporary)
+ stateSourceObject.Load(8, &i.OrderedChildren)
+}
+
+func (d *fdInfoData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.fdInfoData"
+}
+
+func (d *fdInfoData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "fs",
+ "task",
+ "fd",
+ }
+}
+
+func (d *fdInfoData) beforeSave() {}
+
+// +checklocksignore
+func (d *fdInfoData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.fs)
+ stateSinkObject.Save(2, &d.task)
+ stateSinkObject.Save(3, &d.fd)
+}
+
+func (d *fdInfoData) afterLoad() {}
+
+// +checklocksignore
+func (d *fdInfoData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.fs)
+ stateSourceObject.Load(2, &d.task)
+ stateSourceObject.Load(3, &d.fd)
+}
+
+func (d *auxvData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.auxvData"
+}
+
+func (d *auxvData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (d *auxvData) beforeSave() {}
+
+// +checklocksignore
+func (d *auxvData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.task)
+}
+
+func (d *auxvData) afterLoad() {}
+
+// +checklocksignore
+func (d *auxvData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.task)
+}
+
+func (d *cmdlineData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.cmdlineData"
+}
+
+func (d *cmdlineData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ "arg",
+ }
+}
+
+func (d *cmdlineData) beforeSave() {}
+
+// +checklocksignore
+func (d *cmdlineData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.task)
+ stateSinkObject.Save(2, &d.arg)
+}
+
+func (d *cmdlineData) afterLoad() {}
+
+// +checklocksignore
+func (d *cmdlineData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.task)
+ stateSourceObject.Load(2, &d.arg)
+}
+
+func (i *commInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.commInode"
+}
+
+func (i *commInode) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (i *commInode) beforeSave() {}
+
+// +checklocksignore
+func (i *commInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.DynamicBytesFile)
+ stateSinkObject.Save(1, &i.task)
+}
+
+func (i *commInode) afterLoad() {}
+
+// +checklocksignore
+func (i *commInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.DynamicBytesFile)
+ stateSourceObject.Load(1, &i.task)
+}
+
+func (d *commData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.commData"
+}
+
+func (d *commData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (d *commData) beforeSave() {}
+
+// +checklocksignore
+func (d *commData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.task)
+}
+
+func (d *commData) afterLoad() {}
+
+// +checklocksignore
+func (d *commData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.task)
+}
+
+func (d *idMapData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.idMapData"
+}
+
+func (d *idMapData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ "gids",
+ }
+}
+
+func (d *idMapData) beforeSave() {}
+
+// +checklocksignore
+func (d *idMapData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.task)
+ stateSinkObject.Save(2, &d.gids)
+}
+
+func (d *idMapData) afterLoad() {}
+
+// +checklocksignore
+func (d *idMapData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.task)
+ stateSourceObject.Load(2, &d.gids)
+}
+
+func (f *memInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.memInode"
+}
+
+func (f *memInode) StateFields() []string {
+ return []string{
+ "InodeAttrs",
+ "InodeNoStatFS",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "task",
+ "locks",
+ }
+}
+
+func (f *memInode) beforeSave() {}
+
+// +checklocksignore
+func (f *memInode) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.InodeAttrs)
+ stateSinkObject.Save(1, &f.InodeNoStatFS)
+ stateSinkObject.Save(2, &f.InodeNoopRefCount)
+ stateSinkObject.Save(3, &f.InodeNotDirectory)
+ stateSinkObject.Save(4, &f.InodeNotSymlink)
+ stateSinkObject.Save(5, &f.task)
+ stateSinkObject.Save(6, &f.locks)
+}
+
+func (f *memInode) afterLoad() {}
+
+// +checklocksignore
+func (f *memInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.InodeAttrs)
+ stateSourceObject.Load(1, &f.InodeNoStatFS)
+ stateSourceObject.Load(2, &f.InodeNoopRefCount)
+ stateSourceObject.Load(3, &f.InodeNotDirectory)
+ stateSourceObject.Load(4, &f.InodeNotSymlink)
+ stateSourceObject.Load(5, &f.task)
+ stateSourceObject.Load(6, &f.locks)
+}
+
+func (fd *memFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.memFD"
+}
+
+func (fd *memFD) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ "inode",
+ "offset",
+ }
+}
+
+func (fd *memFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *memFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.LockFD)
+ stateSinkObject.Save(3, &fd.inode)
+ stateSinkObject.Save(4, &fd.offset)
+}
+
+func (fd *memFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *memFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.LockFD)
+ stateSourceObject.Load(3, &fd.inode)
+ stateSourceObject.Load(4, &fd.offset)
+}
+
+func (d *mapsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.mapsData"
+}
+
+func (d *mapsData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (d *mapsData) beforeSave() {}
+
+// +checklocksignore
+func (d *mapsData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.task)
+}
+
+func (d *mapsData) afterLoad() {}
+
+// +checklocksignore
+func (d *mapsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.task)
+}
+
+func (d *smapsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.smapsData"
+}
+
+func (d *smapsData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (d *smapsData) beforeSave() {}
+
+// +checklocksignore
+func (d *smapsData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.task)
+}
+
+func (d *smapsData) afterLoad() {}
+
+// +checklocksignore
+func (d *smapsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.task)
+}
+
+func (s *taskStatData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.taskStatData"
+}
+
+func (s *taskStatData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ "tgstats",
+ "pidns",
+ }
+}
+
+func (s *taskStatData) beforeSave() {}
+
+// +checklocksignore
+func (s *taskStatData) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.DynamicBytesFile)
+ stateSinkObject.Save(1, &s.task)
+ stateSinkObject.Save(2, &s.tgstats)
+ stateSinkObject.Save(3, &s.pidns)
+}
+
+func (s *taskStatData) afterLoad() {}
+
+// +checklocksignore
+func (s *taskStatData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.DynamicBytesFile)
+ stateSourceObject.Load(1, &s.task)
+ stateSourceObject.Load(2, &s.tgstats)
+ stateSourceObject.Load(3, &s.pidns)
+}
+
+func (s *statmData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.statmData"
+}
+
+func (s *statmData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (s *statmData) beforeSave() {}
+
+// +checklocksignore
+func (s *statmData) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.DynamicBytesFile)
+ stateSinkObject.Save(1, &s.task)
+}
+
+func (s *statmData) afterLoad() {}
+
+// +checklocksignore
+func (s *statmData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.DynamicBytesFile)
+ stateSourceObject.Load(1, &s.task)
+}
+
+func (s *statusInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.statusInode"
+}
+
+func (s *statusInode) StateFields() []string {
+ return []string{
+ "InodeAttrs",
+ "InodeNoStatFS",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "task",
+ "pidns",
+ "locks",
+ }
+}
+
+func (s *statusInode) beforeSave() {}
+
+// +checklocksignore
+func (s *statusInode) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.InodeAttrs)
+ stateSinkObject.Save(1, &s.InodeNoStatFS)
+ stateSinkObject.Save(2, &s.InodeNoopRefCount)
+ stateSinkObject.Save(3, &s.InodeNotDirectory)
+ stateSinkObject.Save(4, &s.InodeNotSymlink)
+ stateSinkObject.Save(5, &s.task)
+ stateSinkObject.Save(6, &s.pidns)
+ stateSinkObject.Save(7, &s.locks)
+}
+
+func (s *statusInode) afterLoad() {}
+
+// +checklocksignore
+func (s *statusInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.InodeAttrs)
+ stateSourceObject.Load(1, &s.InodeNoStatFS)
+ stateSourceObject.Load(2, &s.InodeNoopRefCount)
+ stateSourceObject.Load(3, &s.InodeNotDirectory)
+ stateSourceObject.Load(4, &s.InodeNotSymlink)
+ stateSourceObject.Load(5, &s.task)
+ stateSourceObject.Load(6, &s.pidns)
+ stateSourceObject.Load(7, &s.locks)
+}
+
+func (s *statusFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.statusFD"
+}
+
+func (s *statusFD) StateFields() []string {
+ return []string{
+ "statusFDLowerBase",
+ "DynamicBytesFileDescriptionImpl",
+ "LockFD",
+ "vfsfd",
+ "inode",
+ "task",
+ "pidns",
+ "userns",
+ }
+}
+
+func (s *statusFD) beforeSave() {}
+
+// +checklocksignore
+func (s *statusFD) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.statusFDLowerBase)
+ stateSinkObject.Save(1, &s.DynamicBytesFileDescriptionImpl)
+ stateSinkObject.Save(2, &s.LockFD)
+ stateSinkObject.Save(3, &s.vfsfd)
+ stateSinkObject.Save(4, &s.inode)
+ stateSinkObject.Save(5, &s.task)
+ stateSinkObject.Save(6, &s.pidns)
+ stateSinkObject.Save(7, &s.userns)
+}
+
+func (s *statusFD) afterLoad() {}
+
+// +checklocksignore
+func (s *statusFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.statusFDLowerBase)
+ stateSourceObject.Load(1, &s.DynamicBytesFileDescriptionImpl)
+ stateSourceObject.Load(2, &s.LockFD)
+ stateSourceObject.Load(3, &s.vfsfd)
+ stateSourceObject.Load(4, &s.inode)
+ stateSourceObject.Load(5, &s.task)
+ stateSourceObject.Load(6, &s.pidns)
+ stateSourceObject.Load(7, &s.userns)
+}
+
+func (s *statusFDLowerBase) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.statusFDLowerBase"
+}
+
+func (s *statusFDLowerBase) StateFields() []string {
+ return []string{
+ "FileDescriptionDefaultImpl",
+ }
+}
+
+func (s *statusFDLowerBase) beforeSave() {}
+
+// +checklocksignore
+func (s *statusFDLowerBase) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.FileDescriptionDefaultImpl)
+}
+
+func (s *statusFDLowerBase) afterLoad() {}
+
+// +checklocksignore
+func (s *statusFDLowerBase) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.FileDescriptionDefaultImpl)
+}
+
+func (i *ioData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.ioData"
+}
+
+func (i *ioData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "ioUsage",
+ }
+}
+
+func (i *ioData) beforeSave() {}
+
+// +checklocksignore
+func (i *ioData) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.DynamicBytesFile)
+ stateSinkObject.Save(1, &i.ioUsage)
+}
+
+func (i *ioData) afterLoad() {}
+
+// +checklocksignore
+func (i *ioData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.DynamicBytesFile)
+ stateSourceObject.Load(1, &i.ioUsage)
+}
+
+func (o *oomScoreAdj) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.oomScoreAdj"
+}
+
+func (o *oomScoreAdj) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "task",
+ }
+}
+
+func (o *oomScoreAdj) beforeSave() {}
+
+// +checklocksignore
+func (o *oomScoreAdj) StateSave(stateSinkObject state.Sink) {
+ o.beforeSave()
+ stateSinkObject.Save(0, &o.DynamicBytesFile)
+ stateSinkObject.Save(1, &o.task)
+}
+
+func (o *oomScoreAdj) afterLoad() {}
+
+// +checklocksignore
+func (o *oomScoreAdj) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &o.DynamicBytesFile)
+ stateSourceObject.Load(1, &o.task)
+}
+
+func (s *exeSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.exeSymlink"
+}
+
+func (s *exeSymlink) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeSymlink",
+ "fs",
+ "task",
+ }
+}
+
+func (s *exeSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *exeSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.implStatFS)
+ stateSinkObject.Save(1, &s.InodeAttrs)
+ stateSinkObject.Save(2, &s.InodeNoopRefCount)
+ stateSinkObject.Save(3, &s.InodeSymlink)
+ stateSinkObject.Save(4, &s.fs)
+ stateSinkObject.Save(5, &s.task)
+}
+
+func (s *exeSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *exeSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.implStatFS)
+ stateSourceObject.Load(1, &s.InodeAttrs)
+ stateSourceObject.Load(2, &s.InodeNoopRefCount)
+ stateSourceObject.Load(3, &s.InodeSymlink)
+ stateSourceObject.Load(4, &s.fs)
+ stateSourceObject.Load(5, &s.task)
+}
+
+func (s *cwdSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.cwdSymlink"
+}
+
+func (s *cwdSymlink) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeSymlink",
+ "fs",
+ "task",
+ }
+}
+
+func (s *cwdSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *cwdSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.implStatFS)
+ stateSinkObject.Save(1, &s.InodeAttrs)
+ stateSinkObject.Save(2, &s.InodeNoopRefCount)
+ stateSinkObject.Save(3, &s.InodeSymlink)
+ stateSinkObject.Save(4, &s.fs)
+ stateSinkObject.Save(5, &s.task)
+}
+
+func (s *cwdSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *cwdSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.implStatFS)
+ stateSourceObject.Load(1, &s.InodeAttrs)
+ stateSourceObject.Load(2, &s.InodeNoopRefCount)
+ stateSourceObject.Load(3, &s.InodeSymlink)
+ stateSourceObject.Load(4, &s.fs)
+ stateSourceObject.Load(5, &s.task)
+}
+
+func (i *mountInfoData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.mountInfoData"
+}
+
+func (i *mountInfoData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "fs",
+ "task",
+ }
+}
+
+func (i *mountInfoData) beforeSave() {}
+
+// +checklocksignore
+func (i *mountInfoData) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.DynamicBytesFile)
+ stateSinkObject.Save(1, &i.fs)
+ stateSinkObject.Save(2, &i.task)
+}
+
+func (i *mountInfoData) afterLoad() {}
+
+// +checklocksignore
+func (i *mountInfoData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.DynamicBytesFile)
+ stateSourceObject.Load(1, &i.fs)
+ stateSourceObject.Load(2, &i.task)
+}
+
+func (i *mountsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.mountsData"
+}
+
+func (i *mountsData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "fs",
+ "task",
+ }
+}
+
+func (i *mountsData) beforeSave() {}
+
+// +checklocksignore
+func (i *mountsData) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.DynamicBytesFile)
+ stateSinkObject.Save(1, &i.fs)
+ stateSinkObject.Save(2, &i.task)
+}
+
+func (i *mountsData) afterLoad() {}
+
+// +checklocksignore
+func (i *mountsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.DynamicBytesFile)
+ stateSourceObject.Load(1, &i.fs)
+ stateSourceObject.Load(2, &i.task)
+}
+
+func (s *namespaceSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.namespaceSymlink"
+}
+
+func (s *namespaceSymlink) StateFields() []string {
+ return []string{
+ "StaticSymlink",
+ "task",
+ }
+}
+
+func (s *namespaceSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *namespaceSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.StaticSymlink)
+ stateSinkObject.Save(1, &s.task)
+}
+
+func (s *namespaceSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *namespaceSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.StaticSymlink)
+ stateSourceObject.Load(1, &s.task)
+}
+
+func (i *namespaceInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.namespaceInode"
+}
+
+func (i *namespaceInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "locks",
+ }
+}
+
+func (i *namespaceInode) beforeSave() {}
+
+// +checklocksignore
+func (i *namespaceInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.implStatFS)
+ stateSinkObject.Save(1, &i.InodeAttrs)
+ stateSinkObject.Save(2, &i.InodeNoopRefCount)
+ stateSinkObject.Save(3, &i.InodeNotDirectory)
+ stateSinkObject.Save(4, &i.InodeNotSymlink)
+ stateSinkObject.Save(5, &i.locks)
+}
+
+func (i *namespaceInode) afterLoad() {}
+
+// +checklocksignore
+func (i *namespaceInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.implStatFS)
+ stateSourceObject.Load(1, &i.InodeAttrs)
+ stateSourceObject.Load(2, &i.InodeNoopRefCount)
+ stateSourceObject.Load(3, &i.InodeNotDirectory)
+ stateSourceObject.Load(4, &i.InodeNotSymlink)
+ stateSourceObject.Load(5, &i.locks)
+}
+
+func (fd *namespaceFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.namespaceFD"
+}
+
+func (fd *namespaceFD) StateFields() []string {
+ return []string{
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ "vfsfd",
+ "inode",
+ }
+}
+
+func (fd *namespaceFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *namespaceFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(1, &fd.LockFD)
+ stateSinkObject.Save(2, &fd.vfsfd)
+ stateSinkObject.Save(3, &fd.inode)
+}
+
+func (fd *namespaceFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *namespaceFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(1, &fd.LockFD)
+ stateSourceObject.Load(2, &fd.vfsfd)
+ stateSourceObject.Load(3, &fd.inode)
+}
+
+func (d *taskCgroupData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.taskCgroupData"
+}
+
+func (d *taskCgroupData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ "task",
+ }
+}
+
+func (d *taskCgroupData) beforeSave() {}
+
+// +checklocksignore
+func (d *taskCgroupData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.dynamicBytesFileSetAttr)
+ stateSinkObject.Save(1, &d.task)
+}
+
+func (d *taskCgroupData) afterLoad() {}
+
+// +checklocksignore
+func (d *taskCgroupData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.dynamicBytesFileSetAttr)
+ stateSourceObject.Load(1, &d.task)
+}
+
+func (r *taskInodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.taskInodeRefs"
+}
+
+func (r *taskInodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *taskInodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *taskInodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *taskInodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (n *ifinet6) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.ifinet6"
+}
+
+func (n *ifinet6) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ }
+}
+
+func (n *ifinet6) beforeSave() {}
+
+// +checklocksignore
+func (n *ifinet6) StateSave(stateSinkObject state.Sink) {
+ n.beforeSave()
+ stateSinkObject.Save(0, &n.DynamicBytesFile)
+ stateSinkObject.Save(1, &n.stack)
+}
+
+func (n *ifinet6) afterLoad() {}
+
+// +checklocksignore
+func (n *ifinet6) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &n.DynamicBytesFile)
+ stateSourceObject.Load(1, &n.stack)
+}
+
+func (n *netDevData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netDevData"
+}
+
+func (n *netDevData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ }
+}
+
+func (n *netDevData) beforeSave() {}
+
+// +checklocksignore
+func (n *netDevData) StateSave(stateSinkObject state.Sink) {
+ n.beforeSave()
+ stateSinkObject.Save(0, &n.DynamicBytesFile)
+ stateSinkObject.Save(1, &n.stack)
+}
+
+func (n *netDevData) afterLoad() {}
+
+// +checklocksignore
+func (n *netDevData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &n.DynamicBytesFile)
+ stateSourceObject.Load(1, &n.stack)
+}
+
+func (n *netUnixData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netUnixData"
+}
+
+func (n *netUnixData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "kernel",
+ }
+}
+
+func (n *netUnixData) beforeSave() {}
+
+// +checklocksignore
+func (n *netUnixData) StateSave(stateSinkObject state.Sink) {
+ n.beforeSave()
+ stateSinkObject.Save(0, &n.DynamicBytesFile)
+ stateSinkObject.Save(1, &n.kernel)
+}
+
+func (n *netUnixData) afterLoad() {}
+
+// +checklocksignore
+func (n *netUnixData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &n.DynamicBytesFile)
+ stateSourceObject.Load(1, &n.kernel)
+}
+
+func (d *netTCPData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netTCPData"
+}
+
+func (d *netTCPData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "kernel",
+ }
+}
+
+func (d *netTCPData) beforeSave() {}
+
+// +checklocksignore
+func (d *netTCPData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.kernel)
+}
+
+func (d *netTCPData) afterLoad() {}
+
+// +checklocksignore
+func (d *netTCPData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.kernel)
+}
+
+func (d *netTCP6Data) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netTCP6Data"
+}
+
+func (d *netTCP6Data) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "kernel",
+ }
+}
+
+func (d *netTCP6Data) beforeSave() {}
+
+// +checklocksignore
+func (d *netTCP6Data) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.kernel)
+}
+
+func (d *netTCP6Data) afterLoad() {}
+
+// +checklocksignore
+func (d *netTCP6Data) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.kernel)
+}
+
+func (d *netUDPData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netUDPData"
+}
+
+func (d *netUDPData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "kernel",
+ }
+}
+
+func (d *netUDPData) beforeSave() {}
+
+// +checklocksignore
+func (d *netUDPData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.kernel)
+}
+
+func (d *netUDPData) afterLoad() {}
+
+// +checklocksignore
+func (d *netUDPData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.kernel)
+}
+
+func (d *netSnmpData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netSnmpData"
+}
+
+func (d *netSnmpData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ }
+}
+
+func (d *netSnmpData) beforeSave() {}
+
+// +checklocksignore
+func (d *netSnmpData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.stack)
+}
+
+func (d *netSnmpData) afterLoad() {}
+
+// +checklocksignore
+func (d *netSnmpData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.stack)
+}
+
+func (s *snmpLine) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.snmpLine"
+}
+
+func (s *snmpLine) StateFields() []string {
+ return []string{
+ "prefix",
+ "header",
+ }
+}
+
+func (s *snmpLine) beforeSave() {}
+
+// +checklocksignore
+func (s *snmpLine) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.prefix)
+ stateSinkObject.Save(1, &s.header)
+}
+
+func (s *snmpLine) afterLoad() {}
+
+// +checklocksignore
+func (s *snmpLine) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.prefix)
+ stateSourceObject.Load(1, &s.header)
+}
+
+func (d *netRouteData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netRouteData"
+}
+
+func (d *netRouteData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ }
+}
+
+func (d *netRouteData) beforeSave() {}
+
+// +checklocksignore
+func (d *netRouteData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.stack)
+}
+
+func (d *netRouteData) afterLoad() {}
+
+// +checklocksignore
+func (d *netRouteData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.stack)
+}
+
+func (d *netStatData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.netStatData"
+}
+
+func (d *netStatData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ }
+}
+
+func (d *netStatData) beforeSave() {}
+
+// +checklocksignore
+func (d *netStatData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.stack)
+}
+
+func (d *netStatData) afterLoad() {}
+
+// +checklocksignore
+func (d *netStatData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.stack)
+}
+
+func (i *tasksInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.tasksInode"
+}
+
+func (i *tasksInode) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeDirectoryNoNewChildren",
+ "InodeNotSymlink",
+ "InodeTemporary",
+ "OrderedChildren",
+ "tasksInodeRefs",
+ "locks",
+ "fs",
+ "pidns",
+ "fakeCgroupControllers",
+ }
+}
+
+func (i *tasksInode) beforeSave() {}
+
+// +checklocksignore
+func (i *tasksInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.implStatFS)
+ stateSinkObject.Save(1, &i.InodeAlwaysValid)
+ stateSinkObject.Save(2, &i.InodeAttrs)
+ stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(4, &i.InodeNotSymlink)
+ stateSinkObject.Save(5, &i.InodeTemporary)
+ stateSinkObject.Save(6, &i.OrderedChildren)
+ stateSinkObject.Save(7, &i.tasksInodeRefs)
+ stateSinkObject.Save(8, &i.locks)
+ stateSinkObject.Save(9, &i.fs)
+ stateSinkObject.Save(10, &i.pidns)
+ stateSinkObject.Save(11, &i.fakeCgroupControllers)
+}
+
+func (i *tasksInode) afterLoad() {}
+
+// +checklocksignore
+func (i *tasksInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.implStatFS)
+ stateSourceObject.Load(1, &i.InodeAlwaysValid)
+ stateSourceObject.Load(2, &i.InodeAttrs)
+ stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(4, &i.InodeNotSymlink)
+ stateSourceObject.Load(5, &i.InodeTemporary)
+ stateSourceObject.Load(6, &i.OrderedChildren)
+ stateSourceObject.Load(7, &i.tasksInodeRefs)
+ stateSourceObject.Load(8, &i.locks)
+ stateSourceObject.Load(9, &i.fs)
+ stateSourceObject.Load(10, &i.pidns)
+ stateSourceObject.Load(11, &i.fakeCgroupControllers)
+}
+
+func (s *staticFileSetStat) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.staticFileSetStat"
+}
+
+func (s *staticFileSetStat) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ "StaticData",
+ }
+}
+
+func (s *staticFileSetStat) beforeSave() {}
+
+// +checklocksignore
+func (s *staticFileSetStat) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.dynamicBytesFileSetAttr)
+ stateSinkObject.Save(1, &s.StaticData)
+}
+
+func (s *staticFileSetStat) afterLoad() {}
+
+// +checklocksignore
+func (s *staticFileSetStat) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.dynamicBytesFileSetAttr)
+ stateSourceObject.Load(1, &s.StaticData)
+}
+
+func (s *selfSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.selfSymlink"
+}
+
+func (s *selfSymlink) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeSymlink",
+ "pidns",
+ }
+}
+
+func (s *selfSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *selfSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.implStatFS)
+ stateSinkObject.Save(1, &s.InodeAttrs)
+ stateSinkObject.Save(2, &s.InodeNoopRefCount)
+ stateSinkObject.Save(3, &s.InodeSymlink)
+ stateSinkObject.Save(4, &s.pidns)
+}
+
+func (s *selfSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *selfSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.implStatFS)
+ stateSourceObject.Load(1, &s.InodeAttrs)
+ stateSourceObject.Load(2, &s.InodeNoopRefCount)
+ stateSourceObject.Load(3, &s.InodeSymlink)
+ stateSourceObject.Load(4, &s.pidns)
+}
+
+func (s *threadSelfSymlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.threadSelfSymlink"
+}
+
+func (s *threadSelfSymlink) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeSymlink",
+ "pidns",
+ }
+}
+
+func (s *threadSelfSymlink) beforeSave() {}
+
+// +checklocksignore
+func (s *threadSelfSymlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.implStatFS)
+ stateSinkObject.Save(1, &s.InodeAttrs)
+ stateSinkObject.Save(2, &s.InodeNoopRefCount)
+ stateSinkObject.Save(3, &s.InodeSymlink)
+ stateSinkObject.Save(4, &s.pidns)
+}
+
+func (s *threadSelfSymlink) afterLoad() {}
+
+// +checklocksignore
+func (s *threadSelfSymlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.implStatFS)
+ stateSourceObject.Load(1, &s.InodeAttrs)
+ stateSourceObject.Load(2, &s.InodeNoopRefCount)
+ stateSourceObject.Load(3, &s.InodeSymlink)
+ stateSourceObject.Load(4, &s.pidns)
+}
+
+func (d *dynamicBytesFileSetAttr) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.dynamicBytesFileSetAttr"
+}
+
+func (d *dynamicBytesFileSetAttr) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ }
+}
+
+func (d *dynamicBytesFileSetAttr) beforeSave() {}
+
+// +checklocksignore
+func (d *dynamicBytesFileSetAttr) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+}
+
+func (d *dynamicBytesFileSetAttr) afterLoad() {}
+
+// +checklocksignore
+func (d *dynamicBytesFileSetAttr) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+}
+
+func (c *cpuStats) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.cpuStats"
+}
+
+func (c *cpuStats) StateFields() []string {
+ return []string{
+ "user",
+ "nice",
+ "system",
+ "idle",
+ "ioWait",
+ "irq",
+ "softirq",
+ "steal",
+ "guest",
+ "guestNice",
+ }
+}
+
+func (c *cpuStats) beforeSave() {}
+
+// +checklocksignore
+func (c *cpuStats) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.user)
+ stateSinkObject.Save(1, &c.nice)
+ stateSinkObject.Save(2, &c.system)
+ stateSinkObject.Save(3, &c.idle)
+ stateSinkObject.Save(4, &c.ioWait)
+ stateSinkObject.Save(5, &c.irq)
+ stateSinkObject.Save(6, &c.softirq)
+ stateSinkObject.Save(7, &c.steal)
+ stateSinkObject.Save(8, &c.guest)
+ stateSinkObject.Save(9, &c.guestNice)
+}
+
+func (c *cpuStats) afterLoad() {}
+
+// +checklocksignore
+func (c *cpuStats) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.user)
+ stateSourceObject.Load(1, &c.nice)
+ stateSourceObject.Load(2, &c.system)
+ stateSourceObject.Load(3, &c.idle)
+ stateSourceObject.Load(4, &c.ioWait)
+ stateSourceObject.Load(5, &c.irq)
+ stateSourceObject.Load(6, &c.softirq)
+ stateSourceObject.Load(7, &c.steal)
+ stateSourceObject.Load(8, &c.guest)
+ stateSourceObject.Load(9, &c.guestNice)
+}
+
+func (s *statData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.statData"
+}
+
+func (s *statData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (s *statData) beforeSave() {}
+
+// +checklocksignore
+func (s *statData) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.dynamicBytesFileSetAttr)
+}
+
+func (s *statData) afterLoad() {}
+
+// +checklocksignore
+func (s *statData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.dynamicBytesFileSetAttr)
+}
+
+func (l *loadavgData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.loadavgData"
+}
+
+func (l *loadavgData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (l *loadavgData) beforeSave() {}
+
+// +checklocksignore
+func (l *loadavgData) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.dynamicBytesFileSetAttr)
+}
+
+func (l *loadavgData) afterLoad() {}
+
+// +checklocksignore
+func (l *loadavgData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.dynamicBytesFileSetAttr)
+}
+
+func (m *meminfoData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.meminfoData"
+}
+
+func (m *meminfoData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (m *meminfoData) beforeSave() {}
+
+// +checklocksignore
+func (m *meminfoData) StateSave(stateSinkObject state.Sink) {
+ m.beforeSave()
+ stateSinkObject.Save(0, &m.dynamicBytesFileSetAttr)
+}
+
+func (m *meminfoData) afterLoad() {}
+
+// +checklocksignore
+func (m *meminfoData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &m.dynamicBytesFileSetAttr)
+}
+
+func (u *uptimeData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.uptimeData"
+}
+
+func (u *uptimeData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (u *uptimeData) beforeSave() {}
+
+// +checklocksignore
+func (u *uptimeData) StateSave(stateSinkObject state.Sink) {
+ u.beforeSave()
+ stateSinkObject.Save(0, &u.dynamicBytesFileSetAttr)
+}
+
+func (u *uptimeData) afterLoad() {}
+
+// +checklocksignore
+func (u *uptimeData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &u.dynamicBytesFileSetAttr)
+}
+
+func (v *versionData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.versionData"
+}
+
+func (v *versionData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (v *versionData) beforeSave() {}
+
+// +checklocksignore
+func (v *versionData) StateSave(stateSinkObject state.Sink) {
+ v.beforeSave()
+ stateSinkObject.Save(0, &v.dynamicBytesFileSetAttr)
+}
+
+func (v *versionData) afterLoad() {}
+
+// +checklocksignore
+func (v *versionData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &v.dynamicBytesFileSetAttr)
+}
+
+func (d *filesystemsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.filesystemsData"
+}
+
+func (d *filesystemsData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ }
+}
+
+func (d *filesystemsData) beforeSave() {}
+
+// +checklocksignore
+func (d *filesystemsData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+}
+
+func (d *filesystemsData) afterLoad() {}
+
+// +checklocksignore
+func (d *filesystemsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+}
+
+func (c *cgroupsData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.cgroupsData"
+}
+
+func (c *cgroupsData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (c *cgroupsData) beforeSave() {}
+
+// +checklocksignore
+func (c *cgroupsData) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.dynamicBytesFileSetAttr)
+}
+
+func (c *cgroupsData) afterLoad() {}
+
+// +checklocksignore
+func (c *cgroupsData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.dynamicBytesFileSetAttr)
+}
+
+func (c *cmdLineData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.cmdLineData"
+}
+
+func (c *cmdLineData) StateFields() []string {
+ return []string{
+ "dynamicBytesFileSetAttr",
+ }
+}
+
+func (c *cmdLineData) beforeSave() {}
+
+// +checklocksignore
+func (c *cmdLineData) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.dynamicBytesFileSetAttr)
+}
+
+func (c *cmdLineData) afterLoad() {}
+
+// +checklocksignore
+func (c *cmdLineData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.dynamicBytesFileSetAttr)
+}
+
+func (r *tasksInodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.tasksInodeRefs"
+}
+
+func (r *tasksInodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *tasksInodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *tasksInodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *tasksInodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (t *tcpMemDir) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.tcpMemDir"
+}
+
+func (t *tcpMemDir) StateFields() []string {
+ return nil
+}
+
+func (d *mmapMinAddrData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.mmapMinAddrData"
+}
+
+func (d *mmapMinAddrData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "k",
+ }
+}
+
+func (d *mmapMinAddrData) beforeSave() {}
+
+// +checklocksignore
+func (d *mmapMinAddrData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.k)
+}
+
+func (d *mmapMinAddrData) afterLoad() {}
+
+// +checklocksignore
+func (d *mmapMinAddrData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.k)
+}
+
+func (h *hostnameData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.hostnameData"
+}
+
+func (h *hostnameData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ }
+}
+
+func (h *hostnameData) beforeSave() {}
+
+// +checklocksignore
+func (h *hostnameData) StateSave(stateSinkObject state.Sink) {
+ h.beforeSave()
+ stateSinkObject.Save(0, &h.DynamicBytesFile)
+}
+
+func (h *hostnameData) afterLoad() {}
+
+// +checklocksignore
+func (h *hostnameData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &h.DynamicBytesFile)
+}
+
+func (d *tcpSackData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.tcpSackData"
+}
+
+func (d *tcpSackData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ "enabled",
+ }
+}
+
+func (d *tcpSackData) beforeSave() {}
+
+// +checklocksignore
+func (d *tcpSackData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.stack)
+ stateSinkObject.Save(2, &d.enabled)
+}
+
+func (d *tcpSackData) afterLoad() {}
+
+// +checklocksignore
+func (d *tcpSackData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.LoadWait(1, &d.stack)
+ stateSourceObject.Load(2, &d.enabled)
+}
+
+func (d *tcpRecoveryData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.tcpRecoveryData"
+}
+
+func (d *tcpRecoveryData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ }
+}
+
+func (d *tcpRecoveryData) beforeSave() {}
+
+// +checklocksignore
+func (d *tcpRecoveryData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.stack)
+}
+
+func (d *tcpRecoveryData) afterLoad() {}
+
+// +checklocksignore
+func (d *tcpRecoveryData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.LoadWait(1, &d.stack)
+}
+
+func (d *tcpMemData) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.tcpMemData"
+}
+
+func (d *tcpMemData) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "dir",
+ "stack",
+ }
+}
+
+func (d *tcpMemData) beforeSave() {}
+
+// +checklocksignore
+func (d *tcpMemData) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.DynamicBytesFile)
+ stateSinkObject.Save(1, &d.dir)
+ stateSinkObject.Save(2, &d.stack)
+}
+
+func (d *tcpMemData) afterLoad() {}
+
+// +checklocksignore
+func (d *tcpMemData) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.DynamicBytesFile)
+ stateSourceObject.Load(1, &d.dir)
+ stateSourceObject.LoadWait(2, &d.stack)
+}
+
+func (ipf *ipForwarding) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.ipForwarding"
+}
+
+func (ipf *ipForwarding) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ "enabled",
+ }
+}
+
+func (ipf *ipForwarding) beforeSave() {}
+
+// +checklocksignore
+func (ipf *ipForwarding) StateSave(stateSinkObject state.Sink) {
+ ipf.beforeSave()
+ stateSinkObject.Save(0, &ipf.DynamicBytesFile)
+ stateSinkObject.Save(1, &ipf.stack)
+ stateSinkObject.Save(2, &ipf.enabled)
+}
+
+func (ipf *ipForwarding) afterLoad() {}
+
+// +checklocksignore
+func (ipf *ipForwarding) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &ipf.DynamicBytesFile)
+ stateSourceObject.LoadWait(1, &ipf.stack)
+ stateSourceObject.Load(2, &ipf.enabled)
+}
+
+func (pr *portRange) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.portRange"
+}
+
+func (pr *portRange) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "stack",
+ "start",
+ "end",
+ }
+}
+
+func (pr *portRange) beforeSave() {}
+
+// +checklocksignore
+func (pr *portRange) StateSave(stateSinkObject state.Sink) {
+ pr.beforeSave()
+ stateSinkObject.Save(0, &pr.DynamicBytesFile)
+ stateSinkObject.Save(1, &pr.stack)
+ stateSinkObject.Save(2, &pr.start)
+ stateSinkObject.Save(3, &pr.end)
+}
+
+func (pr *portRange) afterLoad() {}
+
+// +checklocksignore
+func (pr *portRange) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &pr.DynamicBytesFile)
+ stateSourceObject.LoadWait(1, &pr.stack)
+ stateSourceObject.Load(2, &pr.start)
+ stateSourceObject.Load(3, &pr.end)
+}
+
+func (s *yamaPtraceScope) StateTypeName() string {
+ return "pkg/sentry/fsimpl/proc.yamaPtraceScope"
+}
+
+func (s *yamaPtraceScope) StateFields() []string {
+ return []string{
+ "DynamicBytesFile",
+ "level",
+ }
+}
+
+func (s *yamaPtraceScope) beforeSave() {}
+
+// +checklocksignore
+func (s *yamaPtraceScope) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.DynamicBytesFile)
+ stateSinkObject.Save(1, &s.level)
+}
+
+func (s *yamaPtraceScope) afterLoad() {}
+
+// +checklocksignore
+func (s *yamaPtraceScope) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.DynamicBytesFile)
+ stateSourceObject.Load(1, &s.level)
+}
+
+func init() {
+ state.Register((*fdDirInodeRefs)(nil))
+ state.Register((*fdInfoDirInodeRefs)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*staticFile)(nil))
+ state.Register((*InternalData)(nil))
+ state.Register((*implStatFS)(nil))
+ state.Register((*subtasksInode)(nil))
+ state.Register((*subtasksFD)(nil))
+ state.Register((*subtasksInodeRefs)(nil))
+ state.Register((*taskInode)(nil))
+ state.Register((*taskOwnedInode)(nil))
+ state.Register((*fdDir)(nil))
+ state.Register((*fdDirInode)(nil))
+ state.Register((*fdSymlink)(nil))
+ state.Register((*fdInfoDirInode)(nil))
+ state.Register((*fdInfoData)(nil))
+ state.Register((*auxvData)(nil))
+ state.Register((*cmdlineData)(nil))
+ state.Register((*commInode)(nil))
+ state.Register((*commData)(nil))
+ state.Register((*idMapData)(nil))
+ state.Register((*memInode)(nil))
+ state.Register((*memFD)(nil))
+ state.Register((*mapsData)(nil))
+ state.Register((*smapsData)(nil))
+ state.Register((*taskStatData)(nil))
+ state.Register((*statmData)(nil))
+ state.Register((*statusInode)(nil))
+ state.Register((*statusFD)(nil))
+ state.Register((*statusFDLowerBase)(nil))
+ state.Register((*ioData)(nil))
+ state.Register((*oomScoreAdj)(nil))
+ state.Register((*exeSymlink)(nil))
+ state.Register((*cwdSymlink)(nil))
+ state.Register((*mountInfoData)(nil))
+ state.Register((*mountsData)(nil))
+ state.Register((*namespaceSymlink)(nil))
+ state.Register((*namespaceInode)(nil))
+ state.Register((*namespaceFD)(nil))
+ state.Register((*taskCgroupData)(nil))
+ state.Register((*taskInodeRefs)(nil))
+ state.Register((*ifinet6)(nil))
+ state.Register((*netDevData)(nil))
+ state.Register((*netUnixData)(nil))
+ state.Register((*netTCPData)(nil))
+ state.Register((*netTCP6Data)(nil))
+ state.Register((*netUDPData)(nil))
+ state.Register((*netSnmpData)(nil))
+ state.Register((*snmpLine)(nil))
+ state.Register((*netRouteData)(nil))
+ state.Register((*netStatData)(nil))
+ state.Register((*tasksInode)(nil))
+ state.Register((*staticFileSetStat)(nil))
+ state.Register((*selfSymlink)(nil))
+ state.Register((*threadSelfSymlink)(nil))
+ state.Register((*dynamicBytesFileSetAttr)(nil))
+ state.Register((*cpuStats)(nil))
+ state.Register((*statData)(nil))
+ state.Register((*loadavgData)(nil))
+ state.Register((*meminfoData)(nil))
+ state.Register((*uptimeData)(nil))
+ state.Register((*versionData)(nil))
+ state.Register((*filesystemsData)(nil))
+ state.Register((*cgroupsData)(nil))
+ state.Register((*cmdLineData)(nil))
+ state.Register((*tasksInodeRefs)(nil))
+ state.Register((*tcpMemDir)(nil))
+ state.Register((*mmapMinAddrData)(nil))
+ state.Register((*hostnameData)(nil))
+ state.Register((*tcpSackData)(nil))
+ state.Register((*tcpRecoveryData)(nil))
+ state.Register((*tcpMemData)(nil))
+ state.Register((*ipForwarding)(nil))
+ state.Register((*portRange)(nil))
+ state.Register((*yamaPtraceScope)(nil))
+}
diff --git a/pkg/sentry/fsimpl/proc/subtasks_inode_refs.go b/pkg/sentry/fsimpl/proc/subtasks_inode_refs.go
new file mode 100644
index 000000000..bd4998cbc
--- /dev/null
+++ b/pkg/sentry/fsimpl/proc/subtasks_inode_refs.go
@@ -0,0 +1,140 @@
+package proc
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const subtasksInodeenableLogging = false
+
+// obj 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 subtasksInodeobj *subtasksInode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type subtasksInodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *subtasksInodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *subtasksInodeRefs) RefType() string {
+ return fmt.Sprintf("%T", subtasksInodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *subtasksInodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *subtasksInodeRefs) LogRefs() bool {
+ return subtasksInodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *subtasksInodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *subtasksInodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if subtasksInodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *subtasksInodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if subtasksInodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *subtasksInodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if subtasksInodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *subtasksInodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/proc/task_inode_refs.go b/pkg/sentry/fsimpl/proc/task_inode_refs.go
new file mode 100644
index 000000000..82c63213a
--- /dev/null
+++ b/pkg/sentry/fsimpl/proc/task_inode_refs.go
@@ -0,0 +1,140 @@
+package proc
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const taskInodeenableLogging = false
+
+// obj 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 taskInodeobj *taskInode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type taskInodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *taskInodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *taskInodeRefs) RefType() string {
+ return fmt.Sprintf("%T", taskInodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *taskInodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *taskInodeRefs) LogRefs() bool {
+ return taskInodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *taskInodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *taskInodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if taskInodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *taskInodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if taskInodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *taskInodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if taskInodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *taskInodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/proc/tasks_inode_refs.go b/pkg/sentry/fsimpl/proc/tasks_inode_refs.go
new file mode 100644
index 000000000..73adc5278
--- /dev/null
+++ b/pkg/sentry/fsimpl/proc/tasks_inode_refs.go
@@ -0,0 +1,140 @@
+package proc
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const tasksInodeenableLogging = false
+
+// obj 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 tasksInodeobj *tasksInode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type tasksInodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *tasksInodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *tasksInodeRefs) RefType() string {
+ return fmt.Sprintf("%T", tasksInodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *tasksInodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *tasksInodeRefs) LogRefs() bool {
+ return tasksInodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *tasksInodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *tasksInodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if tasksInodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *tasksInodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if tasksInodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *tasksInodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if tasksInodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *tasksInodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/proc/tasks_sys_test.go b/pkg/sentry/fsimpl/proc/tasks_sys_test.go
deleted file mode 100644
index 19b012f7d..000000000
--- a/pkg/sentry/fsimpl/proc/tasks_sys_test.go
+++ /dev/null
@@ -1,149 +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 proc
-
-import (
- "bytes"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/inet"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-func newIPv6TestStack() *inet.TestStack {
- s := inet.NewTestStack()
- s.SupportsIPv6Flag = true
- return s
-}
-
-func TestIfinet6NoAddresses(t *testing.T) {
- n := &ifinet6{stack: newIPv6TestStack()}
- var buf bytes.Buffer
- n.Generate(contexttest.Context(t), &buf)
- if buf.Len() > 0 {
- t.Errorf("n.Generate() generated = %v, want = %v", buf.Bytes(), []byte{})
- }
-}
-
-func TestIfinet6(t *testing.T) {
- s := newIPv6TestStack()
- s.InterfacesMap[1] = inet.Interface{Name: "eth0"}
- s.InterfaceAddrsMap[1] = []inet.InterfaceAddr{
- {
- Family: linux.AF_INET6,
- PrefixLen: 128,
- Addr: []byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"),
- },
- }
- s.InterfacesMap[2] = inet.Interface{Name: "eth1"}
- s.InterfaceAddrsMap[2] = []inet.InterfaceAddr{
- {
- Family: linux.AF_INET6,
- PrefixLen: 128,
- Addr: []byte("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
- },
- }
- want := map[string]struct{}{
- "000102030405060708090a0b0c0d0e0f 01 80 00 00 eth0\n": {},
- "101112131415161718191a1b1c1d1e1f 02 80 00 00 eth1\n": {},
- }
-
- n := &ifinet6{stack: s}
- contents := n.contents()
- if len(contents) != len(want) {
- t.Errorf("Got len(n.contents()) = %d, want = %d", len(contents), len(want))
- }
- got := map[string]struct{}{}
- for _, l := range contents {
- got[l] = struct{}{}
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Got n.contents() = %v, want = %v", got, want)
- }
-}
-
-// TestIPForwarding tests the implementation of
-// /proc/sys/net/ipv4/ip_forwarding
-func TestConfigureIPForwarding(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
-
- var cases = []struct {
- comment string
- initial bool
- str string
- final bool
- }{
- {
- comment: `Forwarding is disabled; write 1 and enable forwarding`,
- initial: false,
- str: "1",
- final: true,
- },
- {
- comment: `Forwarding is disabled; write 0 and disable forwarding`,
- initial: false,
- str: "0",
- final: false,
- },
- {
- comment: `Forwarding is enabled; write 1 and enable forwarding`,
- initial: true,
- str: "1",
- final: true,
- },
- {
- comment: `Forwarding is enabled; write 0 and disable forwarding`,
- initial: true,
- str: "0",
- final: false,
- },
- {
- comment: `Forwarding is disabled; write 2404 and enable forwarding`,
- initial: false,
- str: "2404",
- final: true,
- },
- {
- comment: `Forwarding is enabled; write 2404 and enable forwarding`,
- initial: true,
- str: "2404",
- final: true,
- },
- }
- for _, c := range cases {
- t.Run(c.comment, func(t *testing.T) {
- s.IPForwarding = c.initial
-
- file := &ipForwarding{stack: s, enabled: c.initial}
-
- // Write the values.
- src := usermem.BytesIOSequence([]byte(c.str))
- if n, err := file.Write(ctx, src, 0); n != int64(len(c.str)) || err != nil {
- t.Errorf("file.Write(ctx, nil, %q, 0) = (%d, %v); want (%d, nil)", c.str, n, err, len(c.str))
- }
-
- // Read the values from the stack and check them.
- if got, want := s.IPForwarding, c.final; got != want {
- t.Errorf("s.IPForwarding incorrect; got: %v, want: %v", got, want)
- }
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/proc/tasks_test.go b/pkg/sentry/fsimpl/proc/tasks_test.go
deleted file mode 100644
index 14f806c3c..000000000
--- a/pkg/sentry/fsimpl/proc/tasks_test.go
+++ /dev/null
@@ -1,511 +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 proc
-
-import (
- "fmt"
- "math"
- "path"
- "strconv"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-var (
- // Next offset 256 by convention. Adds 1 for the next offset.
- selfLink = vfs.Dirent{Type: linux.DT_LNK, NextOff: 256 + 0 + 1}
- threadSelfLink = vfs.Dirent{Type: linux.DT_LNK, NextOff: 256 + 1 + 1}
-
- // /proc/[pid] next offset starts at 256+2 (files above), then adds the
- // PID, and adds 1 for the next offset.
- proc1 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 1 + 1}
- proc2 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 2 + 1}
- proc3 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 3 + 1}
-)
-
-var (
- tasksStaticFiles = map[string]testutil.DirentType{
- "cmdline": linux.DT_REG,
- "cpuinfo": linux.DT_REG,
- "filesystems": linux.DT_REG,
- "loadavg": linux.DT_REG,
- "meminfo": linux.DT_REG,
- "mounts": linux.DT_LNK,
- "net": linux.DT_LNK,
- "self": linux.DT_LNK,
- "stat": linux.DT_REG,
- "sys": linux.DT_DIR,
- "thread-self": linux.DT_LNK,
- "uptime": linux.DT_REG,
- "version": linux.DT_REG,
- }
- tasksStaticFilesNextOffs = map[string]int64{
- "self": selfLink.NextOff,
- "thread-self": threadSelfLink.NextOff,
- }
- taskStaticFiles = map[string]testutil.DirentType{
- "auxv": linux.DT_REG,
- "cgroup": linux.DT_REG,
- "cwd": linux.DT_LNK,
- "cmdline": linux.DT_REG,
- "comm": linux.DT_REG,
- "environ": linux.DT_REG,
- "exe": linux.DT_LNK,
- "fd": linux.DT_DIR,
- "fdinfo": linux.DT_DIR,
- "gid_map": linux.DT_REG,
- "io": linux.DT_REG,
- "maps": linux.DT_REG,
- "mem": linux.DT_REG,
- "mountinfo": linux.DT_REG,
- "mounts": linux.DT_REG,
- "net": linux.DT_DIR,
- "ns": linux.DT_DIR,
- "oom_score": linux.DT_REG,
- "oom_score_adj": linux.DT_REG,
- "smaps": linux.DT_REG,
- "stat": linux.DT_REG,
- "statm": linux.DT_REG,
- "status": linux.DT_REG,
- "task": linux.DT_DIR,
- "uid_map": linux.DT_REG,
- }
-)
-
-func setup(t *testing.T) *testutil.System {
- k, err := testutil.Boot()
- if err != nil {
- t.Fatalf("Error creating kernel: %v", err)
- }
-
- ctx := k.SupervisorContext()
- creds := auth.CredentialsFromContext(ctx)
-
- k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", tmpfs.Name, &vfs.MountOptions{})
- if err != nil {
- t.Fatalf("NewMountNamespace(): %v", err)
- }
- root := mntns.Root()
- root.IncRef()
- defer root.DecRef(ctx)
- pop := &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse("/proc"),
- }
- if err := k.VFS().MkdirAt(ctx, creds, pop, &vfs.MkdirOptions{Mode: 0777}); err != nil {
- t.Fatalf("MkDir(/proc): %v", err)
- }
-
- pop = &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse("/proc"),
- }
- mntOpts := &vfs.MountOptions{
- GetFilesystemOptions: vfs.GetFilesystemOptions{
- InternalData: &InternalData{
- Cgroups: map[string]string{
- "cpuset": "/foo/cpuset",
- "memory": "/foo/memory",
- },
- },
- },
- }
- if _, err := k.VFS().MountAt(ctx, creds, "", pop, Name, mntOpts); err != nil {
- t.Fatalf("MountAt(/proc): %v", err)
- }
- return testutil.NewSystem(ctx, t, k.VFS(), mntns)
-}
-
-func TestTasksEmpty(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- collector := s.ListDirents(s.PathOpAtRoot("/proc"))
- s.AssertAllDirentTypes(collector, tasksStaticFiles)
- s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
-}
-
-func TestTasks(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- expectedDirents := make(map[string]testutil.DirentType)
- for n, d := range tasksStaticFiles {
- expectedDirents[n] = d
- }
-
- k := kernel.KernelFromContext(s.Ctx)
- var tasks []*kernel.Task
- for i := 0; i < 5; i++ {
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root)
- if err != nil {
- t.Fatalf("CreateTask(): %v", err)
- }
- tasks = append(tasks, task)
- expectedDirents[fmt.Sprintf("%d", i+1)] = linux.DT_DIR
- }
-
- collector := s.ListDirents(s.PathOpAtRoot("/proc"))
- s.AssertAllDirentTypes(collector, expectedDirents)
- s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
-
- lastPid := 0
- dirents := collector.OrderedDirents()
- doneSkippingNonTaskDirs := false
- for _, d := range dirents {
- pid, err := strconv.Atoi(d.Name)
- if err != nil {
- if !doneSkippingNonTaskDirs {
- // We haven't gotten to the task dirs yet.
- continue
- }
- t.Fatalf("Invalid process directory %q", d.Name)
- }
- doneSkippingNonTaskDirs = true
- if lastPid > pid {
- t.Errorf("pids not in order: %v", dirents)
- }
- found := false
- for _, t := range tasks {
- if k.TaskSet().Root.IDOfTask(t) == kernel.ThreadID(pid) {
- found = true
- }
- }
- if !found {
- t.Errorf("Additional task ID %d listed: %v", pid, tasks)
- }
- // Next offset starts at 256+2 ('self' and 'thread-self'), then adds the
- // PID, and adds 1 for the next offset.
- if want := int64(256 + 2 + pid + 1); d.NextOff != want {
- t.Errorf("Wrong dirent offset want: %d got: %d: %+v", want, d.NextOff, d)
- }
- }
- if !doneSkippingNonTaskDirs {
- t.Fatalf("Never found any process directories.")
- }
-
- // Test lookup.
- for _, path := range []string{"/proc/1", "/proc/2"} {
- fd, err := s.VFS.OpenAt(
- s.Ctx,
- s.Creds,
- s.PathOpAtRoot(path),
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt(%q) failed: %v", path, err)
- }
- defer fd.DecRef(s.Ctx)
- buf := make([]byte, 1)
- bufIOSeq := usermem.BytesIOSequence(buf)
- if _, err := fd.Read(s.Ctx, bufIOSeq, vfs.ReadOptions{}); !linuxerr.Equals(linuxerr.EISDIR, err) {
- t.Errorf("wrong error reading directory: %v", err)
- }
- }
-
- if _, err := s.VFS.OpenAt(
- s.Ctx,
- s.Creds,
- s.PathOpAtRoot("/proc/9999"),
- &vfs.OpenOptions{},
- ); !linuxerr.Equals(linuxerr.ENOENT, err) {
- t.Fatalf("wrong error from vfsfs.OpenAt(/proc/9999): %v", err)
- }
-}
-
-func TestTasksOffset(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
- for i := 0; i < 3; i++ {
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- if _, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root); err != nil {
- t.Fatalf("CreateTask(): %v", err)
- }
- }
-
- for _, tc := range []struct {
- name string
- offset int64
- wants map[string]vfs.Dirent
- }{
- {
- name: "small offset",
- offset: 100,
- wants: map[string]vfs.Dirent{
- "self": selfLink,
- "thread-self": threadSelfLink,
- "1": proc1,
- "2": proc2,
- "3": proc3,
- },
- },
- {
- name: "offset at start",
- offset: 256,
- wants: map[string]vfs.Dirent{
- "self": selfLink,
- "thread-self": threadSelfLink,
- "1": proc1,
- "2": proc2,
- "3": proc3,
- },
- },
- {
- name: "skip /proc/self",
- offset: 257,
- wants: map[string]vfs.Dirent{
- "thread-self": threadSelfLink,
- "1": proc1,
- "2": proc2,
- "3": proc3,
- },
- },
- {
- name: "skip symlinks",
- offset: 258,
- wants: map[string]vfs.Dirent{
- "1": proc1,
- "2": proc2,
- "3": proc3,
- },
- },
- {
- name: "skip first process",
- offset: 260,
- wants: map[string]vfs.Dirent{
- "2": proc2,
- "3": proc3,
- },
- },
- {
- name: "last process",
- offset: 261,
- wants: map[string]vfs.Dirent{
- "3": proc3,
- },
- },
- {
- name: "after last",
- offset: 262,
- wants: nil,
- },
- {
- name: "TaskLimit+1",
- offset: kernel.TasksLimit + 1,
- wants: nil,
- },
- {
- name: "max",
- offset: math.MaxInt64,
- wants: nil,
- },
- } {
- t.Run(tc.name, func(t *testing.T) {
- s := s.WithSubtest(t)
- fd, err := s.VFS.OpenAt(
- s.Ctx,
- s.Creds,
- s.PathOpAtRoot("/proc"),
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
- }
- defer fd.DecRef(s.Ctx)
- if _, err := fd.Seek(s.Ctx, tc.offset, linux.SEEK_SET); err != nil {
- t.Fatalf("Seek(%d, SEEK_SET): %v", tc.offset, err)
- }
-
- var collector testutil.DirentCollector
- if err := fd.IterDirents(s.Ctx, &collector); err != nil {
- t.Fatalf("IterDirent(): %v", err)
- }
-
- expectedTypes := make(map[string]testutil.DirentType)
- expectedOffsets := make(map[string]int64)
- for name, want := range tc.wants {
- expectedTypes[name] = want.Type
- if want.NextOff != 0 {
- expectedOffsets[name] = want.NextOff
- }
- }
-
- collector.SkipDotsChecks(true) // We seek()ed past the dots.
- s.AssertAllDirentTypes(&collector, expectedTypes)
- s.AssertDirentOffsets(&collector, expectedOffsets)
- })
- }
-}
-
-func TestTask(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- _, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root)
- if err != nil {
- t.Fatalf("CreateTask(): %v", err)
- }
-
- collector := s.ListDirents(s.PathOpAtRoot("/proc/1"))
- s.AssertAllDirentTypes(collector, taskStaticFiles)
-}
-
-func TestProcSelf(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root)
- if err != nil {
- t.Fatalf("CreateTask(): %v", err)
- }
-
- collector := s.WithTemporaryContext(task.AsyncContext()).ListDirents(&vfs.PathOperation{
- Root: s.Root,
- Start: s.Root,
- Path: fspath.Parse("/proc/self/"),
- FollowFinalSymlink: true,
- })
- s.AssertAllDirentTypes(collector, taskStaticFiles)
-}
-
-func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.FileDescription) {
- t.Logf("Iterating: %s", fd.MappedName(ctx))
-
- var collector testutil.DirentCollector
- if err := fd.IterDirents(ctx, &collector); err != nil {
- t.Fatalf("IterDirents(): %v", err)
- }
- if err := collector.Contains(".", linux.DT_DIR); err != nil {
- t.Error(err.Error())
- }
- if err := collector.Contains("..", linux.DT_DIR); err != nil {
- t.Error(err.Error())
- }
-
- for _, d := range collector.Dirents() {
- if d.Name == "." || d.Name == ".." {
- continue
- }
- absPath := path.Join(fd.MappedName(ctx), d.Name)
- if d.Type == linux.DT_LNK {
- link, err := s.VFS.ReadlinkAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)},
- )
- if err != nil {
- t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", absPath, err)
- } else {
- t.Logf("Skipping symlink: %s => %s", absPath, link)
- }
- continue
- }
-
- t.Logf("Opening: %s", absPath)
- child, err := s.VFS.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Errorf("vfsfs.OpenAt(%v) failed: %v", absPath, err)
- continue
- }
- defer child.DecRef(ctx)
- stat, err := child.Stat(ctx, vfs.StatOptions{})
- if err != nil {
- t.Errorf("Stat(%v) failed: %v", absPath, err)
- }
- if got := linux.FileMode(stat.Mode).DirentType(); got != d.Type {
- t.Errorf("wrong file mode, stat: %v, dirent: %v", got, d.Type)
- }
- if d.Type == linux.DT_DIR {
- // Found another dir, let's do it again!
- iterateDir(ctx, t, s, child)
- }
- }
-}
-
-// TestTree iterates all directories and stats every file.
-func TestTree(t *testing.T) {
- s := setup(t)
- defer s.Destroy()
-
- k := kernel.KernelFromContext(s.Ctx)
-
- pop := &vfs.PathOperation{
- Root: s.Root,
- Start: s.Root,
- Path: fspath.Parse("test-file"),
- }
- opts := &vfs.OpenOptions{
- Flags: linux.O_RDONLY | linux.O_CREAT,
- Mode: 0777,
- }
- file, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, opts)
- if err != nil {
- t.Fatalf("failed to create test file: %v", err)
- }
- defer file.DecRef(s.Ctx)
-
- var tasks []*kernel.Task
- for i := 0; i < 5; i++ {
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root)
- if err != nil {
- t.Fatalf("CreateTask(): %v", err)
- }
- // Add file to populate /proc/[pid]/fd and fdinfo directories.
- task.FDTable().NewFDVFS2(task.AsyncContext(), 0, file, kernel.FDFlags{})
- tasks = append(tasks, task)
- }
-
- ctx := tasks[0].AsyncContext()
- fd, err := s.VFS.OpenAt(
- ctx,
- auth.CredentialsFromContext(s.Ctx),
- &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse("/proc")},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt(/proc) failed: %v", err)
- }
- iterateDir(ctx, t, s, fd)
- fd.DecRef(ctx)
-}
diff --git a/pkg/sentry/fsimpl/signalfd/BUILD b/pkg/sentry/fsimpl/signalfd/BUILD
deleted file mode 100644
index 403c6f254..000000000
--- a/pkg/sentry/fsimpl/signalfd/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "signalfd",
- srcs = ["signalfd.go"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/sentry/kernel",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go b/pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go
new file mode 100644
index 000000000..3bf27c6a6
--- /dev/null
+++ b/pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go
@@ -0,0 +1,51 @@
+// automatically generated by stateify.
+
+package signalfd
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (sfd *SignalFileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/signalfd.SignalFileDescription"
+}
+
+func (sfd *SignalFileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "DentryMetadataFileDescriptionImpl",
+ "NoLockFD",
+ "target",
+ "mask",
+ }
+}
+
+func (sfd *SignalFileDescription) beforeSave() {}
+
+// +checklocksignore
+func (sfd *SignalFileDescription) StateSave(stateSinkObject state.Sink) {
+ sfd.beforeSave()
+ stateSinkObject.Save(0, &sfd.vfsfd)
+ stateSinkObject.Save(1, &sfd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &sfd.DentryMetadataFileDescriptionImpl)
+ stateSinkObject.Save(3, &sfd.NoLockFD)
+ stateSinkObject.Save(4, &sfd.target)
+ stateSinkObject.Save(5, &sfd.mask)
+}
+
+func (sfd *SignalFileDescription) afterLoad() {}
+
+// +checklocksignore
+func (sfd *SignalFileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &sfd.vfsfd)
+ stateSourceObject.Load(1, &sfd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &sfd.DentryMetadataFileDescriptionImpl)
+ stateSourceObject.Load(3, &sfd.NoLockFD)
+ stateSourceObject.Load(4, &sfd.target)
+ stateSourceObject.Load(5, &sfd.mask)
+}
+
+func init() {
+ state.Register((*SignalFileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/sockfs/BUILD b/pkg/sentry/fsimpl/sockfs/BUILD
deleted file mode 100644
index 9defca936..000000000
--- a/pkg/sentry/fsimpl/sockfs/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-licenses(["notice"])
-
-go_library(
- name = "sockfs",
- srcs = ["sockfs.go"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- ],
-)
diff --git a/pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go b/pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go
new file mode 100644
index 000000000..cf6eddef2
--- /dev/null
+++ b/pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go
@@ -0,0 +1,96 @@
+// automatically generated by stateify.
+
+package sockfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (fsType *filesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sockfs.filesystemType"
+}
+
+func (fsType *filesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fsType *filesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fsType *filesystemType) StateSave(stateSinkObject state.Sink) {
+ fsType.beforeSave()
+}
+
+func (fsType *filesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fsType *filesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sockfs.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+}
+
+func (i *inode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sockfs.inode"
+}
+
+func (i *inode) StateFields() []string {
+ return []string{
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ }
+}
+
+func (i *inode) beforeSave() {}
+
+// +checklocksignore
+func (i *inode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeAttrs)
+ stateSinkObject.Save(1, &i.InodeNoopRefCount)
+ stateSinkObject.Save(2, &i.InodeNotDirectory)
+ stateSinkObject.Save(3, &i.InodeNotSymlink)
+}
+
+func (i *inode) afterLoad() {}
+
+// +checklocksignore
+func (i *inode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeAttrs)
+ stateSourceObject.Load(1, &i.InodeNoopRefCount)
+ stateSourceObject.Load(2, &i.InodeNotDirectory)
+ stateSourceObject.Load(3, &i.InodeNotSymlink)
+}
+
+func init() {
+ state.Register((*filesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*inode)(nil))
+}
diff --git a/pkg/sentry/fsimpl/sys/BUILD b/pkg/sentry/fsimpl/sys/BUILD
deleted file mode 100644
index ab21f028e..000000000
--- a/pkg/sentry/fsimpl/sys/BUILD
+++ /dev/null
@@ -1,55 +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 = "dir_refs",
- out = "dir_refs.go",
- package = "sys",
- prefix = "dir",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "dir",
- },
-)
-
-go_library(
- name = "sys",
- srcs = [
- "dir_refs.go",
- "kcov.go",
- "sys.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/coverage",
- "//pkg/errors/linuxerr",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/sentry/arch",
- "//pkg/sentry/fsimpl/kernfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "sys_test",
- srcs = ["sys_test.go"],
- deps = [
- ":sys",
- "//pkg/abi/linux",
- "//pkg/sentry/fsimpl/testutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fsimpl/sys/dir_refs.go b/pkg/sentry/fsimpl/sys/dir_refs.go
new file mode 100644
index 000000000..17bc43d2e
--- /dev/null
+++ b/pkg/sentry/fsimpl/sys/dir_refs.go
@@ -0,0 +1,140 @@
+package sys
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const direnableLogging = false
+
+// obj 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 dirobj *dir
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type dirRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *dirRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *dirRefs) RefType() string {
+ return fmt.Sprintf("%T", dirobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *dirRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *dirRefs) LogRefs() bool {
+ return direnableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *dirRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *dirRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if direnableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *dirRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if direnableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *dirRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if direnableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *dirRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/sys/sys_state_autogen.go b/pkg/sentry/fsimpl/sys/sys_state_autogen.go
new file mode 100644
index 000000000..c5adf7db3
--- /dev/null
+++ b/pkg/sentry/fsimpl/sys/sys_state_autogen.go
@@ -0,0 +1,263 @@
+// automatically generated by stateify.
+
+package sys
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (r *dirRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.dirRefs"
+}
+
+func (r *dirRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *dirRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *dirRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *dirRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (i *kcovInode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.kcovInode"
+}
+
+func (i *kcovInode) StateFields() []string {
+ return []string{
+ "InodeAttrs",
+ "InodeNoopRefCount",
+ "InodeNotDirectory",
+ "InodeNotSymlink",
+ "implStatFS",
+ }
+}
+
+func (i *kcovInode) beforeSave() {}
+
+// +checklocksignore
+func (i *kcovInode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.InodeAttrs)
+ stateSinkObject.Save(1, &i.InodeNoopRefCount)
+ stateSinkObject.Save(2, &i.InodeNotDirectory)
+ stateSinkObject.Save(3, &i.InodeNotSymlink)
+ stateSinkObject.Save(4, &i.implStatFS)
+}
+
+func (i *kcovInode) afterLoad() {}
+
+// +checklocksignore
+func (i *kcovInode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.InodeAttrs)
+ stateSourceObject.Load(1, &i.InodeNoopRefCount)
+ stateSourceObject.Load(2, &i.InodeNotDirectory)
+ stateSourceObject.Load(3, &i.InodeNotSymlink)
+ stateSourceObject.Load(4, &i.implStatFS)
+}
+
+func (fd *kcovFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.kcovFD"
+}
+
+func (fd *kcovFD) StateFields() []string {
+ return []string{
+ "FileDescriptionDefaultImpl",
+ "NoLockFD",
+ "vfsfd",
+ "inode",
+ "kcov",
+ }
+}
+
+func (fd *kcovFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *kcovFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(1, &fd.NoLockFD)
+ stateSinkObject.Save(2, &fd.vfsfd)
+ stateSinkObject.Save(3, &fd.inode)
+ stateSinkObject.Save(4, &fd.kcov)
+}
+
+func (fd *kcovFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *kcovFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(1, &fd.NoLockFD)
+ stateSourceObject.Load(2, &fd.vfsfd)
+ stateSourceObject.Load(3, &fd.inode)
+ stateSourceObject.Load(4, &fd.kcov)
+}
+
+func (fsType *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.FilesystemType"
+}
+
+func (fsType *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fsType *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fsType *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fsType.beforeSave()
+}
+
+func (fsType *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fsType *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "Filesystem",
+ "devMinor",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.Filesystem)
+ stateSinkObject.Save(1, &fs.devMinor)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.Filesystem)
+ stateSourceObject.Load(1, &fs.devMinor)
+}
+
+func (d *dir) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.dir"
+}
+
+func (d *dir) StateFields() []string {
+ return []string{
+ "dirRefs",
+ "InodeAlwaysValid",
+ "InodeAttrs",
+ "InodeNotSymlink",
+ "InodeDirectoryNoNewChildren",
+ "InodeTemporary",
+ "OrderedChildren",
+ "locks",
+ }
+}
+
+func (d *dir) beforeSave() {}
+
+// +checklocksignore
+func (d *dir) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.dirRefs)
+ stateSinkObject.Save(1, &d.InodeAlwaysValid)
+ stateSinkObject.Save(2, &d.InodeAttrs)
+ stateSinkObject.Save(3, &d.InodeNotSymlink)
+ stateSinkObject.Save(4, &d.InodeDirectoryNoNewChildren)
+ stateSinkObject.Save(5, &d.InodeTemporary)
+ stateSinkObject.Save(6, &d.OrderedChildren)
+ stateSinkObject.Save(7, &d.locks)
+}
+
+func (d *dir) afterLoad() {}
+
+// +checklocksignore
+func (d *dir) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.dirRefs)
+ stateSourceObject.Load(1, &d.InodeAlwaysValid)
+ stateSourceObject.Load(2, &d.InodeAttrs)
+ stateSourceObject.Load(3, &d.InodeNotSymlink)
+ stateSourceObject.Load(4, &d.InodeDirectoryNoNewChildren)
+ stateSourceObject.Load(5, &d.InodeTemporary)
+ stateSourceObject.Load(6, &d.OrderedChildren)
+ stateSourceObject.Load(7, &d.locks)
+}
+
+func (c *cpuFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.cpuFile"
+}
+
+func (c *cpuFile) StateFields() []string {
+ return []string{
+ "implStatFS",
+ "DynamicBytesFile",
+ "maxCores",
+ }
+}
+
+func (c *cpuFile) beforeSave() {}
+
+// +checklocksignore
+func (c *cpuFile) StateSave(stateSinkObject state.Sink) {
+ c.beforeSave()
+ stateSinkObject.Save(0, &c.implStatFS)
+ stateSinkObject.Save(1, &c.DynamicBytesFile)
+ stateSinkObject.Save(2, &c.maxCores)
+}
+
+func (c *cpuFile) afterLoad() {}
+
+// +checklocksignore
+func (c *cpuFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &c.implStatFS)
+ stateSourceObject.Load(1, &c.DynamicBytesFile)
+ stateSourceObject.Load(2, &c.maxCores)
+}
+
+func (i *implStatFS) StateTypeName() string {
+ return "pkg/sentry/fsimpl/sys.implStatFS"
+}
+
+func (i *implStatFS) StateFields() []string {
+ return []string{}
+}
+
+func (i *implStatFS) beforeSave() {}
+
+// +checklocksignore
+func (i *implStatFS) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+}
+
+func (i *implStatFS) afterLoad() {}
+
+// +checklocksignore
+func (i *implStatFS) StateLoad(stateSourceObject state.Source) {
+}
+
+func init() {
+ state.Register((*dirRefs)(nil))
+ state.Register((*kcovInode)(nil))
+ state.Register((*kcovFD)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*dir)(nil))
+ state.Register((*cpuFile)(nil))
+ state.Register((*implStatFS)(nil))
+}
diff --git a/pkg/sentry/fsimpl/sys/sys_test.go b/pkg/sentry/fsimpl/sys/sys_test.go
deleted file mode 100644
index 0c46a3a13..000000000
--- a/pkg/sentry/fsimpl/sys/sys_test.go
+++ /dev/null
@@ -1,103 +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 sys_test
-
-import (
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/sys"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-func newTestSystem(t *testing.T) *testutil.System {
- k, err := testutil.Boot()
- if err != nil {
- t.Fatalf("Failed to create test kernel: %v", err)
- }
- ctx := k.SupervisorContext()
- creds := auth.CredentialsFromContext(ctx)
- k.VFS().MustRegisterFilesystemType(sys.Name, sys.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- mns, err := k.VFS().NewMountNamespace(ctx, creds, "", sys.Name, &vfs.MountOptions{})
- if err != nil {
- t.Fatalf("Failed to create new mount namespace: %v", err)
- }
- return testutil.NewSystem(ctx, t, k.VFS(), mns)
-}
-
-func TestReadCPUFile(t *testing.T) {
- s := newTestSystem(t)
- defer s.Destroy()
- k := kernel.KernelFromContext(s.Ctx)
- maxCPUCores := k.ApplicationCores()
-
- expected := fmt.Sprintf("0-%d\n", maxCPUCores-1)
-
- for _, fname := range []string{"online", "possible", "present"} {
- pop := s.PathOpAtRoot(fmt.Sprintf("devices/system/cpu/%s", fname))
- fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{})
- if err != nil {
- t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
- }
- defer fd.DecRef(s.Ctx)
- content, err := s.ReadToEnd(fd)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
- if diff := cmp.Diff(expected, content); diff != "" {
- t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff)
- }
- }
-}
-
-func TestSysRootContainsExpectedEntries(t *testing.T) {
- s := newTestSystem(t)
- defer s.Destroy()
- pop := s.PathOpAtRoot("/")
- s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{
- "block": linux.DT_DIR,
- "bus": linux.DT_DIR,
- "class": linux.DT_DIR,
- "dev": linux.DT_DIR,
- "devices": linux.DT_DIR,
- "firmware": linux.DT_DIR,
- "fs": linux.DT_DIR,
- "kernel": linux.DT_DIR,
- "module": linux.DT_DIR,
- "power": linux.DT_DIR,
- })
-}
-
-func TestCgroupMountpointExists(t *testing.T) {
- // Note: The mountpoint is only created if cgroups are available. This is
- // the VFS2 implementation of sysfs and the test runs with VFS2 enabled, so
- // we expect to see the mount point unconditionally.
- s := newTestSystem(t)
- defer s.Destroy()
- pop := s.PathOpAtRoot("/fs")
- s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{
- "cgroup": linux.DT_DIR,
- })
- pop = s.PathOpAtRoot("/fs/cgroup")
- s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ /*empty*/ })
-}
diff --git a/pkg/sentry/fsimpl/testutil/BUILD b/pkg/sentry/fsimpl/testutil/BUILD
deleted file mode 100644
index b3f9d1010..000000000
--- a/pkg/sentry/fsimpl/testutil/BUILD
+++ /dev/null
@@ -1,38 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-licenses(["notice"])
-
-go_library(
- name = "testutil",
- testonly = 1,
- srcs = [
- "kernel.go",
- "testutil.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/cpuid",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/memutil",
- "//pkg/sentry/fsbridge",
- "//pkg/sentry/fsimpl/tmpfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/sched",
- "//pkg/sentry/limits",
- "//pkg/sentry/loader",
- "//pkg/sentry/mm",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/platform/kvm",
- "//pkg/sentry/platform/ptrace",
- "//pkg/sentry/time",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fsimpl/testutil/kernel.go b/pkg/sentry/fsimpl/testutil/kernel.go
deleted file mode 100644
index 473b41cff..000000000
--- a/pkg/sentry/fsimpl/testutil/kernel.go
+++ /dev/null
@@ -1,182 +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 testutil
-
-import (
- "flag"
- "fmt"
- "os"
- "runtime"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/cpuid"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/memutil"
- "gvisor.dev/gvisor/pkg/sentry/fsbridge"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/kernel/sched"
- "gvisor.dev/gvisor/pkg/sentry/limits"
- "gvisor.dev/gvisor/pkg/sentry/loader"
- "gvisor.dev/gvisor/pkg/sentry/mm"
- "gvisor.dev/gvisor/pkg/sentry/pgalloc"
- "gvisor.dev/gvisor/pkg/sentry/platform"
- "gvisor.dev/gvisor/pkg/sentry/time"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-
- // Platforms are plugable.
- _ "gvisor.dev/gvisor/pkg/sentry/platform/kvm"
- _ "gvisor.dev/gvisor/pkg/sentry/platform/ptrace"
-)
-
-var (
- platformFlag = flag.String("platform", "ptrace", "specify which platform to use")
-)
-
-// Boot initializes a new bare bones kernel for test.
-func Boot() (*kernel.Kernel, error) {
- platformCtr, err := platform.Lookup(*platformFlag)
- if err != nil {
- return nil, fmt.Errorf("platform not found: %v", err)
- }
- deviceFile, err := platformCtr.OpenDevice()
- if err != nil {
- return nil, fmt.Errorf("creating platform: %v", err)
- }
- plat, err := platformCtr.New(deviceFile)
- if err != nil {
- return nil, fmt.Errorf("creating platform: %v", err)
- }
-
- kernel.VFS2Enabled = true
- k := &kernel.Kernel{
- Platform: plat,
- }
-
- mf, err := createMemoryFile()
- if err != nil {
- return nil, err
- }
- k.SetMemoryFile(mf)
-
- // Pass k as the platform since it is savable, unlike the actual platform.
- vdso, err := loader.PrepareVDSO(k)
- if err != nil {
- return nil, fmt.Errorf("creating vdso: %v", err)
- }
-
- // Create timekeeper.
- tk := kernel.NewTimekeeper(k, vdso.ParamPage.FileRange())
- tk.SetClocks(time.NewCalibratedClocks())
-
- creds := auth.NewRootCredentials(auth.NewRootUserNamespace())
-
- // Initiate the Kernel object, which is required by the Context passed
- // to createVFS in order to mount (among other things) procfs.
- if err = k.Init(kernel.InitKernelArgs{
- ApplicationCores: uint(runtime.GOMAXPROCS(-1)),
- FeatureSet: cpuid.HostFeatureSet(),
- Timekeeper: tk,
- RootUserNamespace: creds.UserNamespace,
- Vdso: vdso,
- RootUTSNamespace: kernel.NewUTSNamespace("hostname", "domain", creds.UserNamespace),
- RootIPCNamespace: kernel.NewIPCNamespace(creds.UserNamespace),
- RootAbstractSocketNamespace: kernel.NewAbstractSocketNamespace(),
- PIDNamespace: kernel.NewRootPIDNamespace(creds.UserNamespace),
- }); err != nil {
- return nil, fmt.Errorf("initializing kernel: %v", err)
- }
-
- k.VFS().MustRegisterFilesystemType(tmpfs.Name, &tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- AllowUserList: true,
- })
-
- ls, err := limits.NewLinuxLimitSet()
- if err != nil {
- return nil, err
- }
- tg := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, ls)
- k.TestOnlySetGlobalInit(tg)
-
- return k, nil
-}
-
-// CreateTask creates a new bare bones task for tests.
-func CreateTask(ctx context.Context, name string, tc *kernel.ThreadGroup, mntns *vfs.MountNamespace, root, cwd vfs.VirtualDentry) (*kernel.Task, error) {
- k := kernel.KernelFromContext(ctx)
- if k == nil {
- return nil, fmt.Errorf("cannot find kernel from context")
- }
-
- exe, err := newFakeExecutable(ctx, k.VFS(), auth.CredentialsFromContext(ctx), root)
- if err != nil {
- return nil, err
- }
- m := mm.NewMemoryManager(k, k, k.SleepForAddressSpaceActivation)
- m.SetExecutable(ctx, fsbridge.NewVFSFile(exe))
-
- config := &kernel.TaskConfig{
- Kernel: k,
- ThreadGroup: tc,
- TaskImage: &kernel.TaskImage{Name: name, MemoryManager: m},
- Credentials: auth.CredentialsFromContext(ctx),
- NetworkNamespace: k.RootNetworkNamespace(),
- AllowedCPUMask: sched.NewFullCPUSet(k.ApplicationCores()),
- UTSNamespace: kernel.UTSNamespaceFromContext(ctx),
- IPCNamespace: kernel.IPCNamespaceFromContext(ctx),
- AbstractSocketNamespace: kernel.NewAbstractSocketNamespace(),
- MountNamespaceVFS2: mntns,
- FSContext: kernel.NewFSContextVFS2(root, cwd, 0022),
- FDTable: k.NewFDTable(),
- }
- t, err := k.TaskSet().NewTask(ctx, config)
- if err != nil {
- config.ThreadGroup.Release(ctx)
- return nil, err
- }
- return t, nil
-}
-
-func newFakeExecutable(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry) (*vfs.FileDescription, error) {
- const name = "executable"
- pop := &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(name),
- }
- opts := &vfs.OpenOptions{
- Flags: linux.O_RDONLY | linux.O_CREAT,
- Mode: 0777,
- }
- return vfsObj.OpenAt(ctx, creds, pop, opts)
-}
-
-func createMemoryFile() (*pgalloc.MemoryFile, error) {
- const memfileName = "test-memory"
- memfd, err := memutil.CreateMemFD(memfileName, 0)
- if err != nil {
- return nil, fmt.Errorf("error creating memfd: %v", err)
- }
- memfile := os.NewFile(uintptr(memfd), memfileName)
- mf, err := pgalloc.NewMemoryFile(memfile, pgalloc.MemoryFileOpts{})
- if err != nil {
- _ = memfile.Close()
- return nil, fmt.Errorf("error creating pgalloc.MemoryFile: %v", err)
- }
- return mf, nil
-}
diff --git a/pkg/sentry/fsimpl/testutil/testutil.go b/pkg/sentry/fsimpl/testutil/testutil.go
deleted file mode 100644
index 59e6f9c92..000000000
--- a/pkg/sentry/fsimpl/testutil/testutil.go
+++ /dev/null
@@ -1,288 +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 testutil provides common test utilities for kernfs-based
-// filesystems.
-package testutil
-
-import (
- "fmt"
- "io"
- "strings"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/sync"
- "gvisor.dev/gvisor/pkg/usermem"
-
- "gvisor.dev/gvisor/pkg/hostarch"
-)
-
-// System represents the context for a single test.
-//
-// Test systems must be explicitly destroyed with System.Destroy.
-type System struct {
- t *testing.T
- Ctx context.Context
- Creds *auth.Credentials
- VFS *vfs.VirtualFilesystem
- Root vfs.VirtualDentry
- MntNs *vfs.MountNamespace
-}
-
-// NewSystem constructs a System.
-//
-// Precondition: Caller must hold a reference on mns, whose ownership
-// is transferred to the new System.
-func NewSystem(ctx context.Context, t *testing.T, v *vfs.VirtualFilesystem, mns *vfs.MountNamespace) *System {
- root := mns.Root()
- root.IncRef()
- s := &System{
- t: t,
- Ctx: ctx,
- Creds: auth.CredentialsFromContext(ctx),
- VFS: v,
- MntNs: mns,
- Root: root,
- }
- return s
-}
-
-// WithSubtest creates a temporary test system with a new test harness,
-// referencing all other resources from the original system. This is useful when
-// a system is reused for multiple subtests, and the T needs to change for each
-// case. Note that this is safe when test cases run in parallel, as all
-// resources referenced by the system are immutable, or handle interior
-// mutations in a thread-safe manner.
-//
-// The returned system must not outlive the original and should not be destroyed
-// via System.Destroy.
-func (s *System) WithSubtest(t *testing.T) *System {
- return &System{
- t: t,
- Ctx: s.Ctx,
- Creds: s.Creds,
- VFS: s.VFS,
- MntNs: s.MntNs,
- Root: s.Root,
- }
-}
-
-// WithTemporaryContext constructs a temporary test system with a new context
-// ctx. The temporary system borrows all resources and references from the
-// original system. The returned temporary system must not outlive the original
-// system, and should not be destroyed via System.Destroy.
-func (s *System) WithTemporaryContext(ctx context.Context) *System {
- return &System{
- t: s.t,
- Ctx: ctx,
- Creds: s.Creds,
- VFS: s.VFS,
- MntNs: s.MntNs,
- Root: s.Root,
- }
-}
-
-// Destroy release resources associated with a test system.
-func (s *System) Destroy() {
- s.Root.DecRef(s.Ctx)
- s.MntNs.DecRef(s.Ctx) // Reference on MntNs passed to NewSystem.
-}
-
-// ReadToEnd reads the contents of fd until EOF to a string.
-func (s *System) ReadToEnd(fd *vfs.FileDescription) (string, error) {
- buf := make([]byte, hostarch.PageSize)
- bufIOSeq := usermem.BytesIOSequence(buf)
- opts := vfs.ReadOptions{}
-
- var content strings.Builder
- for {
- n, err := fd.Read(s.Ctx, bufIOSeq, opts)
- if n == 0 || err != nil {
- if err == io.EOF {
- err = nil
- }
- return content.String(), err
- }
- content.Write(buf[:n])
- }
-}
-
-// PathOpAtRoot constructs a PathOperation with the given path from
-// the root of the filesystem.
-func (s *System) PathOpAtRoot(path string) *vfs.PathOperation {
- return &vfs.PathOperation{
- Root: s.Root,
- Start: s.Root,
- Path: fspath.Parse(path),
- }
-}
-
-// GetDentryOrDie attempts to resolve a dentry referred to by the
-// provided path operation. If unsuccessful, the test fails.
-func (s *System) GetDentryOrDie(pop *vfs.PathOperation) vfs.VirtualDentry {
- vd, err := s.VFS.GetDentryAt(s.Ctx, s.Creds, pop, &vfs.GetDentryOptions{})
- if err != nil {
- s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err)
- }
- return vd
-}
-
-// DirentType is an alias for values for linux_dirent64.d_type.
-type DirentType = uint8
-
-// ListDirents lists the Dirents for a directory at pop.
-func (s *System) ListDirents(pop *vfs.PathOperation) *DirentCollector {
- fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{Flags: linux.O_RDONLY})
- if err != nil {
- s.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
- }
- defer fd.DecRef(s.Ctx)
-
- collector := &DirentCollector{}
- if err := fd.IterDirents(s.Ctx, collector); err != nil {
- s.t.Fatalf("IterDirent failed: %v", err)
- }
- return collector
-}
-
-// AssertAllDirentTypes verifies that the set of dirents in collector contains
-// exactly the specified set of expected entries. AssertAllDirentTypes respects
-// collector.skipDots, and implicitly checks for "." and ".." accordingly.
-func (s *System) AssertAllDirentTypes(collector *DirentCollector, expected map[string]DirentType) {
- if expected == nil {
- expected = make(map[string]DirentType)
- }
- // Also implicitly check for "." and "..", if enabled.
- if !collector.skipDots {
- expected["."] = linux.DT_DIR
- expected[".."] = linux.DT_DIR
- }
-
- dentryTypes := make(map[string]DirentType)
- collector.mu.Lock()
- for _, dirent := range collector.dirents {
- dentryTypes[dirent.Name] = dirent.Type
- }
- collector.mu.Unlock()
- if diff := cmp.Diff(expected, dentryTypes); diff != "" {
- s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff)
- }
-}
-
-// AssertDirentOffsets verifies that collector contains at least the entries
-// specified in expected, with the given NextOff field. Entries specified in
-// expected but missing from collector result in failure. Extra entries in
-// collector are ignored. AssertDirentOffsets respects collector.skipDots, and
-// implicitly checks for "." and ".." accordingly.
-func (s *System) AssertDirentOffsets(collector *DirentCollector, expected map[string]int64) {
- // Also implicitly check for "." and "..", if enabled.
- if !collector.skipDots {
- expected["."] = 1
- expected[".."] = 2
- }
-
- dentryNextOffs := make(map[string]int64)
- collector.mu.Lock()
- for _, dirent := range collector.dirents {
- // Ignore extra entries in dentries that are not in expected.
- if _, ok := expected[dirent.Name]; ok {
- dentryNextOffs[dirent.Name] = dirent.NextOff
- }
- }
- collector.mu.Unlock()
- if diff := cmp.Diff(expected, dentryNextOffs); diff != "" {
- s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff)
- }
-}
-
-// DirentCollector provides an implementation for vfs.IterDirentsCallback for
-// testing. It simply iterates to the end of a given directory FD and collects
-// all dirents emitted by the callback.
-type DirentCollector struct {
- mu sync.Mutex
- order []*vfs.Dirent
- dirents map[string]*vfs.Dirent
- // When the collector is used in various Assert* functions, should "." and
- // ".." be implicitly checked?
- skipDots bool
-}
-
-// SkipDotsChecks enables or disables the implicit checks on "." and ".." when
-// the collector is used in various Assert* functions. Note that "." and ".."
-// are still collected if passed to d.Handle, so the caller should only disable
-// the checks when they aren't expected.
-func (d *DirentCollector) SkipDotsChecks(value bool) {
- d.skipDots = value
-}
-
-// Handle implements vfs.IterDirentsCallback.Handle.
-func (d *DirentCollector) Handle(dirent vfs.Dirent) error {
- d.mu.Lock()
- if d.dirents == nil {
- d.dirents = make(map[string]*vfs.Dirent)
- }
- d.order = append(d.order, &dirent)
- d.dirents[dirent.Name] = &dirent
- d.mu.Unlock()
- return nil
-}
-
-// Count returns the number of dirents currently in the collector.
-func (d *DirentCollector) Count() int {
- d.mu.Lock()
- defer d.mu.Unlock()
- return len(d.dirents)
-}
-
-// Contains checks whether the collector has a dirent with the given name and
-// type.
-func (d *DirentCollector) Contains(name string, typ uint8) error {
- d.mu.Lock()
- defer d.mu.Unlock()
- dirent, ok := d.dirents[name]
- if !ok {
- return fmt.Errorf("no dirent named %q found", name)
- }
- if dirent.Type != typ {
- return fmt.Errorf("dirent named %q found, but was expecting type %s, got: %+v", name, linux.DirentType.Parse(uint64(typ)), dirent)
- }
- return nil
-}
-
-// Dirents returns all dirents discovered by this collector.
-func (d *DirentCollector) Dirents() map[string]*vfs.Dirent {
- d.mu.Lock()
- dirents := make(map[string]*vfs.Dirent)
- for n, d := range d.dirents {
- dirents[n] = d
- }
- d.mu.Unlock()
- return dirents
-}
-
-// OrderedDirents returns an ordered list of dirents as discovered by this
-// collector.
-func (d *DirentCollector) OrderedDirents() []*vfs.Dirent {
- d.mu.Lock()
- dirents := make([]*vfs.Dirent, len(d.order))
- copy(dirents, d.order)
- d.mu.Unlock()
- return dirents
-}
diff --git a/pkg/sentry/fsimpl/timerfd/BUILD b/pkg/sentry/fsimpl/timerfd/BUILD
deleted file mode 100644
index 2b83d7d9a..000000000
--- a/pkg/sentry/fsimpl/timerfd/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-licenses(["notice"])
-
-go_library(
- name = "timerfd",
- srcs = ["timerfd.go"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/hostarch",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go b/pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go
new file mode 100644
index 000000000..12970f25c
--- /dev/null
+++ b/pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go
@@ -0,0 +1,54 @@
+// automatically generated by stateify.
+
+package timerfd
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (tfd *TimerFileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/timerfd.TimerFileDescription"
+}
+
+func (tfd *TimerFileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "DentryMetadataFileDescriptionImpl",
+ "NoLockFD",
+ "events",
+ "timer",
+ "val",
+ }
+}
+
+func (tfd *TimerFileDescription) beforeSave() {}
+
+// +checklocksignore
+func (tfd *TimerFileDescription) StateSave(stateSinkObject state.Sink) {
+ tfd.beforeSave()
+ stateSinkObject.Save(0, &tfd.vfsfd)
+ stateSinkObject.Save(1, &tfd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &tfd.DentryMetadataFileDescriptionImpl)
+ stateSinkObject.Save(3, &tfd.NoLockFD)
+ stateSinkObject.Save(4, &tfd.events)
+ stateSinkObject.Save(5, &tfd.timer)
+ stateSinkObject.Save(6, &tfd.val)
+}
+
+func (tfd *TimerFileDescription) afterLoad() {}
+
+// +checklocksignore
+func (tfd *TimerFileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &tfd.vfsfd)
+ stateSourceObject.Load(1, &tfd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &tfd.DentryMetadataFileDescriptionImpl)
+ stateSourceObject.Load(3, &tfd.NoLockFD)
+ stateSourceObject.Load(4, &tfd.events)
+ stateSourceObject.Load(5, &tfd.timer)
+ stateSourceObject.Load(6, &tfd.val)
+}
+
+func init() {
+ state.Register((*TimerFileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/tmpfs/BUILD b/pkg/sentry/fsimpl/tmpfs/BUILD
deleted file mode 100644
index 94486bb63..000000000
--- a/pkg/sentry/fsimpl/tmpfs/BUILD
+++ /dev/null
@@ -1,129 +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 = "dentry_list",
- out = "dentry_list.go",
- package = "tmpfs",
- prefix = "dentry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*dentry",
- "Linker": "*dentry",
- },
-)
-
-go_template_instance(
- name = "fstree",
- out = "fstree.go",
- package = "tmpfs",
- prefix = "generic",
- template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
- types = {
- "Dentry": "dentry",
- },
-)
-
-go_template_instance(
- name = "inode_refs",
- out = "inode_refs.go",
- package = "tmpfs",
- prefix = "inode",
- template = "//pkg/refsvfs2:refs_template",
- types = {
- "T": "inode",
- },
-)
-
-go_library(
- name = "tmpfs",
- srcs = [
- "dentry_list.go",
- "device_file.go",
- "directory.go",
- "filesystem.go",
- "fstree.go",
- "inode_refs.go",
- "named_pipe.go",
- "regular_file.go",
- "save_restore.go",
- "socket_file.go",
- "symlink.go",
- "tmpfs.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/amutex",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/arch",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fsmetric",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/pipe",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usage",
- "//pkg/sentry/vfs",
- "//pkg/sentry/vfs/memxattr",
- "//pkg/sync",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "benchmark_test",
- size = "small",
- srcs = ["benchmark_test.go"],
- deps = [
- ":tmpfs",
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/refs",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- ],
-)
-
-go_test(
- name = "tmpfs_test",
- size = "small",
- srcs = [
- "pipe_test.go",
- "regular_file_test.go",
- "stat_test.go",
- "tmpfs_test.go",
- ],
- library = ":tmpfs",
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/sentry/contexttest",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- ],
-)
diff --git a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go
deleted file mode 100644
index 2c29343c1..000000000
--- a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go
+++ /dev/null
@@ -1,488 +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 benchmark_test
-
-import (
- "fmt"
- "runtime"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/refs"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// Differences from stat_benchmark:
-//
-// - Syscall interception, CopyInPath, copyOutStat, and overlayfs overheads are
-// not included.
-//
-// - *MountStat benchmarks use a tmpfs root mount and a tmpfs submount at /tmp.
-// Non-MountStat benchmarks use a tmpfs root mount and no submounts.
-// stat_benchmark uses a varying root mount, a tmpfs submount at /tmp, and a
-// subdirectory /tmp/<top_dir> (assuming TEST_TMPDIR == "/tmp"). Thus
-// stat_benchmark at depth 1 does a comparable amount of work to *MountStat
-// benchmarks at depth 2, and non-MountStat benchmarks at depth 3.
-var depths = []int{1, 2, 3, 8, 64, 100}
-
-const (
- mountPointName = "tmp"
- filename = "gvisor_test_temp_0_1557494568"
-)
-
-// This is copied from syscalls/linux/sys_file.go, with the dependency on
-// kernel.Task stripped out.
-func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent) error) error {
- var (
- d *fs.Dirent // The file.
- rel *fs.Dirent // The relative directory for search (if required.)
- err error
- )
-
- // Extract the working directory (maybe).
- if len(path) > 0 && path[0] == '/' {
- // Absolute path; rel can be nil.
- } else if dirFD == linux.AT_FDCWD {
- // Need to reference the working directory.
- rel = wd
- } else {
- // Need to extract the given FD.
- return linuxerr.EBADF
- }
-
- // Lookup the node.
- remainingTraversals := uint(linux.MaxSymlinkTraversals)
- if resolve {
- d, err = mntns.FindInode(ctx, root, rel, path, &remainingTraversals)
- } else {
- d, err = mntns.FindLink(ctx, root, rel, path, &remainingTraversals)
- }
- if err != nil {
- return err
- }
-
- err = fn(root, d)
- d.DecRef(ctx)
- return err
-}
-
-func BenchmarkVFS1TmpfsStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
-
- // Create VFS.
- tmpfsFS, ok := fs.FindFilesystem("tmpfs")
- if !ok {
- b.Fatalf("failed to find tmpfs filesystem type")
- }
- rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- mntns, err := fs.NewMountNamespace(ctx, rootInode)
- if err != nil {
- b.Fatalf("failed to create mount namespace: %v", err)
- }
- defer mntns.DecRef(ctx)
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- root := mntns.Root()
- defer root.DecRef(ctx)
- d := root
- d.IncRef()
- defer d.DecRef(ctx)
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- next, err := d.Walk(ctx, root, name)
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- d.DecRef(ctx)
- d = next
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- file.DecRef(ctx)
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- dirPath := false
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
- if dirPath && !fs.IsDir(d.Inode.StableAttr) {
- return linuxerr.ENOTDIR
- }
- uattr, err := d.Inode.UnstableAttr(ctx)
- if err != nil {
- return err
- }
- // Sanity check.
- if uattr.Perms.User.Execute {
- b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
- }
- return nil
- })
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- }
- // Don't include deferred cleanup in benchmark time.
- b.StopTimer()
- })
- }
-}
-
-func BenchmarkVFS2TmpfsStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- b.Fatalf("VFS init: %v", err)
- }
- vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- defer mntns.DecRef(ctx)
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- root := mntns.Root()
- root.IncRef()
- defer root.DecRef(ctx)
- vd := root
- vd.IncRef()
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- pop := vfs.PathOperation{
- Root: root,
- Start: vd,
- Path: fspath.Parse(name),
- }
- if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- vd.DecRef(ctx)
- vd = nextVD
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: vd,
- Path: fspath.Parse(filename),
- FollowFinalSymlink: true,
- }, &vfs.OpenOptions{
- Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
- Mode: 0644,
- })
- vd.DecRef(ctx)
- vd = vfs.VirtualDentry{}
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- defer fd.DecRef(ctx)
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(filePath),
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- // Sanity check.
- if stat.Mode&^linux.S_IFMT != 0644 {
- b.Fatalf("got wrong permissions (%0o)", stat.Mode)
- }
- }
- // Don't include deferred cleanup in benchmark time.
- b.StopTimer()
- })
- }
-}
-
-func BenchmarkVFS1TmpfsMountStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
-
- // Create VFS.
- tmpfsFS, ok := fs.FindFilesystem("tmpfs")
- if !ok {
- b.Fatalf("failed to find tmpfs filesystem type")
- }
- rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- mntns, err := fs.NewMountNamespace(ctx, rootInode)
- if err != nil {
- b.Fatalf("failed to create mount namespace: %v", err)
- }
- defer mntns.DecRef(ctx)
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create and mount the submount.
- root := mntns.Root()
- defer root.DecRef(ctx)
- if err := root.Inode.CreateDirectory(ctx, root, mountPointName, fs.FilePermsFromMode(0755)); err != nil {
- b.Fatalf("failed to create mount point: %v", err)
- }
- mountPoint, err := root.Walk(ctx, root, mountPointName)
- if err != nil {
- b.Fatalf("failed to walk to mount point: %v", err)
- }
- defer mountPoint.DecRef(ctx)
- submountInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- b.Fatalf("failed to create tmpfs submount: %v", err)
- }
- if err := mntns.Mount(ctx, mountPoint, submountInode); err != nil {
- b.Fatalf("failed to mount tmpfs submount: %v", err)
- }
- filePathBuilder.WriteString(mountPointName)
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- d, err := root.Walk(ctx, root, mountPointName)
- if err != nil {
- b.Fatalf("failed to walk to mount root: %v", err)
- }
- defer d.DecRef(ctx)
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- next, err := d.Walk(ctx, root, name)
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- d.DecRef(ctx)
- d = next
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- file.DecRef(ctx)
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- dirPath := false
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
- if dirPath && !fs.IsDir(d.Inode.StableAttr) {
- return linuxerr.ENOTDIR
- }
- uattr, err := d.Inode.UnstableAttr(ctx)
- if err != nil {
- return err
- }
- // Sanity check.
- if uattr.Perms.User.Execute {
- b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
- }
- return nil
- })
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- }
- // Don't include deferred cleanup in benchmark time.
- b.StopTimer()
- })
- }
-}
-
-func BenchmarkVFS2TmpfsMountStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- b.Fatalf("VFS init: %v", err)
- }
- vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- defer mntns.DecRef(ctx)
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create the mount point.
- root := mntns.Root()
- root.IncRef()
- defer root.DecRef(ctx)
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(mountPointName),
- }
- if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- b.Fatalf("failed to create mount point: %v", err)
- }
- // Save the mount point for later use.
- mountPoint, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to mount point: %v", err)
- }
- defer mountPoint.DecRef(ctx)
- // Create and mount the submount.
- if _, err := vfsObj.MountAt(ctx, creds, "", &pop, "tmpfs", &vfs.MountOptions{}); err != nil {
- b.Fatalf("failed to mount tmpfs submount: %v", err)
- }
- filePathBuilder.WriteString(mountPointName)
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- vd, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to mount root: %v", err)
- }
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- pop := vfs.PathOperation{
- Root: root,
- Start: vd,
- Path: fspath.Parse(name),
- }
- if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- vd.DecRef(ctx)
- vd = nextVD
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: vd,
- Path: fspath.Parse(filename),
- FollowFinalSymlink: true,
- }, &vfs.OpenOptions{
- Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
- Mode: 0644,
- })
- vd.DecRef(ctx)
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- fd.DecRef(ctx)
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(filePath),
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- // Sanity check.
- if stat.Mode&^linux.S_IFMT != 0644 {
- b.Fatalf("got wrong permissions (%0o)", stat.Mode)
- }
- }
- // Don't include deferred cleanup in benchmark time.
- b.StopTimer()
- })
- }
-}
-
-func init() {
- // Turn off reference leak checking for a fair comparison between vfs1 and
- // vfs2.
- refs.SetLeakMode(refs.NoLeakChecking)
-}
diff --git a/pkg/sentry/fsimpl/tmpfs/dentry_list.go b/pkg/sentry/fsimpl/tmpfs/dentry_list.go
new file mode 100644
index 000000000..b95dd7101
--- /dev/null
+++ b/pkg/sentry/fsimpl/tmpfs/dentry_list.go
@@ -0,0 +1,221 @@
+package tmpfs
+
+// 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 dentryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (dentryElementMapper) linkerFor(elem *dentry) *dentry { 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 dentryList struct {
+ head *dentry
+ tail *dentry
+}
+
+// Reset resets list l to the empty state.
+func (l *dentryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *dentryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Front() *dentry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Back() *dentry {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *dentryList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (dentryElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *dentryList) PushFront(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ dentryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *dentryList) PushBack(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *dentryList) PushBackList(m *dentryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ dentryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *dentryList) InsertAfter(b, e *dentry) {
+ bLinker := dentryElementMapper{}.linkerFor(b)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ dentryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *dentryList) InsertBefore(a, e *dentry) {
+ aLinker := dentryElementMapper{}.linkerFor(a)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ dentryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *dentryList) Remove(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ dentryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ dentryElementMapper{}.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 dentryEntry struct {
+ next *dentry
+ prev *dentry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Next() *dentry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Prev() *dentry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetNext(elem *dentry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetPrev(elem *dentry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/tmpfs/fstree.go b/pkg/sentry/fsimpl/tmpfs/fstree.go
new file mode 100644
index 000000000..d46351488
--- /dev/null
+++ b/pkg/sentry/fsimpl/tmpfs/fstree.go
@@ -0,0 +1,55 @@
+package tmpfs
+
+import (
+ "gvisor.dev/gvisor/pkg/fspath"
+ "gvisor.dev/gvisor/pkg/sentry/vfs"
+)
+
+// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is
+// either d2's parent or an ancestor of d2's parent.
+func genericIsAncestorDentry(d, d2 *dentry) bool {
+ for d2 != nil {
+ if d2.parent == d {
+ return true
+ }
+ if d2.parent == d2 {
+ return false
+ }
+ d2 = d2.parent
+ }
+ return false
+}
+
+// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d.
+func genericParentOrSelf(d *dentry) *dentry {
+ if d.parent != nil {
+ return d.parent
+ }
+ return d
+}
+
+// PrependPath is a generic implementation of FilesystemImpl.PrependPath().
+func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *dentry, b *fspath.Builder) error {
+ for {
+ if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() {
+ return vfs.PrependPathAtVFSRootError{}
+ }
+ if mnt != nil && &d.vfsd == mnt.Root() {
+ return nil
+ }
+ if d.parent == nil {
+ return vfs.PrependPathAtNonMountRootError{}
+ }
+ b.PrependComponent(d.name)
+ d = d.parent
+ }
+}
+
+// DebugPathname returns a pathname to d relative to its filesystem root.
+// DebugPathname does not correspond to any Linux function; it's used to
+// generate dentry pathnames for debugging.
+func genericDebugPathname(d *dentry) string {
+ var b fspath.Builder
+ _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b)
+ return b.String()
+}
diff --git a/pkg/sentry/fsimpl/tmpfs/inode_refs.go b/pkg/sentry/fsimpl/tmpfs/inode_refs.go
new file mode 100644
index 000000000..f0f032e0c
--- /dev/null
+++ b/pkg/sentry/fsimpl/tmpfs/inode_refs.go
@@ -0,0 +1,140 @@
+package tmpfs
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/refsvfs2"
+)
+
+// enableLogging indicates whether reference-related events should be logged (with
+// stack traces). This is false by default and should only be set to true for
+// debugging purposes, as it can generate an extremely large amount of output
+// and drastically degrade performance.
+const inodeenableLogging = false
+
+// obj 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 inodeobj *inode
+
+// Refs implements refs.RefCounter. It keeps a reference count using atomic
+// operations and calls the destructor when the count reaches zero.
+//
+// NOTE: Do not introduce additional fields to the Refs struct. It is used by
+// many filesystem objects, and we want to keep it as small as possible (i.e.,
+// the same size as using an int64 directly) to avoid taking up extra cache
+// space. In general, this template should not be extended at the cost of
+// performance. If it does not offer enough flexibility for a particular object
+// (example: b/187877947), we should implement the RefCounter/CheckedObject
+// interfaces manually.
+//
+// +stateify savable
+type inodeRefs 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
+}
+
+// InitRefs initializes r with one reference and, if enabled, activates leak
+// checking.
+func (r *inodeRefs) InitRefs() {
+ atomic.StoreInt64(&r.refCount, 1)
+ refsvfs2.Register(r)
+}
+
+// RefType implements refsvfs2.CheckedObject.RefType.
+func (r *inodeRefs) RefType() string {
+ return fmt.Sprintf("%T", inodeobj)[1:]
+}
+
+// LeakMessage implements refsvfs2.CheckedObject.LeakMessage.
+func (r *inodeRefs) LeakMessage() string {
+ return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
+}
+
+// LogRefs implements refsvfs2.CheckedObject.LogRefs.
+func (r *inodeRefs) LogRefs() bool {
+ return inodeenableLogging
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *inodeRefs) ReadRefs() int64 {
+ return atomic.LoadInt64(&r.refCount)
+}
+
+// IncRef implements refs.RefCounter.IncRef.
+//
+//go:nosplit
+func (r *inodeRefs) IncRef() {
+ v := atomic.AddInt64(&r.refCount, 1)
+ if inodeenableLogging {
+ refsvfs2.LogIncRef(r, v)
+ }
+ if v <= 1 {
+ panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
+ }
+}
+
+// TryIncRef implements refs.TryRefCounter.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 *inodeRefs) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 {
+
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ v := atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ if inodeenableLogging {
+ refsvfs2.LogTryIncRef(r, v)
+ }
+ 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 *inodeRefs) DecRef(destroy func()) {
+ v := atomic.AddInt64(&r.refCount, -1)
+ if inodeenableLogging {
+ refsvfs2.LogDecRef(r, v)
+ }
+ switch {
+ case v < 0:
+ panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
+
+ case v == 0:
+ refsvfs2.Unregister(r)
+
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+func (r *inodeRefs) afterLoad() {
+ if r.ReadRefs() > 0 {
+ refsvfs2.Register(r)
+ }
+}
diff --git a/pkg/sentry/fsimpl/tmpfs/pipe_test.go b/pkg/sentry/fsimpl/tmpfs/pipe_test.go
deleted file mode 100644
index 99afd9817..000000000
--- a/pkg/sentry/fsimpl/tmpfs/pipe_test.go
+++ /dev/null
@@ -1,239 +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 tmpfs
-
-import (
- "bytes"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-const fileName = "mypipe"
-
-func TestSeparateFDs(t *testing.T) {
- ctx, creds, vfsObj, root := setup(t)
- defer root.DecRef(ctx)
-
- // Open the read side. This is done in a concurrently because opening
- // One end the pipe blocks until the other end is opened.
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(fileName),
- FollowFinalSymlink: true,
- }
- rfdchan := make(chan *vfs.FileDescription)
- go func() {
- openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY}
- rfd, _ := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
- rfdchan <- rfd
- }()
-
- // Open the write side.
- openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY}
- wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
- if err != nil {
- t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
- }
- defer wfd.DecRef(ctx)
-
- rfd, ok := <-rfdchan
- if !ok {
- t.Fatalf("failed to open pipe for reading %q", fileName)
- }
- defer rfd.DecRef(ctx)
-
- const msg = "vamos azul"
- checkEmpty(ctx, t, rfd)
- checkWrite(ctx, t, wfd, msg)
- checkRead(ctx, t, rfd, msg)
-}
-
-func TestNonblockingRead(t *testing.T) {
- ctx, creds, vfsObj, root := setup(t)
- defer root.DecRef(ctx)
-
- // Open the read side as nonblocking.
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(fileName),
- FollowFinalSymlink: true,
- }
- openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_NONBLOCK}
- rfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
- if err != nil {
- t.Fatalf("failed to open pipe for reading %q: %v", fileName, err)
- }
- defer rfd.DecRef(ctx)
-
- // Open the write side.
- openOpts = vfs.OpenOptions{Flags: linux.O_WRONLY}
- wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
- if err != nil {
- t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
- }
- defer wfd.DecRef(ctx)
-
- const msg = "geh blau"
- checkEmpty(ctx, t, rfd)
- checkWrite(ctx, t, wfd, msg)
- checkRead(ctx, t, rfd, msg)
-}
-
-func TestNonblockingWriteError(t *testing.T) {
- ctx, creds, vfsObj, root := setup(t)
- defer root.DecRef(ctx)
-
- // Open the write side as nonblocking, which should return ENXIO.
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(fileName),
- FollowFinalSymlink: true,
- }
- openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY | linux.O_NONBLOCK}
- _, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
- if !linuxerr.Equals(linuxerr.ENXIO, err) {
- t.Fatalf("expected ENXIO, but got error: %v", err)
- }
-}
-
-func TestSingleFD(t *testing.T) {
- ctx, creds, vfsObj, root := setup(t)
- defer root.DecRef(ctx)
-
- // Open the pipe as readable and writable.
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(fileName),
- FollowFinalSymlink: true,
- }
- openOpts := vfs.OpenOptions{Flags: linux.O_RDWR}
- fd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
- if err != nil {
- t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
- }
- defer fd.DecRef(ctx)
-
- const msg = "forza blu"
- checkEmpty(ctx, t, fd)
- checkWrite(ctx, t, fd, msg)
- checkRead(ctx, t, fd, msg)
-}
-
-// setup creates a VFS with a pipe in the root directory at path fileName. The
-// returned VirtualDentry must be DecRef()'d be the caller. It calls t.Fatal
-// upon failure.
-func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry) {
- ctx := contexttest.Context(t)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- t.Fatalf("VFS init: %v", err)
- }
- vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
- if err != nil {
- t.Fatalf("failed to create tmpfs root mount: %v", err)
- }
-
- // Create the pipe.
- root := mntns.Root()
- root.IncRef()
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(fileName),
- }
- mknodOpts := vfs.MknodOptions{Mode: linux.ModeNamedPipe | 0644}
- if err := vfsObj.MknodAt(ctx, creds, &pop, &mknodOpts); err != nil {
- t.Fatalf("failed to create file %q: %v", fileName, err)
- }
-
- // Sanity check: the file pipe exists and has the correct mode.
- stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(fileName),
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- t.Fatalf("stat(%q) failed: %v", fileName, err)
- }
- if stat.Mode&^linux.S_IFMT != 0644 {
- t.Errorf("got wrong permissions (%0o)", stat.Mode)
- }
- if stat.Mode&linux.S_IFMT != linux.ModeNamedPipe {
- t.Errorf("got wrong file type (%0o)", stat.Mode)
- }
-
- return ctx, creds, vfsObj, root
-}
-
-// checkEmpty calls t.Fatal if the pipe in fd is not empty.
-func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
- readData := make([]byte, 1)
- dst := usermem.BytesIOSequence(readData)
- bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
- if err != linuxerr.ErrWouldBlock {
- t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err)
- }
- if bytesRead != 0 {
- t.Fatalf("expected to read 0 bytes, but got %d", bytesRead)
- }
-}
-
-// checkWrite calls t.Fatal if it fails to write all of msg to fd.
-func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
- writeData := []byte(msg)
- src := usermem.BytesIOSequence(writeData)
- bytesWritten, err := fd.Write(ctx, src, vfs.WriteOptions{})
- if err != nil {
- t.Fatalf("error writing to pipe %q: %v", fileName, err)
- }
- if bytesWritten != int64(len(writeData)) {
- t.Fatalf("expected to write %d bytes, but wrote %d", len(writeData), bytesWritten)
- }
-}
-
-// checkRead calls t.Fatal if it fails to read msg from fd.
-func checkRead(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
- readData := make([]byte, len(msg))
- dst := usermem.BytesIOSequence(readData)
- bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
- if err != nil {
- t.Fatalf("error reading from pipe %q: %v", fileName, err)
- }
- if bytesRead != int64(len(msg)) {
- t.Fatalf("expected to read %d bytes, but got %d", len(msg), bytesRead)
- }
- if !bytes.Equal(readData, []byte(msg)) {
- t.Fatalf("expected to read %q from pipe, but got %q", msg, string(readData))
- }
-}
diff --git a/pkg/sentry/fsimpl/tmpfs/regular_file_test.go b/pkg/sentry/fsimpl/tmpfs/regular_file_test.go
deleted file mode 100644
index cb7711b39..000000000
--- a/pkg/sentry/fsimpl/tmpfs/regular_file_test.go
+++ /dev/null
@@ -1,349 +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 tmpfs
-
-import (
- "bytes"
- "fmt"
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs/lock"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-// Test that we can write some data to a file and read it back.`
-func TestSimpleWriteRead(t *testing.T) {
- ctx := contexttest.Context(t)
- fd, cleanup, err := newFileFD(ctx, 0644)
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- // Write.
- data := []byte("foobarbaz")
- n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- t.Fatalf("fd.Write failed: %v", err)
- }
- if n != int64(len(data)) {
- t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
- }
- if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
- t.Errorf("fd.Write left offset at %d, want %d", got, want)
- }
-
- // Seek back to beginning.
- if _, err := fd.Seek(ctx, 0, linux.SEEK_SET); err != nil {
- t.Fatalf("fd.Seek failed: %v", err)
- }
- if got, want := fd.Impl().(*regularFileFD).off, int64(0); got != want {
- t.Errorf("fd.Seek(0) left offset at %d, want %d", got, want)
- }
-
- // Read.
- buf := make([]byte, len(data))
- n, err = fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.Read failed: %v", err)
- }
- if n != int64(len(data)) {
- t.Errorf("fd.Read got short read length %d, want %d", n, len(data))
- }
- if got, want := string(buf), string(data); got != want {
- t.Errorf("Read got %q want %s", got, want)
- }
- if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
- t.Errorf("fd.Write left offset at %d, want %d", got, want)
- }
-}
-
-func TestPWrite(t *testing.T) {
- ctx := contexttest.Context(t)
- fd, cleanup, err := newFileFD(ctx, 0644)
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- // Fill file with 1k 'a's.
- data := bytes.Repeat([]byte{'a'}, 1000)
- n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- t.Fatalf("fd.Write failed: %v", err)
- }
- if n != int64(len(data)) {
- t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
- }
-
- // Write "gVisor is awesome" at various offsets.
- buf := []byte("gVisor is awesome")
- offsets := []int{0, 1, 2, 10, 20, 50, 100, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
- for _, offset := range offsets {
- name := fmt.Sprintf("PWrite offset=%d", offset)
- t.Run(name, func(t *testing.T) {
- n, err := fd.PWrite(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.WriteOptions{})
- if err != nil {
- t.Errorf("fd.PWrite got err %v want nil", err)
- }
- if n != int64(len(buf)) {
- t.Errorf("fd.PWrite got %d bytes want %d", n, len(buf))
- }
-
- // Update data to reflect expected file contents.
- if len(data) < offset+len(buf) {
- data = append(data, make([]byte, (offset+len(buf))-len(data))...)
- }
- copy(data[offset:], buf)
-
- // Read the whole file and compare with data.
- readBuf := make([]byte, len(data))
- n, err = fd.PRead(ctx, usermem.BytesIOSequence(readBuf), 0, vfs.ReadOptions{})
- if err != nil {
- t.Fatalf("fd.PRead failed: %v", err)
- }
- if n != int64(len(data)) {
- t.Errorf("fd.PRead got short read length %d, want %d", n, len(data))
- }
- if got, want := string(readBuf), string(data); got != want {
- t.Errorf("PRead got %q want %s", got, want)
- }
-
- })
- }
-}
-
-func TestLocks(t *testing.T) {
- ctx := contexttest.Context(t)
- fd, cleanup, err := newFileFD(ctx, 0644)
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- uid1 := 123
- uid2 := 456
- if err := fd.Impl().LockBSD(ctx, uid1, 0 /* ownerPID */, lock.ReadLock, nil); err != nil {
- t.Fatalf("fd.Impl().LockBSD failed: err = %v", err)
- }
- if err := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, nil); err != nil {
- t.Fatalf("fd.Impl().LockBSD failed: err = %v", err)
- }
- if got, want := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.WriteLock, nil), linuxerr.ErrWouldBlock; got != want {
- t.Fatalf("fd.Impl().LockBSD failed: got = %v, want = %v", got, want)
- }
- if err := fd.Impl().UnlockBSD(ctx, uid1); err != nil {
- t.Fatalf("fd.Impl().UnlockBSD failed: err = %v", err)
- }
- if err := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.WriteLock, nil); err != nil {
- t.Fatalf("fd.Impl().LockBSD failed: err = %v", err)
- }
-
- if err := fd.Impl().LockPOSIX(ctx, uid1, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 0, End: 1}, nil); err != nil {
- t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err)
- }
- if err := fd.Impl().LockPOSIX(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 1, End: 2}, nil); err != nil {
- t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err)
- }
- if err := fd.Impl().LockPOSIX(ctx, uid1, 0 /* ownerPID */, lock.WriteLock, lock.LockRange{Start: 0, End: 1}, nil); err != nil {
- t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err)
- }
- if got, want := fd.Impl().LockPOSIX(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 0, End: 1}, nil), linuxerr.ErrWouldBlock; got != want {
- t.Fatalf("fd.Impl().LockPOSIX failed: got = %v, want = %v", got, want)
- }
- if err := fd.Impl().UnlockPOSIX(ctx, uid1, lock.LockRange{Start: 0, End: 1}); err != nil {
- t.Fatalf("fd.Impl().UnlockPOSIX failed: err = %v", err)
- }
-}
-
-func TestPRead(t *testing.T) {
- ctx := contexttest.Context(t)
- fd, cleanup, err := newFileFD(ctx, 0644)
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- // Write 100 sequences of 'gVisor is awesome'.
- data := bytes.Repeat([]byte("gVisor is awsome"), 100)
- n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- t.Fatalf("fd.Write failed: %v", err)
- }
- if n != int64(len(data)) {
- t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
- }
-
- // Read various sizes from various offsets.
- sizes := []int{0, 1, 2, 10, 20, 50, 100, 1000}
- offsets := []int{0, 1, 2, 10, 20, 50, 100, 1000, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
-
- for _, size := range sizes {
- for _, offset := range offsets {
- name := fmt.Sprintf("PRead offset=%d size=%d", offset, size)
- t.Run(name, func(t *testing.T) {
- var (
- wantRead []byte
- wantErr error
- )
- if offset < len(data) {
- wantRead = data[offset:]
- } else if size > 0 {
- wantErr = io.EOF
- }
- if offset+size < len(data) {
- wantRead = wantRead[:size]
- }
- buf := make([]byte, size)
- n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.ReadOptions{})
- if err != wantErr {
- t.Errorf("fd.PRead got err %v want %v", err, wantErr)
- }
- if n != int64(len(wantRead)) {
- t.Errorf("fd.PRead got %d bytes want %d", n, len(wantRead))
- }
- if got := string(buf[:n]); got != string(wantRead) {
- t.Errorf("fd.PRead got %q want %q", got, string(wantRead))
- }
- })
- }
- }
-}
-
-func TestTruncate(t *testing.T) {
- ctx := contexttest.Context(t)
- fd, cleanup, err := newFileFD(ctx, 0644)
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- // Fill the file with some data.
- data := bytes.Repeat([]byte("gVisor is awsome"), 100)
- written, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- t.Fatalf("fd.Write failed: %v", err)
- }
-
- // Size should be same as written.
- sizeStatOpts := vfs.StatOptions{Mask: linux.STATX_SIZE}
- stat, err := fd.Stat(ctx, sizeStatOpts)
- if err != nil {
- t.Fatalf("fd.Stat failed: %v", err)
- }
- if got, want := int64(stat.Size), written; got != want {
- t.Errorf("fd.Stat got size %d, want %d", got, want)
- }
-
- // Truncate down.
- newSize := uint64(10)
- if err := fd.SetStat(ctx, vfs.SetStatOptions{
- Stat: linux.Statx{
- Mask: linux.STATX_SIZE,
- Size: newSize,
- },
- }); err != nil {
- t.Errorf("fd.Truncate failed: %v", err)
- }
- // Size should be updated.
- statAfterTruncateDown, err := fd.Stat(ctx, sizeStatOpts)
- if err != nil {
- t.Fatalf("fd.Stat failed: %v", err)
- }
- if got, want := statAfterTruncateDown.Size, newSize; got != want {
- t.Errorf("fd.Stat got size %d, want %d", got, want)
- }
- // We should only read newSize worth of data.
- buf := make([]byte, 1000)
- if n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0, vfs.ReadOptions{}); err != nil && err != io.EOF {
- t.Fatalf("fd.PRead failed: %v", err)
- } else if uint64(n) != newSize {
- t.Errorf("fd.PRead got size %d, want %d", n, newSize)
- }
- // Mtime and Ctime should be bumped.
- if got := statAfterTruncateDown.Mtime.ToNsec(); got <= stat.Mtime.ToNsec() {
- t.Errorf("fd.Stat got Mtime %v, want > %v", got, stat.Mtime)
- }
- if got := statAfterTruncateDown.Ctime.ToNsec(); got <= stat.Ctime.ToNsec() {
- t.Errorf("fd.Stat got Ctime %v, want > %v", got, stat.Ctime)
- }
-
- // Truncate up.
- newSize = 100
- if err := fd.SetStat(ctx, vfs.SetStatOptions{
- Stat: linux.Statx{
- Mask: linux.STATX_SIZE,
- Size: newSize,
- },
- }); err != nil {
- t.Errorf("fd.Truncate failed: %v", err)
- }
- // Size should be updated.
- statAfterTruncateUp, err := fd.Stat(ctx, sizeStatOpts)
- if err != nil {
- t.Fatalf("fd.Stat failed: %v", err)
- }
- if got, want := statAfterTruncateUp.Size, newSize; got != want {
- t.Errorf("fd.Stat got size %d, want %d", got, want)
- }
- // We should read newSize worth of data.
- buf = make([]byte, 1000)
- if n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0, vfs.ReadOptions{}); err != nil && err != io.EOF {
- t.Fatalf("fd.PRead failed: %v", err)
- } else if uint64(n) != newSize {
- t.Errorf("fd.PRead got size %d, want %d", n, newSize)
- }
- // Bytes should be null after 10, since we previously truncated to 10.
- for i := uint64(10); i < newSize; i++ {
- if buf[i] != 0 {
- t.Errorf("fd.PRead got byte %d=%x, want 0", i, buf[i])
- break
- }
- }
- // Mtime and Ctime should be bumped.
- if got := statAfterTruncateUp.Mtime.ToNsec(); got <= statAfterTruncateDown.Mtime.ToNsec() {
- t.Errorf("fd.Stat got Mtime %v, want > %v", got, statAfterTruncateDown.Mtime)
- }
- if got := statAfterTruncateUp.Ctime.ToNsec(); got <= statAfterTruncateDown.Ctime.ToNsec() {
- t.Errorf("fd.Stat got Ctime %v, want > %v", got, stat.Ctime)
- }
-
- // Truncate to the current size.
- newSize = statAfterTruncateUp.Size
- if err := fd.SetStat(ctx, vfs.SetStatOptions{
- Stat: linux.Statx{
- Mask: linux.STATX_SIZE,
- Size: newSize,
- },
- }); err != nil {
- t.Errorf("fd.Truncate failed: %v", err)
- }
- statAfterTruncateNoop, err := fd.Stat(ctx, sizeStatOpts)
- if err != nil {
- t.Fatalf("fd.Stat failed: %v", err)
- }
- // Mtime and Ctime should not be bumped, since operation is a noop.
- if got := statAfterTruncateNoop.Mtime.ToNsec(); got != statAfterTruncateUp.Mtime.ToNsec() {
- t.Errorf("fd.Stat got Mtime %v, want %v", got, statAfterTruncateUp.Mtime)
- }
- if got := statAfterTruncateNoop.Ctime.ToNsec(); got != statAfterTruncateUp.Ctime.ToNsec() {
- t.Errorf("fd.Stat got Ctime %v, want %v", got, statAfterTruncateUp.Ctime)
- }
-}
diff --git a/pkg/sentry/fsimpl/tmpfs/stat_test.go b/pkg/sentry/fsimpl/tmpfs/stat_test.go
deleted file mode 100644
index f7ee4aab2..000000000
--- a/pkg/sentry/fsimpl/tmpfs/stat_test.go
+++ /dev/null
@@ -1,236 +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 tmpfs
-
-import (
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-func TestStatAfterCreate(t *testing.T) {
- ctx := contexttest.Context(t)
- mode := linux.FileMode(0644)
-
- // Run with different file types.
- for _, typ := range []string{"file", "dir", "pipe"} {
- t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) {
- var (
- fd *vfs.FileDescription
- cleanup func()
- err error
- )
- switch typ {
- case "file":
- fd, cleanup, err = newFileFD(ctx, mode)
- case "dir":
- fd, cleanup, err = newDirFD(ctx, mode)
- case "pipe":
- fd, cleanup, err = newPipeFD(ctx, mode)
- default:
- panic(fmt.Sprintf("unknown typ %q", typ))
- }
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- got, err := fd.Stat(ctx, vfs.StatOptions{})
- if err != nil {
- t.Fatalf("Stat failed: %v", err)
- }
-
- // Atime, Ctime, Mtime should all be current time (non-zero).
- atime, ctime, mtime := got.Atime.ToNsec(), got.Ctime.ToNsec(), got.Mtime.ToNsec()
- if atime != ctime || ctime != mtime {
- t.Errorf("got atime=%d ctime=%d mtime=%d, wanted equal values", atime, ctime, mtime)
- }
- if atime == 0 {
- t.Errorf("got atime=%d, want non-zero", atime)
- }
-
- // Btime should be 0, as it is not set by tmpfs.
- if btime := got.Btime.ToNsec(); btime != 0 {
- t.Errorf("got btime %d, want 0", got.Btime.ToNsec())
- }
-
- // Size should be 0 (except for directories, which make up a size
- // of 20 per entry, including the "." and ".." entries present in
- // otherwise-empty directories).
- wantSize := uint64(0)
- if typ == "dir" {
- wantSize = 40
- }
- if got.Size != wantSize {
- t.Errorf("got size %d, want %d", got.Size, wantSize)
- }
-
- // Nlink should be 1 for files, 2 for dirs.
- wantNlink := uint32(1)
- if typ == "dir" {
- wantNlink = 2
- }
- if got.Nlink != wantNlink {
- t.Errorf("got nlink %d, want %d", got.Nlink, wantNlink)
- }
-
- // UID and GID are set from context creds.
- creds := auth.CredentialsFromContext(ctx)
- if got.UID != uint32(creds.EffectiveKUID) {
- t.Errorf("got uid %d, want %d", got.UID, uint32(creds.EffectiveKUID))
- }
- if got.GID != uint32(creds.EffectiveKGID) {
- t.Errorf("got gid %d, want %d", got.GID, uint32(creds.EffectiveKGID))
- }
-
- // Mode.
- wantMode := uint16(mode)
- switch typ {
- case "file":
- wantMode |= linux.S_IFREG
- case "dir":
- wantMode |= linux.S_IFDIR
- case "pipe":
- wantMode |= linux.S_IFIFO
- default:
- panic(fmt.Sprintf("unknown typ %q", typ))
- }
-
- if got.Mode != wantMode {
- t.Errorf("got mode %x, want %x", got.Mode, wantMode)
- }
-
- // Ino.
- if got.Ino == 0 {
- t.Errorf("got ino %d, want not 0", got.Ino)
- }
- })
- }
-}
-
-func TestSetStatAtime(t *testing.T) {
- ctx := contexttest.Context(t)
- fd, cleanup, err := newFileFD(ctx, 0644)
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL}
-
- // Get initial stat.
- initialStat, err := fd.Stat(ctx, allStatOptions)
- if err != nil {
- t.Fatalf("Stat failed: %v", err)
- }
-
- // Set atime, but without the mask.
- if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{
- Mask: 0,
- Atime: linux.NsecToStatxTimestamp(100),
- }}); err != nil {
- t.Errorf("SetStat atime without mask failed: %v", err)
- }
- // Atime should be unchanged.
- if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
- t.Errorf("Stat got error: %v", err)
- } else if gotStat.Atime != initialStat.Atime {
- t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime)
- }
-
- // Set atime, this time included in the mask.
- setStat := linux.Statx{
- Mask: linux.STATX_ATIME,
- Atime: linux.NsecToStatxTimestamp(100),
- }
- if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil {
- t.Errorf("SetStat atime with mask failed: %v", err)
- }
- if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
- t.Errorf("Stat got error: %v", err)
- } else if gotStat.Atime != setStat.Atime {
- t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime)
- }
-}
-
-func TestSetStat(t *testing.T) {
- ctx := contexttest.Context(t)
- mode := linux.FileMode(0644)
-
- // Run with different file types.
- for _, typ := range []string{"file", "dir", "pipe"} {
- t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) {
- var (
- fd *vfs.FileDescription
- cleanup func()
- err error
- )
- switch typ {
- case "file":
- fd, cleanup, err = newFileFD(ctx, mode)
- case "dir":
- fd, cleanup, err = newDirFD(ctx, mode)
- case "pipe":
- fd, cleanup, err = newPipeFD(ctx, mode)
- default:
- panic(fmt.Sprintf("unknown typ %q", typ))
- }
- if err != nil {
- t.Fatal(err)
- }
- defer cleanup()
-
- allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL}
-
- // Get initial stat.
- initialStat, err := fd.Stat(ctx, allStatOptions)
- if err != nil {
- t.Fatalf("Stat failed: %v", err)
- }
-
- // Set atime, but without the mask.
- if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{
- Mask: 0,
- Atime: linux.NsecToStatxTimestamp(100),
- }}); err != nil {
- t.Errorf("SetStat atime without mask failed: %v", err)
- }
- // Atime should be unchanged.
- if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
- t.Errorf("Stat got error: %v", err)
- } else if gotStat.Atime != initialStat.Atime {
- t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime)
- }
-
- // Set atime, this time included in the mask.
- setStat := linux.Statx{
- Mask: linux.STATX_ATIME,
- Atime: linux.NsecToStatxTimestamp(100),
- }
- if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil {
- t.Errorf("SetStat atime with mask failed: %v", err)
- }
- if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
- t.Errorf("Stat got error: %v", err)
- } else if gotStat.Atime != setStat.Atime {
- t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime)
- }
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go b/pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go
new file mode 100644
index 000000000..9a30a69a3
--- /dev/null
+++ b/pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go
@@ -0,0 +1,599 @@
+// automatically generated by stateify.
+
+package tmpfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *dentryList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.dentryList"
+}
+
+func (l *dentryList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *dentryList) beforeSave() {}
+
+// +checklocksignore
+func (l *dentryList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *dentryList) afterLoad() {}
+
+// +checklocksignore
+func (l *dentryList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *dentryEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.dentryEntry"
+}
+
+func (e *dentryEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *dentryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *dentryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (d *deviceFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.deviceFile"
+}
+
+func (d *deviceFile) StateFields() []string {
+ return []string{
+ "inode",
+ "kind",
+ "major",
+ "minor",
+ }
+}
+
+func (d *deviceFile) beforeSave() {}
+
+// +checklocksignore
+func (d *deviceFile) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.inode)
+ stateSinkObject.Save(1, &d.kind)
+ stateSinkObject.Save(2, &d.major)
+ stateSinkObject.Save(3, &d.minor)
+}
+
+func (d *deviceFile) afterLoad() {}
+
+// +checklocksignore
+func (d *deviceFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.inode)
+ stateSourceObject.Load(1, &d.kind)
+ stateSourceObject.Load(2, &d.major)
+ stateSourceObject.Load(3, &d.minor)
+}
+
+func (dir *directory) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.directory"
+}
+
+func (dir *directory) StateFields() []string {
+ return []string{
+ "dentry",
+ "inode",
+ "childMap",
+ "numChildren",
+ "childList",
+ }
+}
+
+func (dir *directory) beforeSave() {}
+
+// +checklocksignore
+func (dir *directory) StateSave(stateSinkObject state.Sink) {
+ dir.beforeSave()
+ stateSinkObject.Save(0, &dir.dentry)
+ stateSinkObject.Save(1, &dir.inode)
+ stateSinkObject.Save(2, &dir.childMap)
+ stateSinkObject.Save(3, &dir.numChildren)
+ stateSinkObject.Save(4, &dir.childList)
+}
+
+func (dir *directory) afterLoad() {}
+
+// +checklocksignore
+func (dir *directory) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &dir.dentry)
+ stateSourceObject.Load(1, &dir.inode)
+ stateSourceObject.Load(2, &dir.childMap)
+ stateSourceObject.Load(3, &dir.numChildren)
+ stateSourceObject.Load(4, &dir.childList)
+}
+
+func (fd *directoryFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.directoryFD"
+}
+
+func (fd *directoryFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "DirectoryFileDescriptionDefaultImpl",
+ "iter",
+ "off",
+ }
+}
+
+func (fd *directoryFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *directoryFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.iter)
+ stateSinkObject.Save(3, &fd.off)
+}
+
+func (fd *directoryFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *directoryFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.iter)
+ stateSourceObject.Load(3, &fd.off)
+}
+
+func (r *inodeRefs) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.inodeRefs"
+}
+
+func (r *inodeRefs) StateFields() []string {
+ return []string{
+ "refCount",
+ }
+}
+
+func (r *inodeRefs) beforeSave() {}
+
+// +checklocksignore
+func (r *inodeRefs) StateSave(stateSinkObject state.Sink) {
+ r.beforeSave()
+ stateSinkObject.Save(0, &r.refCount)
+}
+
+// +checklocksignore
+func (r *inodeRefs) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &r.refCount)
+ stateSourceObject.AfterLoad(r.afterLoad)
+}
+
+func (n *namedPipe) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.namedPipe"
+}
+
+func (n *namedPipe) StateFields() []string {
+ return []string{
+ "inode",
+ "pipe",
+ }
+}
+
+func (n *namedPipe) beforeSave() {}
+
+// +checklocksignore
+func (n *namedPipe) StateSave(stateSinkObject state.Sink) {
+ n.beforeSave()
+ stateSinkObject.Save(0, &n.inode)
+ stateSinkObject.Save(1, &n.pipe)
+}
+
+func (n *namedPipe) afterLoad() {}
+
+// +checklocksignore
+func (n *namedPipe) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &n.inode)
+ stateSourceObject.Load(1, &n.pipe)
+}
+
+func (rf *regularFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.regularFile"
+}
+
+func (rf *regularFile) StateFields() []string {
+ return []string{
+ "inode",
+ "memoryUsageKind",
+ "mappings",
+ "writableMappingPages",
+ "data",
+ "seals",
+ "size",
+ }
+}
+
+func (rf *regularFile) beforeSave() {}
+
+// +checklocksignore
+func (rf *regularFile) StateSave(stateSinkObject state.Sink) {
+ rf.beforeSave()
+ stateSinkObject.Save(0, &rf.inode)
+ stateSinkObject.Save(1, &rf.memoryUsageKind)
+ stateSinkObject.Save(2, &rf.mappings)
+ stateSinkObject.Save(3, &rf.writableMappingPages)
+ stateSinkObject.Save(4, &rf.data)
+ stateSinkObject.Save(5, &rf.seals)
+ stateSinkObject.Save(6, &rf.size)
+}
+
+// +checklocksignore
+func (rf *regularFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &rf.inode)
+ stateSourceObject.Load(1, &rf.memoryUsageKind)
+ stateSourceObject.Load(2, &rf.mappings)
+ stateSourceObject.Load(3, &rf.writableMappingPages)
+ stateSourceObject.Load(4, &rf.data)
+ stateSourceObject.Load(5, &rf.seals)
+ stateSourceObject.Load(6, &rf.size)
+ stateSourceObject.AfterLoad(rf.afterLoad)
+}
+
+func (fd *regularFileFD) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.regularFileFD"
+}
+
+func (fd *regularFileFD) StateFields() []string {
+ return []string{
+ "fileDescription",
+ "off",
+ }
+}
+
+func (fd *regularFileFD) beforeSave() {}
+
+// +checklocksignore
+func (fd *regularFileFD) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.fileDescription)
+ stateSinkObject.Save(1, &fd.off)
+}
+
+func (fd *regularFileFD) afterLoad() {}
+
+// +checklocksignore
+func (fd *regularFileFD) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.fileDescription)
+ stateSourceObject.Load(1, &fd.off)
+}
+
+func (s *socketFile) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.socketFile"
+}
+
+func (s *socketFile) StateFields() []string {
+ return []string{
+ "inode",
+ "ep",
+ }
+}
+
+func (s *socketFile) beforeSave() {}
+
+// +checklocksignore
+func (s *socketFile) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.inode)
+ stateSinkObject.Save(1, &s.ep)
+}
+
+func (s *socketFile) afterLoad() {}
+
+// +checklocksignore
+func (s *socketFile) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.inode)
+ stateSourceObject.Load(1, &s.ep)
+}
+
+func (s *symlink) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.symlink"
+}
+
+func (s *symlink) StateFields() []string {
+ return []string{
+ "inode",
+ "target",
+ }
+}
+
+func (s *symlink) beforeSave() {}
+
+// +checklocksignore
+func (s *symlink) StateSave(stateSinkObject state.Sink) {
+ s.beforeSave()
+ stateSinkObject.Save(0, &s.inode)
+ stateSinkObject.Save(1, &s.target)
+}
+
+func (s *symlink) afterLoad() {}
+
+// +checklocksignore
+func (s *symlink) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &s.inode)
+ stateSourceObject.Load(1, &s.target)
+}
+
+func (fstype *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.FilesystemType"
+}
+
+func (fstype *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fstype *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fstype.beforeSave()
+}
+
+func (fstype *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "vfsfs",
+ "mfp",
+ "clock",
+ "devMinor",
+ "mopts",
+ "usage",
+ "nextInoMinusOne",
+ "root",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.vfsfs)
+ stateSinkObject.Save(1, &fs.mfp)
+ stateSinkObject.Save(2, &fs.clock)
+ stateSinkObject.Save(3, &fs.devMinor)
+ stateSinkObject.Save(4, &fs.mopts)
+ stateSinkObject.Save(5, &fs.usage)
+ stateSinkObject.Save(6, &fs.nextInoMinusOne)
+ stateSinkObject.Save(7, &fs.root)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.vfsfs)
+ stateSourceObject.Load(1, &fs.mfp)
+ stateSourceObject.Load(2, &fs.clock)
+ stateSourceObject.Load(3, &fs.devMinor)
+ stateSourceObject.Load(4, &fs.mopts)
+ stateSourceObject.Load(5, &fs.usage)
+ stateSourceObject.Load(6, &fs.nextInoMinusOne)
+ stateSourceObject.Load(7, &fs.root)
+}
+
+func (f *FilesystemOpts) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.FilesystemOpts"
+}
+
+func (f *FilesystemOpts) StateFields() []string {
+ return []string{
+ "RootFileType",
+ "RootSymlinkTarget",
+ "FilesystemType",
+ "Usage",
+ }
+}
+
+func (f *FilesystemOpts) beforeSave() {}
+
+// +checklocksignore
+func (f *FilesystemOpts) StateSave(stateSinkObject state.Sink) {
+ f.beforeSave()
+ stateSinkObject.Save(0, &f.RootFileType)
+ stateSinkObject.Save(1, &f.RootSymlinkTarget)
+ stateSinkObject.Save(2, &f.FilesystemType)
+ stateSinkObject.Save(3, &f.Usage)
+}
+
+func (f *FilesystemOpts) afterLoad() {}
+
+// +checklocksignore
+func (f *FilesystemOpts) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &f.RootFileType)
+ stateSourceObject.Load(1, &f.RootSymlinkTarget)
+ stateSourceObject.Load(2, &f.FilesystemType)
+ stateSourceObject.Load(3, &f.Usage)
+}
+
+func (d *dentry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.dentry"
+}
+
+func (d *dentry) StateFields() []string {
+ return []string{
+ "vfsd",
+ "parent",
+ "name",
+ "dentryEntry",
+ "inode",
+ }
+}
+
+func (d *dentry) beforeSave() {}
+
+// +checklocksignore
+func (d *dentry) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.vfsd)
+ stateSinkObject.Save(1, &d.parent)
+ stateSinkObject.Save(2, &d.name)
+ stateSinkObject.Save(3, &d.dentryEntry)
+ stateSinkObject.Save(4, &d.inode)
+}
+
+func (d *dentry) afterLoad() {}
+
+// +checklocksignore
+func (d *dentry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.vfsd)
+ stateSourceObject.Load(1, &d.parent)
+ stateSourceObject.Load(2, &d.name)
+ stateSourceObject.Load(3, &d.dentryEntry)
+ stateSourceObject.Load(4, &d.inode)
+}
+
+func (i *inode) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.inode"
+}
+
+func (i *inode) StateFields() []string {
+ return []string{
+ "fs",
+ "refs",
+ "xattrs",
+ "mode",
+ "nlink",
+ "uid",
+ "gid",
+ "ino",
+ "atime",
+ "ctime",
+ "mtime",
+ "locks",
+ "watches",
+ "impl",
+ }
+}
+
+func (i *inode) beforeSave() {}
+
+// +checklocksignore
+func (i *inode) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.fs)
+ stateSinkObject.Save(1, &i.refs)
+ stateSinkObject.Save(2, &i.xattrs)
+ stateSinkObject.Save(3, &i.mode)
+ stateSinkObject.Save(4, &i.nlink)
+ stateSinkObject.Save(5, &i.uid)
+ stateSinkObject.Save(6, &i.gid)
+ stateSinkObject.Save(7, &i.ino)
+ stateSinkObject.Save(8, &i.atime)
+ stateSinkObject.Save(9, &i.ctime)
+ stateSinkObject.Save(10, &i.mtime)
+ stateSinkObject.Save(11, &i.locks)
+ stateSinkObject.Save(12, &i.watches)
+ stateSinkObject.Save(13, &i.impl)
+}
+
+func (i *inode) afterLoad() {}
+
+// +checklocksignore
+func (i *inode) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.fs)
+ stateSourceObject.Load(1, &i.refs)
+ stateSourceObject.Load(2, &i.xattrs)
+ stateSourceObject.Load(3, &i.mode)
+ stateSourceObject.Load(4, &i.nlink)
+ stateSourceObject.Load(5, &i.uid)
+ stateSourceObject.Load(6, &i.gid)
+ stateSourceObject.Load(7, &i.ino)
+ stateSourceObject.Load(8, &i.atime)
+ stateSourceObject.Load(9, &i.ctime)
+ stateSourceObject.Load(10, &i.mtime)
+ stateSourceObject.Load(11, &i.locks)
+ stateSourceObject.Load(12, &i.watches)
+ stateSourceObject.Load(13, &i.impl)
+}
+
+func (fd *fileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/tmpfs.fileDescription"
+}
+
+func (fd *fileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "LockFD",
+ }
+}
+
+func (fd *fileDescription) beforeSave() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.LockFD)
+}
+
+func (fd *fileDescription) afterLoad() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.LockFD)
+}
+
+func init() {
+ state.Register((*dentryList)(nil))
+ state.Register((*dentryEntry)(nil))
+ state.Register((*deviceFile)(nil))
+ state.Register((*directory)(nil))
+ state.Register((*directoryFD)(nil))
+ state.Register((*inodeRefs)(nil))
+ state.Register((*namedPipe)(nil))
+ state.Register((*regularFile)(nil))
+ state.Register((*regularFileFD)(nil))
+ state.Register((*socketFile)(nil))
+ state.Register((*symlink)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*FilesystemOpts)(nil))
+ state.Register((*dentry)(nil))
+ state.Register((*inode)(nil))
+ state.Register((*fileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go b/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go
deleted file mode 100644
index fc5323abc..000000000
--- a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go
+++ /dev/null
@@ -1,157 +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 tmpfs
-
-import (
- "fmt"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// nextFileID is used to generate unique file names.
-var nextFileID int64
-
-// newTmpfsRoot creates a new tmpfs mount, and returns the root. If the error
-// is not nil, then cleanup should be called when the root is no longer needed.
-func newTmpfsRoot(ctx context.Context) (*vfs.VirtualFilesystem, vfs.VirtualDentry, func(), error) {
- creds := auth.CredentialsFromContext(ctx)
-
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err)
- }
-
- vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
- if err != nil {
- return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err)
- }
- root := mntns.Root()
- root.IncRef()
- return vfsObj, root, func() {
- root.DecRef(ctx)
- mntns.DecRef(ctx)
- }, nil
-}
-
-// newFileFD creates a new file in a new tmpfs mount, and returns the FD. If
-// the returned err is not nil, then cleanup should be called when the FD is no
-// longer needed.
-func newFileFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) {
- creds := auth.CredentialsFromContext(ctx)
- vfsObj, root, cleanup, err := newTmpfsRoot(ctx)
- if err != nil {
- return nil, nil, err
- }
-
- filename := fmt.Sprintf("tmpfs-test-file-%d", atomic.AddInt64(&nextFileID, 1))
-
- // Create the file that will be write/read.
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(filename),
- }, &vfs.OpenOptions{
- Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
- Mode: linux.ModeRegular | mode,
- })
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("failed to create file %q: %v", filename, err)
- }
-
- return fd, cleanup, nil
-}
-
-// newDirFD is like newFileFD, but for directories.
-func newDirFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) {
- creds := auth.CredentialsFromContext(ctx)
- vfsObj, root, cleanup, err := newTmpfsRoot(ctx)
- if err != nil {
- return nil, nil, err
- }
-
- dirname := fmt.Sprintf("tmpfs-test-dir-%d", atomic.AddInt64(&nextFileID, 1))
-
- // Create the dir.
- if err := vfsObj.MkdirAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(dirname),
- }, &vfs.MkdirOptions{
- Mode: linux.ModeDirectory | mode,
- }); err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("failed to create directory %q: %v", dirname, err)
- }
-
- // Open the dir and return it.
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(dirname),
- }, &vfs.OpenOptions{
- Flags: linux.O_RDONLY | linux.O_DIRECTORY,
- })
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("failed to open directory %q: %v", dirname, err)
- }
-
- return fd, cleanup, nil
-}
-
-// newPipeFD is like newFileFD, but for pipes.
-func newPipeFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) {
- creds := auth.CredentialsFromContext(ctx)
- vfsObj, root, cleanup, err := newTmpfsRoot(ctx)
- if err != nil {
- return nil, nil, err
- }
-
- name := fmt.Sprintf("tmpfs-test-%d", atomic.AddInt64(&nextFileID, 1))
-
- if err := vfsObj.MknodAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(name),
- }, &vfs.MknodOptions{
- Mode: linux.ModeNamedPipe | mode,
- }); err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("failed to create pipe %q: %v", name, err)
- }
-
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(name),
- }, &vfs.OpenOptions{
- Flags: linux.O_RDWR,
- })
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("failed to open pipe %q: %v", name, err)
- }
-
- return fd, cleanup, nil
-}
diff --git a/pkg/sentry/fsimpl/verity/BUILD b/pkg/sentry/fsimpl/verity/BUILD
deleted file mode 100644
index c12abdf33..000000000
--- a/pkg/sentry/fsimpl/verity/BUILD
+++ /dev/null
@@ -1,68 +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 = "dentry_list",
- out = "dentry_list.go",
- package = "verity",
- prefix = "dentry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*dentry",
- "Linker": "*dentry",
- },
-)
-
-go_library(
- name = "verity",
- srcs = [
- "dentry_list.go",
- "filesystem.go",
- "save_restore.go",
- "verity.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/marshal/primitive",
- "//pkg/merkletree",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/arch",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "verity_test",
- srcs = [
- "verity_test.go",
- ],
- library = ":verity",
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/sentry/arch",
- "//pkg/sentry/fsimpl/testutil",
- "//pkg/sentry/fsimpl/tmpfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- ],
-)
diff --git a/pkg/sentry/fsimpl/verity/dentry_list.go b/pkg/sentry/fsimpl/verity/dentry_list.go
new file mode 100644
index 000000000..ace6086b7
--- /dev/null
+++ b/pkg/sentry/fsimpl/verity/dentry_list.go
@@ -0,0 +1,221 @@
+package verity
+
+// 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 dentryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (dentryElementMapper) linkerFor(elem *dentry) *dentry { 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 dentryList struct {
+ head *dentry
+ tail *dentry
+}
+
+// Reset resets list l to the empty state.
+func (l *dentryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *dentryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Front() *dentry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Back() *dentry {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *dentryList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (dentryElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *dentryList) PushFront(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ dentryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *dentryList) PushBack(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *dentryList) PushBackList(m *dentryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ dentryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *dentryList) InsertAfter(b, e *dentry) {
+ bLinker := dentryElementMapper{}.linkerFor(b)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ dentryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *dentryList) InsertBefore(a, e *dentry) {
+ aLinker := dentryElementMapper{}.linkerFor(a)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ dentryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *dentryList) Remove(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ dentryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ dentryElementMapper{}.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 dentryEntry struct {
+ next *dentry
+ prev *dentry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Next() *dentry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Prev() *dentry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetNext(elem *dentry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetPrev(elem *dentry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/verity/verity_state_autogen.go b/pkg/sentry/fsimpl/verity/verity_state_autogen.go
new file mode 100644
index 000000000..302823121
--- /dev/null
+++ b/pkg/sentry/fsimpl/verity/verity_state_autogen.go
@@ -0,0 +1,316 @@
+// automatically generated by stateify.
+
+package verity
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *dentryList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.dentryList"
+}
+
+func (l *dentryList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *dentryList) beforeSave() {}
+
+// +checklocksignore
+func (l *dentryList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *dentryList) afterLoad() {}
+
+// +checklocksignore
+func (l *dentryList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *dentryEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.dentryEntry"
+}
+
+func (e *dentryEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *dentryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *dentryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (fstype *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.FilesystemType"
+}
+
+func (fstype *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fstype *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fstype.beforeSave()
+}
+
+func (fstype *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "vfsfs",
+ "creds",
+ "allowRuntimeEnable",
+ "lowerMount",
+ "rootDentry",
+ "alg",
+ "action",
+ "opts",
+ "cachedDentries",
+ "cachedDentriesLen",
+ "maxCachedDentries",
+ "released",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.vfsfs)
+ stateSinkObject.Save(1, &fs.creds)
+ stateSinkObject.Save(2, &fs.allowRuntimeEnable)
+ stateSinkObject.Save(3, &fs.lowerMount)
+ stateSinkObject.Save(4, &fs.rootDentry)
+ stateSinkObject.Save(5, &fs.alg)
+ stateSinkObject.Save(6, &fs.action)
+ stateSinkObject.Save(7, &fs.opts)
+ stateSinkObject.Save(8, &fs.cachedDentries)
+ stateSinkObject.Save(9, &fs.cachedDentriesLen)
+ stateSinkObject.Save(10, &fs.maxCachedDentries)
+ stateSinkObject.Save(11, &fs.released)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.vfsfs)
+ stateSourceObject.Load(1, &fs.creds)
+ stateSourceObject.Load(2, &fs.allowRuntimeEnable)
+ stateSourceObject.Load(3, &fs.lowerMount)
+ stateSourceObject.Load(4, &fs.rootDentry)
+ stateSourceObject.Load(5, &fs.alg)
+ stateSourceObject.Load(6, &fs.action)
+ stateSourceObject.Load(7, &fs.opts)
+ stateSourceObject.Load(8, &fs.cachedDentries)
+ stateSourceObject.Load(9, &fs.cachedDentriesLen)
+ stateSourceObject.Load(10, &fs.maxCachedDentries)
+ stateSourceObject.Load(11, &fs.released)
+}
+
+func (i *InternalFilesystemOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.InternalFilesystemOptions"
+}
+
+func (i *InternalFilesystemOptions) StateFields() []string {
+ return []string{
+ "LowerName",
+ "Alg",
+ "AllowRuntimeEnable",
+ "LowerGetFSOptions",
+ "Action",
+ }
+}
+
+func (i *InternalFilesystemOptions) beforeSave() {}
+
+// +checklocksignore
+func (i *InternalFilesystemOptions) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.LowerName)
+ stateSinkObject.Save(1, &i.Alg)
+ stateSinkObject.Save(2, &i.AllowRuntimeEnable)
+ stateSinkObject.Save(3, &i.LowerGetFSOptions)
+ stateSinkObject.Save(4, &i.Action)
+}
+
+func (i *InternalFilesystemOptions) afterLoad() {}
+
+// +checklocksignore
+func (i *InternalFilesystemOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.LowerName)
+ stateSourceObject.Load(1, &i.Alg)
+ stateSourceObject.Load(2, &i.AllowRuntimeEnable)
+ stateSourceObject.Load(3, &i.LowerGetFSOptions)
+ stateSourceObject.Load(4, &i.Action)
+}
+
+func (d *dentry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.dentry"
+}
+
+func (d *dentry) StateFields() []string {
+ return []string{
+ "vfsd",
+ "refs",
+ "fs",
+ "mode",
+ "uid",
+ "gid",
+ "size",
+ "parent",
+ "name",
+ "children",
+ "childrenNames",
+ "childrenList",
+ "lowerVD",
+ "lowerMerkleVD",
+ "symlinkTarget",
+ "hash",
+ "cached",
+ "dentryEntry",
+ }
+}
+
+func (d *dentry) beforeSave() {}
+
+// +checklocksignore
+func (d *dentry) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.vfsd)
+ stateSinkObject.Save(1, &d.refs)
+ stateSinkObject.Save(2, &d.fs)
+ stateSinkObject.Save(3, &d.mode)
+ stateSinkObject.Save(4, &d.uid)
+ stateSinkObject.Save(5, &d.gid)
+ stateSinkObject.Save(6, &d.size)
+ stateSinkObject.Save(7, &d.parent)
+ stateSinkObject.Save(8, &d.name)
+ stateSinkObject.Save(9, &d.children)
+ stateSinkObject.Save(10, &d.childrenNames)
+ stateSinkObject.Save(11, &d.childrenList)
+ stateSinkObject.Save(12, &d.lowerVD)
+ stateSinkObject.Save(13, &d.lowerMerkleVD)
+ stateSinkObject.Save(14, &d.symlinkTarget)
+ stateSinkObject.Save(15, &d.hash)
+ stateSinkObject.Save(16, &d.cached)
+ stateSinkObject.Save(17, &d.dentryEntry)
+}
+
+// +checklocksignore
+func (d *dentry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.vfsd)
+ stateSourceObject.Load(1, &d.refs)
+ stateSourceObject.Load(2, &d.fs)
+ stateSourceObject.Load(3, &d.mode)
+ stateSourceObject.Load(4, &d.uid)
+ stateSourceObject.Load(5, &d.gid)
+ stateSourceObject.Load(6, &d.size)
+ stateSourceObject.Load(7, &d.parent)
+ stateSourceObject.Load(8, &d.name)
+ stateSourceObject.Load(9, &d.children)
+ stateSourceObject.Load(10, &d.childrenNames)
+ stateSourceObject.Load(11, &d.childrenList)
+ stateSourceObject.Load(12, &d.lowerVD)
+ stateSourceObject.Load(13, &d.lowerMerkleVD)
+ stateSourceObject.Load(14, &d.symlinkTarget)
+ stateSourceObject.Load(15, &d.hash)
+ stateSourceObject.Load(16, &d.cached)
+ stateSourceObject.Load(17, &d.dentryEntry)
+ stateSourceObject.AfterLoad(d.afterLoad)
+}
+
+func (fd *fileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.fileDescription"
+}
+
+func (fd *fileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "d",
+ "isDir",
+ "lowerFD",
+ "lowerMappable",
+ "merkleReader",
+ "merkleWriter",
+ "parentMerkleWriter",
+ "off",
+ }
+}
+
+func (fd *fileDescription) beforeSave() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.d)
+ stateSinkObject.Save(3, &fd.isDir)
+ stateSinkObject.Save(4, &fd.lowerFD)
+ stateSinkObject.Save(5, &fd.lowerMappable)
+ stateSinkObject.Save(6, &fd.merkleReader)
+ stateSinkObject.Save(7, &fd.merkleWriter)
+ stateSinkObject.Save(8, &fd.parentMerkleWriter)
+ stateSinkObject.Save(9, &fd.off)
+}
+
+func (fd *fileDescription) afterLoad() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.d)
+ stateSourceObject.Load(3, &fd.isDir)
+ stateSourceObject.Load(4, &fd.lowerFD)
+ stateSourceObject.Load(5, &fd.lowerMappable)
+ stateSourceObject.Load(6, &fd.merkleReader)
+ stateSourceObject.Load(7, &fd.merkleWriter)
+ stateSourceObject.Load(8, &fd.parentMerkleWriter)
+ stateSourceObject.Load(9, &fd.off)
+}
+
+func init() {
+ state.Register((*dentryList)(nil))
+ state.Register((*dentryEntry)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*InternalFilesystemOptions)(nil))
+ state.Register((*dentry)(nil))
+ state.Register((*fileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/verity/verity_test.go b/pkg/sentry/fsimpl/verity/verity_test.go
deleted file mode 100644
index af041bd50..000000000
--- a/pkg/sentry/fsimpl/verity/verity_test.go
+++ /dev/null
@@ -1,1211 +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 verity
-
-import (
- "fmt"
- "io"
- "math/rand"
- "strconv"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/arch"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-const (
- // rootMerkleFilename is the name of the root Merkle tree file.
- rootMerkleFilename = "root.verity"
- // maxDataSize is the maximum data size of a test file.
- maxDataSize = 100000
-)
-
-var hashAlgs = []HashAlgorithm{SHA256, SHA512}
-
-func dentryFromVD(t *testing.T, vd vfs.VirtualDentry) *dentry {
- t.Helper()
- d, ok := vd.Dentry().Impl().(*dentry)
- if !ok {
- t.Fatalf("can't assert %T as a *dentry", vd)
- }
- return d
-}
-
-// dentryFromFD returns the dentry corresponding to fd.
-func dentryFromFD(t *testing.T, fd *vfs.FileDescription) *dentry {
- t.Helper()
- f, ok := fd.Impl().(*fileDescription)
- if !ok {
- t.Fatalf("can't assert %T as a *fileDescription", fd)
- }
- return f.d
-}
-
-// newVerityRoot creates a new verity mount, and returns the root. The
-// underlying file system is tmpfs. If the error is not nil, then cleanup
-// should be called when the root is no longer needed.
-func newVerityRoot(t *testing.T, hashAlg HashAlgorithm) (*vfs.VirtualFilesystem, vfs.VirtualDentry, context.Context, error) {
- t.Helper()
- k, err := testutil.Boot()
- if err != nil {
- t.Fatalf("testutil.Boot: %v", err)
- }
-
- ctx := k.SupervisorContext()
-
- rand.Seed(time.Now().UnixNano())
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err)
- }
-
- vfsObj.MustRegisterFilesystemType("verity", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- data := "root_name=" + rootMerkleFilename
- mntns, err := vfsObj.NewMountNamespace(ctx, auth.CredentialsFromContext(ctx), "", "verity", &vfs.MountOptions{
- GetFilesystemOptions: vfs.GetFilesystemOptions{
- Data: data,
- InternalData: InternalFilesystemOptions{
- LowerName: "tmpfs",
- Alg: hashAlg,
- AllowRuntimeEnable: true,
- Action: ErrorOnViolation,
- },
- },
- })
- if err != nil {
- return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("NewMountNamespace: %v", err)
- }
- root := mntns.Root()
- root.IncRef()
-
- // Use lowerRoot in the task as we modify the lower file system
- // directly in many tests.
- lowerRoot := root.Dentry().Impl().(*dentry).lowerVD
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(ctx, "name", tc, mntns, lowerRoot, lowerRoot)
- if err != nil {
- t.Fatalf("testutil.CreateTask: %v", err)
- }
-
- t.Cleanup(func() {
- root.DecRef(ctx)
- mntns.DecRef(ctx)
- })
- return vfsObj, root, task.AsyncContext(), nil
-}
-
-// openVerityAt opens a verity file.
-//
-// TODO(chongc): release reference from opening the file when done.
-func openVerityAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, vd vfs.VirtualDentry, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) {
- return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: vd,
- Start: vd,
- Path: fspath.Parse(path),
- }, &vfs.OpenOptions{
- Flags: flags,
- Mode: mode,
- })
-}
-
-// openLowerAt opens the file in the underlying file system.
-//
-// TODO(chongc): release reference from opening the file when done.
-func (d *dentry) openLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) {
- return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(path),
- }, &vfs.OpenOptions{
- Flags: flags,
- Mode: mode,
- })
-}
-
-// openLowerMerkleAt opens the Merkle file in the underlying file system.
-//
-// TODO(chongc): release reference from opening the file when done.
-func (d *dentry) openLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) {
- return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerMerkleVD,
- Start: d.lowerMerkleVD,
- }, &vfs.OpenOptions{
- Flags: flags,
- Mode: mode,
- })
-}
-
-// mkdirLowerAt creates a directory in the underlying file system.
-func (d *dentry) mkdirLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, mode linux.FileMode) error {
- return vfsObj.MkdirAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(path),
- }, &vfs.MkdirOptions{
- Mode: mode,
- })
-}
-
-// unlinkLowerAt deletes the file in the underlying file system.
-func (d *dentry) unlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error {
- return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(path),
- })
-}
-
-// unlinkLowerMerkleAt deletes the Merkle file in the underlying file system.
-func (d *dentry) unlinkLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error {
- return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(merklePrefix + path),
- })
-}
-
-// renameLowerAt renames file name to newName in the underlying file system.
-func (d *dentry) renameLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error {
- return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(name),
- }, &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(newName),
- }, &vfs.RenameOptions{})
-}
-
-// renameLowerMerkleAt renames Merkle file name to newName in the underlying
-// file system.
-func (d *dentry) renameLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error {
- return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(merklePrefix + name),
- }, &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(merklePrefix + newName),
- }, &vfs.RenameOptions{})
-}
-
-// symlinkLowerAt creates a symbolic link at symlink referring to the given target
-// in the underlying filesystem.
-func (d *dentry) symlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, target, symlink string) error {
- return vfsObj.SymlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(symlink),
- }, target)
-}
-
-// newFileFD creates a new file in the verity mount, and returns the FD. The FD
-// points to a file that has random data generated.
-func newFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, int, error) {
- // Create the file in the underlying file system.
- lowerFD, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode)
- if err != nil {
- return nil, 0, err
- }
-
- // Generate random data to be written to the file.
- dataSize := rand.Intn(maxDataSize) + 1
- data := make([]byte, dataSize)
- rand.Read(data)
-
- // Write directly to the underlying FD, since verity FD is read-only.
- n, err := lowerFD.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- return nil, 0, err
- }
-
- if n != int64(len(data)) {
- return nil, 0, fmt.Errorf("lowerFD.Write got write length %d, want %d", n, len(data))
- }
-
- lowerFD.DecRef(ctx)
-
- // Now open the verity file descriptor.
- fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode)
- return fd, dataSize, err
-}
-
-// newDirFD creates a new directory in the verity mount, and returns the FD.
-func newDirFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, dirPath string, mode linux.FileMode) (*vfs.FileDescription, error) {
- // Create the directory in the underlying file system.
- if err := dentryFromVD(t, root).mkdirLowerAt(ctx, vfsObj, dirPath, linux.ModeRegular|mode); err != nil {
- return nil, err
- }
- if _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, linux.ModeRegular|mode); err != nil {
- return nil, err
- }
- return openVerityAt(ctx, vfsObj, root, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, mode)
-}
-
-// newEmptyFileFD creates a new empty file in the verity mount, and returns the FD.
-func newEmptyFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, error) {
- // Create the file in the underlying file system.
- _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode)
- if err != nil {
- return nil, err
- }
- // Now open the verity file descriptor.
- fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode)
- return fd, err
-}
-
-// flipRandomBit randomly flips a bit in the file represented by fd.
-func flipRandomBit(ctx context.Context, fd *vfs.FileDescription, size int) error {
- randomPos := int64(rand.Intn(size))
- byteToModify := make([]byte, 1)
- if _, err := fd.PRead(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.ReadOptions{}); err != nil {
- return fmt.Errorf("lowerFD.PRead: %v", err)
- }
- byteToModify[0] ^= 1
- if _, err := fd.PWrite(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.WriteOptions{}); err != nil {
- return fmt.Errorf("lowerFD.PWrite: %v", err)
- }
- return nil
-}
-
-func enableVerity(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
- t.Helper()
- var args arch.SyscallArguments
- args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
- if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
- t.Fatalf("enable verity: %v", err)
- }
-}
-
-// TestOpen ensures that when a file is created, the corresponding Merkle tree
-// file and the root Merkle tree file exist.
-func TestOpen(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Ensure that the corresponding Merkle tree file is created.
- if _, err = dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("OpenAt Merkle tree file %s: %v", merklePrefix+filename, err)
- }
-
- // Ensure the root merkle tree file is created.
- if _, err = dentryFromVD(t, root).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("OpenAt root Merkle tree file %s: %v", merklePrefix+rootMerkleFilename, err)
- }
- }
-}
-
-// TestPReadUnmodifiedFileSucceeds ensures that pread from an untouched verity
-// file succeeds after enabling verity for it.
-func TestPReadUnmodifiedFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- buf := make([]byte, size)
- n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.PRead: %v", err)
- }
-
- if n != int64(size) {
- t.Errorf("fd.PRead got read length %d, want %d", n, size)
- }
- }
-}
-
-// TestReadUnmodifiedFileSucceeds ensures that read from an untouched verity
-// file succeeds after enabling verity for it.
-func TestReadUnmodifiedFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- buf := make([]byte, size)
- n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.Read: %v", err)
- }
-
- if n != int64(size) {
- t.Errorf("fd.PRead got read length %d, want %d", n, size)
- }
- }
-}
-
-// TestReadUnmodifiedEmptyFileSucceeds ensures that read from an untouched empty verity
-// file succeeds after enabling verity for it.
-func TestReadUnmodifiedEmptyFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-empty-file"
- fd, err := newEmptyFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newEmptyFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- var buf []byte
- n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.Read: %v", err)
- }
-
- if n != 0 {
- t.Errorf("fd.Read got read length %d, expected 0", n)
- }
- }
-}
-
-// TestReopenUnmodifiedFileSucceeds ensures that reopen an untouched verity file
-// succeeds after enabling verity for it.
-func TestReopenUnmodifiedFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirms a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- // Ensure reopening the verity enabled file succeeds.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("reopen enabled file failed: %v", err)
- }
- }
-}
-
-// TestOpenNonexistentFile ensures that opening a nonexistent file does not
-// trigger verification failure, even if the parent directory is verified.
-func TestOpenNonexistentFile(t *testing.T) {
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirms a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- // Enable verity on the parent directory.
- parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
- enableVerity(ctx, t, parentFD)
-
- // Ensure open an unexpected file in the parent directory fails with
- // ENOENT rather than verification failure.
- if _, err = openVerityAt(ctx, vfsObj, root, filename+"abc", linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.ENOENT, err) {
- t.Errorf("OpenAt unexpected error: %v", err)
- }
-}
-
-// TestPReadModifiedFileFails ensures that read from a modified verity file
-// fails.
-func TestPReadModifiedFileFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Open a new lowerFD that's read/writable.
- lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- if err := flipRandomBit(ctx, lowerFD, size); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- // Confirm that read from the modified file fails.
- buf := make([]byte, size)
- if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil {
- t.Fatalf("fd.PRead succeeded, expected failure")
- }
- }
-}
-
-// TestReadModifiedFileFails ensures that read from a modified verity file
-// fails.
-func TestReadModifiedFileFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Open a new lowerFD that's read/writable.
- lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- if err := flipRandomBit(ctx, lowerFD, size); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- // Confirm that read from the modified file fails.
- buf := make([]byte, size)
- if _, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}); err == nil {
- t.Fatalf("fd.Read succeeded, expected failure")
- }
- }
-}
-
-// TestModifiedMerkleFails ensures that read from a verity file fails if the
-// corresponding Merkle tree file is modified.
-func TestModifiedMerkleFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Open a new lowerMerkleFD that's read/writable.
- lowerMerkleFD, err := dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- // Flip a random bit in the Merkle tree file.
- stat, err := lowerMerkleFD.Stat(ctx, vfs.StatOptions{})
- if err != nil {
- t.Errorf("lowerMerkleFD.Stat: %v", err)
- }
-
- if err := flipRandomBit(ctx, lowerMerkleFD, int(stat.Size)); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- // Confirm that read from a file with modified Merkle tree fails.
- buf := make([]byte, size)
- if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil {
- t.Fatalf("fd.PRead succeeded with modified Merkle file")
- }
- }
-}
-
-// TestModifiedParentMerkleFails ensures that open a verity enabled file in a
-// verity enabled directory fails if the hashes related to the target file in
-// the parent Merkle tree file is modified.
-func TestModifiedParentMerkleFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Enable verity on the parent directory.
- parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
- enableVerity(ctx, t, parentFD)
-
- // Open a new lowerMerkleFD that's read/writable.
- parentLowerMerkleFD, err := dentryFromFD(t, fd).parent.openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- // Flip a random bit in the parent Merkle tree file.
- // This parent directory contains only one child, so any random
- // modification in the parent Merkle tree should cause verification
- // failure when opening the child file.
- sizeString, err := parentLowerMerkleFD.GetXattr(ctx, &vfs.GetXattrOptions{
- Name: childrenOffsetXattr,
- Size: sizeOfStringInt32,
- })
- if err != nil {
- t.Fatalf("parentLowerMerkleFD.GetXattr: %v", err)
- }
- parentMerkleSize, err := strconv.Atoi(sizeString)
- if err != nil {
- t.Fatalf("Failed convert size to int: %v", err)
- }
- if err := flipRandomBit(ctx, parentLowerMerkleFD, parentMerkleSize); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- parentLowerMerkleFD.DecRef(ctx)
-
- // Ensure reopening the verity enabled file fails.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); err == nil {
- t.Errorf("OpenAt file with modified parent Merkle succeeded")
- }
- }
-}
-
-// TestUnmodifiedStatSucceeds ensures that stat of an untouched verity file
-// succeeds after enabling verity for it.
-func TestUnmodifiedStatSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm that stat succeeds.
- enableVerity(ctx, t, fd)
- if _, err := fd.Stat(ctx, vfs.StatOptions{}); err != nil {
- t.Errorf("fd.Stat: %v", err)
- }
- }
-}
-
-// TestModifiedStatFails checks that getting stat for a file with modified stat
-// should fail.
-func TestModifiedStatFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- lowerFD := fd.Impl().(*fileDescription).lowerFD
- // Change the stat of the underlying file, and check that stat fails.
- if err := lowerFD.SetStat(ctx, vfs.SetStatOptions{
- Stat: linux.Statx{
- Mask: uint32(linux.STATX_MODE),
- Mode: 0777,
- },
- }); err != nil {
- t.Fatalf("lowerFD.SetStat: %v", err)
- }
-
- if _, err := fd.Stat(ctx, vfs.StatOptions{}); err == nil {
- t.Errorf("fd.Stat succeeded when it should fail")
- }
- }
-}
-
-// TestOpenDeletedFileFails ensures that opening a deleted verity enabled file
-// and/or the corresponding Merkle tree file fails with the verity error.
-func TestOpenDeletedFileFails(t *testing.T) {
- testCases := []struct {
- name string
- // The original file is removed if changeFile is true.
- changeFile bool
- // The Merkle tree file is removed if changeMerkleFile is true.
- changeMerkleFile bool
- }{
- {
- name: "FileOnly",
- changeFile: true,
- changeMerkleFile: false,
- },
- {
- name: "MerkleOnly",
- changeFile: false,
- changeMerkleFile: true,
- },
- {
- name: "FileAndMerkle",
- changeFile: true,
- changeMerkleFile: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- if tc.changeFile {
- if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, filename); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
- if tc.changeMerkleFile {
- if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, filename); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
-
- // Ensure reopening the verity enabled file fails.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("got OpenAt error: %v, expected EIO", err)
- }
- })
- }
-}
-
-// TestOpenRenamedFileFails ensures that opening a renamed verity enabled file
-// and/or the corresponding Merkle tree file fails with the verity error.
-func TestOpenRenamedFileFails(t *testing.T) {
- testCases := []struct {
- name string
- // The original file is renamed if changeFile is true.
- changeFile bool
- // The Merkle tree file is renamed if changeMerkleFile is true.
- changeMerkleFile bool
- }{
- {
- name: "FileOnly",
- changeFile: true,
- changeMerkleFile: false,
- },
- {
- name: "MerkleOnly",
- changeFile: false,
- changeMerkleFile: true,
- },
- {
- name: "FileAndMerkle",
- changeFile: true,
- changeMerkleFile: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- newFilename := "renamed-test-file"
- if tc.changeFile {
- if err := dentryFromVD(t, root).renameLowerAt(ctx, vfsObj, filename, newFilename); err != nil {
- t.Fatalf("RenameAt: %v", err)
- }
- }
- if tc.changeMerkleFile {
- if err := dentryFromVD(t, root).renameLowerMerkleAt(ctx, vfsObj, filename, newFilename); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
-
- // Ensure reopening the verity enabled file fails.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("got OpenAt error: %v, expected EIO", err)
- }
- })
- }
-}
-
-// TestUnmodifiedSymlinkFileReadSucceeds ensures that readlink() for an
-// unmodified verity enabled symlink succeeds.
-func TestUnmodifiedSymlinkFileReadSucceeds(t *testing.T) {
- testCases := []struct {
- name string
- // The symlink target is a directory.
- hasDirectoryTarget bool
- // The symlink target is a directory and contains a regular file which will be
- // used to test walking a symlink.
- testWalk bool
- }{
- {
- name: "RegularFileTarget",
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DirectoryTarget",
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "RegularFileInSymlinkDirectory",
- hasDirectoryTarget: true,
- testWalk: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.testWalk && !tc.hasDirectoryTarget {
- t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk")
- }
-
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- var target string
- if tc.hasDirectoryTarget {
- target = "verity-test-dir"
- if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- target = "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- if tc.testWalk {
- fileInTargetDirectory := target + "/" + "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- symlink := "verity-test-symlink"
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular)
-
- if err != nil {
- t.Fatalf("openVerityAt symlink: %v", err)
- }
-
- enableVerity(ctx, t, fd)
-
- if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }); err != nil {
- t.Fatalf("ReadlinkAt: %v", err)
- }
-
- if tc.testWalk {
- fileInSymlinkDirectory := symlink + "/verity-test-file"
- // Ensure opening the verity enabled file in the symlink directory succeeds.
- if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("open enabled file failed: %v", err)
- }
- }
- })
- }
-}
-
-// TestDeletedSymlinkFileReadFails ensures that reading value of a deleted verity enabled
-// symlink fails.
-func TestDeletedSymlinkFileReadFails(t *testing.T) {
- testCases := []struct {
- name string
- // The original symlink is unlinked if deleteLink is true.
- deleteLink bool
- // The Merkle tree file is renamed if deleteMerkleFile is true.
- deleteMerkleFile bool
- // The symlink target is a directory.
- hasDirectoryTarget bool
- // The symlink target is a directory and contains a regular file which will be
- // used to test walking a symlink.
- testWalk bool
- }{
- {
- name: "DeleteLinkRegularFile",
- deleteLink: true,
- deleteMerkleFile: false,
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DeleteMerkleRegFile",
- deleteLink: false,
- deleteMerkleFile: true,
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DeleteLinkAndMerkleRegFile",
- deleteLink: true,
- deleteMerkleFile: true,
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DeleteLinkDirectory",
- deleteLink: true,
- deleteMerkleFile: false,
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "DeleteMerkleDirectory",
- deleteLink: false,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "DeleteLinkAndMerkleDirectory",
- deleteLink: true,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "DeleteLinkDirectoryWalk",
- deleteLink: true,
- deleteMerkleFile: false,
- hasDirectoryTarget: true,
- testWalk: true,
- },
- {
- name: "DeleteMerkleDirectoryWalk",
- deleteLink: false,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: true,
- },
- {
- name: "DeleteLinkAndMerkleDirectoryWalk",
- deleteLink: true,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.testWalk && !tc.hasDirectoryTarget {
- t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk")
- }
-
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- var target string
- if tc.hasDirectoryTarget {
- target = "verity-test-dir"
- if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- target = "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- symlink := "verity-test-symlink"
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular)
-
- if err != nil {
- t.Fatalf("openVerityAt symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInTargetDirectory := target + "/" + "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- enableVerity(ctx, t, fd)
-
- if tc.deleteLink {
- if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
- if tc.deleteMerkleFile {
- if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, symlink); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
- if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInSymlinkDirectory := symlink + "/verity-test-file"
- // Ensure opening the verity enabled file in the symlink directory fails.
- if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("Open succeeded with modified symlink: %v", err)
- }
- }
- })
- }
-}
-
-// TestModifiedSymlinkFileReadFails ensures that reading value of a modified verity enabled
-// symlink fails.
-func TestModifiedSymlinkFileReadFails(t *testing.T) {
- testCases := []struct {
- name string
- // The symlink target is a directory.
- hasDirectoryTarget bool
- // The symlink target is a directory and contains a regular file which will be
- // used to test walking a symlink.
- testWalk bool
- }{
- {
- name: "RegularFileTarget",
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DirectoryTarget",
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "RegularFileInSymlinkDirectory",
- hasDirectoryTarget: true,
- testWalk: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.testWalk && !tc.hasDirectoryTarget {
- t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk")
- }
-
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- var target string
- if tc.hasDirectoryTarget {
- target = "verity-test-dir"
- if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- target = "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- // Create symlink which points to target file.
- symlink := "verity-test-symlink"
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- // Open symlink file to get the fd for ioctl in new step.
- fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInTargetDirectory := target + "/" + "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- enableVerity(ctx, t, fd)
-
- var newTarget string
- if tc.hasDirectoryTarget {
- newTarget = "verity-test-dir-new"
- if _, err := newDirFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- newTarget = "verity-test-file-new"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- // Unlink symlink->target.
- if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
-
- // Link symlink->newTarget.
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, newTarget, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- // Freshen lower dentry for symlink.
- symlinkVD, err := vfsObj.GetDentryAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }, &vfs.GetDentryOptions{})
- if err != nil {
- t.Fatalf("Failed to get symlink dentry: %v", err)
- }
- symlinkDentry := dentryFromVD(t, symlinkVD)
-
- symlinkLowerVD, err := dentryFromVD(t, root).getLowerAt(ctx, vfsObj, symlink)
- if err != nil {
- t.Fatalf("Failed to get symlink lower dentry: %v", err)
- }
- symlinkDentry.lowerVD = symlinkLowerVD
-
- // Verify ReadlinkAt() fails.
- if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInSymlinkDirectory := symlink + "/verity-test-file"
- // Ensure opening the verity enabled file in the symlink directory fails.
- if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("Open succeeded with modified symlink: %v", err)
- }
- }
- })
- }
-}