summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fs')
-rw-r--r--pkg/sentry/fs/BUILD136
-rw-r--r--pkg/sentry/fs/README.md229
-rw-r--r--pkg/sentry/fs/anon/BUILD21
-rwxr-xr-xpkg/sentry/fs/anon/anon_state_autogen.go4
-rw-r--r--pkg/sentry/fs/copy_up_test.go183
-rw-r--r--pkg/sentry/fs/dev/BUILD35
-rwxr-xr-xpkg/sentry/fs/dev/dev_state_autogen.go130
-rw-r--r--pkg/sentry/fs/dirent_cache_test.go247
-rwxr-xr-xpkg/sentry/fs/dirent_list.go173
-rw-r--r--pkg/sentry/fs/dirent_refs_test.go418
-rwxr-xr-xpkg/sentry/fs/event_list.go173
-rw-r--r--pkg/sentry/fs/fdpipe/BUILD49
-rwxr-xr-xpkg/sentry/fs/fdpipe/fdpipe_state_autogen.go27
-rw-r--r--pkg/sentry/fs/fdpipe/pipe_opener_test.go523
-rw-r--r--pkg/sentry/fs/fdpipe/pipe_test.go505
-rw-r--r--pkg/sentry/fs/file_overlay_test.go275
-rw-r--r--pkg/sentry/fs/filetest/BUILD20
-rw-r--r--pkg/sentry/fs/filetest/filetest.go61
-rwxr-xr-xpkg/sentry/fs/fs_state_autogen.go634
-rw-r--r--pkg/sentry/fs/fsutil/BUILD119
-rw-r--r--pkg/sentry/fs/fsutil/README.md207
-rwxr-xr-xpkg/sentry/fs/fsutil/dirty_set_impl.go1274
-rw-r--r--pkg/sentry/fs/fsutil/dirty_set_test.go38
-rwxr-xr-xpkg/sentry/fs/fsutil/file_range_set_impl.go1274
-rwxr-xr-xpkg/sentry/fs/fsutil/frame_ref_set_impl.go1274
-rwxr-xr-xpkg/sentry/fs/fsutil/fsutil_state_autogen.go363
-rw-r--r--pkg/sentry/fs/fsutil/inode_cached_test.go389
-rw-r--r--pkg/sentry/fs/g3doc/inotify.md122
-rw-r--r--pkg/sentry/fs/gofer/BUILD66
-rwxr-xr-xpkg/sentry/fs/gofer/gofer_state_autogen.go117
-rw-r--r--pkg/sentry/fs/gofer/gofer_test.go310
-rw-r--r--pkg/sentry/fs/host/BUILD86
-rw-r--r--pkg/sentry/fs/host/descriptor_test.go78
-rw-r--r--pkg/sentry/fs/host/fs_test.go380
-rwxr-xr-xpkg/sentry/fs/host/host_state_autogen.go140
-rw-r--r--pkg/sentry/fs/host/inode_test.go112
-rw-r--r--pkg/sentry/fs/host/socket_test.go246
-rwxr-xr-x[-rw-r--r--]pkg/sentry/fs/host/util_amd64_unsafe.go0
-rwxr-xr-x[-rw-r--r--]pkg/sentry/fs/host/util_arm64_unsafe.go0
-rw-r--r--pkg/sentry/fs/host/wait_test.go70
-rw-r--r--pkg/sentry/fs/inode_overlay_test.go470
-rw-r--r--pkg/sentry/fs/lock/BUILD59
-rwxr-xr-xpkg/sentry/fs/lock/lock_range.go62
-rw-r--r--pkg/sentry/fs/lock/lock_range_test.go136
-rwxr-xr-xpkg/sentry/fs/lock/lock_set.go1270
-rwxr-xr-xpkg/sentry/fs/lock/lock_state_autogen.go106
-rw-r--r--pkg/sentry/fs/lock/lock_test.go1059
-rw-r--r--pkg/sentry/fs/mount_test.go271
-rw-r--r--pkg/sentry/fs/mounts_test.go105
-rw-r--r--pkg/sentry/fs/path_test.go289
-rw-r--r--pkg/sentry/fs/proc/BUILD74
-rw-r--r--pkg/sentry/fs/proc/README.md332
-rw-r--r--pkg/sentry/fs/proc/device/BUILD11
-rwxr-xr-xpkg/sentry/fs/proc/device/device_state_autogen.go4
-rw-r--r--pkg/sentry/fs/proc/net_test.go74
-rwxr-xr-xpkg/sentry/fs/proc/proc_state_autogen.go717
-rw-r--r--pkg/sentry/fs/proc/seqfile/BUILD36
-rwxr-xr-xpkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go58
-rw-r--r--pkg/sentry/fs/proc/seqfile/seqfile_test.go279
-rw-r--r--pkg/sentry/fs/proc/sys_net_test.go125
-rw-r--r--pkg/sentry/fs/ramfs/BUILD38
-rwxr-xr-xpkg/sentry/fs/ramfs/ramfs_state_autogen.go94
-rw-r--r--pkg/sentry/fs/ramfs/tree_test.go80
-rw-r--r--pkg/sentry/fs/sys/BUILD25
-rwxr-xr-xpkg/sentry/fs/sys/sys_state_autogen.go34
-rw-r--r--pkg/sentry/fs/timerfd/BUILD20
-rwxr-xr-xpkg/sentry/fs/timerfd/timerfd_state_autogen.go25
-rw-r--r--pkg/sentry/fs/tmpfs/BUILD51
-rw-r--r--pkg/sentry/fs/tmpfs/file_test.go72
-rwxr-xr-xpkg/sentry/fs/tmpfs/tmpfs_state_autogen.go108
-rw-r--r--pkg/sentry/fs/tty/BUILD48
-rwxr-xr-xpkg/sentry/fs/tty/tty_state_autogen.go210
-rw-r--r--pkg/sentry/fs/tty/tty_test.go56
73 files changed, 8271 insertions, 8635 deletions
diff --git a/pkg/sentry/fs/BUILD b/pkg/sentry/fs/BUILD
deleted file mode 100644
index c035ffff7..000000000
--- a/pkg/sentry/fs/BUILD
+++ /dev/null
@@ -1,136 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fs",
- srcs = [
- "attr.go",
- "context.go",
- "copy_up.go",
- "dentry.go",
- "dirent.go",
- "dirent_cache.go",
- "dirent_cache_limiter.go",
- "dirent_list.go",
- "dirent_state.go",
- "event_list.go",
- "file.go",
- "file_operations.go",
- "file_overlay.go",
- "file_state.go",
- "filesystems.go",
- "flags.go",
- "fs.go",
- "inode.go",
- "inode_inotify.go",
- "inode_operations.go",
- "inode_overlay.go",
- "inotify.go",
- "inotify_event.go",
- "inotify_watch.go",
- "mock.go",
- "mount.go",
- "mount_overlay.go",
- "mounts.go",
- "offset.go",
- "overlay.go",
- "path.go",
- "restore.go",
- "save.go",
- "seek.go",
- "splice.go",
- "sync.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/amutex",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/p9",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/memmap",
- "//pkg/sentry/platform",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/state",
- "//pkg/syncutil",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_template_instance(
- name = "dirent_list",
- out = "dirent_list.go",
- package = "fs",
- prefix = "dirent",
- template = "//pkg/ilist:generic_list",
- types = {
- "Linker": "*Dirent",
- "Element": "*Dirent",
- },
-)
-
-go_template_instance(
- name = "event_list",
- out = "event_list.go",
- package = "fs",
- prefix = "event",
- template = "//pkg/ilist:generic_list",
- types = {
- "Linker": "*Event",
- "Element": "*Event",
- },
-)
-
-go_test(
- name = "fs_x_test",
- size = "small",
- srcs = [
- "copy_up_test.go",
- "file_overlay_test.go",
- "inode_overlay_test.go",
- "mounts_test.go",
- ],
- deps = [
- ":fs",
- "//pkg/sentry/context",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/kernel/contexttest",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "fs_test",
- size = "small",
- srcs = [
- "dirent_cache_test.go",
- "dirent_refs_test.go",
- "mount_test.go",
- "path_test.go",
- ],
- embed = [":fs"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- ],
-)
diff --git a/pkg/sentry/fs/README.md b/pkg/sentry/fs/README.md
deleted file mode 100644
index db4a1b730..000000000
--- a/pkg/sentry/fs/README.md
+++ /dev/null
@@ -1,229 +0,0 @@
-This package provides an implementation of the Linux virtual filesystem.
-
-[TOC]
-
-## Overview
-
-- An `fs.Dirent` caches an `fs.Inode` in memory at a path in the VFS, giving
- the `fs.Inode` a relative position with respect to other `fs.Inode`s.
-
-- If an `fs.Dirent` is referenced by two file descriptors, then those file
- descriptors are coherent with each other: they depend on the same
- `fs.Inode`.
-
-- A mount point is an `fs.Dirent` for which `fs.Dirent.mounted` is true. It
- exposes the root of a mounted filesystem.
-
-- The `fs.Inode` produced by a registered filesystem on mount(2) owns an
- `fs.MountedFilesystem` from which other `fs.Inode`s will be looked up. For a
- remote filesystem, the `fs.MountedFilesystem` owns the connection to that
- remote filesystem.
-
-- In general:
-
-```
-fs.Inode <------------------------------
-| |
-| |
-produced by |
-exactly one |
-| responsible for the
-| virtual identity of
-v |
-fs.MountedFilesystem -------------------
-```
-
-Glossary:
-
-- VFS: virtual filesystem.
-
-- inode: a virtual file object holding a cached view of a file on a backing
- filesystem (includes metadata and page caches).
-
-- superblock: the virtual state of a mounted filesystem (e.g. the virtual
- inode number set).
-
-- mount namespace: a view of the mounts under a root (during path traversal,
- the VFS makes visible/follows the mount point that is in the current task's
- mount namespace).
-
-## Save and restore
-
-An application's hard dependencies on filesystem state can be broken down into
-two categories:
-
-- The state necessary to execute a traversal on or view the *virtual*
- filesystem hierarchy, regardless of what files an application has open.
-
-- The state necessary to represent open files.
-
-The first is always necessary to save and restore. An application may never have
-any open file descriptors, but across save and restore it should see a coherent
-view of any mount namespace. NOTE(b/63601033): Currently only one "initial"
-mount namespace is supported.
-
-The second is so that system calls across save and restore are coherent with
-each other (e.g. so that unintended re-reads or overwrites do not occur).
-
-Specifically this state is:
-
-- An `fs.MountManager` containing mount points.
-
-- A `kernel.FDTable` containing pointers to open files.
-
-Anything else managed by the VFS that can be easily loaded into memory from a
-filesystem is synced back to those filesystems and is not saved. Examples are
-pages in page caches used for optimizations (i.e. readahead and writeback), and
-directory entries used to accelerate path lookups.
-
-### Mount points
-
-Saving and restoring a mount point means saving and restoring:
-
-- The root of the mounted filesystem.
-
-- Mount flags, which control how the VFS interacts with the mounted
- filesystem.
-
-- Any relevant metadata about the mounted filesystem.
-
-- All `fs.Inode`s referenced by the application that reside under the mount
- point.
-
-`fs.MountedFilesystem` is metadata about a filesystem that is mounted. It is
-referenced by every `fs.Inode` loaded into memory under the mount point
-including the `fs.Inode` of the mount point itself. The `fs.MountedFilesystem`
-maps file objects on the filesystem to a virtualized `fs.Inode` number and vice
-versa.
-
-To restore all `fs.Inode`s under a given mount point, each `fs.Inode` leverages
-its dependency on an `fs.MountedFilesystem`. Since the `fs.MountedFilesystem`
-knows how an `fs.Inode` maps to a file object on a backing filesystem, this
-mapping can be trivially consulted by each `fs.Inode` when the `fs.Inode` is
-restored.
-
-In detail, a mount point is saved in two steps:
-
-- First, after the kernel is paused but before state.Save, we walk all mount
- namespaces and install a mapping from `fs.Inode` numbers to file paths
- relative to the root of the mounted filesystem in each
- `fs.MountedFilesystem`. This is subsequently called the set of `fs.Inode`
- mappings.
-
-- Second, during state.Save, each `fs.MountedFilesystem` decides whether to
- save the set of `fs.Inode` mappings. In-memory filesystems, like tmpfs, have
- no need to save a set of `fs.Inode` mappings, since the `fs.Inode`s can be
- entirely encoded in state file. Each `fs.MountedFilesystem` also optionally
- saves the device name from when the filesystem was originally mounted. Each
- `fs.Inode` saves its virtual identifier and a reference to a
- `fs.MountedFilesystem`.
-
-A mount point is restored in two steps:
-
-- First, before state.Load, all mount configurations are stored in a global
- `fs.RestoreEnvironment`. This tells us what mount points the user wants to
- restore and how to re-establish pointers to backing filesystems.
-
-- Second, during state.Load, each `fs.MountedFilesystem` optionally searches
- for a mount in the `fs.RestoreEnvironment` that matches its saved device
- name. The `fs.MountedFilesystem` then reestablishes a pointer to the root of
- the mounted filesystem. For example, the mount specification provides the
- network connection for a mounted remote filesystem client to communicate
- with its remote file server. The `fs.MountedFilesystem` also trivially loads
- its set of `fs.Inode` mappings. When an `fs.Inode` is encountered, the
- `fs.Inode` loads its virtual identifier and its reference a
- `fs.MountedFilesystem`. It uses the `fs.MountedFilesystem` to obtain the
- root of the mounted filesystem and the `fs.Inode` mappings to obtain the
- relative file path to its data. With these, the `fs.Inode` re-establishes a
- pointer to its file object.
-
-A mount point can trivially restore its `fs.Inode`s in parallel since
-`fs.Inode`s have a restore dependency on their `fs.MountedFilesystem` and not on
-each other.
-
-### Open files
-
-An `fs.File` references the following filesystem objects:
-
-```go
-fs.File -> fs.Dirent -> fs.Inode -> fs.MountedFilesystem
-```
-
-The `fs.Inode` is restored using its `fs.MountedFilesystem`. The
-[Mount points](#mount-points) section above describes how this happens in
-detail. The `fs.Dirent` restores its pointer to an `fs.Inode`, pointers to
-parent and children `fs.Dirents`, and the basename of the file.
-
-Otherwise an `fs.File` restores flags, an offset, and a unique identifier (only
-used internally).
-
-It may use the `fs.Inode`, which it indirectly holds a reference on through the
-`fs.Dirent`, to reestablish an open file handle on the backing filesystem (e.g.
-to continue reading and writing).
-
-## Overlay
-
-The overlay implementation in the fs package takes Linux overlayfs as a frame of
-reference but corrects for several POSIX consistency errors.
-
-In Linux overlayfs, the `struct inode` used for reading and writing to the same
-file may be different. This is because the `struct inode` is dissociated with
-the process of copying up the file from the upper to the lower directory. Since
-flock(2) and fcntl(2) locks, inotify(7) watches, page caches, and a file's
-identity are all stored directly or indirectly off the `struct inode`, these
-properties of the `struct inode` may be stale after the first modification. This
-can lead to file locking bugs, missed inotify events, and inconsistent data in
-shared memory mappings of files, to name a few problems.
-
-The fs package maintains a single `fs.Inode` to represent a directory entry in
-an overlay and defines operations on this `fs.Inode` which synchronize with the
-copy up process. This achieves several things:
-
-+ File locks, inotify watches, and the identity of the file need not be copied
- at all.
-
-+ Memory mappings of files coordinate with the copy up process so that if a
- file in the lower directory is memory mapped, all references to it are
- invalidated, forcing the application to re-fault on memory mappings of the
- file under the upper directory.
-
-The `fs.Inode` holds metadata about files in the upper and/or lower directories
-via an `fs.overlayEntry`. The `fs.overlayEntry` implements the `fs.Mappable`
-interface. It multiplexes between upper and lower directory memory mappings and
-stores a copy of memory references so they can be transferred to the upper
-directory `fs.Mappable` when the file is copied up.
-
-The lower filesystem in an overlay may contain another (nested) overlay, but the
-upper filesystem may not contain another overlay. In other words, nested
-overlays form a tree structure that only allows branching in the lower
-filesystem.
-
-Caching decisions in the overlay are delegated to the upper filesystem, meaning
-that the Keep and Revalidate methods on the overlay return the same values as
-the upper filesystem. A small wrinkle is that the lower filesystem is not
-allowed to return `true` from Revalidate, as the overlay can not reload inodes
-from the lower filesystem. A lower filesystem that does return `true` from
-Revalidate will trigger a panic.
-
-The `fs.Inode` also holds a reference to a `fs.MountedFilesystem` that
-normalizes across the mounted filesystem state of the upper and lower
-directories.
-
-When a file is copied from the lower to the upper directory, attempts to
-interact with the file block until the copy completes. All copying synchronizes
-with rename(2).
-
-## Future Work
-
-### Overlay
-
-When a file is copied from a lower directory to an upper directory, several
-locks are taken: the global renamuMu and the copyMu of the `fs.Inode` being
-copied. This blocks operations on the file, including fault handling of memory
-mappings. Performance could be improved by copying files into a temporary
-directory that resides on the same filesystem as the upper directory and doing
-an atomic rename, holding locks only during the rename operation.
-
-Additionally files are copied up synchronously. For large files, this causes a
-noticeable latency. Performance could be improved by pipelining copies at
-non-overlapping file offsets.
diff --git a/pkg/sentry/fs/anon/BUILD b/pkg/sentry/fs/anon/BUILD
deleted file mode 100644
index ae1c9cf76..000000000
--- a/pkg/sentry/fs/anon/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "anon",
- srcs = [
- "anon.go",
- "device.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/anon",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/anon/anon_state_autogen.go b/pkg/sentry/fs/anon/anon_state_autogen.go
new file mode 100755
index 000000000..fcb914212
--- /dev/null
+++ b/pkg/sentry/fs/anon/anon_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package anon
+
diff --git a/pkg/sentry/fs/copy_up_test.go b/pkg/sentry/fs/copy_up_test.go
deleted file mode 100644
index 1d80bf15a..000000000
--- a/pkg/sentry/fs/copy_up_test.go
+++ /dev/null
@@ -1,183 +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 fs_test
-
-import (
- "bytes"
- "crypto/rand"
- "fmt"
- "io"
- "sync"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/fs"
- _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-const (
- // origFileSize is the original file size. This many bytes should be
- // copied up before the test file is modified.
- origFileSize = 4096
-
- // truncatedFileSize is the size to truncate all test files.
- truncateFileSize = 10
-)
-
-// TestConcurrentCopyUp is a copy up stress test for an overlay.
-//
-// It creates a 64-level deep directory tree in the lower filesystem and
-// populates the last subdirectory with 64 files containing random content:
-//
-// /lower
-// /sudir0/.../subdir63/
-// /file0
-// ...
-// /file63
-//
-// The files are truncated concurrently by 4 goroutines per file.
-// These goroutines contend with copying up all parent 64 subdirectories
-// as well as the final file content.
-//
-// At the end of the test, we assert that the files respect the new truncated
-// size and contain the content we expect.
-func TestConcurrentCopyUp(t *testing.T) {
- ctx := contexttest.Context(t)
- files := makeOverlayTestFiles(t)
-
- var wg sync.WaitGroup
- for _, file := range files {
- for i := 0; i < 4; i++ {
- wg.Add(1)
- go func(o *overlayTestFile) {
- if err := o.File.Dirent.Inode.Truncate(ctx, o.File.Dirent, truncateFileSize); err != nil {
- t.Fatalf("failed to copy up: %v", err)
- }
- wg.Done()
- }(file)
- }
- }
- wg.Wait()
-
- for _, file := range files {
- got := make([]byte, origFileSize)
- n, err := file.File.Readv(ctx, usermem.BytesIOSequence(got))
- if int(n) != truncateFileSize {
- t.Fatalf("read %d bytes from file, want %d", n, truncateFileSize)
- }
- if err != nil && err != io.EOF {
- t.Fatalf("read got error %v, want nil", err)
- }
- if !bytes.Equal(got[:n], file.content[:truncateFileSize]) {
- t.Fatalf("file content is %v, want %v", got[:n], file.content[:truncateFileSize])
- }
- }
-}
-
-type overlayTestFile struct {
- File *fs.File
- name string
- content []byte
-}
-
-func makeOverlayTestFiles(t *testing.T) []*overlayTestFile {
- ctx := contexttest.Context(t)
-
- // Create a lower tmpfs mount.
- fsys, _ := fs.FindFilesystem("tmpfs")
- lower, err := fsys.Mount(contexttest.Context(t), "", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- t.Fatalf("failed to mount tmpfs: %v", err)
- }
- lowerRoot := fs.NewDirent(ctx, lower, "")
-
- // Make a deep set of subdirectories that everyone shares.
- next := lowerRoot
- for i := 0; i < 64; i++ {
- name := fmt.Sprintf("subdir%d", i)
- err := next.CreateDirectory(ctx, lowerRoot, name, fs.FilePermsFromMode(0777))
- if err != nil {
- t.Fatalf("failed to create dir %q: %v", name, err)
- }
- next, err = next.Walk(ctx, lowerRoot, name)
- if err != nil {
- t.Fatalf("failed to walk to %q: %v", name, err)
- }
- }
-
- // Make a bunch of files in the last directory.
- var files []*overlayTestFile
- for i := 0; i < 64; i++ {
- name := fmt.Sprintf("file%d", i)
- f, err := next.Create(ctx, next, name, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- t.Fatalf("failed to create file %q: %v", name, err)
- }
- defer f.DecRef()
-
- relname, _ := f.Dirent.FullName(lowerRoot)
-
- o := &overlayTestFile{
- name: relname,
- content: make([]byte, origFileSize),
- }
-
- if _, err := rand.Read(o.content); err != nil {
- t.Fatalf("failed to read from /dev/urandom: %v", err)
- }
-
- if _, err := f.Writev(ctx, usermem.BytesIOSequence(o.content)); err != nil {
- t.Fatalf("failed to write content to file %q: %v", name, err)
- }
-
- files = append(files, o)
- }
-
- // Create an empty upper tmpfs mount which we will copy up into.
- upper, err := fsys.Mount(ctx, "", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- t.Fatalf("failed to mount tmpfs: %v", err)
- }
-
- // Construct an overlay root.
- overlay, err := fs.NewOverlayRoot(ctx, upper, lower, fs.MountSourceFlags{})
- if err != nil {
- t.Fatalf("failed to construct overlay root: %v", err)
- }
-
- // Create a MountNamespace to traverse the file system.
- mns, err := fs.NewMountNamespace(ctx, overlay)
- if err != nil {
- t.Fatalf("failed to construct mount manager: %v", err)
- }
-
- // Walk to all of the files in the overlay, open them readable.
- for _, f := range files {
- maxTraversals := uint(0)
- d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, &maxTraversals)
- if err != nil {
- t.Fatalf("failed to find %q: %v", f.name, err)
- }
- defer d.DecRef()
-
- f.File, err = d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("failed to open file %q readable: %v", f.name, err)
- }
- }
-
- return files
-}
diff --git a/pkg/sentry/fs/dev/BUILD b/pkg/sentry/fs/dev/BUILD
deleted file mode 100644
index a0d9e8496..000000000
--- a/pkg/sentry/fs/dev/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "dev",
- srcs = [
- "dev.go",
- "device.go",
- "fs.go",
- "full.go",
- "null.go",
- "random.go",
- "tty.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/dev",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/rand",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/memmap",
- "//pkg/sentry/mm",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/dev/dev_state_autogen.go b/pkg/sentry/fs/dev/dev_state_autogen.go
new file mode 100755
index 000000000..a997f3ecf
--- /dev/null
+++ b/pkg/sentry/fs/dev/dev_state_autogen.go
@@ -0,0 +1,130 @@
+// automatically generated by stateify.
+
+package dev
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *fullDevice) beforeSave() {}
+func (x *fullDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *fullDevice) afterLoad() {}
+func (x *fullDevice) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *fullFileOperations) beforeSave() {}
+func (x *fullFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *fullFileOperations) afterLoad() {}
+func (x *fullFileOperations) load(m state.Map) {
+}
+
+func (x *nullDevice) beforeSave() {}
+func (x *nullDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *nullDevice) afterLoad() {}
+func (x *nullDevice) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *nullFileOperations) beforeSave() {}
+func (x *nullFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *nullFileOperations) afterLoad() {}
+func (x *nullFileOperations) load(m state.Map) {
+}
+
+func (x *zeroDevice) beforeSave() {}
+func (x *zeroDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nullDevice", &x.nullDevice)
+}
+
+func (x *zeroDevice) afterLoad() {}
+func (x *zeroDevice) load(m state.Map) {
+ m.Load("nullDevice", &x.nullDevice)
+}
+
+func (x *zeroFileOperations) beforeSave() {}
+func (x *zeroFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *zeroFileOperations) afterLoad() {}
+func (x *zeroFileOperations) load(m state.Map) {
+}
+
+func (x *randomDevice) beforeSave() {}
+func (x *randomDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *randomDevice) afterLoad() {}
+func (x *randomDevice) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *randomFileOperations) beforeSave() {}
+func (x *randomFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *randomFileOperations) afterLoad() {}
+func (x *randomFileOperations) load(m state.Map) {
+}
+
+func (x *ttyInodeOperations) beforeSave() {}
+func (x *ttyInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *ttyInodeOperations) afterLoad() {}
+func (x *ttyInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *ttyFileOperations) beforeSave() {}
+func (x *ttyFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *ttyFileOperations) afterLoad() {}
+func (x *ttyFileOperations) load(m state.Map) {
+}
+
+func init() {
+ state.Register("dev.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("dev.fullDevice", (*fullDevice)(nil), state.Fns{Save: (*fullDevice).save, Load: (*fullDevice).load})
+ state.Register("dev.fullFileOperations", (*fullFileOperations)(nil), state.Fns{Save: (*fullFileOperations).save, Load: (*fullFileOperations).load})
+ state.Register("dev.nullDevice", (*nullDevice)(nil), state.Fns{Save: (*nullDevice).save, Load: (*nullDevice).load})
+ state.Register("dev.nullFileOperations", (*nullFileOperations)(nil), state.Fns{Save: (*nullFileOperations).save, Load: (*nullFileOperations).load})
+ state.Register("dev.zeroDevice", (*zeroDevice)(nil), state.Fns{Save: (*zeroDevice).save, Load: (*zeroDevice).load})
+ state.Register("dev.zeroFileOperations", (*zeroFileOperations)(nil), state.Fns{Save: (*zeroFileOperations).save, Load: (*zeroFileOperations).load})
+ state.Register("dev.randomDevice", (*randomDevice)(nil), state.Fns{Save: (*randomDevice).save, Load: (*randomDevice).load})
+ state.Register("dev.randomFileOperations", (*randomFileOperations)(nil), state.Fns{Save: (*randomFileOperations).save, Load: (*randomFileOperations).load})
+ state.Register("dev.ttyInodeOperations", (*ttyInodeOperations)(nil), state.Fns{Save: (*ttyInodeOperations).save, Load: (*ttyInodeOperations).load})
+ state.Register("dev.ttyFileOperations", (*ttyFileOperations)(nil), state.Fns{Save: (*ttyFileOperations).save, Load: (*ttyFileOperations).load})
+}
diff --git a/pkg/sentry/fs/dirent_cache_test.go b/pkg/sentry/fs/dirent_cache_test.go
deleted file mode 100644
index 395c879f5..000000000
--- a/pkg/sentry/fs/dirent_cache_test.go
+++ /dev/null
@@ -1,247 +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 fs
-
-import (
- "testing"
-)
-
-func TestDirentCache(t *testing.T) {
- const maxSize = 5
-
- c := NewDirentCache(maxSize)
-
- // Size starts at 0.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Create a Dirent d.
- d := NewNegativeDirent("")
-
- // c does not contain d.
- if got, want := c.contains(d), false; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add d to the cache.
- c.Add(d)
-
- // Size is now 1.
- if got, want := c.Size(), uint64(1); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add maxSize-1 more elements. d should be oldest element.
- for i := 0; i < maxSize-1; i++ {
- c.Add(NewNegativeDirent(""))
- }
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // "Bump" d to the front by re-adding it.
- c.Add(d)
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add maxSize-1 more elements. d should again be oldest element.
- for i := 0; i < maxSize-1; i++ {
- c.Add(NewNegativeDirent(""))
- }
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add one more element, which will bump d from the cache.
- c.Add(NewNegativeDirent(""))
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c does not contain d.
- if got, want := c.contains(d), false; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Invalidating causes size to be 0 and list to be empty.
- c.Invalidate()
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
- if got, want := c.list.Empty(), true; got != want {
- t.Errorf("c.list.Empty() got %v, want %v", got, want)
- }
-
- // Fill cache with maxSize dirents.
- for i := 0; i < maxSize; i++ {
- c.Add(NewNegativeDirent(""))
- }
-}
-
-func TestDirentCacheLimiter(t *testing.T) {
- const (
- globalMaxSize = 5
- maxSize = 3
- )
-
- limit := NewDirentCacheLimiter(globalMaxSize)
- c1 := NewDirentCache(maxSize)
- c1.limit = limit
- c2 := NewDirentCache(maxSize)
- c2.limit = limit
-
- // Create a Dirent d.
- d := NewNegativeDirent("")
-
- // Add d to the cache.
- c1.Add(d)
- if got, want := c1.Size(), uint64(1); got != want {
- t.Errorf("c1.Size() got %v, want %v", got, want)
- }
-
- // Add maxSize-1 more elements. d should be oldest element.
- for i := 0; i < maxSize-1; i++ {
- c1.Add(NewNegativeDirent(""))
- }
- if got, want := c1.Size(), uint64(maxSize); got != want {
- t.Errorf("c1.Size() got %v, want %v", got, want)
- }
-
- // Check that d is still there.
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
-
- // Fill up the other cache, it will start dropping old entries from the cache
- // when the global limit is reached.
- for i := 0; i < maxSize; i++ {
- c2.Add(NewNegativeDirent(""))
- }
-
- // Check is what's remaining from global max.
- if got, want := c2.Size(), globalMaxSize-maxSize; int(got) != want {
- t.Errorf("c2.Size() got %v, want %v", got, want)
- }
-
- // Check that d was not dropped.
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
-
- // Add an entry that will eventually be dropped. Check is done later...
- drop := NewNegativeDirent("")
- c1.Add(drop)
-
- // Check that d is bumped to front even when global limit is reached.
- c1.Add(d)
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
-
- // Add 2 more element and check that:
- // - d is still in the list: to verify that d was bumped
- // - d2/d3 are in the list: older entries are dropped when global limit is
- // reached.
- // - drop is not in the list: indeed older elements are dropped.
- d2 := NewNegativeDirent("")
- c1.Add(d2)
- d3 := NewNegativeDirent("")
- c1.Add(d3)
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
- if got, want := c1.contains(d2), true; got != want {
- t.Errorf("c1.contains(d2) got %v want %v", got, want)
- }
- if got, want := c1.contains(d3), true; got != want {
- t.Errorf("c1.contains(d3) got %v want %v", got, want)
- }
- if got, want := c1.contains(drop), false; got != want {
- t.Errorf("c1.contains(drop) got %v want %v", got, want)
- }
-
- // Drop all entries from one cache. The other will be allowed to grow.
- c1.Invalidate()
- c2.Add(NewNegativeDirent(""))
- if got, want := c2.Size(), uint64(maxSize); got != want {
- t.Errorf("c2.Size() got %v, want %v", got, want)
- }
-}
-
-// TestNilDirentCache tests that a nil cache supports all cache operations, but
-// treats them as noop.
-func TestNilDirentCache(t *testing.T) {
- // Create a nil cache.
- var c *DirentCache
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Call Add.
- c.Add(NewNegativeDirent(""))
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Call Remove.
- c.Remove(NewNegativeDirent(""))
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Call Invalidate.
- c.Invalidate()
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-}
diff --git a/pkg/sentry/fs/dirent_list.go b/pkg/sentry/fs/dirent_list.go
new file mode 100755
index 000000000..750961a48
--- /dev/null
+++ b/pkg/sentry/fs/dirent_list.go
@@ -0,0 +1,173 @@
+package fs
+
+// 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 direntElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (direntElementMapper) linkerFor(elem *Dirent) *Dirent { 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 direntList struct {
+ head *Dirent
+ tail *Dirent
+}
+
+// Reset resets list l to the empty state.
+func (l *direntList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *direntList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *direntList) Front() *Dirent {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *direntList) Back() *Dirent {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *direntList) PushFront(e *Dirent) {
+ direntElementMapper{}.linkerFor(e).SetNext(l.head)
+ direntElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ direntElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *direntList) PushBack(e *Dirent) {
+ direntElementMapper{}.linkerFor(e).SetNext(nil)
+ direntElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ direntElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *direntList) PushBackList(m *direntList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ direntElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ direntElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *direntList) InsertAfter(b, e *Dirent) {
+ a := direntElementMapper{}.linkerFor(b).Next()
+ direntElementMapper{}.linkerFor(e).SetNext(a)
+ direntElementMapper{}.linkerFor(e).SetPrev(b)
+ direntElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ direntElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *direntList) InsertBefore(a, e *Dirent) {
+ b := direntElementMapper{}.linkerFor(a).Prev()
+ direntElementMapper{}.linkerFor(e).SetNext(a)
+ direntElementMapper{}.linkerFor(e).SetPrev(b)
+ direntElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ direntElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *direntList) Remove(e *Dirent) {
+ prev := direntElementMapper{}.linkerFor(e).Prev()
+ next := direntElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ direntElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ direntElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// 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 direntEntry struct {
+ next *Dirent
+ prev *Dirent
+}
+
+// Next returns the entry that follows e in the list.
+func (e *direntEntry) Next() *Dirent {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *direntEntry) Prev() *Dirent {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *direntEntry) SetNext(elem *Dirent) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *direntEntry) SetPrev(elem *Dirent) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fs/dirent_refs_test.go b/pkg/sentry/fs/dirent_refs_test.go
deleted file mode 100644
index 47bc72a88..000000000
--- a/pkg/sentry/fs/dirent_refs_test.go
+++ /dev/null
@@ -1,418 +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 fs
-
-import (
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
-)
-
-func newMockDirInode(ctx context.Context, cache *DirentCache) *Inode {
- return NewMockInode(ctx, NewMockMountSource(cache), StableAttr{Type: Directory})
-}
-
-func TestWalkPositive(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, newMockDirInode(ctx, nil), "root")
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- name := "d"
- d, err := root.walk(ctx, root, name, false)
- if err != nil {
- t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
- }
-
- if got := root.ReadRefs(); got != 2 {
- t.Fatalf("root has a ref count of %d, want %d", got, 2)
- }
-
- if got := d.ReadRefs(); got != 1 {
- t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 1)
- }
-
- d.DecRef()
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- if got := d.ReadRefs(); got != 0 {
- t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 0)
- }
-
- root.flush()
-
- if got := len(root.children); got != 0 {
- t.Fatalf("root has %d children, want %d", got, 0)
- }
-}
-
-func TestWalkNegative(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, NewEmptyDir(ctx, nil), "root")
- mn := root.Inode.InodeOperations.(*mockInodeOperationsLookupNegative)
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- name := "d"
- for i := 0; i < 100; i++ {
- _, err := root.walk(ctx, root, name, false)
- if err != syscall.ENOENT {
- t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, syscall.ENOENT)
- }
- }
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- if got := len(root.children); got != 1 {
- t.Fatalf("root has %d children, want %d", got, 1)
- }
-
- w, ok := root.children[name]
- if !ok {
- t.Fatalf("root wants child at %q", name)
- }
-
- child := w.Get()
- if child == nil {
- t.Fatalf("root wants to resolve weak reference")
- }
-
- if !child.(*Dirent).IsNegative() {
- t.Fatalf("root found positive child at %q, want negative", name)
- }
-
- if got := child.(*Dirent).ReadRefs(); got != 2 {
- t.Fatalf("child has a ref count of %d, want %d", got, 2)
- }
-
- child.DecRef()
-
- if got := child.(*Dirent).ReadRefs(); got != 1 {
- t.Fatalf("child has a ref count of %d, want %d", got, 1)
- }
-
- if got := len(root.children); got != 1 {
- t.Fatalf("root has %d children, want %d", got, 1)
- }
-
- root.DecRef()
-
- if got := root.ReadRefs(); got != 0 {
- t.Fatalf("root has a ref count of %d, want %d", got, 0)
- }
-
- AsyncBarrier()
-
- if got := mn.releaseCalled; got != true {
- t.Fatalf("root.Close was called %v, want true", got)
- }
-}
-
-type mockInodeOperationsLookupNegative struct {
- *MockInodeOperations
- releaseCalled bool
-}
-
-func NewEmptyDir(ctx context.Context, cache *DirentCache) *Inode {
- m := NewMockMountSource(cache)
- return NewInode(ctx, &mockInodeOperationsLookupNegative{
- MockInodeOperations: NewMockInodeOperations(ctx),
- }, m, StableAttr{Type: Directory})
-}
-
-func (m *mockInodeOperationsLookupNegative) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) {
- return NewNegativeDirent(p), nil
-}
-
-func (m *mockInodeOperationsLookupNegative) Release(context.Context) {
- m.releaseCalled = true
-}
-
-func TestHashNegativeToPositive(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, NewEmptyDir(ctx, nil), "root")
-
- name := "d"
- _, err := root.walk(ctx, root, name, false)
- if err != syscall.ENOENT {
- t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, syscall.ENOENT)
- }
-
- if got := root.exists(ctx, root, name); got != false {
- t.Fatalf("got %q exists, want does not exist", name)
- }
-
- f, err := root.Create(ctx, root, name, FileFlags{}, FilePermissions{})
- if err != nil {
- t.Fatalf("root.Create(%q, _), got error %v, want nil", name, err)
- }
- d := f.Dirent
-
- if d.IsNegative() {
- t.Fatalf("got negative Dirent, want positive")
- }
-
- if got := d.ReadRefs(); got != 1 {
- t.Fatalf("child %q has a ref count of %d, want %d", name, got, 1)
- }
-
- if got := root.ReadRefs(); got != 2 {
- t.Fatalf("root has a ref count of %d, want %d", got, 2)
- }
-
- if got := len(root.children); got != 1 {
- t.Fatalf("got %d children, want %d", got, 1)
- }
-
- w, ok := root.children[name]
- if !ok {
- t.Fatalf("failed to find weak reference to %q", name)
- }
-
- child := w.Get()
- if child == nil {
- t.Fatalf("want to resolve weak reference")
- }
-
- if child.(*Dirent) != d {
- t.Fatalf("got foreign child")
- }
-}
-
-func TestRevalidate(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // Whether to make negative Dirents.
- makeNegative bool
- }{
- {
- desc: "Revalidate negative Dirent",
- makeNegative: true,
- },
- {
- desc: "Revalidate positive Dirent",
- makeNegative: false,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, NewMockInodeRevalidate(ctx, test.makeNegative), "root")
-
- name := "d"
- d1, err := root.walk(ctx, root, name, false)
- if !test.makeNegative && err != nil {
- t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
- }
- d2, err := root.walk(ctx, root, name, false)
- if !test.makeNegative && err != nil {
- t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
- }
- if !test.makeNegative && d1 == d2 {
- t.Fatalf("revalidating walk got same *Dirent, want different")
- }
- if got := len(root.children); got != 1 {
- t.Errorf("revalidating walk got %d children, want %d", got, 1)
- }
- })
- }
-}
-
-type MockInodeOperationsRevalidate struct {
- *MockInodeOperations
- makeNegative bool
-}
-
-func NewMockInodeRevalidate(ctx context.Context, makeNegative bool) *Inode {
- mn := NewMockInodeOperations(ctx)
- m := NewMockMountSource(nil)
- m.MountSourceOperations.(*MockMountSourceOps).revalidate = true
- return NewInode(ctx, &MockInodeOperationsRevalidate{MockInodeOperations: mn, makeNegative: makeNegative}, m, StableAttr{Type: Directory})
-}
-
-func (m *MockInodeOperationsRevalidate) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) {
- if !m.makeNegative {
- return m.MockInodeOperations.Lookup(ctx, dir, p)
- }
- return NewNegativeDirent(p), nil
-}
-
-func TestCreateExtraRefs(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // root is the Dirent to create from.
- root *Dirent
-
- // expected references on walked Dirent.
- refs int64
- }{
- {
- desc: "Create caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, NewDirentCache(1)), "root"),
- refs: 2,
- },
- {
- desc: "Create not caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, nil), "root"),
- refs: 1,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- name := "d"
- f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{})
- if err != nil {
- t.Fatalf("root.Create(root, %q) failed: %v", name, err)
- }
- d := f.Dirent
-
- if got := d.ReadRefs(); got != test.refs {
- t.Errorf("dirent has a ref count of %d, want %d", got, test.refs)
- }
- })
- }
-}
-
-func TestRemoveExtraRefs(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // root is the Dirent to make and remove from.
- root *Dirent
- }{
- {
- desc: "Remove caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, NewDirentCache(1)), "root"),
- },
- {
- desc: "Remove not caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, nil), "root"),
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- name := "d"
- f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{})
- if err != nil {
- t.Fatalf("root.Create(%q, _) failed: %v", name, err)
- }
- d := f.Dirent
-
- if err := test.root.Remove(contexttest.Context(t), test.root, name, false /* dirPath */); err != nil {
- t.Fatalf("root.Remove(root, %q) failed: %v", name, err)
- }
-
- if got := d.ReadRefs(); got != 1 {
- t.Fatalf("dirent has a ref count of %d, want %d", got, 1)
- }
-
- d.DecRef()
-
- test.root.flush()
-
- if got := len(test.root.children); got != 0 {
- t.Errorf("root has %d children, want %d", got, 0)
- }
- })
- }
-}
-
-func TestRenameExtraRefs(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // cache of extra Dirent references, may be nil.
- cache *DirentCache
- }{
- {
- desc: "Rename no caching",
- cache: nil,
- },
- {
- desc: "Rename caching",
- cache: NewDirentCache(5),
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- ctx := contexttest.Context(t)
-
- dirAttr := StableAttr{Type: Directory}
-
- oldParent := NewDirent(ctx, NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "old_parent")
- newParent := NewDirent(ctx, NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "new_parent")
-
- renamed, err := oldParent.Walk(ctx, oldParent, "old_child")
- if err != nil {
- t.Fatalf("Walk(oldParent, %q) got error %v, want nil", "old_child", err)
- }
- replaced, err := newParent.Walk(ctx, oldParent, "new_child")
- if err != nil {
- t.Fatalf("Walk(newParent, %q) got error %v, want nil", "new_child", err)
- }
-
- if err := Rename(contexttest.RootContext(t), oldParent /*root */, oldParent, "old_child", newParent, "new_child"); err != nil {
- t.Fatalf("Rename got error %v, want nil", err)
- }
-
- oldParent.flush()
- newParent.flush()
-
- // Expect to have only active references.
- if got := renamed.ReadRefs(); got != 1 {
- t.Errorf("renamed has ref count %d, want only active references %d", got, 1)
- }
- if got := replaced.ReadRefs(); got != 1 {
- t.Errorf("replaced has ref count %d, want only active references %d", got, 1)
- }
- })
- }
-}
diff --git a/pkg/sentry/fs/event_list.go b/pkg/sentry/fs/event_list.go
new file mode 100755
index 000000000..c94cb03e1
--- /dev/null
+++ b/pkg/sentry/fs/event_list.go
@@ -0,0 +1,173 @@
+package fs
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type eventElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (eventElementMapper) linkerFor(elem *Event) *Event { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type eventList struct {
+ head *Event
+ tail *Event
+}
+
+// Reset resets list l to the empty state.
+func (l *eventList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *eventList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *eventList) Front() *Event {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *eventList) Back() *Event {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *eventList) PushFront(e *Event) {
+ eventElementMapper{}.linkerFor(e).SetNext(l.head)
+ eventElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ eventElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *eventList) PushBack(e *Event) {
+ eventElementMapper{}.linkerFor(e).SetNext(nil)
+ eventElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ eventElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *eventList) PushBackList(m *eventList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ eventElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ eventElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *eventList) InsertAfter(b, e *Event) {
+ a := eventElementMapper{}.linkerFor(b).Next()
+ eventElementMapper{}.linkerFor(e).SetNext(a)
+ eventElementMapper{}.linkerFor(e).SetPrev(b)
+ eventElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ eventElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *eventList) InsertBefore(a, e *Event) {
+ b := eventElementMapper{}.linkerFor(a).Prev()
+ eventElementMapper{}.linkerFor(e).SetNext(a)
+ eventElementMapper{}.linkerFor(e).SetPrev(b)
+ eventElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ eventElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *eventList) Remove(e *Event) {
+ prev := eventElementMapper{}.linkerFor(e).Prev()
+ next := eventElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ eventElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ eventElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type eventEntry struct {
+ next *Event
+ prev *Event
+}
+
+// Next returns the entry that follows e in the list.
+func (e *eventEntry) Next() *Event {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *eventEntry) Prev() *Event {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *eventEntry) SetNext(elem *Event) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *eventEntry) SetPrev(elem *Event) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fs/fdpipe/BUILD b/pkg/sentry/fs/fdpipe/BUILD
deleted file mode 100644
index 277ee4c31..000000000
--- a/pkg/sentry/fs/fdpipe/BUILD
+++ /dev/null
@@ -1,49 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fdpipe",
- srcs = [
- "pipe.go",
- "pipe_opener.go",
- "pipe_state.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/fdpipe",
- imports = ["gvisor.dev/gvisor/pkg/sentry/fs"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/log",
- "//pkg/secio",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "fdpipe_test",
- size = "small",
- srcs = [
- "pipe_opener_test.go",
- "pipe_test.go",
- ],
- embed = [":fdpipe"],
- deps = [
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "@com_github_google_uuid//:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fs/fdpipe/fdpipe_state_autogen.go b/pkg/sentry/fs/fdpipe/fdpipe_state_autogen.go
new file mode 100755
index 000000000..38c1ed916
--- /dev/null
+++ b/pkg/sentry/fs/fdpipe/fdpipe_state_autogen.go
@@ -0,0 +1,27 @@
+// automatically generated by stateify.
+
+package fdpipe
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/sentry/fs"
+)
+
+func (x *pipeOperations) save(m state.Map) {
+ x.beforeSave()
+ var flags fs.FileFlags = x.saveFlags()
+ m.SaveValue("flags", flags)
+ m.Save("opener", &x.opener)
+ m.Save("readAheadBuffer", &x.readAheadBuffer)
+}
+
+func (x *pipeOperations) load(m state.Map) {
+ m.LoadWait("opener", &x.opener)
+ m.Load("readAheadBuffer", &x.readAheadBuffer)
+ m.LoadValue("flags", new(fs.FileFlags), func(y interface{}) { x.loadFlags(y.(fs.FileFlags)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func init() {
+ state.Register("fdpipe.pipeOperations", (*pipeOperations)(nil), state.Fns{Save: (*pipeOperations).save, Load: (*pipeOperations).load})
+}
diff --git a/pkg/sentry/fs/fdpipe/pipe_opener_test.go b/pkg/sentry/fs/fdpipe/pipe_opener_test.go
deleted file mode 100644
index 577445148..000000000
--- a/pkg/sentry/fs/fdpipe/pipe_opener_test.go
+++ /dev/null
@@ -1,523 +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 fdpipe
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path"
- "syscall"
- "testing"
- "time"
-
- "github.com/google/uuid"
-
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type hostOpener struct {
- name string
-}
-
-func (h *hostOpener) NonBlockingOpen(_ context.Context, p fs.PermMask) (*fd.FD, error) {
- var flags int
- switch {
- case p.Read && p.Write:
- flags = syscall.O_RDWR
- case p.Write:
- flags = syscall.O_WRONLY
- case p.Read:
- flags = syscall.O_RDONLY
- default:
- return nil, syscall.EINVAL
- }
- f, err := syscall.Open(h.name, flags|syscall.O_NONBLOCK, 0666)
- if err != nil {
- return nil, err
- }
- return fd.New(f), nil
-}
-
-func pipename() string {
- return fmt.Sprintf(path.Join(os.TempDir(), "test-named-pipe-%s"), uuid.New())
-}
-
-func mkpipe(name string) error {
- return syscall.Mknod(name, syscall.S_IFIFO|0666, 0)
-}
-
-func TestTryOpen(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // makePipe is true if the test case should create the pipe.
- makePipe bool
-
- // flags are the fs.FileFlags used to open the pipe.
- flags fs.FileFlags
-
- // expectFile is true if a fs.File is expected.
- expectFile bool
-
- // err is the expected error
- err error
- }{
- {
- desc: "FileFlags lacking Read and Write are invalid",
- makePipe: false,
- flags: fs.FileFlags{}, /* bogus */
- expectFile: false,
- err: syscall.EINVAL,
- },
- {
- desc: "NonBlocking Read only error returns immediately",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Read: true, NonBlocking: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "NonBlocking Read only success returns immediately",
- makePipe: true,
- flags: fs.FileFlags{Read: true, NonBlocking: true},
- expectFile: true,
- err: nil,
- },
- {
- desc: "NonBlocking Write only error returns immediately",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Write: true, NonBlocking: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "NonBlocking Write only no reader error returns immediately",
- makePipe: true,
- flags: fs.FileFlags{Write: true, NonBlocking: true},
- expectFile: false,
- err: syscall.ENXIO,
- },
- {
- desc: "ReadWrite error returns immediately",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Read: true, Write: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "ReadWrite returns immediately",
- makePipe: true,
- flags: fs.FileFlags{Read: true, Write: true},
- expectFile: true,
- err: nil,
- },
- {
- desc: "Blocking Write only returns open error",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Write: true},
- expectFile: false,
- err: syscall.ENOENT, /* from bogus perms */
- },
- {
- desc: "Blocking Read only returns open error",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Read: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "Blocking Write only returns with syserror.ErrWouldBlock",
- makePipe: true,
- flags: fs.FileFlags{Write: true},
- expectFile: false,
- err: syserror.ErrWouldBlock,
- },
- {
- desc: "Blocking Read only returns with syserror.ErrWouldBlock",
- makePipe: true,
- flags: fs.FileFlags{Read: true},
- expectFile: false,
- err: syserror.ErrWouldBlock,
- },
- } {
- name := pipename()
- if test.makePipe {
- // Create the pipe. We do this per-test case to keep tests independent.
- if err := mkpipe(name); err != nil {
- t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
- continue
- }
- defer syscall.Unlink(name)
- }
-
- // Use a host opener to keep things simple.
- opener := &hostOpener{name: name}
-
- pipeOpenState := &pipeOpenState{}
- ctx := contexttest.Context(t)
- pipeOps, err := pipeOpenState.TryOpen(ctx, opener, test.flags)
- if unwrapError(err) != test.err {
- t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
- if pipeOps != nil {
- // Cleanup the state of the pipe, and remove the fd from the
- // fdnotifier. Sadly this needed to maintain the correctness
- // of other tests because the fdnotifier is global.
- pipeOps.Release()
- }
- continue
- }
- if (pipeOps != nil) != test.expectFile {
- t.Errorf("%s: got non-nil file %v, want %v", test.desc, pipeOps != nil, test.expectFile)
- }
- if pipeOps != nil {
- // Same as above.
- pipeOps.Release()
- }
- }
-}
-
-func TestPipeOpenUnblocksEventually(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // partnerIsReader is true if the goroutine opening the same pipe as the test case
- // should open the pipe read only. Otherwise write only. This also means that the
- // test case will open the pipe in the opposite way.
- partnerIsReader bool
-
- // partnerIsBlocking is true if the goroutine opening the same pipe as the test case
- // should do so without the O_NONBLOCK flag, otherwise opens the pipe with O_NONBLOCK
- // until ENXIO is not returned.
- partnerIsBlocking bool
- }{
- {
- desc: "Blocking Read with blocking writer partner opens eventually",
- partnerIsReader: false,
- partnerIsBlocking: true,
- },
- {
- desc: "Blocking Write with blocking reader partner opens eventually",
- partnerIsReader: true,
- partnerIsBlocking: true,
- },
- {
- desc: "Blocking Read with non-blocking writer partner opens eventually",
- partnerIsReader: false,
- partnerIsBlocking: false,
- },
- {
- desc: "Blocking Write with non-blocking reader partner opens eventually",
- partnerIsReader: true,
- partnerIsBlocking: false,
- },
- } {
- // Create the pipe. We do this per-test case to keep tests independent.
- name := pipename()
- if err := mkpipe(name); err != nil {
- t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
- continue
- }
- defer syscall.Unlink(name)
-
- // Spawn the partner.
- type fderr struct {
- fd int
- err error
- }
- errch := make(chan fderr, 1)
- go func() {
- var flags int
- if test.partnerIsReader {
- flags = syscall.O_RDONLY
- } else {
- flags = syscall.O_WRONLY
- }
- if test.partnerIsBlocking {
- fd, err := syscall.Open(name, flags, 0666)
- errch <- fderr{fd: fd, err: err}
- } else {
- var fd int
- err := error(syscall.ENXIO)
- for err == syscall.ENXIO {
- fd, err = syscall.Open(name, flags|syscall.O_NONBLOCK, 0666)
- time.Sleep(1 * time.Second)
- }
- errch <- fderr{fd: fd, err: err}
- }
- }()
-
- // Setup file flags for either a read only or write only open.
- flags := fs.FileFlags{
- Read: !test.partnerIsReader,
- Write: test.partnerIsReader,
- }
-
- // Open the pipe in a blocking way, which should succeed eventually.
- opener := &hostOpener{name: name}
- ctx := contexttest.Context(t)
- pipeOps, err := Open(ctx, opener, flags)
- if pipeOps != nil {
- // Same as TestTryOpen.
- pipeOps.Release()
- }
-
- // Check that the partner opened the file successfully.
- e := <-errch
- if e.err != nil {
- t.Errorf("%s: partner got error %v, wanted nil", test.desc, e.err)
- continue
- }
- // If so, then close the partner fd to avoid leaking an fd.
- syscall.Close(e.fd)
-
- // Check that our blocking open was successful.
- if err != nil {
- t.Errorf("%s: blocking open got error %v, wanted nil", test.desc, err)
- continue
- }
- if pipeOps == nil {
- t.Errorf("%s: blocking open got nil file, wanted non-nil", test.desc)
- continue
- }
- }
-}
-
-func TestCopiedReadAheadBuffer(t *testing.T) {
- // Create the pipe.
- name := pipename()
- if err := mkpipe(name); err != nil {
- t.Fatalf("failed to make host pipe: %v", err)
- }
- defer syscall.Unlink(name)
-
- // We're taking advantage of the fact that pipes opened read only always return
- // success, but internally they are not deemed "opened" until we're sure that
- // another writer comes along. This means we can open the same pipe write only
- // with no problems + write to it, given that opener.Open already tried to open
- // the pipe RDONLY and succeeded, which we know happened if TryOpen returns
- // syserror.ErrwouldBlock.
- //
- // This simulates the open(RDONLY) <-> open(WRONLY)+write race we care about, but
- // does not cause our test to be racy (which would be terrible).
- opener := &hostOpener{name: name}
- pipeOpenState := &pipeOpenState{}
- ctx := contexttest.Context(t)
- pipeOps, err := pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true})
- if pipeOps != nil {
- pipeOps.Release()
- t.Fatalf("open(%s, %o) got file, want nil", name, syscall.O_RDONLY)
- }
- if err != syserror.ErrWouldBlock {
- t.Fatalf("open(%s, %o) got error %v, want %v", name, syscall.O_RDONLY, err, syserror.ErrWouldBlock)
- }
-
- // Then open the same pipe write only and write some bytes to it. The next
- // time we try to open the pipe read only again via the pipeOpenState, we should
- // succeed and buffer some of the bytes written.
- fd, err := syscall.Open(name, syscall.O_WRONLY, 0666)
- if err != nil {
- t.Fatalf("open(%s, %o) got error %v, want nil", name, syscall.O_WRONLY, err)
- }
- defer syscall.Close(fd)
-
- data := []byte("hello")
- if n, err := syscall.Write(fd, data); n != len(data) || err != nil {
- t.Fatalf("write(%v) got (%d, %v), want (%d, nil)", data, n, err, len(data))
- }
-
- // Try the read again, knowing that it should succeed this time.
- pipeOps, err = pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true})
- if pipeOps == nil {
- t.Fatalf("open(%s, %o) got nil file, want not nil", name, syscall.O_RDONLY)
- }
- defer pipeOps.Release()
-
- if err != nil {
- t.Fatalf("open(%s, %o) got error %v, want nil", name, syscall.O_RDONLY, err)
- }
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, pipeOps)
-
- // Check that the file we opened points to a pipe with a non-empty read ahead buffer.
- bufsize := len(pipeOps.readAheadBuffer)
- if bufsize != 1 {
- t.Fatalf("read ahead buffer got %d bytes, want %d", bufsize, 1)
- }
-
- // Now for the final test, try to read everything in, expecting to get back all of
- // the bytes that were written at once. Note that in the wild there is no atomic
- // read size so expecting to get all bytes from a single writer when there are
- // multiple readers is a bad expectation.
- buf := make([]byte, len(data))
- ioseq := usermem.BytesIOSequence(buf)
- n, err := pipeOps.Read(ctx, file, ioseq, 0)
- if err != nil {
- t.Fatalf("read request got error %v, want nil", err)
- }
- if n != int64(len(data)) {
- t.Fatalf("read request got %d bytes, want %d", n, len(data))
- }
- if !bytes.Equal(buf, data) {
- t.Errorf("read request got bytes [%v], want [%v]", buf, data)
- }
-}
-
-func TestPipeHangup(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // flags control how we open our end of the pipe and must be read
- // only or write only. They also dicate how a coordinating partner
- // fd is opened, which is their inverse (read only -> write only, etc).
- flags fs.FileFlags
-
- // hangupSelf if true causes the test case to close our end of the pipe
- // and causes hangup errors to be asserted on our coordinating partner's
- // fd. If hangupSelf is false, then our partner's fd is closed and the
- // hangup errors are expected on our end of the pipe.
- hangupSelf bool
- }{
- {
- desc: "Read only gets hangup error",
- flags: fs.FileFlags{Read: true},
- },
- {
- desc: "Write only gets hangup error",
- flags: fs.FileFlags{Write: true},
- },
- {
- desc: "Read only generates hangup error",
- flags: fs.FileFlags{Read: true},
- hangupSelf: true,
- },
- {
- desc: "Write only generates hangup error",
- flags: fs.FileFlags{Write: true},
- hangupSelf: true,
- },
- } {
- if test.flags.Read == test.flags.Write {
- t.Errorf("%s: test requires a single reader or writer", test.desc)
- continue
- }
-
- // Create the pipe. We do this per-test case to keep tests independent.
- name := pipename()
- if err := mkpipe(name); err != nil {
- t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
- continue
- }
- defer syscall.Unlink(name)
-
- // Fire off a partner routine which tries to open the same pipe blocking,
- // which will synchronize with us. The channel allows us to get back the
- // fd once we expect this partner routine to succeed, so we can manifest
- // hangup events more directly.
- fdchan := make(chan int, 1)
- go func() {
- // Be explicit about the flags to protect the test from
- // misconfiguration.
- var flags int
- if test.flags.Read {
- flags = syscall.O_WRONLY
- } else {
- flags = syscall.O_RDONLY
- }
- fd, err := syscall.Open(name, flags, 0666)
- if err != nil {
- t.Logf("Open(%q, %o, 0666) partner failed: %v", name, flags, err)
- }
- fdchan <- fd
- }()
-
- // Open our end in a blocking way to ensure that we coordinate.
- opener := &hostOpener{name: name}
- ctx := contexttest.Context(t)
- pipeOps, err := Open(ctx, opener, test.flags)
- if err != nil {
- t.Errorf("%s: Open got error %v, want nil", test.desc, err)
- continue
- }
- // Don't defer file.DecRef here because that causes the hangup we're
- // trying to test for.
-
- // Expect the partner routine to have coordinated with us and get back
- // its open fd.
- f := <-fdchan
- if f < 0 {
- t.Errorf("%s: partner routine got fd %d, want > 0", test.desc, f)
- pipeOps.Release()
- continue
- }
-
- if test.hangupSelf {
- // Hangup self and assert that our partner got the expected hangup
- // error.
- pipeOps.Release()
-
- if test.flags.Read {
- // Partner is writer.
- assertWriterHungup(t, test.desc, fd.NewReadWriter(f))
- } else {
- // Partner is reader.
- assertReaderHungup(t, test.desc, fd.NewReadWriter(f))
- }
- } else {
- // Hangup our partner and expect us to get the hangup error.
- syscall.Close(f)
- defer pipeOps.Release()
-
- if test.flags.Read {
- assertReaderHungup(t, test.desc, pipeOps.(*pipeOperations).file)
- } else {
- assertWriterHungup(t, test.desc, pipeOps.(*pipeOperations).file)
- }
- }
- }
-}
-
-func assertReaderHungup(t *testing.T, desc string, reader io.Reader) bool {
- // Drain the pipe completely, it might have crap in it, but expect EOF eventually.
- var err error
- for err == nil {
- _, err = reader.Read(make([]byte, 10))
- }
- if err != io.EOF {
- t.Errorf("%s: read from self after hangup got error %v, want %v", desc, err, io.EOF)
- return false
- }
- return true
-}
-
-func assertWriterHungup(t *testing.T, desc string, writer io.Writer) bool {
- if _, err := writer.Write([]byte("hello")); unwrapError(err) != syscall.EPIPE {
- t.Errorf("%s: write to self after hangup got error %v, want %v", desc, err, syscall.EPIPE)
- return false
- }
- return true
-}
diff --git a/pkg/sentry/fs/fdpipe/pipe_test.go b/pkg/sentry/fs/fdpipe/pipe_test.go
deleted file mode 100644
index 69abc1e71..000000000
--- a/pkg/sentry/fs/fdpipe/pipe_test.go
+++ /dev/null
@@ -1,505 +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 fdpipe
-
-import (
- "bytes"
- "io"
- "os"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/fdnotifier"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func singlePipeFD() (int, error) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- return -1, err
- }
- syscall.Close(fds[1])
- return fds[0], nil
-}
-
-func singleDirFD() (int, error) {
- return syscall.Open(os.TempDir(), syscall.O_RDONLY, 0666)
-}
-
-func mockPipeDirent(t *testing.T) *fs.Dirent {
- ctx := contexttest.Context(t)
- node := fs.NewMockInodeOperations(ctx)
- node.UAttr = fs.UnstableAttr{
- Perms: fs.FilePermissions{
- User: fs.PermMask{Read: true, Write: true},
- },
- }
- inode := fs.NewInode(ctx, node, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- BlockSize: usermem.PageSize,
- })
- return fs.NewDirent(ctx, inode, "")
-}
-
-func TestNewPipe(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // getfd generates the fd to pass to newPipeOperations.
- getfd func() (int, error)
-
- // flags are the fs.FileFlags passed to newPipeOperations.
- flags fs.FileFlags
-
- // readAheadBuffer is the buffer passed to newPipeOperations.
- readAheadBuffer []byte
-
- // err is the expected error.
- err error
- }{
- {
- desc: "Cannot make new pipe from bad fd",
- getfd: func() (int, error) { return -1, nil },
- err: syscall.EINVAL,
- },
- {
- desc: "Cannot make new pipe from non-pipe fd",
- getfd: singleDirFD,
- err: syscall.EINVAL,
- },
- {
- desc: "Can make new pipe from pipe fd",
- getfd: singlePipeFD,
- flags: fs.FileFlags{Read: true},
- readAheadBuffer: []byte("hello"),
- },
- } {
- gfd, err := test.getfd()
- if err != nil {
- t.Errorf("%s: getfd got (%d, %v), want (fd, nil)", test.desc, gfd, err)
- continue
- }
- f := fd.New(gfd)
-
- p, err := newPipeOperations(contexttest.Context(t), nil, test.flags, f, test.readAheadBuffer)
- if p != nil {
- // This is necessary to remove the fd from the global fd notifier.
- defer p.Release()
- } else {
- // If there is no p to DecRef on, because newPipeOperations failed, then the
- // file still needs to be closed.
- defer f.Close()
- }
-
- if err != test.err {
- t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
- continue
- }
- // Check the state of the pipe given that it was successfully opened.
- if err == nil {
- if p == nil {
- t.Errorf("%s: got nil pipe and nil error, want (pipe, nil)", test.desc)
- continue
- }
- if flags := p.flags; test.flags != flags {
- t.Errorf("%s: got file flags %s, want %s", test.desc, flags, test.flags)
- continue
- }
- if len(test.readAheadBuffer) != len(p.readAheadBuffer) {
- t.Errorf("%s: got read ahead buffer length %d, want %d", test.desc, len(p.readAheadBuffer), len(test.readAheadBuffer))
- continue
- }
- fileFlags, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(p.file.FD()), syscall.F_GETFL, 0)
- if errno != 0 {
- t.Errorf("%s: failed to get file flags for fd %d, got %v, want 0", test.desc, p.file.FD(), errno)
- continue
- }
- if fileFlags&syscall.O_NONBLOCK == 0 {
- t.Errorf("%s: pipe is blocking, expected non-blocking", test.desc)
- continue
- }
- if !fdnotifier.HasFD(int32(f.FD())) {
- t.Errorf("%s: pipe fd %d is not registered for events", test.desc, f.FD)
- }
- }
- }
-}
-
-func TestPipeDestruction(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- f := fd.New(fds[0])
-
- // We don't care about the other end, just use the read end.
- syscall.Close(fds[1])
-
- // Test the read end, but it doesn't really matter which.
- p, err := newPipeOperations(contexttest.Context(t), nil, fs.FileFlags{Read: true}, f, nil)
- if err != nil {
- f.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- // Drop our only reference, which should trigger the destructor.
- p.Release()
-
- if fdnotifier.HasFD(int32(fds[0])) {
- t.Fatalf("after DecRef fdnotifier has fd %d, want no longer registered", fds[0])
- }
- if p.file != nil {
- t.Errorf("after DecRef got file, want nil")
- }
-}
-
-type Seek struct{}
-
-type ReadDir struct{}
-
-type Writev struct {
- Src usermem.IOSequence
-}
-
-type Readv struct {
- Dst usermem.IOSequence
-}
-
-type Fsync struct{}
-
-func TestPipeRequest(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // request to execute.
- context interface{}
-
- // flags determines whether to use the read or write end
- // of the pipe, for this test it can only be Read or Write.
- flags fs.FileFlags
-
- // keepOpenPartner if false closes the other end of the pipe,
- // otherwise this is delayed until the end of the test.
- keepOpenPartner bool
-
- // expected error
- err error
- }{
- {
- desc: "ReadDir on pipe returns ENOTDIR",
- context: &ReadDir{},
- err: syscall.ENOTDIR,
- },
- {
- desc: "Fsync on pipe returns EINVAL",
- context: &Fsync{},
- err: syscall.EINVAL,
- },
- {
- desc: "Seek on pipe returns ESPIPE",
- context: &Seek{},
- err: syscall.ESPIPE,
- },
- {
- desc: "Readv on pipe from empty buffer returns nil",
- context: &Readv{Dst: usermem.BytesIOSequence(nil)},
- flags: fs.FileFlags{Read: true},
- },
- {
- desc: "Readv on pipe from non-empty buffer and closed partner returns EOF",
- context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))},
- flags: fs.FileFlags{Read: true},
- err: io.EOF,
- },
- {
- desc: "Readv on pipe from non-empty buffer and open partner returns EWOULDBLOCK",
- context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))},
- flags: fs.FileFlags{Read: true},
- keepOpenPartner: true,
- err: syserror.ErrWouldBlock,
- },
- {
- desc: "Writev on pipe from empty buffer returns nil",
- context: &Writev{Src: usermem.BytesIOSequence(nil)},
- flags: fs.FileFlags{Write: true},
- },
- {
- desc: "Writev on pipe from non-empty buffer and closed partner returns EPIPE",
- context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))},
- flags: fs.FileFlags{Write: true},
- err: syscall.EPIPE,
- },
- {
- desc: "Writev on pipe from non-empty buffer and open partner succeeds",
- context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))},
- flags: fs.FileFlags{Write: true},
- keepOpenPartner: true,
- },
- } {
- if test.flags.Read && test.flags.Write {
- panic("both read and write not supported for this test")
- }
-
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Errorf("%s: failed to create pipes: got %v, want nil", test.desc, err)
- continue
- }
-
- // Configure the fd and partner fd based on the file flags.
- testFd, partnerFd := fds[0], fds[1]
- if test.flags.Write {
- testFd, partnerFd = fds[1], fds[0]
- }
-
- // Configure closing the fds.
- if test.keepOpenPartner {
- defer syscall.Close(partnerFd)
- } else {
- syscall.Close(partnerFd)
- }
-
- // Create the pipe.
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, test.flags, fd.New(testFd), nil)
- if err != nil {
- t.Fatalf("%s: newPipeOperations got error %v, want nil", test.desc, err)
- }
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe})
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- // Issue request via the appropriate function.
- switch c := test.context.(type) {
- case *Seek:
- _, err = p.Seek(ctx, file, 0, 0)
- case *ReadDir:
- _, err = p.Readdir(ctx, file, nil)
- case *Readv:
- _, err = p.Read(ctx, file, c.Dst, 0)
- case *Writev:
- _, err = p.Write(ctx, file, c.Src, 0)
- case *Fsync:
- err = p.Fsync(ctx, file, 0, fs.FileMaxOffset, fs.SyncAll)
- default:
- t.Errorf("%s: unknown request type %T", test.desc, test.context)
- }
-
- if unwrapError(err) != test.err {
- t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
- }
- }
-}
-
-func TestPipeReadAheadBuffer(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- rfile := fd.New(fds[0])
-
- // Eventually close the write end, which is not wrapped in a pipe object.
- defer syscall.Close(fds[1])
-
- // Write some bytes to this end.
- data := []byte("world")
- if n, err := syscall.Write(fds[1], data); n != len(data) || err != nil {
- rfile.Close()
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data))
- }
- // Close the write end immediately, we don't care about it.
-
- buffered := []byte("hello ")
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, buffered)
- if err != nil {
- rfile.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- // In total we expect to read data + buffered.
- total := append(buffered, data...)
-
- buf := make([]byte, len(total))
- iov := usermem.BytesIOSequence(buf)
- n, err := p.Read(contexttest.Context(t), file, iov, 0)
- if err != nil {
- t.Fatalf("read request got error %v, want nil", err)
- }
- if n != int64(len(total)) {
- t.Fatalf("read request got %d bytes, want %d", n, len(total))
- }
- if !bytes.Equal(buf, total) {
- t.Errorf("read request got bytes [%v], want [%v]", buf, total)
- }
-}
-
-// This is very important for pipes in general because they can return
-// EWOULDBLOCK and for those that block they must continue until they have read
-// all of the data (and report it as such).
-func TestPipeReadsAccumulate(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- rfile := fd.New(fds[0])
-
- // Eventually close the write end, it doesn't depend on a pipe object.
- defer syscall.Close(fds[1])
-
- // Get a new read only pipe reference.
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, nil)
- if err != nil {
- rfile.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- // Don't forget to remove the fd from the fd notifier. Otherwise other tests will
- // likely be borked, because it's global :(
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- // Write some some bytes to the pipe.
- data := []byte("some message")
- if n, err := syscall.Write(fds[1], data); n != len(data) || err != nil {
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data))
- }
-
- // Construct a segment vec that is a bit more than we have written so we
- // trigger an EWOULDBLOCK.
- wantBytes := len(data) + 1
- readBuffer := make([]byte, wantBytes)
- iov := usermem.BytesIOSequence(readBuffer)
- n, err := p.Read(ctx, file, iov, 0)
- total := n
- iov = iov.DropFirst64(n)
- if err != syserror.ErrWouldBlock {
- t.Fatalf("Readv got error %v, want %v", err, syserror.ErrWouldBlock)
- }
-
- // Write a few more bytes to allow us to read more/accumulate.
- extra := []byte("extra")
- if n, err := syscall.Write(fds[1], extra); n != len(extra) || err != nil {
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(extra))
- }
-
- // This time, using the same request, we should not block.
- n, err = p.Read(ctx, file, iov, 0)
- total += n
- if err != nil {
- t.Fatalf("Readv got error %v, want nil", err)
- }
-
- // Assert that the result we got back is cumulative.
- if total != int64(wantBytes) {
- t.Fatalf("Readv sequence got %d bytes, want %d", total, wantBytes)
- }
-
- if want := append(data, extra[0]); !bytes.Equal(readBuffer, want) {
- t.Errorf("Readv sequence got %v, want %v", readBuffer, want)
- }
-}
-
-// Same as TestReadsAccumulate.
-func TestPipeWritesAccumulate(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- wfile := fd.New(fds[1])
-
- // Eventually close the read end, it doesn't depend on a pipe object.
- defer syscall.Close(fds[0])
-
- // Get a new write only pipe reference.
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, fs.FileFlags{Write: true}, wfile, nil)
- if err != nil {
- wfile.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- // Don't forget to remove the fd from the fd notifier. Otherwise other tests
- // will likely be borked, because it's global :(
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- pipeSize, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(wfile.FD()), syscall.F_GETPIPE_SZ, 0)
- if errno != 0 {
- t.Fatalf("fcntl(F_GETPIPE_SZ) failed: %v", errno)
- }
- t.Logf("Pipe buffer size: %d", pipeSize)
-
- // Construct a segment vec that is larger than the pipe size to trigger an
- // EWOULDBLOCK.
- wantBytes := int(pipeSize) * 2
- writeBuffer := make([]byte, wantBytes)
- for i := 0; i < wantBytes; i++ {
- writeBuffer[i] = 'a'
- }
- iov := usermem.BytesIOSequence(writeBuffer)
- n, err := p.Write(ctx, file, iov, 0)
- if err != syserror.ErrWouldBlock {
- t.Fatalf("Writev got error %v, want %v", err, syserror.ErrWouldBlock)
- }
- if n != int64(pipeSize) {
- t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize)
- }
- total := n
- iov = iov.DropFirst64(n)
-
- // Read the entire pipe buf size to make space for the second half.
- readBuffer := make([]byte, n)
- if n, err := syscall.Read(fds[0], readBuffer); n != len(readBuffer) || err != nil {
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(readBuffer))
- }
- if !bytes.Equal(readBuffer, writeBuffer[:len(readBuffer)]) {
- t.Fatalf("wrong data read from pipe, got: %v, want: %v", readBuffer, writeBuffer)
- }
-
- // This time we should not block.
- n, err = p.Write(ctx, file, iov, 0)
- if err != nil {
- t.Fatalf("Writev got error %v, want nil", err)
- }
- if n != int64(pipeSize) {
- t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize)
- }
- total += n
-
- // Assert that the result we got back is cumulative.
- if total != int64(wantBytes) {
- t.Fatalf("Writev sequence got %d bytes, want %d", total, wantBytes)
- }
-}
diff --git a/pkg/sentry/fs/file_overlay_test.go b/pkg/sentry/fs/file_overlay_test.go
deleted file mode 100644
index 2fb824d5c..000000000
--- a/pkg/sentry/fs/file_overlay_test.go
+++ /dev/null
@@ -1,275 +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 fs_test
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
-)
-
-func TestReaddir(t *testing.T) {
- ctx := contexttest.Context(t)
- ctx = &rootContext{
- Context: ctx,
- root: fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root"),
- }
- for _, test := range []struct {
- // Test description.
- desc string
-
- // Lookup parameters.
- dir *fs.Inode
-
- // Want from lookup.
- err error
- names []string
- }{
- {
- desc: "no upper, lower has entries",
- dir: fs.NewTestOverlayDir(ctx,
- nil, /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- {name: "b"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "b"},
- },
- {
- desc: "upper has entries, no lower",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- {name: "b"},
- }, nil), /* upper */
- nil, /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "b"},
- },
- {
- desc: "upper and lower, entries combine",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "b"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "b"},
- },
- {
- desc: "upper and lower, entries combine, none are masked",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- }, []string{"b"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "c"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "c"},
- },
- {
- desc: "upper and lower, entries combine, upper masks some of lower",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- }, []string{"b"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "b"}, /* will be masked */
- {name: "c"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "c"},
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- openDir, err := test.dir.GetFile(ctx, fs.NewDirent(ctx, test.dir, "stub"), fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("GetFile got error %v, want nil", err)
- }
- stubSerializer := &fs.CollectEntriesSerializer{}
- err = openDir.Readdir(ctx, stubSerializer)
- if err != test.err {
- t.Fatalf("Readdir got error %v, want nil", err)
- }
- if err != nil {
- return
- }
- if !reflect.DeepEqual(stubSerializer.Order, test.names) {
- t.Errorf("Readdir got names %v, want %v", stubSerializer.Order, test.names)
- }
- })
- }
-}
-
-func TestReaddirRevalidation(t *testing.T) {
- ctx := contexttest.Context(t)
- ctx = &rootContext{
- Context: ctx,
- root: fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root"),
- }
-
- // Create an overlay with two directories, each with one file.
- upper := newTestRamfsDir(ctx, []dirContent{{name: "a"}}, nil)
- lower := newTestRamfsDir(ctx, []dirContent{{name: "b"}}, nil)
- overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */)
-
- // Get a handle to the dirent in the upper filesystem so that we can
- // modify it without going through the dirent.
- upperDir := upper.InodeOperations.(*dir).InodeOperations.(*ramfs.Dir)
-
- // Check that overlay returns the files from both upper and lower.
- openDir, err := overlay.GetFile(ctx, fs.NewDirent(ctx, overlay, "stub"), fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("GetFile got error %v, want nil", err)
- }
- ser := &fs.CollectEntriesSerializer{}
- if err := openDir.Readdir(ctx, ser); err != nil {
- t.Fatalf("Readdir got error %v, want nil", err)
- }
- got, want := ser.Order, []string{".", "..", "a", "b"}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-
- // Remove "a" from the upper and add "c".
- if err := upperDir.Remove(ctx, upper, "a"); err != nil {
- t.Fatalf("error removing child: %v", err)
- }
- upperDir.AddChild(ctx, "c", fs.NewInode(ctx, fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermissions{}, 0),
- upper.MountSource, fs.StableAttr{Type: fs.RegularFile}))
-
- // Seek to beginning of the directory and do the readdir again.
- if _, err := openDir.Seek(ctx, fs.SeekSet, 0); err != nil {
- t.Fatalf("error seeking to beginning of dir: %v", err)
- }
- ser = &fs.CollectEntriesSerializer{}
- if err := openDir.Readdir(ctx, ser); err != nil {
- t.Fatalf("Readdir got error %v, want nil", err)
- }
-
- // Readdir should return the updated children.
- got, want = ser.Order, []string{".", "..", "b", "c"}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-}
-
-// TestReaddirOverlayFrozen tests that calling Readdir on an overlay file with
-// a frozen dirent tree does not make Readdir calls to the underlying files.
-func TestReaddirOverlayFrozen(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Create an overlay with two directories, each with two files.
- upper := newTestRamfsDir(ctx, []dirContent{{name: "upper-file1"}, {name: "upper-file2"}}, nil)
- lower := newTestRamfsDir(ctx, []dirContent{{name: "lower-file1"}, {name: "lower-file2"}}, nil)
- overlayInode := fs.NewTestOverlayDir(ctx, upper, lower, false)
-
- // Set that overlay as the root.
- root := fs.NewDirent(ctx, overlayInode, "root")
- ctx = &rootContext{
- Context: ctx,
- root: root,
- }
-
- // Check that calling Readdir on the root now returns all 4 files (2
- // from each layer in the overlay).
- rootFile, err := root.Inode.GetFile(ctx, root, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("root.Inode.GetFile failed: %v", err)
- }
- defer rootFile.DecRef()
- ser := &fs.CollectEntriesSerializer{}
- if err := rootFile.Readdir(ctx, ser); err != nil {
- t.Fatalf("rootFile.Readdir failed: %v", err)
- }
- if got, want := ser.Order, []string{".", "..", "lower-file1", "lower-file2", "upper-file1", "upper-file2"}; !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-
- // Readdir should have been called on upper and lower.
- upperDir := upper.InodeOperations.(*dir)
- lowerDir := lower.InodeOperations.(*dir)
- if !upperDir.ReaddirCalled {
- t.Errorf("upperDir.ReaddirCalled got %v, want true", upperDir.ReaddirCalled)
- }
- if !lowerDir.ReaddirCalled {
- t.Errorf("lowerDir.ReaddirCalled got %v, want true", lowerDir.ReaddirCalled)
- }
-
- // Reset.
- upperDir.ReaddirCalled = false
- lowerDir.ReaddirCalled = false
-
- // Take references on "upper-file1" and "lower-file1", pinning them in
- // the dirent tree.
- for _, name := range []string{"upper-file1", "lower-file1"} {
- if _, err := root.Walk(ctx, root, name); err != nil {
- t.Fatalf("root.Walk(%q) failed: %v", name, err)
- }
- // Don't drop a reference on the returned dirent so that it
- // will stay in the tree.
- }
-
- // Freeze the dirent tree.
- root.Freeze()
-
- // Seek back to the beginning of the file.
- if _, err := rootFile.Seek(ctx, fs.SeekSet, 0); err != nil {
- t.Fatalf("error seeking to beginning of directory: %v", err)
- }
-
- // Calling Readdir on the root now will return only the pinned
- // children.
- ser = &fs.CollectEntriesSerializer{}
- if err := rootFile.Readdir(ctx, ser); err != nil {
- t.Fatalf("rootFile.Readdir failed: %v", err)
- }
- if got, want := ser.Order, []string{".", "..", "lower-file1", "upper-file1"}; !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-
- // Readdir should NOT have been called on upper or lower.
- if upperDir.ReaddirCalled {
- t.Errorf("upperDir.ReaddirCalled got %v, want false", upperDir.ReaddirCalled)
- }
- if lowerDir.ReaddirCalled {
- t.Errorf("lowerDir.ReaddirCalled got %v, want false", lowerDir.ReaddirCalled)
- }
-}
-
-type rootContext struct {
- context.Context
- root *fs.Dirent
-}
-
-// Value implements context.Context.
-func (r *rootContext) Value(key interface{}) interface{} {
- switch key {
- case fs.CtxRoot:
- r.root.IncRef()
- return r.root
- default:
- return r.Context.Value(key)
- }
-}
diff --git a/pkg/sentry/fs/filetest/BUILD b/pkg/sentry/fs/filetest/BUILD
deleted file mode 100644
index 358dc2be3..000000000
--- a/pkg/sentry/fs/filetest/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "filetest",
- testonly = 1,
- srcs = ["filetest.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/filetest",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/filetest/filetest.go b/pkg/sentry/fs/filetest/filetest.go
deleted file mode 100644
index 22270a494..000000000
--- a/pkg/sentry/fs/filetest/filetest.go
+++ /dev/null
@@ -1,61 +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 filetest provides a test implementation of an fs.File.
-package filetest
-
-import (
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/anon"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// TestFileOperations is an implementation of the File interface. It provides all
-// required methods.
-type TestFileOperations struct {
- fsutil.FileNoopRelease `state:"nosave"`
- fsutil.FilePipeSeek `state:"nosave"`
- fsutil.FileNotDirReaddir `state:"nosave"`
- fsutil.FileNoFsync `state:"nosave"`
- fsutil.FileNoopFlush `state:"nosave"`
- fsutil.FileNoMMap `state:"nosave"`
- fsutil.FileNoIoctl `state:"nosave"`
- fsutil.FileNoSplice `state:"nosave"`
- fsutil.FileUseInodeUnstableAttr `state:"nosave"`
- waiter.AlwaysReady `state:"nosave"`
-}
-
-// NewTestFile creates and initializes a new test file.
-func NewTestFile(tb testing.TB) *fs.File {
- ctx := contexttest.Context(tb)
- dirent := fs.NewDirent(ctx, anon.NewInode(ctx), "test")
- return fs.NewFile(ctx, dirent, fs.FileFlags{}, &TestFileOperations{})
-}
-
-// Read just fails the request.
-func (*TestFileOperations) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
- return 0, fmt.Errorf("Readv not implemented")
-}
-
-// Write just fails the request.
-func (*TestFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
- return 0, fmt.Errorf("Writev not implemented")
-}
diff --git a/pkg/sentry/fs/fs_state_autogen.go b/pkg/sentry/fs/fs_state_autogen.go
new file mode 100755
index 000000000..a6820867d
--- /dev/null
+++ b/pkg/sentry/fs/fs_state_autogen.go
@@ -0,0 +1,634 @@
+// automatically generated by stateify.
+
+package fs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *StableAttr) beforeSave() {}
+func (x *StableAttr) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("DeviceID", &x.DeviceID)
+ m.Save("InodeID", &x.InodeID)
+ m.Save("BlockSize", &x.BlockSize)
+ m.Save("DeviceFileMajor", &x.DeviceFileMajor)
+ m.Save("DeviceFileMinor", &x.DeviceFileMinor)
+}
+
+func (x *StableAttr) afterLoad() {}
+func (x *StableAttr) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("DeviceID", &x.DeviceID)
+ m.Load("InodeID", &x.InodeID)
+ m.Load("BlockSize", &x.BlockSize)
+ m.Load("DeviceFileMajor", &x.DeviceFileMajor)
+ m.Load("DeviceFileMinor", &x.DeviceFileMinor)
+}
+
+func (x *UnstableAttr) beforeSave() {}
+func (x *UnstableAttr) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Size", &x.Size)
+ m.Save("Usage", &x.Usage)
+ m.Save("Perms", &x.Perms)
+ m.Save("Owner", &x.Owner)
+ m.Save("AccessTime", &x.AccessTime)
+ m.Save("ModificationTime", &x.ModificationTime)
+ m.Save("StatusChangeTime", &x.StatusChangeTime)
+ m.Save("Links", &x.Links)
+}
+
+func (x *UnstableAttr) afterLoad() {}
+func (x *UnstableAttr) load(m state.Map) {
+ m.Load("Size", &x.Size)
+ m.Load("Usage", &x.Usage)
+ m.Load("Perms", &x.Perms)
+ m.Load("Owner", &x.Owner)
+ m.Load("AccessTime", &x.AccessTime)
+ m.Load("ModificationTime", &x.ModificationTime)
+ m.Load("StatusChangeTime", &x.StatusChangeTime)
+ m.Load("Links", &x.Links)
+}
+
+func (x *AttrMask) beforeSave() {}
+func (x *AttrMask) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("DeviceID", &x.DeviceID)
+ m.Save("InodeID", &x.InodeID)
+ m.Save("BlockSize", &x.BlockSize)
+ m.Save("Size", &x.Size)
+ m.Save("Usage", &x.Usage)
+ m.Save("Perms", &x.Perms)
+ m.Save("UID", &x.UID)
+ m.Save("GID", &x.GID)
+ m.Save("AccessTime", &x.AccessTime)
+ m.Save("ModificationTime", &x.ModificationTime)
+ m.Save("StatusChangeTime", &x.StatusChangeTime)
+ m.Save("Links", &x.Links)
+}
+
+func (x *AttrMask) afterLoad() {}
+func (x *AttrMask) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("DeviceID", &x.DeviceID)
+ m.Load("InodeID", &x.InodeID)
+ m.Load("BlockSize", &x.BlockSize)
+ m.Load("Size", &x.Size)
+ m.Load("Usage", &x.Usage)
+ m.Load("Perms", &x.Perms)
+ m.Load("UID", &x.UID)
+ m.Load("GID", &x.GID)
+ m.Load("AccessTime", &x.AccessTime)
+ m.Load("ModificationTime", &x.ModificationTime)
+ m.Load("StatusChangeTime", &x.StatusChangeTime)
+ m.Load("Links", &x.Links)
+}
+
+func (x *PermMask) beforeSave() {}
+func (x *PermMask) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Read", &x.Read)
+ m.Save("Write", &x.Write)
+ m.Save("Execute", &x.Execute)
+}
+
+func (x *PermMask) afterLoad() {}
+func (x *PermMask) load(m state.Map) {
+ m.Load("Read", &x.Read)
+ m.Load("Write", &x.Write)
+ m.Load("Execute", &x.Execute)
+}
+
+func (x *FilePermissions) beforeSave() {}
+func (x *FilePermissions) save(m state.Map) {
+ x.beforeSave()
+ m.Save("User", &x.User)
+ m.Save("Group", &x.Group)
+ m.Save("Other", &x.Other)
+ m.Save("Sticky", &x.Sticky)
+ m.Save("SetUID", &x.SetUID)
+ m.Save("SetGID", &x.SetGID)
+}
+
+func (x *FilePermissions) afterLoad() {}
+func (x *FilePermissions) load(m state.Map) {
+ m.Load("User", &x.User)
+ m.Load("Group", &x.Group)
+ m.Load("Other", &x.Other)
+ m.Load("Sticky", &x.Sticky)
+ m.Load("SetUID", &x.SetUID)
+ m.Load("SetGID", &x.SetGID)
+}
+
+func (x *FileOwner) beforeSave() {}
+func (x *FileOwner) save(m state.Map) {
+ x.beforeSave()
+ m.Save("UID", &x.UID)
+ m.Save("GID", &x.GID)
+}
+
+func (x *FileOwner) afterLoad() {}
+func (x *FileOwner) load(m state.Map) {
+ m.Load("UID", &x.UID)
+ m.Load("GID", &x.GID)
+}
+
+func (x *DentAttr) beforeSave() {}
+func (x *DentAttr) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("InodeID", &x.InodeID)
+}
+
+func (x *DentAttr) afterLoad() {}
+func (x *DentAttr) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("InodeID", &x.InodeID)
+}
+
+func (x *SortedDentryMap) beforeSave() {}
+func (x *SortedDentryMap) save(m state.Map) {
+ x.beforeSave()
+ m.Save("names", &x.names)
+ m.Save("entries", &x.entries)
+}
+
+func (x *SortedDentryMap) afterLoad() {}
+func (x *SortedDentryMap) load(m state.Map) {
+ m.Load("names", &x.names)
+ m.Load("entries", &x.entries)
+}
+
+func (x *Dirent) save(m state.Map) {
+ x.beforeSave()
+ var children map[string]*Dirent = x.saveChildren()
+ m.SaveValue("children", children)
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("userVisible", &x.userVisible)
+ m.Save("Inode", &x.Inode)
+ m.Save("name", &x.name)
+ m.Save("parent", &x.parent)
+ m.Save("deleted", &x.deleted)
+ m.Save("frozen", &x.frozen)
+ m.Save("mounted", &x.mounted)
+}
+
+func (x *Dirent) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("userVisible", &x.userVisible)
+ m.Load("Inode", &x.Inode)
+ m.Load("name", &x.name)
+ m.Load("parent", &x.parent)
+ m.Load("deleted", &x.deleted)
+ m.Load("frozen", &x.frozen)
+ m.Load("mounted", &x.mounted)
+ m.LoadValue("children", new(map[string]*Dirent), func(y interface{}) { x.loadChildren(y.(map[string]*Dirent)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *DirentCache) beforeSave() {}
+func (x *DirentCache) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.currentSize) { m.Failf("currentSize is %v, expected zero", x.currentSize) }
+ if !state.IsZeroValue(x.list) { m.Failf("list is %v, expected zero", x.list) }
+ m.Save("maxSize", &x.maxSize)
+ m.Save("limit", &x.limit)
+}
+
+func (x *DirentCache) afterLoad() {}
+func (x *DirentCache) load(m state.Map) {
+ m.Load("maxSize", &x.maxSize)
+ m.Load("limit", &x.limit)
+}
+
+func (x *DirentCacheLimiter) beforeSave() {}
+func (x *DirentCacheLimiter) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.count) { m.Failf("count is %v, expected zero", x.count) }
+ m.Save("max", &x.max)
+}
+
+func (x *DirentCacheLimiter) afterLoad() {}
+func (x *DirentCacheLimiter) load(m state.Map) {
+ m.Load("max", &x.max)
+}
+
+func (x *direntList) beforeSave() {}
+func (x *direntList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *direntList) afterLoad() {}
+func (x *direntList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *direntEntry) beforeSave() {}
+func (x *direntEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *direntEntry) afterLoad() {}
+func (x *direntEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *eventList) beforeSave() {}
+func (x *eventList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *eventList) afterLoad() {}
+func (x *eventList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *eventEntry) beforeSave() {}
+func (x *eventEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *eventEntry) afterLoad() {}
+func (x *eventEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *File) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("UniqueID", &x.UniqueID)
+ m.Save("Dirent", &x.Dirent)
+ m.Save("flags", &x.flags)
+ m.Save("async", &x.async)
+ m.Save("FileOperations", &x.FileOperations)
+ m.Save("offset", &x.offset)
+}
+
+func (x *File) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("UniqueID", &x.UniqueID)
+ m.Load("Dirent", &x.Dirent)
+ m.Load("flags", &x.flags)
+ m.Load("async", &x.async)
+ m.LoadWait("FileOperations", &x.FileOperations)
+ m.Load("offset", &x.offset)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *overlayFileOperations) beforeSave() {}
+func (x *overlayFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("upper", &x.upper)
+ m.Save("lower", &x.lower)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *overlayFileOperations) afterLoad() {}
+func (x *overlayFileOperations) load(m state.Map) {
+ m.Load("upper", &x.upper)
+ m.Load("lower", &x.lower)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *overlayMappingIdentity) beforeSave() {}
+func (x *overlayMappingIdentity) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("id", &x.id)
+ m.Save("overlayFile", &x.overlayFile)
+}
+
+func (x *overlayMappingIdentity) afterLoad() {}
+func (x *overlayMappingIdentity) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("id", &x.id)
+ m.Load("overlayFile", &x.overlayFile)
+}
+
+func (x *MountSourceFlags) beforeSave() {}
+func (x *MountSourceFlags) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ReadOnly", &x.ReadOnly)
+ m.Save("NoAtime", &x.NoAtime)
+ m.Save("ForcePageCache", &x.ForcePageCache)
+ m.Save("NoExec", &x.NoExec)
+}
+
+func (x *MountSourceFlags) afterLoad() {}
+func (x *MountSourceFlags) load(m state.Map) {
+ m.Load("ReadOnly", &x.ReadOnly)
+ m.Load("NoAtime", &x.NoAtime)
+ m.Load("ForcePageCache", &x.ForcePageCache)
+ m.Load("NoExec", &x.NoExec)
+}
+
+func (x *FileFlags) beforeSave() {}
+func (x *FileFlags) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Direct", &x.Direct)
+ m.Save("NonBlocking", &x.NonBlocking)
+ m.Save("DSync", &x.DSync)
+ m.Save("Sync", &x.Sync)
+ m.Save("Append", &x.Append)
+ m.Save("Read", &x.Read)
+ m.Save("Write", &x.Write)
+ m.Save("Pread", &x.Pread)
+ m.Save("Pwrite", &x.Pwrite)
+ m.Save("Directory", &x.Directory)
+ m.Save("Async", &x.Async)
+ m.Save("LargeFile", &x.LargeFile)
+ m.Save("NonSeekable", &x.NonSeekable)
+ m.Save("Truncate", &x.Truncate)
+}
+
+func (x *FileFlags) afterLoad() {}
+func (x *FileFlags) load(m state.Map) {
+ m.Load("Direct", &x.Direct)
+ m.Load("NonBlocking", &x.NonBlocking)
+ m.Load("DSync", &x.DSync)
+ m.Load("Sync", &x.Sync)
+ m.Load("Append", &x.Append)
+ m.Load("Read", &x.Read)
+ m.Load("Write", &x.Write)
+ m.Load("Pread", &x.Pread)
+ m.Load("Pwrite", &x.Pwrite)
+ m.Load("Directory", &x.Directory)
+ m.Load("Async", &x.Async)
+ m.Load("LargeFile", &x.LargeFile)
+ m.Load("NonSeekable", &x.NonSeekable)
+ m.Load("Truncate", &x.Truncate)
+}
+
+func (x *Inode) beforeSave() {}
+func (x *Inode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("InodeOperations", &x.InodeOperations)
+ m.Save("StableAttr", &x.StableAttr)
+ m.Save("LockCtx", &x.LockCtx)
+ m.Save("Watches", &x.Watches)
+ m.Save("MountSource", &x.MountSource)
+ m.Save("overlay", &x.overlay)
+}
+
+func (x *Inode) afterLoad() {}
+func (x *Inode) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("InodeOperations", &x.InodeOperations)
+ m.Load("StableAttr", &x.StableAttr)
+ m.Load("LockCtx", &x.LockCtx)
+ m.Load("Watches", &x.Watches)
+ m.Load("MountSource", &x.MountSource)
+ m.Load("overlay", &x.overlay)
+}
+
+func (x *LockCtx) beforeSave() {}
+func (x *LockCtx) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Posix", &x.Posix)
+ m.Save("BSD", &x.BSD)
+}
+
+func (x *LockCtx) afterLoad() {}
+func (x *LockCtx) load(m state.Map) {
+ m.Load("Posix", &x.Posix)
+ m.Load("BSD", &x.BSD)
+}
+
+func (x *Watches) beforeSave() {}
+func (x *Watches) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ws", &x.ws)
+ m.Save("unlinked", &x.unlinked)
+}
+
+func (x *Watches) afterLoad() {}
+func (x *Watches) load(m state.Map) {
+ m.Load("ws", &x.ws)
+ m.Load("unlinked", &x.unlinked)
+}
+
+func (x *Inotify) beforeSave() {}
+func (x *Inotify) save(m state.Map) {
+ x.beforeSave()
+ m.Save("id", &x.id)
+ m.Save("events", &x.events)
+ m.Save("scratch", &x.scratch)
+ m.Save("nextWatch", &x.nextWatch)
+ m.Save("watches", &x.watches)
+}
+
+func (x *Inotify) afterLoad() {}
+func (x *Inotify) load(m state.Map) {
+ m.Load("id", &x.id)
+ m.Load("events", &x.events)
+ m.Load("scratch", &x.scratch)
+ m.Load("nextWatch", &x.nextWatch)
+ m.Load("watches", &x.watches)
+}
+
+func (x *Event) beforeSave() {}
+func (x *Event) save(m state.Map) {
+ x.beforeSave()
+ m.Save("eventEntry", &x.eventEntry)
+ m.Save("wd", &x.wd)
+ m.Save("mask", &x.mask)
+ m.Save("cookie", &x.cookie)
+ m.Save("len", &x.len)
+ m.Save("name", &x.name)
+}
+
+func (x *Event) afterLoad() {}
+func (x *Event) load(m state.Map) {
+ m.Load("eventEntry", &x.eventEntry)
+ m.Load("wd", &x.wd)
+ m.Load("mask", &x.mask)
+ m.Load("cookie", &x.cookie)
+ m.Load("len", &x.len)
+ m.Load("name", &x.name)
+}
+
+func (x *Watch) beforeSave() {}
+func (x *Watch) save(m state.Map) {
+ x.beforeSave()
+ m.Save("owner", &x.owner)
+ m.Save("wd", &x.wd)
+ m.Save("target", &x.target)
+ m.Save("unpinned", &x.unpinned)
+ m.Save("mask", &x.mask)
+ m.Save("pins", &x.pins)
+}
+
+func (x *Watch) afterLoad() {}
+func (x *Watch) load(m state.Map) {
+ m.Load("owner", &x.owner)
+ m.Load("wd", &x.wd)
+ m.Load("target", &x.target)
+ m.Load("unpinned", &x.unpinned)
+ m.Load("mask", &x.mask)
+ m.Load("pins", &x.pins)
+}
+
+func (x *MountSource) beforeSave() {}
+func (x *MountSource) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("MountSourceOperations", &x.MountSourceOperations)
+ m.Save("FilesystemType", &x.FilesystemType)
+ m.Save("Flags", &x.Flags)
+ m.Save("fscache", &x.fscache)
+ m.Save("direntRefs", &x.direntRefs)
+}
+
+func (x *MountSource) afterLoad() {}
+func (x *MountSource) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("MountSourceOperations", &x.MountSourceOperations)
+ m.Load("FilesystemType", &x.FilesystemType)
+ m.Load("Flags", &x.Flags)
+ m.Load("fscache", &x.fscache)
+ m.Load("direntRefs", &x.direntRefs)
+}
+
+func (x *SimpleMountSourceOperations) beforeSave() {}
+func (x *SimpleMountSourceOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("keep", &x.keep)
+ m.Save("revalidate", &x.revalidate)
+ m.Save("cacheReaddir", &x.cacheReaddir)
+}
+
+func (x *SimpleMountSourceOperations) afterLoad() {}
+func (x *SimpleMountSourceOperations) load(m state.Map) {
+ m.Load("keep", &x.keep)
+ m.Load("revalidate", &x.revalidate)
+ m.Load("cacheReaddir", &x.cacheReaddir)
+}
+
+func (x *overlayMountSourceOperations) beforeSave() {}
+func (x *overlayMountSourceOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("upper", &x.upper)
+ m.Save("lower", &x.lower)
+}
+
+func (x *overlayMountSourceOperations) afterLoad() {}
+func (x *overlayMountSourceOperations) load(m state.Map) {
+ m.Load("upper", &x.upper)
+ m.Load("lower", &x.lower)
+}
+
+func (x *overlayFilesystem) beforeSave() {}
+func (x *overlayFilesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *overlayFilesystem) afterLoad() {}
+func (x *overlayFilesystem) load(m state.Map) {
+}
+
+func (x *Mount) beforeSave() {}
+func (x *Mount) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ID", &x.ID)
+ m.Save("ParentID", &x.ParentID)
+ m.Save("root", &x.root)
+ m.Save("previous", &x.previous)
+}
+
+func (x *Mount) afterLoad() {}
+func (x *Mount) load(m state.Map) {
+ m.Load("ID", &x.ID)
+ m.Load("ParentID", &x.ParentID)
+ m.Load("root", &x.root)
+ m.Load("previous", &x.previous)
+}
+
+func (x *MountNamespace) beforeSave() {}
+func (x *MountNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("userns", &x.userns)
+ m.Save("root", &x.root)
+ m.Save("mounts", &x.mounts)
+ m.Save("mountID", &x.mountID)
+}
+
+func (x *MountNamespace) afterLoad() {}
+func (x *MountNamespace) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("userns", &x.userns)
+ m.Load("root", &x.root)
+ m.Load("mounts", &x.mounts)
+ m.Load("mountID", &x.mountID)
+}
+
+func (x *overlayEntry) beforeSave() {}
+func (x *overlayEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("lowerExists", &x.lowerExists)
+ m.Save("lower", &x.lower)
+ m.Save("mappings", &x.mappings)
+ m.Save("upper", &x.upper)
+ m.Save("dirCache", &x.dirCache)
+}
+
+func (x *overlayEntry) afterLoad() {}
+func (x *overlayEntry) load(m state.Map) {
+ m.Load("lowerExists", &x.lowerExists)
+ m.Load("lower", &x.lower)
+ m.Load("mappings", &x.mappings)
+ m.Load("upper", &x.upper)
+ m.Load("dirCache", &x.dirCache)
+}
+
+func init() {
+ state.Register("fs.StableAttr", (*StableAttr)(nil), state.Fns{Save: (*StableAttr).save, Load: (*StableAttr).load})
+ state.Register("fs.UnstableAttr", (*UnstableAttr)(nil), state.Fns{Save: (*UnstableAttr).save, Load: (*UnstableAttr).load})
+ state.Register("fs.AttrMask", (*AttrMask)(nil), state.Fns{Save: (*AttrMask).save, Load: (*AttrMask).load})
+ state.Register("fs.PermMask", (*PermMask)(nil), state.Fns{Save: (*PermMask).save, Load: (*PermMask).load})
+ state.Register("fs.FilePermissions", (*FilePermissions)(nil), state.Fns{Save: (*FilePermissions).save, Load: (*FilePermissions).load})
+ state.Register("fs.FileOwner", (*FileOwner)(nil), state.Fns{Save: (*FileOwner).save, Load: (*FileOwner).load})
+ state.Register("fs.DentAttr", (*DentAttr)(nil), state.Fns{Save: (*DentAttr).save, Load: (*DentAttr).load})
+ state.Register("fs.SortedDentryMap", (*SortedDentryMap)(nil), state.Fns{Save: (*SortedDentryMap).save, Load: (*SortedDentryMap).load})
+ state.Register("fs.Dirent", (*Dirent)(nil), state.Fns{Save: (*Dirent).save, Load: (*Dirent).load})
+ state.Register("fs.DirentCache", (*DirentCache)(nil), state.Fns{Save: (*DirentCache).save, Load: (*DirentCache).load})
+ state.Register("fs.DirentCacheLimiter", (*DirentCacheLimiter)(nil), state.Fns{Save: (*DirentCacheLimiter).save, Load: (*DirentCacheLimiter).load})
+ state.Register("fs.direntList", (*direntList)(nil), state.Fns{Save: (*direntList).save, Load: (*direntList).load})
+ state.Register("fs.direntEntry", (*direntEntry)(nil), state.Fns{Save: (*direntEntry).save, Load: (*direntEntry).load})
+ state.Register("fs.eventList", (*eventList)(nil), state.Fns{Save: (*eventList).save, Load: (*eventList).load})
+ state.Register("fs.eventEntry", (*eventEntry)(nil), state.Fns{Save: (*eventEntry).save, Load: (*eventEntry).load})
+ state.Register("fs.File", (*File)(nil), state.Fns{Save: (*File).save, Load: (*File).load})
+ state.Register("fs.overlayFileOperations", (*overlayFileOperations)(nil), state.Fns{Save: (*overlayFileOperations).save, Load: (*overlayFileOperations).load})
+ state.Register("fs.overlayMappingIdentity", (*overlayMappingIdentity)(nil), state.Fns{Save: (*overlayMappingIdentity).save, Load: (*overlayMappingIdentity).load})
+ state.Register("fs.MountSourceFlags", (*MountSourceFlags)(nil), state.Fns{Save: (*MountSourceFlags).save, Load: (*MountSourceFlags).load})
+ state.Register("fs.FileFlags", (*FileFlags)(nil), state.Fns{Save: (*FileFlags).save, Load: (*FileFlags).load})
+ state.Register("fs.Inode", (*Inode)(nil), state.Fns{Save: (*Inode).save, Load: (*Inode).load})
+ state.Register("fs.LockCtx", (*LockCtx)(nil), state.Fns{Save: (*LockCtx).save, Load: (*LockCtx).load})
+ state.Register("fs.Watches", (*Watches)(nil), state.Fns{Save: (*Watches).save, Load: (*Watches).load})
+ state.Register("fs.Inotify", (*Inotify)(nil), state.Fns{Save: (*Inotify).save, Load: (*Inotify).load})
+ state.Register("fs.Event", (*Event)(nil), state.Fns{Save: (*Event).save, Load: (*Event).load})
+ state.Register("fs.Watch", (*Watch)(nil), state.Fns{Save: (*Watch).save, Load: (*Watch).load})
+ state.Register("fs.MountSource", (*MountSource)(nil), state.Fns{Save: (*MountSource).save, Load: (*MountSource).load})
+ state.Register("fs.SimpleMountSourceOperations", (*SimpleMountSourceOperations)(nil), state.Fns{Save: (*SimpleMountSourceOperations).save, Load: (*SimpleMountSourceOperations).load})
+ state.Register("fs.overlayMountSourceOperations", (*overlayMountSourceOperations)(nil), state.Fns{Save: (*overlayMountSourceOperations).save, Load: (*overlayMountSourceOperations).load})
+ state.Register("fs.overlayFilesystem", (*overlayFilesystem)(nil), state.Fns{Save: (*overlayFilesystem).save, Load: (*overlayFilesystem).load})
+ state.Register("fs.Mount", (*Mount)(nil), state.Fns{Save: (*Mount).save, Load: (*Mount).load})
+ state.Register("fs.MountNamespace", (*MountNamespace)(nil), state.Fns{Save: (*MountNamespace).save, Load: (*MountNamespace).load})
+ state.Register("fs.overlayEntry", (*overlayEntry)(nil), state.Fns{Save: (*overlayEntry).save, Load: (*overlayEntry).load})
+}
diff --git a/pkg/sentry/fs/fsutil/BUILD b/pkg/sentry/fs/fsutil/BUILD
deleted file mode 100644
index b2e8d9c77..000000000
--- a/pkg/sentry/fs/fsutil/BUILD
+++ /dev/null
@@ -1,119 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_template_instance(
- name = "dirty_set_impl",
- out = "dirty_set_impl.go",
- imports = {
- "memmap": "gvisor.dev/gvisor/pkg/sentry/memmap",
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "fsutil",
- prefix = "Dirty",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "memmap.MappableRange",
- "Value": "DirtyInfo",
- "Functions": "dirtySetFunctions",
- },
-)
-
-go_template_instance(
- name = "frame_ref_set_impl",
- out = "frame_ref_set_impl.go",
- imports = {
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "fsutil",
- prefix = "frameRef",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "platform.FileRange",
- "Value": "uint64",
- "Functions": "frameRefSetFunctions",
- },
-)
-
-go_template_instance(
- name = "file_range_set_impl",
- out = "file_range_set_impl.go",
- imports = {
- "memmap": "gvisor.dev/gvisor/pkg/sentry/memmap",
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "fsutil",
- prefix = "FileRange",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "memmap.MappableRange",
- "Value": "uint64",
- "Functions": "fileRangeSetFunctions",
- },
-)
-
-go_library(
- name = "fsutil",
- srcs = [
- "dirty_set.go",
- "dirty_set_impl.go",
- "file.go",
- "file_range_set.go",
- "file_range_set_impl.go",
- "frame_ref_set.go",
- "frame_ref_set_impl.go",
- "fsutil.go",
- "host_file_mapper.go",
- "host_file_mapper_state.go",
- "host_file_mapper_unsafe.go",
- "host_mappable.go",
- "inode.go",
- "inode_cached.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/fsutil",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/state",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "fsutil_test",
- size = "small",
- srcs = [
- "dirty_set_test.go",
- "inode_cached_test.go",
- ],
- embed = [":fsutil"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/fs/fsutil/README.md b/pkg/sentry/fs/fsutil/README.md
deleted file mode 100644
index 8be367334..000000000
--- a/pkg/sentry/fs/fsutil/README.md
+++ /dev/null
@@ -1,207 +0,0 @@
-This package provides utilities for implementing virtual filesystem objects.
-
-[TOC]
-
-## Page cache
-
-`CachingInodeOperations` implements a page cache for files that cannot use the
-host page cache. Normally these are files that store their data in a remote
-filesystem. This also applies to files that are accessed on a platform that does
-not support directly memory mapping host file descriptors (e.g. the ptrace
-platform).
-
-An `CachingInodeOperations` buffers regions of a single file into memory. It is
-owned by an `fs.Inode`, the in-memory representation of a file (all open file
-descriptors are backed by an `fs.Inode`). The `fs.Inode` provides operations for
-reading memory into an `CachingInodeOperations`, to represent the contents of
-the file in-memory, and for writing memory out, to relieve memory pressure on
-the kernel and to synchronize in-memory changes to filesystems.
-
-An `CachingInodeOperations` enables readable and/or writable memory access to
-file content. Files can be mapped shared or private, see mmap(2). When a file is
-mapped shared, changes to the file via write(2) and truncate(2) are reflected in
-the shared memory region. Conversely, when the shared memory region is modified,
-changes to the file are visible via read(2). Multiple shared mappings of the
-same file are coherent with each other. This is consistent with Linux.
-
-When a file is mapped private, updates to the mapped memory are not visible to
-other memory mappings. Updates to the mapped memory are also not reflected in
-the file content as seen by read(2). If the file is changed after a private
-mapping is created, for instance by write(2), the change to the file may or may
-not be reflected in the private mapping. This is consistent with Linux.
-
-An `CachingInodeOperations` keeps track of ranges of memory that were modified
-(or "dirtied"). When the file is explicitly synced via fsync(2), only the dirty
-ranges are written out to the filesystem. Any error returned indicates a failure
-to write all dirty memory of an `CachingInodeOperations` to the filesystem. In
-this case the filesystem may be in an inconsistent state. The same operation can
-be performed on the shared memory itself using msync(2). If neither fsync(2) nor
-msync(2) is performed, then the dirty memory is written out in accordance with
-the `CachingInodeOperations` eviction strategy (see below) and there is no
-guarantee that memory will be written out successfully in full.
-
-### Memory allocation and eviction
-
-An `CachingInodeOperations` implements the following allocation and eviction
-strategy:
-
-- Memory is allocated and brought up to date with the contents of a file when
- a region of mapped memory is accessed (or "faulted on").
-
-- Dirty memory is written out to filesystems when an fsync(2) or msync(2)
- operation is performed on a memory mapped file, for all memory mapped files
- when saved, and/or when there are no longer any memory mappings of a range
- of a file, see munmap(2). As the latter implies, in the absence of a panic
- or SIGKILL, dirty memory is written out for all memory mapped files when an
- application exits.
-
-- Memory is freed when there are no longer any memory mappings of a range of a
- file (e.g. when an application exits). This behavior is consistent with
- Linux for shared memory that has been locked via mlock(2).
-
-Notably, memory is not allocated for read(2) or write(2) operations. This means
-that reads and writes to the file are only accelerated by an
-`CachingInodeOperations` if the file being read or written has been memory
-mapped *and* if the shared memory has been accessed at the region being read or
-written. This diverges from Linux which buffers memory into a page cache on
-read(2) proactively (i.e. readahead) and delays writing it out to filesystems on
-write(2) (i.e. writeback). The absence of these optimizations is not visible to
-applications beyond less than optimal performance when repeatedly reading and/or
-writing to same region of a file. See [Future Work](#future-work) for plans to
-implement these optimizations.
-
-Additionally, memory held by `CachingInodeOperationss` is currently unbounded in
-size. An `CachingInodeOperations` does not write out dirty memory and free it
-under system memory pressure. This can cause pathological memory usage.
-
-When memory is written back, an `CachingInodeOperations` may write regions of
-shared memory that were never modified. This is due to the strategy of
-minimizing page faults (see below) and handling only a subset of memory write
-faults. In the absence of an application or sentry crash, it is guaranteed that
-if a region of shared memory was written to, it is written back to a filesystem.
-
-### Life of a shared memory mapping
-
-A file is memory mapped via mmap(2). For example, if `A` is an address, an
-application may execute:
-
-```
-mmap(A, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
-```
-
-This creates a shared mapping of fd that reflects 4k of the contents of fd
-starting at offset 0, accessible at address `A`. This in turn creates a virtual
-memory area region ("vma") which indicates that [`A`, `A`+0x1000) is now a valid
-address range for this application to access.
-
-At this point, memory has not been allocated in the file's
-`CachingInodeOperations`. It is also the case that the address range [`A`,
-`A`+0x1000) has not been mapped on the host on behalf of the application. If the
-application then tries to modify 8 bytes of the shared memory:
-
-```
-char buffer[] = "aaaaaaaa";
-memcpy(A, buffer, 8);
-```
-
-The host then sends a `SIGSEGV` to the sentry because the address range [`A`,
-`A`+8) is not mapped on the host. The `SIGSEGV` indicates that the memory was
-accessed writable. The sentry looks up the vma associated with [`A`, `A`+8),
-finds the file that was mapped and its `CachingInodeOperations`. It then calls
-`CachingInodeOperations.Translate` which allocates memory to back [`A`, `A`+8).
-It may choose to allocate more memory (i.e. do "readahead") to minimize
-subsequent faults.
-
-Memory that is allocated comes from a host tmpfs file (see
-`pgalloc.MemoryFile`). The host tmpfs file memory is brought up to date with the
-contents of the mapped file on its filesystem. The region of the host tmpfs file
-that reflects the mapped file is then mapped into the host address space of the
-application so that subsequent memory accesses do not repeatedly generate a
-`SIGSEGV`.
-
-The range that was allocated, including any extra memory allocation to minimize
-faults, is marked dirty due to the write fault. This overcounts dirty memory if
-the extra memory allocated is never modified.
-
-To make the scenario more interesting, imagine that this application spawns
-another process and maps the same file in the exact same way:
-
-```
-mmap(A, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
-```
-
-Imagine that this process then tries to modify the file again but with only 4
-bytes:
-
-```
-char buffer[] = "bbbb";
-memcpy(A, buffer, 4);
-```
-
-Since the first process has already mapped and accessed the same region of the
-file writable, `CachingInodeOperations.Translate` is called but returns the
-memory that has already been allocated rather than allocating new memory. The
-address range [`A`, `A`+0x1000) reflects the same cached view of the file as the
-first process sees. For example, reading 8 bytes from the file from either
-process via read(2) starting at offset 0 returns a consistent "bbbbaaaa".
-
-When this process no longer needs the shared memory, it may do:
-
-```
-munmap(A, 0x1000);
-```
-
-At this point, the modified memory cached by the `CachingInodeOperations` is not
-written back to the file because it is still in use by the first process that
-mapped it. When the first process also does:
-
-```
-munmap(A, 0x1000);
-```
-
-Then the last memory mapping of the file at the range [0, 0x1000) is gone. The
-file's `CachingInodeOperations` then starts writing back memory marked dirty to
-the file on its filesystem. Once writing completes, regardless of whether it was
-successful, the `CachingInodeOperations` frees the memory cached at the range
-[0, 0x1000).
-
-Subsequent read(2) or write(2) operations on the file go directly to the
-filesystem since there no longer exists memory for it in its
-`CachingInodeOperations`.
-
-## Future Work
-
-### Page cache
-
-The sentry does not yet implement the readahead and writeback optimizations for
-read(2) and write(2) respectively. To do so, on read(2) and/or write(2) the
-sentry must ensure that memory is allocated in a page cache to read or write
-into. However, the sentry cannot boundlessly allocate memory. If it did, the
-host would eventually OOM-kill the sentry+application process. This means that
-the sentry must implement a page cache memory allocation strategy that is
-bounded by a global user or container imposed limit. When this limit is
-approached, the sentry must decide from which page cache memory should be freed
-so that it can allocate more memory. If it makes a poor decision, the sentry may
-end up freeing and re-allocating memory to back regions of files that are
-frequently used, nullifying the optimization (and in some cases causing worse
-performance due to the overhead of memory allocation and general management).
-This is a form of "cache thrashing".
-
-In Linux, much research has been done to select and implement a lightweight but
-optimal page cache eviction algorithm. Linux makes use of hardware page bits to
-keep track of whether memory has been accessed. The sentry does not have direct
-access to hardware. Implementing a similarly lightweight and optimal page cache
-eviction algorithm will need to either introduce a kernel interface to obtain
-these page bits or find a suitable alternative proxy for access events.
-
-In Linux, readahead happens by default but is not always ideal. For instance,
-for files that are not read sequentially, it would be more ideal to simply read
-from only those regions of the file rather than to optimistically cache some
-number of bytes ahead of the read (up to 2MB in Linux) if the bytes cached won't
-be accessed. Linux implements the fadvise64(2) system call for applications to
-specify that a range of a file will not be accessed sequentially. The advice bit
-FADV_RANDOM turns off the readahead optimization for the given range in the
-given file. However fadvise64 is rarely used by applications so Linux implements
-a readahead backoff strategy if reads are not sequential. To ensure that
-application performance is not degraded, the sentry must implement a similar
-backoff strategy.
diff --git a/pkg/sentry/fs/fsutil/dirty_set_impl.go b/pkg/sentry/fs/fsutil/dirty_set_impl.go
new file mode 100755
index 000000000..2510b81b3
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/dirty_set_impl.go
@@ -0,0 +1,1274 @@
+package fsutil
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/memmap"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ DirtyminDegree = 3
+
+ DirtymaxDegree = 2 * DirtyminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type DirtySet struct {
+ root Dirtynode `state:".(*DirtySegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *DirtySet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *DirtySet) IsEmptyRange(r __generics_imported0.MappableRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *DirtySet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *DirtySet) SpanRange(r __generics_imported0.MappableRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *DirtySet) FirstSegment() DirtyIterator {
+ if s.root.nrSegments == 0 {
+ return DirtyIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *DirtySet) LastSegment() DirtyIterator {
+ if s.root.nrSegments == 0 {
+ return DirtyIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *DirtySet) FirstGap() DirtyGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return DirtyGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *DirtySet) LastGap() DirtyGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return DirtyGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *DirtySet) Find(key uint64) (DirtyIterator, DirtyGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return DirtyIterator{n, i}, DirtyGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return DirtyIterator{}, DirtyGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *DirtySet) FindSegment(key uint64) DirtyIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *DirtySet) LowerBoundSegment(min uint64) DirtyIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *DirtySet) UpperBoundSegment(max uint64) DirtyIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *DirtySet) FindGap(key uint64) DirtyGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *DirtySet) LowerBoundGap(min uint64) DirtyGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *DirtySet) UpperBoundGap(max uint64) DirtyGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *DirtySet) Add(r __generics_imported0.MappableRange, val DirtyInfo) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *DirtySet) AddWithoutMerging(r __generics_imported0.MappableRange, val DirtyInfo) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *DirtySet) Insert(gap DirtyGapIterator, r __generics_imported0.MappableRange, val DirtyInfo) DirtyIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (dirtySetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (dirtySetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (dirtySetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *DirtySet) InsertWithoutMerging(gap DirtyGapIterator, r __generics_imported0.MappableRange, val DirtyInfo) DirtyIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *DirtySet) InsertWithoutMergingUnchecked(gap DirtyGapIterator, r __generics_imported0.MappableRange, val DirtyInfo) DirtyIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return DirtyIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *DirtySet) Remove(seg DirtyIterator) DirtyGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ dirtySetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(DirtyGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *DirtySet) RemoveAll() {
+ s.root = Dirtynode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *DirtySet) RemoveRange(r __generics_imported0.MappableRange) DirtyGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *DirtySet) Merge(first, second DirtyIterator) DirtyIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *DirtySet) MergeUnchecked(first, second DirtyIterator) DirtyIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (dirtySetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return DirtyIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *DirtySet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *DirtySet) MergeRange(r __generics_imported0.MappableRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *DirtySet) MergeAdjacent(r __generics_imported0.MappableRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *DirtySet) Split(seg DirtyIterator, split uint64) (DirtyIterator, DirtyIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *DirtySet) SplitUnchecked(seg DirtyIterator, split uint64) (DirtyIterator, DirtyIterator) {
+ val1, val2 := (dirtySetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.MappableRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *DirtySet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *DirtySet) Isolate(seg DirtyIterator, r __generics_imported0.MappableRange) DirtyIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *DirtySet) ApplyContiguous(r __generics_imported0.MappableRange, fn func(seg DirtyIterator)) DirtyGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return DirtyGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return DirtyGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type Dirtynode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *Dirtynode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [DirtymaxDegree - 1]__generics_imported0.MappableRange
+ values [DirtymaxDegree - 1]DirtyInfo
+ children [DirtymaxDegree]*Dirtynode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Dirtynode) firstSegment() DirtyIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return DirtyIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Dirtynode) lastSegment() DirtyIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return DirtyIterator{n, n.nrSegments - 1}
+}
+
+func (n *Dirtynode) prevSibling() *Dirtynode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *Dirtynode) nextSibling() *Dirtynode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *Dirtynode) rebalanceBeforeInsert(gap DirtyGapIterator) DirtyGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < DirtymaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &Dirtynode{
+ nrSegments: DirtyminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &Dirtynode{
+ nrSegments: DirtyminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:DirtyminDegree-1], n.keys[:DirtyminDegree-1])
+ copy(left.values[:DirtyminDegree-1], n.values[:DirtyminDegree-1])
+ copy(right.keys[:DirtyminDegree-1], n.keys[DirtyminDegree:])
+ copy(right.values[:DirtyminDegree-1], n.values[DirtyminDegree:])
+ n.keys[0], n.values[0] = n.keys[DirtyminDegree-1], n.values[DirtyminDegree-1]
+ DirtyzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:DirtyminDegree], n.children[:DirtyminDegree])
+ copy(right.children[:DirtyminDegree], n.children[DirtyminDegree:])
+ DirtyzeroNodeSlice(n.children[2:])
+ for i := 0; i < DirtyminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < DirtyminDegree {
+ return DirtyGapIterator{left, gap.index}
+ }
+ return DirtyGapIterator{right, gap.index - DirtyminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[DirtyminDegree-1], n.values[DirtyminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &Dirtynode{
+ nrSegments: DirtyminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:DirtyminDegree-1], n.keys[DirtyminDegree:])
+ copy(sibling.values[:DirtyminDegree-1], n.values[DirtyminDegree:])
+ DirtyzeroValueSlice(n.values[DirtyminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:DirtyminDegree], n.children[DirtyminDegree:])
+ DirtyzeroNodeSlice(n.children[DirtyminDegree:])
+ for i := 0; i < DirtyminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = DirtyminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < DirtyminDegree {
+ return gap
+ }
+ return DirtyGapIterator{sibling, gap.index - DirtyminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *Dirtynode) rebalanceAfterRemove(gap DirtyGapIterator) DirtyGapIterator {
+ for {
+ if n.nrSegments >= DirtyminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= DirtyminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ dirtySetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return DirtyGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return DirtyGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= DirtyminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ dirtySetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return DirtyGapIterator{n, n.nrSegments}
+ }
+ return DirtyGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return DirtyGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return DirtyGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *Dirtynode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = DirtyGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ dirtySetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type DirtyIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *Dirtynode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg DirtyIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg DirtyIterator) Range() __generics_imported0.MappableRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg DirtyIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg DirtyIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg DirtyIterator) SetRangeUnchecked(r __generics_imported0.MappableRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg DirtyIterator) SetRange(r __generics_imported0.MappableRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg DirtyIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg DirtyIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg DirtyIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg DirtyIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg DirtyIterator) Value() DirtyInfo {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg DirtyIterator) ValuePtr() *DirtyInfo {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg DirtyIterator) SetValue(val DirtyInfo) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg DirtyIterator) PrevSegment() DirtyIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return DirtyIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return DirtyIterator{}
+ }
+ return DirtysegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg DirtyIterator) NextSegment() DirtyIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return DirtyIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return DirtyIterator{}
+ }
+ return DirtysegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg DirtyIterator) PrevGap() DirtyGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return DirtyGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg DirtyIterator) NextGap() DirtyGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return DirtyGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg DirtyIterator) PrevNonEmpty() (DirtyIterator, DirtyGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return DirtyIterator{}, gap
+ }
+ return gap.PrevSegment(), DirtyGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg DirtyIterator) NextNonEmpty() (DirtyIterator, DirtyGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return DirtyIterator{}, gap
+ }
+ return gap.NextSegment(), DirtyGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type DirtyGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *Dirtynode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap DirtyGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap DirtyGapIterator) Range() __generics_imported0.MappableRange {
+ return __generics_imported0.MappableRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap DirtyGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return dirtySetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap DirtyGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return dirtySetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap DirtyGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap DirtyGapIterator) PrevSegment() DirtyIterator {
+ return DirtysegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap DirtyGapIterator) NextSegment() DirtyIterator {
+ return DirtysegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap DirtyGapIterator) PrevGap() DirtyGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return DirtyGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap DirtyGapIterator) NextGap() DirtyGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return DirtyGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func DirtysegmentBeforePosition(n *Dirtynode, i int) DirtyIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return DirtyIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return DirtyIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func DirtysegmentAfterPosition(n *Dirtynode, i int) DirtyIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return DirtyIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return DirtyIterator{n, i}
+}
+
+func DirtyzeroValueSlice(slice []DirtyInfo) {
+
+ for i := range slice {
+ dirtySetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func DirtyzeroNodeSlice(slice []*Dirtynode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *DirtySet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *Dirtynode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *Dirtynode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type DirtySegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []DirtyInfo
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *DirtySet) ExportSortedSlices() *DirtySegmentDataSlices {
+ var sds DirtySegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *DirtySet) ImportSortedSlices(sds *DirtySegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.MappableRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *DirtySet) saveRoot() *DirtySegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *DirtySet) loadRoot(sds *DirtySegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/fsutil/dirty_set_test.go b/pkg/sentry/fs/fsutil/dirty_set_test.go
deleted file mode 100644
index 75575d994..000000000
--- a/pkg/sentry/fs/fsutil/dirty_set_test.go
+++ /dev/null
@@ -1,38 +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 fsutil
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestDirtySet(t *testing.T) {
- var set DirtySet
- set.MarkDirty(memmap.MappableRange{0, 2 * usermem.PageSize})
- set.KeepDirty(memmap.MappableRange{usermem.PageSize, 2 * usermem.PageSize})
- set.MarkClean(memmap.MappableRange{0, 2 * usermem.PageSize})
- want := &DirtySegmentDataSlices{
- Start: []uint64{usermem.PageSize},
- End: []uint64{2 * usermem.PageSize},
- Values: []DirtyInfo{{Keep: true}},
- }
- if got := set.ExportSortedSlices(); !reflect.DeepEqual(got, want) {
- t.Errorf("set:\n\tgot %v,\n\twant %v", got, want)
- }
-}
diff --git a/pkg/sentry/fs/fsutil/file_range_set_impl.go b/pkg/sentry/fs/fsutil/file_range_set_impl.go
new file mode 100755
index 000000000..0548bba08
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/file_range_set_impl.go
@@ -0,0 +1,1274 @@
+package fsutil
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/memmap"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ FileRangeminDegree = 3
+
+ FileRangemaxDegree = 2 * FileRangeminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type FileRangeSet struct {
+ root FileRangenode `state:".(*FileRangeSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *FileRangeSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *FileRangeSet) IsEmptyRange(r __generics_imported0.MappableRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *FileRangeSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *FileRangeSet) SpanRange(r __generics_imported0.MappableRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *FileRangeSet) FirstSegment() FileRangeIterator {
+ if s.root.nrSegments == 0 {
+ return FileRangeIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *FileRangeSet) LastSegment() FileRangeIterator {
+ if s.root.nrSegments == 0 {
+ return FileRangeIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *FileRangeSet) FirstGap() FileRangeGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return FileRangeGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *FileRangeSet) LastGap() FileRangeGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return FileRangeGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *FileRangeSet) Find(key uint64) (FileRangeIterator, FileRangeGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return FileRangeIterator{n, i}, FileRangeGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return FileRangeIterator{}, FileRangeGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *FileRangeSet) FindSegment(key uint64) FileRangeIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *FileRangeSet) LowerBoundSegment(min uint64) FileRangeIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *FileRangeSet) UpperBoundSegment(max uint64) FileRangeIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *FileRangeSet) FindGap(key uint64) FileRangeGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *FileRangeSet) LowerBoundGap(min uint64) FileRangeGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *FileRangeSet) UpperBoundGap(max uint64) FileRangeGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *FileRangeSet) Add(r __generics_imported0.MappableRange, val uint64) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *FileRangeSet) AddWithoutMerging(r __generics_imported0.MappableRange, val uint64) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *FileRangeSet) Insert(gap FileRangeGapIterator, r __generics_imported0.MappableRange, val uint64) FileRangeIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (fileRangeSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (fileRangeSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (fileRangeSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *FileRangeSet) InsertWithoutMerging(gap FileRangeGapIterator, r __generics_imported0.MappableRange, val uint64) FileRangeIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *FileRangeSet) InsertWithoutMergingUnchecked(gap FileRangeGapIterator, r __generics_imported0.MappableRange, val uint64) FileRangeIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return FileRangeIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *FileRangeSet) Remove(seg FileRangeIterator) FileRangeGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ fileRangeSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(FileRangeGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *FileRangeSet) RemoveAll() {
+ s.root = FileRangenode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *FileRangeSet) RemoveRange(r __generics_imported0.MappableRange) FileRangeGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *FileRangeSet) Merge(first, second FileRangeIterator) FileRangeIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *FileRangeSet) MergeUnchecked(first, second FileRangeIterator) FileRangeIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (fileRangeSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return FileRangeIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *FileRangeSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *FileRangeSet) MergeRange(r __generics_imported0.MappableRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *FileRangeSet) MergeAdjacent(r __generics_imported0.MappableRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *FileRangeSet) Split(seg FileRangeIterator, split uint64) (FileRangeIterator, FileRangeIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *FileRangeSet) SplitUnchecked(seg FileRangeIterator, split uint64) (FileRangeIterator, FileRangeIterator) {
+ val1, val2 := (fileRangeSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.MappableRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *FileRangeSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *FileRangeSet) Isolate(seg FileRangeIterator, r __generics_imported0.MappableRange) FileRangeIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *FileRangeSet) ApplyContiguous(r __generics_imported0.MappableRange, fn func(seg FileRangeIterator)) FileRangeGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return FileRangeGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return FileRangeGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type FileRangenode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *FileRangenode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [FileRangemaxDegree - 1]__generics_imported0.MappableRange
+ values [FileRangemaxDegree - 1]uint64
+ children [FileRangemaxDegree]*FileRangenode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *FileRangenode) firstSegment() FileRangeIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return FileRangeIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *FileRangenode) lastSegment() FileRangeIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return FileRangeIterator{n, n.nrSegments - 1}
+}
+
+func (n *FileRangenode) prevSibling() *FileRangenode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *FileRangenode) nextSibling() *FileRangenode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *FileRangenode) rebalanceBeforeInsert(gap FileRangeGapIterator) FileRangeGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < FileRangemaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &FileRangenode{
+ nrSegments: FileRangeminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &FileRangenode{
+ nrSegments: FileRangeminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:FileRangeminDegree-1], n.keys[:FileRangeminDegree-1])
+ copy(left.values[:FileRangeminDegree-1], n.values[:FileRangeminDegree-1])
+ copy(right.keys[:FileRangeminDegree-1], n.keys[FileRangeminDegree:])
+ copy(right.values[:FileRangeminDegree-1], n.values[FileRangeminDegree:])
+ n.keys[0], n.values[0] = n.keys[FileRangeminDegree-1], n.values[FileRangeminDegree-1]
+ FileRangezeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:FileRangeminDegree], n.children[:FileRangeminDegree])
+ copy(right.children[:FileRangeminDegree], n.children[FileRangeminDegree:])
+ FileRangezeroNodeSlice(n.children[2:])
+ for i := 0; i < FileRangeminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < FileRangeminDegree {
+ return FileRangeGapIterator{left, gap.index}
+ }
+ return FileRangeGapIterator{right, gap.index - FileRangeminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[FileRangeminDegree-1], n.values[FileRangeminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &FileRangenode{
+ nrSegments: FileRangeminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:FileRangeminDegree-1], n.keys[FileRangeminDegree:])
+ copy(sibling.values[:FileRangeminDegree-1], n.values[FileRangeminDegree:])
+ FileRangezeroValueSlice(n.values[FileRangeminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:FileRangeminDegree], n.children[FileRangeminDegree:])
+ FileRangezeroNodeSlice(n.children[FileRangeminDegree:])
+ for i := 0; i < FileRangeminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = FileRangeminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < FileRangeminDegree {
+ return gap
+ }
+ return FileRangeGapIterator{sibling, gap.index - FileRangeminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *FileRangenode) rebalanceAfterRemove(gap FileRangeGapIterator) FileRangeGapIterator {
+ for {
+ if n.nrSegments >= FileRangeminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= FileRangeminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ fileRangeSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return FileRangeGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return FileRangeGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= FileRangeminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ fileRangeSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return FileRangeGapIterator{n, n.nrSegments}
+ }
+ return FileRangeGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return FileRangeGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return FileRangeGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *FileRangenode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = FileRangeGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ fileRangeSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type FileRangeIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *FileRangenode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg FileRangeIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg FileRangeIterator) Range() __generics_imported0.MappableRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg FileRangeIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg FileRangeIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg FileRangeIterator) SetRangeUnchecked(r __generics_imported0.MappableRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg FileRangeIterator) SetRange(r __generics_imported0.MappableRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg FileRangeIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg FileRangeIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg FileRangeIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg FileRangeIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg FileRangeIterator) Value() uint64 {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg FileRangeIterator) ValuePtr() *uint64 {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg FileRangeIterator) SetValue(val uint64) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg FileRangeIterator) PrevSegment() FileRangeIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return FileRangeIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return FileRangeIterator{}
+ }
+ return FileRangesegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg FileRangeIterator) NextSegment() FileRangeIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return FileRangeIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return FileRangeIterator{}
+ }
+ return FileRangesegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg FileRangeIterator) PrevGap() FileRangeGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return FileRangeGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg FileRangeIterator) NextGap() FileRangeGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return FileRangeGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg FileRangeIterator) PrevNonEmpty() (FileRangeIterator, FileRangeGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return FileRangeIterator{}, gap
+ }
+ return gap.PrevSegment(), FileRangeGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg FileRangeIterator) NextNonEmpty() (FileRangeIterator, FileRangeGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return FileRangeIterator{}, gap
+ }
+ return gap.NextSegment(), FileRangeGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type FileRangeGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *FileRangenode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap FileRangeGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap FileRangeGapIterator) Range() __generics_imported0.MappableRange {
+ return __generics_imported0.MappableRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap FileRangeGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return fileRangeSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap FileRangeGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return fileRangeSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap FileRangeGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap FileRangeGapIterator) PrevSegment() FileRangeIterator {
+ return FileRangesegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap FileRangeGapIterator) NextSegment() FileRangeIterator {
+ return FileRangesegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap FileRangeGapIterator) PrevGap() FileRangeGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return FileRangeGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap FileRangeGapIterator) NextGap() FileRangeGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return FileRangeGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func FileRangesegmentBeforePosition(n *FileRangenode, i int) FileRangeIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return FileRangeIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return FileRangeIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func FileRangesegmentAfterPosition(n *FileRangenode, i int) FileRangeIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return FileRangeIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return FileRangeIterator{n, i}
+}
+
+func FileRangezeroValueSlice(slice []uint64) {
+
+ for i := range slice {
+ fileRangeSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func FileRangezeroNodeSlice(slice []*FileRangenode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *FileRangeSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *FileRangenode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *FileRangenode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type FileRangeSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []uint64
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *FileRangeSet) ExportSortedSlices() *FileRangeSegmentDataSlices {
+ var sds FileRangeSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *FileRangeSet) ImportSortedSlices(sds *FileRangeSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.MappableRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *FileRangeSet) saveRoot() *FileRangeSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *FileRangeSet) loadRoot(sds *FileRangeSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/fsutil/frame_ref_set_impl.go b/pkg/sentry/fs/fsutil/frame_ref_set_impl.go
new file mode 100755
index 000000000..d4601bffa
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/frame_ref_set_impl.go
@@ -0,0 +1,1274 @@
+package fsutil
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/platform"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ frameRefminDegree = 3
+
+ frameRefmaxDegree = 2 * frameRefminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type frameRefSet struct {
+ root frameRefnode `state:".(*frameRefSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *frameRefSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *frameRefSet) IsEmptyRange(r __generics_imported0.FileRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *frameRefSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *frameRefSet) SpanRange(r __generics_imported0.FileRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *frameRefSet) FirstSegment() frameRefIterator {
+ if s.root.nrSegments == 0 {
+ return frameRefIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *frameRefSet) LastSegment() frameRefIterator {
+ if s.root.nrSegments == 0 {
+ return frameRefIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *frameRefSet) FirstGap() frameRefGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return frameRefGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *frameRefSet) LastGap() frameRefGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return frameRefGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *frameRefSet) Find(key uint64) (frameRefIterator, frameRefGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return frameRefIterator{n, i}, frameRefGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return frameRefIterator{}, frameRefGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *frameRefSet) FindSegment(key uint64) frameRefIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *frameRefSet) LowerBoundSegment(min uint64) frameRefIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *frameRefSet) UpperBoundSegment(max uint64) frameRefIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *frameRefSet) FindGap(key uint64) frameRefGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *frameRefSet) LowerBoundGap(min uint64) frameRefGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *frameRefSet) UpperBoundGap(max uint64) frameRefGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *frameRefSet) Add(r __generics_imported0.FileRange, val uint64) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *frameRefSet) AddWithoutMerging(r __generics_imported0.FileRange, val uint64) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *frameRefSet) Insert(gap frameRefGapIterator, r __generics_imported0.FileRange, val uint64) frameRefIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (frameRefSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (frameRefSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (frameRefSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *frameRefSet) InsertWithoutMerging(gap frameRefGapIterator, r __generics_imported0.FileRange, val uint64) frameRefIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *frameRefSet) InsertWithoutMergingUnchecked(gap frameRefGapIterator, r __generics_imported0.FileRange, val uint64) frameRefIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return frameRefIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *frameRefSet) Remove(seg frameRefIterator) frameRefGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ frameRefSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(frameRefGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *frameRefSet) RemoveAll() {
+ s.root = frameRefnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *frameRefSet) RemoveRange(r __generics_imported0.FileRange) frameRefGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *frameRefSet) Merge(first, second frameRefIterator) frameRefIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *frameRefSet) MergeUnchecked(first, second frameRefIterator) frameRefIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (frameRefSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return frameRefIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *frameRefSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *frameRefSet) MergeRange(r __generics_imported0.FileRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *frameRefSet) MergeAdjacent(r __generics_imported0.FileRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *frameRefSet) Split(seg frameRefIterator, split uint64) (frameRefIterator, frameRefIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *frameRefSet) SplitUnchecked(seg frameRefIterator, split uint64) (frameRefIterator, frameRefIterator) {
+ val1, val2 := (frameRefSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.FileRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *frameRefSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *frameRefSet) Isolate(seg frameRefIterator, r __generics_imported0.FileRange) frameRefIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *frameRefSet) ApplyContiguous(r __generics_imported0.FileRange, fn func(seg frameRefIterator)) frameRefGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return frameRefGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return frameRefGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type frameRefnode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *frameRefnode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [frameRefmaxDegree - 1]__generics_imported0.FileRange
+ values [frameRefmaxDegree - 1]uint64
+ children [frameRefmaxDegree]*frameRefnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *frameRefnode) firstSegment() frameRefIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return frameRefIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *frameRefnode) lastSegment() frameRefIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return frameRefIterator{n, n.nrSegments - 1}
+}
+
+func (n *frameRefnode) prevSibling() *frameRefnode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *frameRefnode) nextSibling() *frameRefnode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *frameRefnode) rebalanceBeforeInsert(gap frameRefGapIterator) frameRefGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < frameRefmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &frameRefnode{
+ nrSegments: frameRefminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &frameRefnode{
+ nrSegments: frameRefminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:frameRefminDegree-1], n.keys[:frameRefminDegree-1])
+ copy(left.values[:frameRefminDegree-1], n.values[:frameRefminDegree-1])
+ copy(right.keys[:frameRefminDegree-1], n.keys[frameRefminDegree:])
+ copy(right.values[:frameRefminDegree-1], n.values[frameRefminDegree:])
+ n.keys[0], n.values[0] = n.keys[frameRefminDegree-1], n.values[frameRefminDegree-1]
+ frameRefzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:frameRefminDegree], n.children[:frameRefminDegree])
+ copy(right.children[:frameRefminDegree], n.children[frameRefminDegree:])
+ frameRefzeroNodeSlice(n.children[2:])
+ for i := 0; i < frameRefminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < frameRefminDegree {
+ return frameRefGapIterator{left, gap.index}
+ }
+ return frameRefGapIterator{right, gap.index - frameRefminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[frameRefminDegree-1], n.values[frameRefminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &frameRefnode{
+ nrSegments: frameRefminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:frameRefminDegree-1], n.keys[frameRefminDegree:])
+ copy(sibling.values[:frameRefminDegree-1], n.values[frameRefminDegree:])
+ frameRefzeroValueSlice(n.values[frameRefminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:frameRefminDegree], n.children[frameRefminDegree:])
+ frameRefzeroNodeSlice(n.children[frameRefminDegree:])
+ for i := 0; i < frameRefminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = frameRefminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < frameRefminDegree {
+ return gap
+ }
+ return frameRefGapIterator{sibling, gap.index - frameRefminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *frameRefnode) rebalanceAfterRemove(gap frameRefGapIterator) frameRefGapIterator {
+ for {
+ if n.nrSegments >= frameRefminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= frameRefminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ frameRefSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return frameRefGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return frameRefGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= frameRefminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ frameRefSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return frameRefGapIterator{n, n.nrSegments}
+ }
+ return frameRefGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return frameRefGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return frameRefGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *frameRefnode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = frameRefGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ frameRefSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type frameRefIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *frameRefnode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg frameRefIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg frameRefIterator) Range() __generics_imported0.FileRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg frameRefIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg frameRefIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg frameRefIterator) SetRangeUnchecked(r __generics_imported0.FileRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg frameRefIterator) SetRange(r __generics_imported0.FileRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg frameRefIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg frameRefIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg frameRefIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg frameRefIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg frameRefIterator) Value() uint64 {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg frameRefIterator) ValuePtr() *uint64 {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg frameRefIterator) SetValue(val uint64) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg frameRefIterator) PrevSegment() frameRefIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return frameRefIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return frameRefIterator{}
+ }
+ return frameRefsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg frameRefIterator) NextSegment() frameRefIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return frameRefIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return frameRefIterator{}
+ }
+ return frameRefsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg frameRefIterator) PrevGap() frameRefGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return frameRefGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg frameRefIterator) NextGap() frameRefGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return frameRefGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg frameRefIterator) PrevNonEmpty() (frameRefIterator, frameRefGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return frameRefIterator{}, gap
+ }
+ return gap.PrevSegment(), frameRefGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg frameRefIterator) NextNonEmpty() (frameRefIterator, frameRefGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return frameRefIterator{}, gap
+ }
+ return gap.NextSegment(), frameRefGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type frameRefGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *frameRefnode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap frameRefGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap frameRefGapIterator) Range() __generics_imported0.FileRange {
+ return __generics_imported0.FileRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap frameRefGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return frameRefSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap frameRefGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return frameRefSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap frameRefGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap frameRefGapIterator) PrevSegment() frameRefIterator {
+ return frameRefsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap frameRefGapIterator) NextSegment() frameRefIterator {
+ return frameRefsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap frameRefGapIterator) PrevGap() frameRefGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return frameRefGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap frameRefGapIterator) NextGap() frameRefGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return frameRefGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func frameRefsegmentBeforePosition(n *frameRefnode, i int) frameRefIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return frameRefIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return frameRefIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func frameRefsegmentAfterPosition(n *frameRefnode, i int) frameRefIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return frameRefIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return frameRefIterator{n, i}
+}
+
+func frameRefzeroValueSlice(slice []uint64) {
+
+ for i := range slice {
+ frameRefSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func frameRefzeroNodeSlice(slice []*frameRefnode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *frameRefSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *frameRefnode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *frameRefnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type frameRefSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []uint64
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *frameRefSet) ExportSortedSlices() *frameRefSegmentDataSlices {
+ var sds frameRefSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *frameRefSet) ImportSortedSlices(sds *frameRefSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.FileRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *frameRefSet) saveRoot() *frameRefSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *frameRefSet) loadRoot(sds *frameRefSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/fsutil/fsutil_state_autogen.go b/pkg/sentry/fs/fsutil/fsutil_state_autogen.go
new file mode 100755
index 000000000..10671eb59
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/fsutil_state_autogen.go
@@ -0,0 +1,363 @@
+// automatically generated by stateify.
+
+package fsutil
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *DirtyInfo) beforeSave() {}
+func (x *DirtyInfo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Keep", &x.Keep)
+}
+
+func (x *DirtyInfo) afterLoad() {}
+func (x *DirtyInfo) load(m state.Map) {
+ m.Load("Keep", &x.Keep)
+}
+
+func (x *DirtySet) beforeSave() {}
+func (x *DirtySet) save(m state.Map) {
+ x.beforeSave()
+ var root *DirtySegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *DirtySet) afterLoad() {}
+func (x *DirtySet) load(m state.Map) {
+ m.LoadValue("root", new(*DirtySegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*DirtySegmentDataSlices)) })
+}
+
+func (x *Dirtynode) beforeSave() {}
+func (x *Dirtynode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *Dirtynode) afterLoad() {}
+func (x *Dirtynode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *DirtySegmentDataSlices) beforeSave() {}
+func (x *DirtySegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *DirtySegmentDataSlices) afterLoad() {}
+func (x *DirtySegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *StaticDirFileOperations) beforeSave() {}
+func (x *StaticDirFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("dentryMap", &x.dentryMap)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *StaticDirFileOperations) afterLoad() {}
+func (x *StaticDirFileOperations) load(m state.Map) {
+ m.Load("dentryMap", &x.dentryMap)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *NoReadWriteFile) beforeSave() {}
+func (x *NoReadWriteFile) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *NoReadWriteFile) afterLoad() {}
+func (x *NoReadWriteFile) load(m state.Map) {
+}
+
+func (x *FileStaticContentReader) beforeSave() {}
+func (x *FileStaticContentReader) save(m state.Map) {
+ x.beforeSave()
+ m.Save("content", &x.content)
+}
+
+func (x *FileStaticContentReader) afterLoad() {}
+func (x *FileStaticContentReader) load(m state.Map) {
+ m.Load("content", &x.content)
+}
+
+func (x *FileRangeSet) beforeSave() {}
+func (x *FileRangeSet) save(m state.Map) {
+ x.beforeSave()
+ var root *FileRangeSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *FileRangeSet) afterLoad() {}
+func (x *FileRangeSet) load(m state.Map) {
+ m.LoadValue("root", new(*FileRangeSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*FileRangeSegmentDataSlices)) })
+}
+
+func (x *FileRangenode) beforeSave() {}
+func (x *FileRangenode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *FileRangenode) afterLoad() {}
+func (x *FileRangenode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *FileRangeSegmentDataSlices) beforeSave() {}
+func (x *FileRangeSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *FileRangeSegmentDataSlices) afterLoad() {}
+func (x *FileRangeSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *frameRefSet) beforeSave() {}
+func (x *frameRefSet) save(m state.Map) {
+ x.beforeSave()
+ var root *frameRefSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *frameRefSet) afterLoad() {}
+func (x *frameRefSet) load(m state.Map) {
+ m.LoadValue("root", new(*frameRefSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*frameRefSegmentDataSlices)) })
+}
+
+func (x *frameRefnode) beforeSave() {}
+func (x *frameRefnode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *frameRefnode) afterLoad() {}
+func (x *frameRefnode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *frameRefSegmentDataSlices) beforeSave() {}
+func (x *frameRefSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *frameRefSegmentDataSlices) afterLoad() {}
+func (x *frameRefSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *HostFileMapper) beforeSave() {}
+func (x *HostFileMapper) save(m state.Map) {
+ x.beforeSave()
+ m.Save("refs", &x.refs)
+}
+
+func (x *HostFileMapper) load(m state.Map) {
+ m.Load("refs", &x.refs)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *HostMappable) beforeSave() {}
+func (x *HostMappable) save(m state.Map) {
+ x.beforeSave()
+ m.Save("hostFileMapper", &x.hostFileMapper)
+ m.Save("backingFile", &x.backingFile)
+ m.Save("mappings", &x.mappings)
+}
+
+func (x *HostMappable) afterLoad() {}
+func (x *HostMappable) load(m state.Map) {
+ m.Load("hostFileMapper", &x.hostFileMapper)
+ m.Load("backingFile", &x.backingFile)
+ m.Load("mappings", &x.mappings)
+}
+
+func (x *SimpleFileInode) beforeSave() {}
+func (x *SimpleFileInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *SimpleFileInode) afterLoad() {}
+func (x *SimpleFileInode) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *NoReadWriteFileInode) beforeSave() {}
+func (x *NoReadWriteFileInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *NoReadWriteFileInode) afterLoad() {}
+func (x *NoReadWriteFileInode) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *InodeSimpleAttributes) beforeSave() {}
+func (x *InodeSimpleAttributes) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fsType", &x.fsType)
+ m.Save("unstable", &x.unstable)
+}
+
+func (x *InodeSimpleAttributes) afterLoad() {}
+func (x *InodeSimpleAttributes) load(m state.Map) {
+ m.Load("fsType", &x.fsType)
+ m.Load("unstable", &x.unstable)
+}
+
+func (x *InodeSimpleExtendedAttributes) beforeSave() {}
+func (x *InodeSimpleExtendedAttributes) save(m state.Map) {
+ x.beforeSave()
+ m.Save("xattrs", &x.xattrs)
+}
+
+func (x *InodeSimpleExtendedAttributes) afterLoad() {}
+func (x *InodeSimpleExtendedAttributes) load(m state.Map) {
+ m.Load("xattrs", &x.xattrs)
+}
+
+func (x *staticFile) beforeSave() {}
+func (x *staticFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("FileStaticContentReader", &x.FileStaticContentReader)
+}
+
+func (x *staticFile) afterLoad() {}
+func (x *staticFile) load(m state.Map) {
+ m.Load("FileStaticContentReader", &x.FileStaticContentReader)
+}
+
+func (x *InodeStaticFileGetter) beforeSave() {}
+func (x *InodeStaticFileGetter) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Contents", &x.Contents)
+}
+
+func (x *InodeStaticFileGetter) afterLoad() {}
+func (x *InodeStaticFileGetter) load(m state.Map) {
+ m.Load("Contents", &x.Contents)
+}
+
+func (x *CachingInodeOperations) beforeSave() {}
+func (x *CachingInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("backingFile", &x.backingFile)
+ m.Save("mfp", &x.mfp)
+ m.Save("opts", &x.opts)
+ m.Save("attr", &x.attr)
+ m.Save("dirtyAttr", &x.dirtyAttr)
+ m.Save("mappings", &x.mappings)
+ m.Save("cache", &x.cache)
+ m.Save("dirty", &x.dirty)
+ m.Save("hostFileMapper", &x.hostFileMapper)
+ m.Save("refs", &x.refs)
+}
+
+func (x *CachingInodeOperations) afterLoad() {}
+func (x *CachingInodeOperations) load(m state.Map) {
+ m.Load("backingFile", &x.backingFile)
+ m.Load("mfp", &x.mfp)
+ m.Load("opts", &x.opts)
+ m.Load("attr", &x.attr)
+ m.Load("dirtyAttr", &x.dirtyAttr)
+ m.Load("mappings", &x.mappings)
+ m.Load("cache", &x.cache)
+ m.Load("dirty", &x.dirty)
+ m.Load("hostFileMapper", &x.hostFileMapper)
+ m.Load("refs", &x.refs)
+}
+
+func (x *CachingInodeOperationsOptions) beforeSave() {}
+func (x *CachingInodeOperationsOptions) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ForcePageCache", &x.ForcePageCache)
+ m.Save("LimitHostFDTranslation", &x.LimitHostFDTranslation)
+}
+
+func (x *CachingInodeOperationsOptions) afterLoad() {}
+func (x *CachingInodeOperationsOptions) load(m state.Map) {
+ m.Load("ForcePageCache", &x.ForcePageCache)
+ m.Load("LimitHostFDTranslation", &x.LimitHostFDTranslation)
+}
+
+func init() {
+ state.Register("fsutil.DirtyInfo", (*DirtyInfo)(nil), state.Fns{Save: (*DirtyInfo).save, Load: (*DirtyInfo).load})
+ state.Register("fsutil.DirtySet", (*DirtySet)(nil), state.Fns{Save: (*DirtySet).save, Load: (*DirtySet).load})
+ state.Register("fsutil.Dirtynode", (*Dirtynode)(nil), state.Fns{Save: (*Dirtynode).save, Load: (*Dirtynode).load})
+ state.Register("fsutil.DirtySegmentDataSlices", (*DirtySegmentDataSlices)(nil), state.Fns{Save: (*DirtySegmentDataSlices).save, Load: (*DirtySegmentDataSlices).load})
+ state.Register("fsutil.StaticDirFileOperations", (*StaticDirFileOperations)(nil), state.Fns{Save: (*StaticDirFileOperations).save, Load: (*StaticDirFileOperations).load})
+ state.Register("fsutil.NoReadWriteFile", (*NoReadWriteFile)(nil), state.Fns{Save: (*NoReadWriteFile).save, Load: (*NoReadWriteFile).load})
+ state.Register("fsutil.FileStaticContentReader", (*FileStaticContentReader)(nil), state.Fns{Save: (*FileStaticContentReader).save, Load: (*FileStaticContentReader).load})
+ state.Register("fsutil.FileRangeSet", (*FileRangeSet)(nil), state.Fns{Save: (*FileRangeSet).save, Load: (*FileRangeSet).load})
+ state.Register("fsutil.FileRangenode", (*FileRangenode)(nil), state.Fns{Save: (*FileRangenode).save, Load: (*FileRangenode).load})
+ state.Register("fsutil.FileRangeSegmentDataSlices", (*FileRangeSegmentDataSlices)(nil), state.Fns{Save: (*FileRangeSegmentDataSlices).save, Load: (*FileRangeSegmentDataSlices).load})
+ state.Register("fsutil.frameRefSet", (*frameRefSet)(nil), state.Fns{Save: (*frameRefSet).save, Load: (*frameRefSet).load})
+ state.Register("fsutil.frameRefnode", (*frameRefnode)(nil), state.Fns{Save: (*frameRefnode).save, Load: (*frameRefnode).load})
+ state.Register("fsutil.frameRefSegmentDataSlices", (*frameRefSegmentDataSlices)(nil), state.Fns{Save: (*frameRefSegmentDataSlices).save, Load: (*frameRefSegmentDataSlices).load})
+ state.Register("fsutil.HostFileMapper", (*HostFileMapper)(nil), state.Fns{Save: (*HostFileMapper).save, Load: (*HostFileMapper).load})
+ state.Register("fsutil.HostMappable", (*HostMappable)(nil), state.Fns{Save: (*HostMappable).save, Load: (*HostMappable).load})
+ state.Register("fsutil.SimpleFileInode", (*SimpleFileInode)(nil), state.Fns{Save: (*SimpleFileInode).save, Load: (*SimpleFileInode).load})
+ state.Register("fsutil.NoReadWriteFileInode", (*NoReadWriteFileInode)(nil), state.Fns{Save: (*NoReadWriteFileInode).save, Load: (*NoReadWriteFileInode).load})
+ state.Register("fsutil.InodeSimpleAttributes", (*InodeSimpleAttributes)(nil), state.Fns{Save: (*InodeSimpleAttributes).save, Load: (*InodeSimpleAttributes).load})
+ state.Register("fsutil.InodeSimpleExtendedAttributes", (*InodeSimpleExtendedAttributes)(nil), state.Fns{Save: (*InodeSimpleExtendedAttributes).save, Load: (*InodeSimpleExtendedAttributes).load})
+ state.Register("fsutil.staticFile", (*staticFile)(nil), state.Fns{Save: (*staticFile).save, Load: (*staticFile).load})
+ state.Register("fsutil.InodeStaticFileGetter", (*InodeStaticFileGetter)(nil), state.Fns{Save: (*InodeStaticFileGetter).save, Load: (*InodeStaticFileGetter).load})
+ state.Register("fsutil.CachingInodeOperations", (*CachingInodeOperations)(nil), state.Fns{Save: (*CachingInodeOperations).save, Load: (*CachingInodeOperations).load})
+ state.Register("fsutil.CachingInodeOperationsOptions", (*CachingInodeOperationsOptions)(nil), state.Fns{Save: (*CachingInodeOperationsOptions).save, Load: (*CachingInodeOperationsOptions).load})
+}
diff --git a/pkg/sentry/fs/fsutil/inode_cached_test.go b/pkg/sentry/fs/fsutil/inode_cached_test.go
deleted file mode 100644
index 129f314c8..000000000
--- a/pkg/sentry/fs/fsutil/inode_cached_test.go
+++ /dev/null
@@ -1,389 +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 fsutil
-
-import (
- "bytes"
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/safemem"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type noopBackingFile struct{}
-
-func (noopBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
- return dsts.NumBytes(), nil
-}
-
-func (noopBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
- return srcs.NumBytes(), nil
-}
-
-func (noopBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr, bool) error {
- return nil
-}
-
-func (noopBackingFile) Sync(context.Context) error {
- return nil
-}
-
-func (noopBackingFile) FD() int {
- return -1
-}
-
-func (noopBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
- return nil
-}
-
-func TestSetPermissions(t *testing.T) {
- ctx := contexttest.Context(t)
-
- uattr := fs.WithCurrentTime(ctx, fs.UnstableAttr{
- Perms: fs.FilePermsFromMode(0444),
- })
- iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- perms := fs.FilePermsFromMode(0777)
- if !iops.SetPermissions(ctx, nil, perms) {
- t.Fatalf("SetPermissions failed, want success")
- }
-
- // Did permissions change?
- if iops.attr.Perms != perms {
- t.Fatalf("got perms +%v, want +%v", iops.attr.Perms, perms)
- }
-
- // Did status change time change?
- if !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("got status change time not dirty, want dirty")
- }
- if iops.attr.StatusChangeTime.Equal(uattr.StatusChangeTime) {
- t.Fatalf("got status change time unchanged")
- }
-}
-
-func TestSetTimestamps(t *testing.T) {
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- desc string
- ts fs.TimeSpec
- wantChanged fs.AttrMask
- }{
- {
- desc: "noop",
- ts: fs.TimeSpec{
- ATimeOmit: true,
- MTimeOmit: true,
- },
- wantChanged: fs.AttrMask{},
- },
- {
- desc: "access time only",
- ts: fs.TimeSpec{
- ATime: ktime.NowFromContext(ctx),
- MTimeOmit: true,
- },
- wantChanged: fs.AttrMask{
- AccessTime: true,
- },
- },
- {
- desc: "modification time only",
- ts: fs.TimeSpec{
- ATimeOmit: true,
- MTime: ktime.NowFromContext(ctx),
- },
- wantChanged: fs.AttrMask{
- ModificationTime: true,
- },
- },
- {
- desc: "access and modification time",
- ts: fs.TimeSpec{
- ATime: ktime.NowFromContext(ctx),
- MTime: ktime.NowFromContext(ctx),
- },
- wantChanged: fs.AttrMask{
- AccessTime: true,
- ModificationTime: true,
- },
- },
- {
- desc: "system time access and modification time",
- ts: fs.TimeSpec{
- ATimeSetSystemTime: true,
- MTimeSetSystemTime: true,
- },
- wantChanged: fs.AttrMask{
- AccessTime: true,
- ModificationTime: true,
- },
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- ctx := contexttest.Context(t)
-
- epoch := ktime.ZeroTime
- uattr := fs.UnstableAttr{
- AccessTime: epoch,
- ModificationTime: epoch,
- StatusChangeTime: epoch,
- }
- iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- if err := iops.SetTimestamps(ctx, nil, test.ts); err != nil {
- t.Fatalf("SetTimestamps got error %v, want nil", err)
- }
- if test.wantChanged.AccessTime {
- if !iops.attr.AccessTime.After(uattr.AccessTime) {
- t.Fatalf("diritied access time did not advance, want %v > %v", iops.attr.AccessTime, uattr.AccessTime)
- }
- if !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("dirty access time requires dirty status change time")
- }
- if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
- t.Fatalf("dirtied status change time did not advance")
- }
- }
- if test.wantChanged.ModificationTime {
- if !iops.attr.ModificationTime.After(uattr.ModificationTime) {
- t.Fatalf("diritied modification time did not advance")
- }
- if !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("dirty modification time requires dirty status change time")
- }
- if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
- t.Fatalf("dirtied status change time did not advance")
- }
- }
- })
- }
-}
-
-func TestTruncate(t *testing.T) {
- ctx := contexttest.Context(t)
-
- uattr := fs.UnstableAttr{
- Size: 0,
- }
- iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- if err := iops.Truncate(ctx, nil, uattr.Size); err != nil {
- t.Fatalf("Truncate got error %v, want nil", err)
- }
- var size int64 = 4096
- if err := iops.Truncate(ctx, nil, size); err != nil {
- t.Fatalf("Truncate got error %v, want nil", err)
- }
- if iops.attr.Size != size {
- t.Fatalf("Truncate got %d, want %d", iops.attr.Size, size)
- }
- if !iops.dirtyAttr.ModificationTime || !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("Truncate did not dirty modification and status change time")
- }
- if !iops.attr.ModificationTime.After(uattr.ModificationTime) {
- t.Fatalf("dirtied modification time did not change")
- }
- if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
- t.Fatalf("dirtied status change time did not change")
- }
-}
-
-type sliceBackingFile struct {
- data []byte
-}
-
-func newSliceBackingFile(data []byte) *sliceBackingFile {
- return &sliceBackingFile{data}
-}
-
-func (f *sliceBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
- r := safemem.BlockSeqReader{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)}
- return r.ReadToBlocks(dsts)
-}
-
-func (f *sliceBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
- w := safemem.BlockSeqWriter{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)}
- return w.WriteFromBlocks(srcs)
-}
-
-func (*sliceBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr, bool) error {
- return nil
-}
-
-func (*sliceBackingFile) Sync(context.Context) error {
- return nil
-}
-
-func (*sliceBackingFile) FD() int {
- return -1
-}
-
-func (f *sliceBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
- return syserror.EOPNOTSUPP
-}
-
-type noopMappingSpace struct{}
-
-// Invalidate implements memmap.MappingSpace.Invalidate.
-func (noopMappingSpace) Invalidate(ar usermem.AddrRange, opts memmap.InvalidateOpts) {
-}
-
-func anonInode(ctx context.Context) *fs.Inode {
- return fs.NewInode(ctx, &SimpleFileInode{
- InodeSimpleAttributes: NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), fs.FilePermissions{
- User: fs.PermMask{Read: true, Write: true},
- }, 0),
- }, fs.NewPseudoMountSource(ctx), fs.StableAttr{
- Type: fs.Anonymous,
- BlockSize: usermem.PageSize,
- })
-}
-
-func pagesOf(bs ...byte) []byte {
- buf := make([]byte, 0, len(bs)*usermem.PageSize)
- for _, b := range bs {
- buf = append(buf, bytes.Repeat([]byte{b}, usermem.PageSize)...)
- }
- return buf
-}
-
-func TestRead(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Construct a 3-page file.
- buf := pagesOf('a', 'b', 'c')
- file := fs.NewFile(ctx, fs.NewDirent(ctx, anonInode(ctx), "anon"), fs.FileFlags{}, nil)
- uattr := fs.UnstableAttr{
- Size: int64(len(buf)),
- }
- iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- // Expect the cache to be initially empty.
- if cached := iops.cache.Span(); cached != 0 {
- t.Errorf("Span got %d, want 0", cached)
- }
-
- // Create a memory mapping of the second page (as CachingInodeOperations
- // expects to only cache mapped pages), then call Translate to force it to
- // be cached.
- var ms noopMappingSpace
- ar := usermem.AddrRange{usermem.PageSize, 2 * usermem.PageSize}
- if err := iops.AddMapping(ctx, ms, ar, usermem.PageSize, true); err != nil {
- t.Fatalf("AddMapping got %v, want nil", err)
- }
- mr := memmap.MappableRange{usermem.PageSize, 2 * usermem.PageSize}
- if _, err := iops.Translate(ctx, mr, mr, usermem.Read); err != nil {
- t.Fatalf("Translate got %v, want nil", err)
- }
- if cached := iops.cache.Span(); cached != usermem.PageSize {
- t.Errorf("SpanRange got %d, want %d", cached, usermem.PageSize)
- }
-
- // Try to read 4 pages. The first and third pages should be read directly
- // from the "file", the second page should be read from the cache, and only
- // 3 pages (the size of the file) should be readable.
- rbuf := make([]byte, 4*usermem.PageSize)
- dst := usermem.BytesIOSequence(rbuf)
- n, err := iops.Read(ctx, file, dst, 0)
- if n != 3*usermem.PageSize || (err != nil && err != io.EOF) {
- t.Fatalf("Read got (%d, %v), want (%d, nil or EOF)", n, err, 3*usermem.PageSize)
- }
- rbuf = rbuf[:3*usermem.PageSize]
-
- // Did we get the bytes we expect?
- if !bytes.Equal(rbuf, buf) {
- t.Errorf("Read back bytes %v, want %v", rbuf, buf)
- }
-
- // Delete the memory mapping before iops.Release(). The cached page will
- // either be evicted by ctx's pgalloc.MemoryFile, or dropped by
- // iops.Release().
- iops.RemoveMapping(ctx, ms, ar, usermem.PageSize, true)
-}
-
-func TestWrite(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Construct a 4-page file.
- buf := pagesOf('a', 'b', 'c', 'd')
- orig := append([]byte(nil), buf...)
- inode := anonInode(ctx)
- uattr := fs.UnstableAttr{
- Size: int64(len(buf)),
- }
- iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- // Expect the cache to be initially empty.
- if cached := iops.cache.Span(); cached != 0 {
- t.Errorf("Span got %d, want 0", cached)
- }
-
- // Create a memory mapping of the second and third pages (as
- // CachingInodeOperations expects to only cache mapped pages), then call
- // Translate to force them to be cached.
- var ms noopMappingSpace
- ar := usermem.AddrRange{usermem.PageSize, 3 * usermem.PageSize}
- if err := iops.AddMapping(ctx, ms, ar, usermem.PageSize, true); err != nil {
- t.Fatalf("AddMapping got %v, want nil", err)
- }
- defer iops.RemoveMapping(ctx, ms, ar, usermem.PageSize, true)
- mr := memmap.MappableRange{usermem.PageSize, 3 * usermem.PageSize}
- if _, err := iops.Translate(ctx, mr, mr, usermem.Read); err != nil {
- t.Fatalf("Translate got %v, want nil", err)
- }
- if cached := iops.cache.Span(); cached != 2*usermem.PageSize {
- t.Errorf("SpanRange got %d, want %d", cached, 2*usermem.PageSize)
- }
-
- // Write to the first 2 pages.
- wbuf := pagesOf('e', 'f')
- src := usermem.BytesIOSequence(wbuf)
- n, err := iops.Write(ctx, src, 0)
- if n != 2*usermem.PageSize || err != nil {
- t.Fatalf("Write got (%d, %v), want (%d, nil)", n, err, 2*usermem.PageSize)
- }
-
- // The first page should have been written directly, since it was not cached.
- want := append([]byte(nil), orig...)
- copy(want, pagesOf('e'))
- if !bytes.Equal(buf, want) {
- t.Errorf("File contents are %v, want %v", buf, want)
- }
-
- // Sync back to the "backing file".
- if err := iops.WriteOut(ctx, inode); err != nil {
- t.Errorf("Sync got %v, want nil", err)
- }
-
- // Now the second page should have been written as well.
- copy(want[usermem.PageSize:], pagesOf('f'))
- if !bytes.Equal(buf, want) {
- t.Errorf("File contents are %v, want %v", buf, want)
- }
-}
diff --git a/pkg/sentry/fs/g3doc/inotify.md b/pkg/sentry/fs/g3doc/inotify.md
deleted file mode 100644
index 71a577d9d..000000000
--- a/pkg/sentry/fs/g3doc/inotify.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Inotify
-
-Inotify implements the like-named filesystem event notification system for the
-sentry, see `inotify(7)`.
-
-## Architecture
-
-For the most part, the sentry implementation of inotify mirrors the Linux
-architecture. Inotify instances (i.e. the fd returned by inotify_init(2)) are
-backed by a pseudo-filesystem. Events are generated from various places in the
-sentry, including the [syscall layer][syscall_dir], the [vfs layer][dirent] and
-the [process fd table][fd_table]. Watches are stored in inodes and generated
-events are queued to the inotify instance owning the watches for delivery to the
-user.
-
-## Objects
-
-Here is a brief description of the existing and new objects involved in the
-sentry inotify mechanism, and how they interact:
-
-### [`fs.Inotify`][inotify]
-
-- An inotify instances, created by inotify_init(2)/inotify_init1(2).
-- The inotify fd has a `fs.Dirent`, supports filesystem syscalls to read
- events.
-- Has multiple `fs.Watch`es, with at most one watch per target inode, per
- inotify instance.
-- Has an instance `id` which is globally unique. This is *not* the fd number
- for this instance, since the fd can be duped. This `id` is not externally
- visible.
-
-### [`fs.Watch`][watch]
-
-- An inotify watch, created/deleted by
- inotify_add_watch(2)/inotify_rm_watch(2).
-- Owned by an `fs.Inotify` instance, each watch keeps a pointer to the
- `owner`.
-- Associated with a single `fs.Inode`, which is the watch `target`. While the
- watch is active, it indirectly pins `target` to memory. See the "Reference
- Model" section for a detailed explanation.
-- Filesystem operations on `target` generate `fs.Event`s.
-
-### [`fs.Event`][event]
-
-- A simple struct encapsulating all the fields for an inotify event.
-- Generated by `fs.Watch`es and forwarded to the watches' `owner`s.
-- Serialized to the user during read(2) syscalls on the associated
- `fs.Inotify`'s fd.
-
-### [`fs.Dirent`][dirent]
-
-- Many inotify events are generated inside dirent methods. Events are
- generated in the dirent methods rather than `fs.Inode` methods because some
- events carry the name of the subject node, and node names are generally
- unavailable in an `fs.Inode`.
-- Dirents do not directly contain state for any watches. Instead, they forward
- notifications to the underlying `fs.Inode`.
-
-### [`fs.Inode`][inode]
-
-- Interacts with inotify through `fs.Watch`es.
-- Inodes contain a map of all active `fs.Watch`es on them.
-- An `fs.Inotify` instance can have at most one `fs.Watch` per inode.
- `fs.Watch`es on an inode are indexed by their `owner`'s `id`.
-- All inotify logic is encapsulated in the [`Watches`][inode_watches] struct
- in an inode. Logically, `Watches` is the set of inotify watches on the
- inode.
-
-## Reference Model
-
-The sentry inotify implementation has a complex reference model. An inotify
-watch observes a single inode. For efficient lookup, the state for a watch is
-stored directly on the target inode. This state needs to be persistent for the
-lifetime of watch. Unlike usual filesystem metadata, the watch state has no
-"on-disk" representation, so they cannot be reconstructed by the filesystem if
-the inode is flushed from memory. This effectively means we need to keep any
-inodes with actives watches pinned to memory.
-
-We can't just hold an extra ref on the inode to pin it to memory because some
-filesystems (such as gofer-based filesystems) don't have persistent inodes. In
-such a filesystem, if we just pin the inode, nothing prevents the enclosing
-dirent from being GCed. Once the dirent is GCed, the pinned inode is
-unreachable -- these filesystems generate a new inode by re-reading the node
-state on the next walk. Incidentally, hardlinks also don't work on these
-filesystems for this reason.
-
-To prevent the above scenario, when a new watch is added on an inode, we *pin*
-the dirent we used to reach the inode. Note that due to hardlinks, this dirent
-may not be the only dirent pointing to the inode. Attempting to set an inotify
-watch via multiple hardlinks to the same file results in the same watch being
-returned for both links. However, for each new dirent we use to reach the same
-inode, we add a new pin. We need a new pin for each new dirent used to reach the
-inode because we have no guarantees about the deletion order of the different
-links to the inode.
-
-## Lock Ordering
-
-There are 4 locks related to the inotify implementation:
-
-- `Inotify.mu`: the inotify instance lock.
-- `Inotify.evMu`: the inotify event queue lock.
-- `Watch.mu`: the watch lock, used to protect pins.
-- `fs.Watches.mu`: the inode watch set mu, used to protect the collection of
- watches on the inode.
-
-The correct lock ordering for inotify code is:
-
-`Inotify.mu` -> `fs.Watches.mu` -> `Watch.mu` -> `Inotify.evMu`.
-
-We need a distinct lock for the event queue because by the time a goroutine
-attempts to queue a new event, it is already holding `fs.Watches.mu`. If we used
-`Inotify.mu` to also protect the event queue, this would violate the above lock
-ordering.
-
-[dirent]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/dirent.go
-[event]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inotify_event.go
-[fd_table]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/kernel/fd_table.go
-[inode]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inode.go
-[inode_watches]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inode_inotify.go
-[inotify]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inotify.go
-[syscall_dir]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/syscalls/linux/
-[watch]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inotify_watch.go
diff --git a/pkg/sentry/fs/gofer/BUILD b/pkg/sentry/fs/gofer/BUILD
deleted file mode 100644
index 4a005c605..000000000
--- a/pkg/sentry/fs/gofer/BUILD
+++ /dev/null
@@ -1,66 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "gofer",
- srcs = [
- "attr.go",
- "cache_policy.go",
- "context_file.go",
- "device.go",
- "file.go",
- "file_state.go",
- "fs.go",
- "handles.go",
- "inode.go",
- "inode_state.go",
- "path.go",
- "session.go",
- "session_state.go",
- "socket.go",
- "util.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/gofer",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fd",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/p9",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fdpipe",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/host",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/unet",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "gofer_test",
- size = "small",
- srcs = ["gofer_test.go"],
- embed = [":gofer"],
- deps = [
- "//pkg/p9",
- "//pkg/p9/p9test",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- ],
-)
diff --git a/pkg/sentry/fs/gofer/gofer_state_autogen.go b/pkg/sentry/fs/gofer/gofer_state_autogen.go
new file mode 100755
index 000000000..5ad9b0101
--- /dev/null
+++ b/pkg/sentry/fs/gofer/gofer_state_autogen.go
@@ -0,0 +1,117 @@
+// automatically generated by stateify.
+
+package gofer
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *fileOperations) beforeSave() {}
+func (x *fileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("inodeOperations", &x.inodeOperations)
+ m.Save("dirCursor", &x.dirCursor)
+ m.Save("flags", &x.flags)
+}
+
+func (x *fileOperations) load(m state.Map) {
+ m.LoadWait("inodeOperations", &x.inodeOperations)
+ m.Load("dirCursor", &x.dirCursor)
+ m.LoadWait("flags", &x.flags)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *inodeOperations) beforeSave() {}
+func (x *inodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fileState", &x.fileState)
+ m.Save("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeOperations) afterLoad() {}
+func (x *inodeOperations) load(m state.Map) {
+ m.LoadWait("fileState", &x.fileState)
+ m.Load("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeFileState) save(m state.Map) {
+ x.beforeSave()
+ var loading struct{} = x.saveLoading()
+ m.SaveValue("loading", loading)
+ m.Save("s", &x.s)
+ m.Save("sattr", &x.sattr)
+ m.Save("savedUAttr", &x.savedUAttr)
+ m.Save("hostMappable", &x.hostMappable)
+}
+
+func (x *inodeFileState) load(m state.Map) {
+ m.LoadWait("s", &x.s)
+ m.LoadWait("sattr", &x.sattr)
+ m.Load("savedUAttr", &x.savedUAttr)
+ m.Load("hostMappable", &x.hostMappable)
+ m.LoadValue("loading", new(struct{}), func(y interface{}) { x.loadLoading(y.(struct{})) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *endpointMaps) beforeSave() {}
+func (x *endpointMaps) save(m state.Map) {
+ x.beforeSave()
+ m.Save("direntMap", &x.direntMap)
+ m.Save("pathMap", &x.pathMap)
+}
+
+func (x *endpointMaps) afterLoad() {}
+func (x *endpointMaps) load(m state.Map) {
+ m.Load("direntMap", &x.direntMap)
+ m.Load("pathMap", &x.pathMap)
+}
+
+func (x *session) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("msize", &x.msize)
+ m.Save("version", &x.version)
+ m.Save("cachePolicy", &x.cachePolicy)
+ m.Save("aname", &x.aname)
+ m.Save("superBlockFlags", &x.superBlockFlags)
+ m.Save("limitHostFDTranslation", &x.limitHostFDTranslation)
+ m.Save("overlayfsStaleRead", &x.overlayfsStaleRead)
+ m.Save("connID", &x.connID)
+ m.Save("inodeMappings", &x.inodeMappings)
+ m.Save("mounter", &x.mounter)
+ m.Save("endpoints", &x.endpoints)
+}
+
+func (x *session) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.LoadWait("msize", &x.msize)
+ m.LoadWait("version", &x.version)
+ m.LoadWait("cachePolicy", &x.cachePolicy)
+ m.LoadWait("aname", &x.aname)
+ m.LoadWait("superBlockFlags", &x.superBlockFlags)
+ m.Load("limitHostFDTranslation", &x.limitHostFDTranslation)
+ m.Load("overlayfsStaleRead", &x.overlayfsStaleRead)
+ m.LoadWait("connID", &x.connID)
+ m.LoadWait("inodeMappings", &x.inodeMappings)
+ m.LoadWait("mounter", &x.mounter)
+ m.LoadWait("endpoints", &x.endpoints)
+ m.AfterLoad(x.afterLoad)
+}
+
+func init() {
+ state.Register("gofer.fileOperations", (*fileOperations)(nil), state.Fns{Save: (*fileOperations).save, Load: (*fileOperations).load})
+ state.Register("gofer.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("gofer.inodeOperations", (*inodeOperations)(nil), state.Fns{Save: (*inodeOperations).save, Load: (*inodeOperations).load})
+ state.Register("gofer.inodeFileState", (*inodeFileState)(nil), state.Fns{Save: (*inodeFileState).save, Load: (*inodeFileState).load})
+ state.Register("gofer.endpointMaps", (*endpointMaps)(nil), state.Fns{Save: (*endpointMaps).save, Load: (*endpointMaps).load})
+ state.Register("gofer.session", (*session)(nil), state.Fns{Save: (*session).save, Load: (*session).load})
+}
diff --git a/pkg/sentry/fs/gofer/gofer_test.go b/pkg/sentry/fs/gofer/gofer_test.go
deleted file mode 100644
index 7fc3c32ae..000000000
--- a/pkg/sentry/fs/gofer/gofer_test.go
+++ /dev/null
@@ -1,310 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gofer
-
-import (
- "fmt"
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/p9"
- "gvisor.dev/gvisor/pkg/p9/p9test"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// rootTest runs a test with a p9 mock and an fs.InodeOperations created from
-// the attached root directory. The root file will be closed and client
-// disconnected, but additional files must be closed manually.
-func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context, *p9test.Harness, *p9test.Mock, *fs.Inode)) {
- t.Run(name, func(t *testing.T) {
- h, c := p9test.NewHarness(t)
- defer h.Finish()
-
- // Create a new root. Note that we pass an empty, but non-nil
- // map here. This allows tests to extend the root children
- // dynamically.
- root := h.NewDirectory(map[string]p9test.Generator{})(nil)
-
- // Return this as the root.
- h.Attacher.EXPECT().Attach().Return(root, nil).Times(1)
-
- // ... and open via the client.
- rootFile, err := c.Attach("/")
- if err != nil {
- t.Fatalf("unable to attach: %v", err)
- }
- defer rootFile.Close()
-
- // Wrap an a session.
- s := &session{
- mounter: fs.RootOwner,
- cachePolicy: cp,
- client: c,
- }
-
- // ... and an INode, with only the mode being explicitly valid for now.
- ctx := contexttest.Context(t)
- sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{
- file: rootFile,
- }, root.QID, p9.AttrMaskAll(), root.Attr, false /* socket */)
- m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{})
- rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr)
-
- // Ensure that the cache is fully invalidated, so that any
- // close actions actually take place before the full harness is
- // torn down.
- defer func() {
- m.FlushDirentRefs()
-
- // Wait for all resources to be released, otherwise the
- // operations may fail after we close the rootFile.
- fs.AsyncBarrier()
- }()
-
- // Execute the test.
- fn(ctx, h, root, rootInode)
- })
-}
-
-func TestLookup(t *testing.T) {
- type lookupTest struct {
- // Name of the test.
- name string
-
- // Expected return value.
- want error
- }
-
- tests := []lookupTest{
- {
- name: "mock Walk passes (function succeeds)",
- want: nil,
- },
- {
- name: "mock Walk fails (function fails)",
- want: syscall.ENOENT,
- },
- }
-
- const file = "file" // The walked target file.
-
- for _, test := range tests {
- rootTest(t, test.name, cacheNone, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
- // Setup the appropriate result.
- rootFile.WalkCallback = func() error {
- return test.want
- }
- if test.want == nil {
- // Set the contents of the root. We expect a
- // normal file generator for ppp above. This is
- // overriden by setting WalkErr in the mock.
- rootFile.AddChild(file, h.NewFile())
- }
-
- // Call function.
- dirent, err := rootInode.Lookup(ctx, file)
-
- // Unwrap the InodeOperations.
- var newInodeOperations fs.InodeOperations
- if dirent != nil {
- if dirent.IsNegative() {
- err = syscall.ENOENT
- } else {
- newInodeOperations = dirent.Inode.InodeOperations
- }
- }
-
- // Check return values.
- if err != test.want {
- t.Errorf("Lookup got err %v, want %v", err, test.want)
- }
- if err == nil && newInodeOperations == nil {
- t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil")
- }
- })
- }
-}
-
-func TestRevalidation(t *testing.T) {
- type revalidationTest struct {
- cachePolicy cachePolicy
-
- // Whether dirent should be reloaded before any modifications.
- preModificationWantReload bool
-
- // Whether dirent should be reloaded after updating an unstable
- // attribute on the remote fs.
- postModificationWantReload bool
-
- // Whether dirent unstable attributes should be updated after
- // updating an attribute on the remote fs.
- postModificationWantUpdatedAttrs bool
-
- // Whether dirent should be reloaded after the remote has
- // removed the file.
- postRemovalWantReload bool
- }
-
- tests := []revalidationTest{
- {
- // Policy cacheNone causes Revalidate to always return
- // true.
- cachePolicy: cacheNone,
- preModificationWantReload: true,
- postModificationWantReload: true,
- postModificationWantUpdatedAttrs: true,
- postRemovalWantReload: true,
- },
- {
- // Policy cacheAll causes Revalidate to always return
- // false.
- cachePolicy: cacheAll,
- preModificationWantReload: false,
- postModificationWantReload: false,
- postModificationWantUpdatedAttrs: false,
- postRemovalWantReload: false,
- },
- {
- // Policy cacheAllWritethrough causes Revalidate to
- // always return false.
- cachePolicy: cacheAllWritethrough,
- preModificationWantReload: false,
- postModificationWantReload: false,
- postModificationWantUpdatedAttrs: false,
- postRemovalWantReload: false,
- },
- {
- // Policy cacheRemoteRevalidating causes Revalidate to
- // return update cached unstable attrs, and returns
- // true only when the remote inode itself has been
- // removed or replaced.
- cachePolicy: cacheRemoteRevalidating,
- preModificationWantReload: false,
- postModificationWantReload: false,
- postModificationWantUpdatedAttrs: true,
- postRemovalWantReload: true,
- },
- }
-
- const file = "file" // The file walked below.
-
- for _, test := range tests {
- name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy)
- rootTest(t, name, test.cachePolicy, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
- // Wrap in a dirent object.
- rootDir := fs.NewDirent(ctx, rootInode, "root")
-
- // Create a mock file a child of the root. We save when
- // this is generated, so that when the time changed, we
- // can update the original entry.
- var origMocks []*p9test.Mock
- rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
- // Regular a regular file that has a consistent
- // path number. This might be used by
- // validation so we don't change it.
- m := h.NewMock(parent, 0, p9.Attr{
- Mode: p9.ModeRegular,
- })
- origMocks = append(origMocks, m)
- return m
- })
-
- // Do the walk.
- dirent, err := rootDir.Walk(ctx, rootDir, file)
- if err != nil {
- t.Fatalf("Lookup failed: %v", err)
- }
-
- // We must release the dirent, of the test will fail
- // with a reference leak. This is tracked by p9test.
- defer dirent.DecRef()
-
- // Walk again. Depending on the cache policy, we may
- // get a new dirent.
- newDirent, err := rootDir.Walk(ctx, rootDir, file)
- if err != nil {
- t.Fatalf("Lookup failed: %v", err)
- }
- if test.preModificationWantReload && dirent == newDirent {
- t.Errorf("Lookup with cachePolicy=%s got old dirent %+v, wanted a new dirent", test.cachePolicy, dirent)
- }
- if !test.preModificationWantReload && dirent != newDirent {
- t.Errorf("Lookup with cachePolicy=%s got new dirent %+v, wanted old dirent %+v", test.cachePolicy, newDirent, dirent)
- }
- newDirent.DecRef() // See above.
-
- // Modify the underlying mocked file's modification
- // time for the next walk that occurs.
- nowSeconds := time.Now().Unix()
- rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
- // Ensure that the path is the same as above,
- // but we change only the modification time of
- // the file.
- return h.NewMock(parent, 0, p9.Attr{
- Mode: p9.ModeRegular,
- MTimeSeconds: uint64(nowSeconds),
- })
- })
-
- // We also modify the original time, so that GetAttr
- // behaves as expected for the caching case.
- for _, m := range origMocks {
- m.Attr.MTimeSeconds = uint64(nowSeconds)
- }
-
- // Walk again. Depending on the cache policy, we may
- // get a new dirent.
- newDirent, err = rootDir.Walk(ctx, rootDir, file)
- if err != nil {
- t.Fatalf("Lookup failed: %v", err)
- }
- if test.postModificationWantReload && dirent == newDirent {
- t.Errorf("Lookup with cachePolicy=%s got old dirent, wanted a new dirent", test.cachePolicy)
- }
- if !test.postModificationWantReload && dirent != newDirent {
- t.Errorf("Lookup with cachePolicy=%s got new dirent, wanted old dirent", test.cachePolicy)
- }
- uattrs, err := newDirent.Inode.UnstableAttr(ctx)
- if err != nil {
- t.Fatalf("Error getting unstable attrs: %v", err)
- }
- gotModTimeSeconds := uattrs.ModificationTime.Seconds()
- if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds {
- t.Fatalf("Lookup with cachePolicy=%s got new modification time %v, wanted %v", test.cachePolicy, gotModTimeSeconds, nowSeconds)
- }
- newDirent.DecRef() // See above.
-
- // Remove the file from the remote fs, subsequent walks
- // should now fail to find anything.
- rootFile.RemoveChild(file)
-
- // Walk again. Depending on the cache policy, we may
- // get ENOENT.
- newDirent, err = rootDir.Walk(ctx, rootDir, file)
- if test.postRemovalWantReload && err == nil {
- t.Errorf("Lookup with cachePolicy=%s got nil error, wanted ENOENT", test.cachePolicy)
- }
- if !test.postRemovalWantReload && (err != nil || dirent != newDirent) {
- t.Errorf("Lookup with cachePolicy=%s got new dirent and error %v, wanted old dirent and nil error", test.cachePolicy, err)
- }
- if err == nil {
- newDirent.DecRef() // See above.
- }
- })
- }
-}
diff --git a/pkg/sentry/fs/host/BUILD b/pkg/sentry/fs/host/BUILD
deleted file mode 100644
index 23daeb528..000000000
--- a/pkg/sentry/fs/host/BUILD
+++ /dev/null
@@ -1,86 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "host",
- srcs = [
- "control.go",
- "descriptor.go",
- "descriptor_state.go",
- "device.go",
- "file.go",
- "fs.go",
- "inode.go",
- "inode_state.go",
- "ioctl_unsafe.go",
- "socket.go",
- "socket_iovec.go",
- "socket_state.go",
- "socket_unsafe.go",
- "tty.go",
- "util.go",
- "util_amd64_unsafe.go",
- "util_arm64_unsafe.go",
- "util_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/host",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/unet",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "host_test",
- size = "small",
- srcs = [
- "descriptor_test.go",
- "fs_test.go",
- "inode_test.go",
- "socket_test.go",
- "wait_test.go",
- ],
- embed = [":host"],
- deps = [
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/tcpip",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/host/descriptor_test.go b/pkg/sentry/fs/host/descriptor_test.go
deleted file mode 100644
index 4205981f5..000000000
--- a/pkg/sentry/fs/host/descriptor_test.go
+++ /dev/null
@@ -1,78 +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 host
-
-import (
- "io/ioutil"
- "path/filepath"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fdnotifier"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestDescriptorRelease(t *testing.T) {
- for _, tc := range []struct {
- name string
- saveable bool
- wouldBlock bool
- }{
- {name: "all false"},
- {name: "saveable", saveable: true},
- {name: "wouldBlock", wouldBlock: true},
- } {
- t.Run(tc.name, func(t *testing.T) {
- dir, err := ioutil.TempDir("", "descriptor_test")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
-
- fd, err := syscall.Open(filepath.Join(dir, "file"), syscall.O_RDWR|syscall.O_CREAT, 0666)
- if err != nil {
- t.Fatal("failed to open temp file:", err)
- }
-
- // FD ownership is transferred to the descritor.
- queue := &waiter.Queue{}
- d, err := newDescriptor(fd, false /* donated*/, tc.saveable, tc.wouldBlock, queue)
- if err != nil {
- syscall.Close(fd)
- t.Fatalf("newDescriptor(%d, %t, false, %t, queue) failed, err: %v", fd, tc.saveable, tc.wouldBlock, err)
- }
- if tc.saveable {
- if d.origFD < 0 {
- t.Errorf("saveable descriptor must preserve origFD, desc: %+v", d)
- }
- }
- if tc.wouldBlock {
- if !fdnotifier.HasFD(int32(d.value)) {
- t.Errorf("FD not registered with notifier, desc: %+v", d)
- }
- }
-
- oldVal := d.value
- d.Release()
- if d.value != -1 {
- t.Errorf("d.value want: -1, got: %d", d.value)
- }
- if tc.wouldBlock {
- if fdnotifier.HasFD(int32(oldVal)) {
- t.Errorf("FD not unregistered with notifier, desc: %+v", d)
- }
- }
- })
- }
-}
diff --git a/pkg/sentry/fs/host/fs_test.go b/pkg/sentry/fs/host/fs_test.go
deleted file mode 100644
index c6852ee30..000000000
--- a/pkg/sentry/fs/host/fs_test.go
+++ /dev/null
@@ -1,380 +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 host
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "reflect"
- "sort"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// newTestMountNamespace creates a MountNamespace with a ramfs root.
-// It returns the host folder created, which should be removed when done.
-func newTestMountNamespace(t *testing.T) (*fs.MountNamespace, string, error) {
- p, err := ioutil.TempDir("", "root")
- if err != nil {
- return nil, "", err
- }
-
- fd, err := open(nil, p)
- if err != nil {
- os.RemoveAll(p)
- return nil, "", err
- }
- ctx := contexttest.Context(t)
- root, err := newInode(ctx, newMountSource(ctx, p, fs.RootOwner, &Filesystem{}, fs.MountSourceFlags{}, false), fd, false, false)
- if err != nil {
- os.RemoveAll(p)
- return nil, "", err
- }
- mm, err := fs.NewMountNamespace(ctx, root)
- if err != nil {
- os.RemoveAll(p)
- return nil, "", err
- }
- return mm, p, nil
-}
-
-// createTestDirs populates the root with some test files and directories.
-// /a/a1.txt
-// /a/a2.txt
-// /b/b1.txt
-// /b/c/c1.txt
-// /symlinks/normal.txt
-// /symlinks/to_normal.txt -> /symlinks/normal.txt
-// /symlinks/recursive -> /symlinks
-func createTestDirs(ctx context.Context, t *testing.T, m *fs.MountNamespace) error {
- r := m.Root()
- defer r.DecRef()
-
- if err := r.CreateDirectory(ctx, r, "a", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- a, err := r.Walk(ctx, r, "a")
- if err != nil {
- return err
- }
- defer a.DecRef()
-
- a1, err := a.Create(ctx, r, "a1.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- a1.DecRef()
-
- a2, err := a.Create(ctx, r, "a2.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- a2.DecRef()
-
- if err := r.CreateDirectory(ctx, r, "b", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- b, err := r.Walk(ctx, r, "b")
- if err != nil {
- return err
- }
- defer b.DecRef()
-
- b1, err := b.Create(ctx, r, "b1.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- b1.DecRef()
-
- if err := b.CreateDirectory(ctx, r, "c", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- c, err := b.Walk(ctx, r, "c")
- if err != nil {
- return err
- }
- defer c.DecRef()
-
- c1, err := c.Create(ctx, r, "c1.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- c1.DecRef()
-
- if err := r.CreateDirectory(ctx, r, "symlinks", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- symlinks, err := r.Walk(ctx, r, "symlinks")
- if err != nil {
- return err
- }
- defer symlinks.DecRef()
-
- normal, err := symlinks.Create(ctx, r, "normal.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- normal.DecRef()
-
- if err := symlinks.CreateLink(ctx, r, "/symlinks/normal.txt", "to_normal.txt"); err != nil {
- return err
- }
-
- return symlinks.CreateLink(ctx, r, "/symlinks", "recursive")
-}
-
-// allPaths returns a slice of all paths of entries visible in the rootfs.
-func allPaths(ctx context.Context, t *testing.T, m *fs.MountNamespace, base string) ([]string, error) {
- var paths []string
- root := m.Root()
- defer root.DecRef()
-
- maxTraversals := uint(1)
- d, err := m.FindLink(ctx, root, nil, base, &maxTraversals)
- if err != nil {
- t.Logf("FindLink failed for %q", base)
- return paths, err
- }
- defer d.DecRef()
-
- if fs.IsDir(d.Inode.StableAttr) {
- dir, err := d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
- if err != nil {
- return nil, fmt.Errorf("failed to open directory %q: %v", base, err)
- }
- iter, ok := dir.FileOperations.(fs.DirIterator)
- if !ok {
- return nil, fmt.Errorf("cannot directly iterate on host directory %q", base)
- }
- dirCtx := &fs.DirCtx{
- Serializer: noopDentrySerializer{},
- }
- if _, err := fs.DirentReaddir(ctx, d, iter, root, dirCtx, 0); err != nil {
- return nil, err
- }
- for name := range dirCtx.DentAttrs() {
- if name == "." || name == ".." {
- continue
- }
-
- fullName := path.Join(base, name)
- paths = append(paths, fullName)
-
- // Recurse.
- subpaths, err := allPaths(ctx, t, m, fullName)
- if err != nil {
- return paths, err
- }
- paths = append(paths, subpaths...)
- }
- }
-
- return paths, nil
-}
-
-type noopDentrySerializer struct{}
-
-func (noopDentrySerializer) CopyOut(string, fs.DentAttr) error {
- return nil
-}
-func (noopDentrySerializer) Written() int {
- return 4096
-}
-
-// pathsEqual returns true if the two string slices contain the same entries.
-func pathsEqual(got, want []string) bool {
- sort.Strings(got)
- sort.Strings(want)
-
- if len(got) != len(want) {
- return false
- }
-
- for i := range got {
- if got[i] != want[i] {
- return false
- }
- }
-
- return true
-}
-
-func TestWhitelist(t *testing.T) {
- for _, test := range []struct {
- // description of the test.
- desc string
- // paths are the paths to whitelist
- paths []string
- // want are all of the directory entries that should be
- // visible (nothing beyond this set should be visible).
- want []string
- }{
- {
- desc: "root",
- paths: []string{"/"},
- want: []string{"/a", "/a/a1.txt", "/a/a2.txt", "/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt", "/symlinks", "/symlinks/normal.txt", "/symlinks/to_normal.txt", "/symlinks/recursive"},
- },
- {
- desc: "top-level directories",
- paths: []string{"/a", "/b"},
- want: []string{"/a", "/a/a1.txt", "/a/a2.txt", "/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "nested directories (1/2)",
- paths: []string{"/b", "/b/c"},
- want: []string{"/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "nested directories (2/2)",
- paths: []string{"/b/c", "/b"},
- want: []string{"/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "single file",
- paths: []string{"/b/c/c1.txt"},
- want: []string{"/b", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "single file and directory",
- paths: []string{"/a/a1.txt", "/b/c"},
- want: []string{"/a", "/a/a1.txt", "/b", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "symlink",
- paths: []string{"/symlinks/to_normal.txt"},
- want: []string{"/symlinks", "/symlinks/normal.txt", "/symlinks/to_normal.txt"},
- },
- {
- desc: "recursive symlink",
- paths: []string{"/symlinks/recursive/normal.txt"},
- want: []string{"/symlinks", "/symlinks/normal.txt", "/symlinks/recursive"},
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- m, p, err := newTestMountNamespace(t)
- if err != nil {
- t.Errorf("Failed to create MountNamespace: %v", err)
- }
- defer os.RemoveAll(p)
-
- ctx := withRoot(contexttest.RootContext(t), m.Root())
- if err := createTestDirs(ctx, t, m); err != nil {
- t.Errorf("Failed to create test dirs: %v", err)
- }
-
- if err := installWhitelist(ctx, m, test.paths); err != nil {
- t.Errorf("installWhitelist(%v) err got %v want nil", test.paths, err)
- }
-
- got, err := allPaths(ctx, t, m, "/")
- if err != nil {
- t.Fatalf("Failed to lookup paths (whitelisted: %v): %v", test.paths, err)
- }
-
- if !pathsEqual(got, test.want) {
- t.Errorf("For paths %v got %v want %v", test.paths, got, test.want)
- }
- })
- }
-}
-
-func TestRootPath(t *testing.T) {
- // Create a temp dir, which will be the root of our mounted fs.
- rootPath, err := ioutil.TempDir(os.TempDir(), "root")
- if err != nil {
- t.Fatalf("TempDir failed: %v", err)
- }
- defer os.RemoveAll(rootPath)
-
- // Create two files inside the new root, one which will be whitelisted
- // and one not.
- whitelisted, err := ioutil.TempFile(rootPath, "white")
- if err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
- if _, err := ioutil.TempFile(rootPath, "black"); err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
-
- // Create a mount with a root path and single whitelisted file.
- hostFS := &Filesystem{}
- ctx := contexttest.Context(t)
- data := fmt.Sprintf("%s=%s,%s=%s", rootPathKey, rootPath, whitelistKey, whitelisted.Name())
- inode, err := hostFS.Mount(ctx, "", fs.MountSourceFlags{}, data, nil)
- if err != nil {
- t.Fatalf("Mount failed: %v", err)
- }
- mm, err := fs.NewMountNamespace(ctx, inode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- if err := hostFS.InstallWhitelist(ctx, mm); err != nil {
- t.Fatalf("InstallWhitelist failed: %v", err)
- }
-
- // Get the contents of the root directory.
- rootDir := mm.Root()
- rctx := withRoot(ctx, rootDir)
- f, err := rootDir.Inode.GetFile(rctx, rootDir, fs.FileFlags{})
- if err != nil {
- t.Fatalf("GetFile failed: %v", err)
- }
- c := &fs.CollectEntriesSerializer{}
- if err := f.Readdir(rctx, c); err != nil {
- t.Fatalf("Readdir failed: %v", err)
- }
-
- // We should have only our whitelisted file, plus the dots.
- want := []string{path.Base(whitelisted.Name()), ".", ".."}
- got := c.Order
- sort.Strings(want)
- sort.Strings(got)
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got %v, wanted %v", got, want)
- }
-}
-
-type rootContext struct {
- context.Context
- root *fs.Dirent
-}
-
-// withRoot returns a copy of ctx with the given root.
-func withRoot(ctx context.Context, root *fs.Dirent) context.Context {
- return &rootContext{
- Context: ctx,
- root: root,
- }
-}
-
-// Value implements Context.Value.
-func (rc rootContext) Value(key interface{}) interface{} {
- switch key {
- case fs.CtxRoot:
- rc.root.IncRef()
- return rc.root
- default:
- return rc.Context.Value(key)
- }
-}
diff --git a/pkg/sentry/fs/host/host_state_autogen.go b/pkg/sentry/fs/host/host_state_autogen.go
new file mode 100755
index 000000000..e5c487320
--- /dev/null
+++ b/pkg/sentry/fs/host/host_state_autogen.go
@@ -0,0 +1,140 @@
+// automatically generated by stateify.
+
+package host
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *descriptor) save(m state.Map) {
+ x.beforeSave()
+ m.Save("donated", &x.donated)
+ m.Save("origFD", &x.origFD)
+ m.Save("wouldBlock", &x.wouldBlock)
+}
+
+func (x *descriptor) load(m state.Map) {
+ m.Load("donated", &x.donated)
+ m.Load("origFD", &x.origFD)
+ m.Load("wouldBlock", &x.wouldBlock)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *fileOperations) beforeSave() {}
+func (x *fileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *fileOperations) afterLoad() {}
+func (x *fileOperations) load(m state.Map) {
+ m.LoadWait("iops", &x.iops)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *Filesystem) beforeSave() {}
+func (x *Filesystem) save(m state.Map) {
+ x.beforeSave()
+ m.Save("paths", &x.paths)
+}
+
+func (x *Filesystem) afterLoad() {}
+func (x *Filesystem) load(m state.Map) {
+ m.Load("paths", &x.paths)
+}
+
+func (x *superOperations) beforeSave() {}
+func (x *superOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleMountSourceOperations", &x.SimpleMountSourceOperations)
+ m.Save("root", &x.root)
+ m.Save("inodeMappings", &x.inodeMappings)
+ m.Save("mounter", &x.mounter)
+ m.Save("dontTranslateOwnership", &x.dontTranslateOwnership)
+}
+
+func (x *superOperations) afterLoad() {}
+func (x *superOperations) load(m state.Map) {
+ m.Load("SimpleMountSourceOperations", &x.SimpleMountSourceOperations)
+ m.Load("root", &x.root)
+ m.Load("inodeMappings", &x.inodeMappings)
+ m.Load("mounter", &x.mounter)
+ m.Load("dontTranslateOwnership", &x.dontTranslateOwnership)
+}
+
+func (x *inodeOperations) beforeSave() {}
+func (x *inodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fileState", &x.fileState)
+ m.Save("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeOperations) afterLoad() {}
+func (x *inodeOperations) load(m state.Map) {
+ m.LoadWait("fileState", &x.fileState)
+ m.Load("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeFileState) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.queue) { m.Failf("queue is %v, expected zero", x.queue) }
+ m.Save("mops", &x.mops)
+ m.Save("descriptor", &x.descriptor)
+ m.Save("sattr", &x.sattr)
+ m.Save("savedUAttr", &x.savedUAttr)
+}
+
+func (x *inodeFileState) load(m state.Map) {
+ m.LoadWait("mops", &x.mops)
+ m.LoadWait("descriptor", &x.descriptor)
+ m.LoadWait("sattr", &x.sattr)
+ m.Load("savedUAttr", &x.savedUAttr)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *ConnectedEndpoint) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ref", &x.ref)
+ m.Save("queue", &x.queue)
+ m.Save("path", &x.path)
+ m.Save("srfd", &x.srfd)
+ m.Save("stype", &x.stype)
+}
+
+func (x *ConnectedEndpoint) load(m state.Map) {
+ m.Load("ref", &x.ref)
+ m.Load("queue", &x.queue)
+ m.Load("path", &x.path)
+ m.LoadWait("srfd", &x.srfd)
+ m.Load("stype", &x.stype)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *TTYFileOperations) beforeSave() {}
+func (x *TTYFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fileOperations", &x.fileOperations)
+ m.Save("session", &x.session)
+ m.Save("fgProcessGroup", &x.fgProcessGroup)
+ m.Save("termios", &x.termios)
+}
+
+func (x *TTYFileOperations) afterLoad() {}
+func (x *TTYFileOperations) load(m state.Map) {
+ m.Load("fileOperations", &x.fileOperations)
+ m.Load("session", &x.session)
+ m.Load("fgProcessGroup", &x.fgProcessGroup)
+ m.Load("termios", &x.termios)
+}
+
+func init() {
+ state.Register("host.descriptor", (*descriptor)(nil), state.Fns{Save: (*descriptor).save, Load: (*descriptor).load})
+ state.Register("host.fileOperations", (*fileOperations)(nil), state.Fns{Save: (*fileOperations).save, Load: (*fileOperations).load})
+ state.Register("host.Filesystem", (*Filesystem)(nil), state.Fns{Save: (*Filesystem).save, Load: (*Filesystem).load})
+ state.Register("host.superOperations", (*superOperations)(nil), state.Fns{Save: (*superOperations).save, Load: (*superOperations).load})
+ state.Register("host.inodeOperations", (*inodeOperations)(nil), state.Fns{Save: (*inodeOperations).save, Load: (*inodeOperations).load})
+ state.Register("host.inodeFileState", (*inodeFileState)(nil), state.Fns{Save: (*inodeFileState).save, Load: (*inodeFileState).load})
+ state.Register("host.ConnectedEndpoint", (*ConnectedEndpoint)(nil), state.Fns{Save: (*ConnectedEndpoint).save, Load: (*ConnectedEndpoint).load})
+ state.Register("host.TTYFileOperations", (*TTYFileOperations)(nil), state.Fns{Save: (*TTYFileOperations).save, Load: (*TTYFileOperations).load})
+}
diff --git a/pkg/sentry/fs/host/inode_test.go b/pkg/sentry/fs/host/inode_test.go
deleted file mode 100644
index 2d959f10d..000000000
--- a/pkg/sentry/fs/host/inode_test.go
+++ /dev/null
@@ -1,112 +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 host
-
-import (
- "io/ioutil"
- "os"
- "path"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// TestMultipleReaddir verifies that multiple Readdir calls return the same
-// thing if they use different dir contexts.
-func TestMultipleReaddir(t *testing.T) {
- p, err := ioutil.TempDir("", "readdir")
- if err != nil {
- t.Fatalf("Failed to create test dir: %v", err)
- }
- defer os.RemoveAll(p)
-
- f, err := os.Create(path.Join(p, "a.txt"))
- if err != nil {
- t.Fatalf("Failed to create a.txt: %v", err)
- }
- f.Close()
-
- f, err = os.Create(path.Join(p, "b.txt"))
- if err != nil {
- t.Fatalf("Failed to create b.txt: %v", err)
- }
- f.Close()
-
- fd, err := open(nil, p)
- if err != nil {
- t.Fatalf("Failed to open %q: %v", p, err)
- }
- ctx := contexttest.Context(t)
- n, err := newInode(ctx, newMountSource(ctx, p, fs.RootOwner, &Filesystem{}, fs.MountSourceFlags{}, false), fd, false, false)
- if err != nil {
- t.Fatalf("Failed to create inode: %v", err)
- }
-
- dirent := fs.NewDirent(ctx, n, "readdir")
- openFile, err := n.GetFile(ctx, dirent, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("Failed to get file: %v", err)
- }
- defer openFile.DecRef()
-
- c1 := &fs.DirCtx{DirCursor: new(string)}
- if _, err := openFile.FileOperations.(*fileOperations).IterateDir(ctx, dirent, c1, 0); err != nil {
- t.Fatalf("First Readdir failed: %v", err)
- }
-
- c2 := &fs.DirCtx{DirCursor: new(string)}
- if _, err := openFile.FileOperations.(*fileOperations).IterateDir(ctx, dirent, c2, 0); err != nil {
- t.Errorf("Second Readdir failed: %v", err)
- }
-
- if _, ok := c1.DentAttrs()["a.txt"]; !ok {
- t.Errorf("want a.txt in first Readdir, got %v", c1.DentAttrs())
- }
- if _, ok := c1.DentAttrs()["b.txt"]; !ok {
- t.Errorf("want b.txt in first Readdir, got %v", c1.DentAttrs())
- }
-
- if _, ok := c2.DentAttrs()["a.txt"]; !ok {
- t.Errorf("want a.txt in second Readdir, got %v", c2.DentAttrs())
- }
- if _, ok := c2.DentAttrs()["b.txt"]; !ok {
- t.Errorf("want b.txt in second Readdir, got %v", c2.DentAttrs())
- }
-}
-
-// TestCloseFD verifies fds will be closed.
-func TestCloseFD(t *testing.T) {
- var p [2]int
- if err := syscall.Pipe(p[0:]); err != nil {
- t.Fatalf("Failed to create pipe %v", err)
- }
- defer syscall.Close(p[0])
- defer syscall.Close(p[1])
-
- // Use the write-end because we will detect if it's closed on the read end.
- ctx := contexttest.Context(t)
- file, err := NewFile(ctx, p[1], fs.RootOwner)
- if err != nil {
- t.Fatalf("Failed to create File: %v", err)
- }
- file.DecRef()
-
- s := make([]byte, 10)
- if c, err := syscall.Read(p[0], s); c != 0 || err != nil {
- t.Errorf("want 0, nil (EOF) from read end, got %v, %v", c, err)
- }
-}
diff --git a/pkg/sentry/fs/host/socket_test.go b/pkg/sentry/fs/host/socket_test.go
deleted file mode 100644
index 68b38fd1c..000000000
--- a/pkg/sentry/fs/host/socket_test.go
+++ /dev/null
@@ -1,246 +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 host
-
-import (
- "reflect"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/fdnotifier"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
- "gvisor.dev/gvisor/pkg/sentry/socket"
- "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserr"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-var (
- // Make sure that ConnectedEndpoint implements transport.ConnectedEndpoint.
- _ = transport.ConnectedEndpoint(new(ConnectedEndpoint))
-
- // Make sure that ConnectedEndpoint implements transport.Receiver.
- _ = transport.Receiver(new(ConnectedEndpoint))
-)
-
-func getFl(fd int) (uint32, error) {
- fl, _, err := syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_GETFL, 0)
- if err == 0 {
- return uint32(fl), nil
- }
- return 0, err
-}
-
-func TestSocketIsBlocking(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
-
- fl, err := getFl(pair[0])
- if err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[0], err)
- }
- if fl&syscall.O_NONBLOCK == syscall.O_NONBLOCK {
- t.Fatalf("Expected socket %v to be blocking", pair[0])
- }
- if fl, err = getFl(pair[1]); err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[1], err)
- }
- if fl&syscall.O_NONBLOCK == syscall.O_NONBLOCK {
- t.Fatalf("Expected socket %v to be blocking", pair[1])
- }
- sock, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) failed => %v", pair[0], err)
- }
- defer sock.DecRef()
- // Test that the socket now is non-blocking.
- if fl, err = getFl(pair[0]); err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[0], err)
- }
- if fl&syscall.O_NONBLOCK != syscall.O_NONBLOCK {
- t.Errorf("Expected socket %v to have become non-blocking", pair[0])
- }
- if fl, err = getFl(pair[1]); err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[1], err)
- }
- if fl&syscall.O_NONBLOCK == syscall.O_NONBLOCK {
- t.Errorf("Did not expect socket %v to become non-blocking", pair[1])
- }
-}
-
-func TestSocketWritev(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
- socket, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer socket.DecRef()
- buf := []byte("hello world\n")
- n, err := socket.Writev(contexttest.Context(t), usermem.BytesIOSequence(buf))
- if err != nil {
- t.Fatalf("socket writev failed: %v", err)
- }
-
- if n != int64(len(buf)) {
- t.Fatalf("socket writev wrote incorrect bytes: %d", n)
- }
-}
-
-func TestSocketWritevLen0(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
- socket, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer socket.DecRef()
- n, err := socket.Writev(contexttest.Context(t), usermem.BytesIOSequence(nil))
- if err != nil {
- t.Fatalf("socket writev failed: %v", err)
- }
-
- if n != 0 {
- t.Fatalf("socket writev wrote incorrect bytes: %d", n)
- }
-}
-
-func TestSocketSendMsgLen0(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
- sfile, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer sfile.DecRef()
-
- s := sfile.FileOperations.(socket.Socket)
- n, terr := s.SendMsg(nil, usermem.BytesIOSequence(nil), []byte{}, 0, false, ktime.Time{}, socket.ControlMessages{})
- if n != 0 {
- t.Fatalf("socket sendmsg() failed: %v wrote: %d", terr, n)
- }
-
- if terr != nil {
- t.Fatalf("socket sendmsg() failed: %v", terr)
- }
-}
-
-func TestListen(t *testing.T) {
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) => %v", err)
- }
- sfile1, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer sfile1.DecRef()
- socket1 := sfile1.FileOperations.(socket.Socket)
-
- sfile2, err := newSocket(contexttest.Context(t), pair[1], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[1], err)
- }
- defer sfile2.DecRef()
- socket2 := sfile2.FileOperations.(socket.Socket)
-
- // Socketpairs can not be listened to.
- if err := socket1.Listen(nil, 64); err != syserr.ErrInvalidEndpointState {
- t.Fatalf("socket1.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err)
- }
- if err := socket2.Listen(nil, 64); err != syserr.ErrInvalidEndpointState {
- t.Fatalf("socket2.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err)
- }
-
- // Create a Unix socket, do not bind it.
- sock, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) => %v", err)
- }
- sfile3, err := newSocket(contexttest.Context(t), sock, false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", sock, err)
- }
- defer sfile3.DecRef()
- socket3 := sfile3.FileOperations.(socket.Socket)
-
- // This socket is not bound so we can't listen on it.
- if err := socket3.Listen(nil, 64); err != syserr.ErrInvalidEndpointState {
- t.Fatalf("socket3.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err)
- }
-}
-
-func TestPasscred(t *testing.T) {
- e := ConnectedEndpoint{}
- if got, want := e.Passcred(), false; got != want {
- t.Errorf("Got %#v.Passcred() = %t, want = %t", e, got, want)
- }
-}
-
-func TestGetLocalAddress(t *testing.T) {
- e := ConnectedEndpoint{path: "foo"}
- want := tcpip.FullAddress{Addr: tcpip.Address("foo")}
- if got, err := e.GetLocalAddress(); err != nil || got != want {
- t.Errorf("Got %#v.GetLocalAddress() = %#v, %v, want = %#v, %v", e, got, err, want, nil)
- }
-}
-
-func TestQueuedSize(t *testing.T) {
- e := ConnectedEndpoint{}
- tests := []struct {
- name string
- f func() int64
- }{
- {"SendQueuedSize", e.SendQueuedSize},
- {"RecvQueuedSize", e.RecvQueuedSize},
- }
-
- for _, test := range tests {
- if got, want := test.f(), int64(-1); got != want {
- t.Errorf("Got %#v.%s() = %d, want = %d", e, test.name, got, want)
- }
- }
-}
-
-func TestRelease(t *testing.T) {
- f, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
- if err != nil {
- t.Fatal("Creating socket:", err)
- }
- c := &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)}
- want := &ConnectedEndpoint{queue: c.queue}
- want.ref.DecRef()
- fdnotifier.AddFD(int32(c.file.FD()), nil)
- c.Release()
- if !reflect.DeepEqual(c, want) {
- t.Errorf("got = %#v, want = %#v", c, want)
- }
-}
diff --git a/pkg/sentry/fs/host/util_amd64_unsafe.go b/pkg/sentry/fs/host/util_amd64_unsafe.go
index 66da6e9f5..66da6e9f5 100644..100755
--- a/pkg/sentry/fs/host/util_amd64_unsafe.go
+++ b/pkg/sentry/fs/host/util_amd64_unsafe.go
diff --git a/pkg/sentry/fs/host/util_arm64_unsafe.go b/pkg/sentry/fs/host/util_arm64_unsafe.go
index e8cb94aeb..e8cb94aeb 100644..100755
--- a/pkg/sentry/fs/host/util_arm64_unsafe.go
+++ b/pkg/sentry/fs/host/util_arm64_unsafe.go
diff --git a/pkg/sentry/fs/host/wait_test.go b/pkg/sentry/fs/host/wait_test.go
deleted file mode 100644
index 88d24d693..000000000
--- a/pkg/sentry/fs/host/wait_test.go
+++ /dev/null
@@ -1,70 +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 host
-
-import (
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestWait(t *testing.T) {
- var fds [2]int
- err := syscall.Pipe(fds[:])
- if err != nil {
- t.Fatalf("Unable to create pipe: %v", err)
- }
-
- defer syscall.Close(fds[1])
-
- ctx := contexttest.Context(t)
- file, err := NewFile(ctx, fds[0], fs.RootOwner)
- if err != nil {
- syscall.Close(fds[0])
- t.Fatalf("NewFile failed: %v", err)
- }
-
- defer file.DecRef()
-
- r := file.Readiness(waiter.EventIn)
- if r != 0 {
- t.Fatalf("File is ready for read when it shouldn't be.")
- }
-
- e, ch := waiter.NewChannelEntry(nil)
- file.EventRegister(&e, waiter.EventIn)
- defer file.EventUnregister(&e)
-
- // Check that there are no notifications yet.
- if len(ch) != 0 {
- t.Fatalf("Channel is non-empty")
- }
-
- // Write to the pipe, so it should be writable now.
- syscall.Write(fds[1], []byte{1})
-
- // Check that we get a notification. We need to yield the current thread
- // so that the fdnotifier can deliver notifications, so we use a
- // 1-second timeout instead of just checking the length of the channel.
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Channel not notified")
- }
-}
diff --git a/pkg/sentry/fs/inode_overlay_test.go b/pkg/sentry/fs/inode_overlay_test.go
deleted file mode 100644
index 8935aad65..000000000
--- a/pkg/sentry/fs/inode_overlay_test.go
+++ /dev/null
@@ -1,470 +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 fs_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func TestLookup(t *testing.T) {
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- // Test description.
- desc string
-
- // Lookup parameters.
- dir *fs.Inode
- name string
-
- // Want from lookup.
- found bool
- hasUpper bool
- hasLower bool
- }{
- {
- desc: "no upper, lower has name",
- dir: fs.NewTestOverlayDir(ctx,
- nil, /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: false,
- hasLower: true,
- },
- {
- desc: "no lower, upper has name",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* upper */
- nil, /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: false,
- },
- {
- desc: "upper and lower, only lower has name",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "b",
- dir: false,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: false,
- hasLower: true,
- },
- {
- desc: "upper and lower, only upper has name",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "b",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: false,
- },
- {
- desc: "upper and lower, both have file",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: false,
- },
- {
- desc: "upper and lower, both have directory",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: true,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: true,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: true,
- },
- {
- desc: "upper and lower, upper negative masks lower file",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, nil, []string{"a"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: false,
- hasUpper: false,
- hasLower: false,
- },
- {
- desc: "upper and lower, upper negative does not mask lower file",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, nil, []string{"b"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: false,
- hasLower: true,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- dirent, err := test.dir.Lookup(ctx, test.name)
- if test.found && (err == syserror.ENOENT || dirent.IsNegative()) {
- t.Fatalf("lookup %q expected to find positive dirent, got dirent %v err %v", test.name, dirent, err)
- }
- if !test.found {
- if err != syserror.ENOENT && !dirent.IsNegative() {
- t.Errorf("lookup %q expected to return ENOENT or negative dirent, got dirent %v err %v", test.name, dirent, err)
- }
- // Nothing more to check.
- return
- }
- if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper {
- t.Fatalf("lookup got upper filesystem %v, want %v", hasUpper, test.hasUpper)
- }
- if hasLower := dirent.Inode.TestHasLowerFS(); hasLower != test.hasLower {
- t.Errorf("lookup got lower filesystem %v, want %v", hasLower, test.hasLower)
- }
- })
- }
-}
-
-func TestLookupRevalidation(t *testing.T) {
- // File name used in the tests.
- fileName := "foofile"
- ctx := contexttest.Context(t)
- for _, tc := range []struct {
- // Test description.
- desc string
-
- // Upper and lower fs for the overlay.
- upper *fs.Inode
- lower *fs.Inode
-
- // Whether the upper requires revalidation.
- revalidate bool
-
- // Whether we should get the same dirent on second lookup.
- wantSame bool
- }{
- {
- desc: "file from upper with no revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, nil, nil),
- revalidate: false,
- wantSame: true,
- },
- {
- desc: "file from upper with revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, nil, nil),
- revalidate: true,
- wantSame: false,
- },
- {
- desc: "file from lower with no revalidation",
- upper: newTestRamfsDir(ctx, nil, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: false,
- wantSame: true,
- },
- {
- desc: "file from lower with revalidation",
- upper: newTestRamfsDir(ctx, nil, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: true,
- // The file does not exist in the upper, so we do not
- // need to revalidate it.
- wantSame: true,
- },
- {
- desc: "file from upper and lower with no revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: false,
- wantSame: true,
- },
- {
- desc: "file from upper and lower with revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: true,
- wantSame: false,
- },
- } {
- t.Run(tc.desc, func(t *testing.T) {
- root := fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root")
- ctx = &rootContext{
- Context: ctx,
- root: root,
- }
- overlay := fs.NewDirent(ctx, fs.NewTestOverlayDir(ctx, tc.upper, tc.lower, tc.revalidate), "overlay")
- // Lookup the file twice through the overlay.
- first, err := overlay.Walk(ctx, root, fileName)
- if err != nil {
- t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
- }
- second, err := overlay.Walk(ctx, root, fileName)
- if err != nil {
- t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
- }
-
- if tc.wantSame && first != second {
- t.Errorf("dirent lookup got different dirents, wanted same\nfirst=%+v\nsecond=%+v", first, second)
- } else if !tc.wantSame && first == second {
- t.Errorf("dirent lookup got the same dirent, wanted different: %+v", first)
- }
- })
- }
-}
-
-func TestCacheFlush(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Upper and lower each have a file.
- upperFileName := "file-from-upper"
- lowerFileName := "file-from-lower"
- upper := newTestRamfsDir(ctx, []dirContent{{name: upperFileName}}, nil)
- lower := newTestRamfsDir(ctx, []dirContent{{name: lowerFileName}}, nil)
-
- overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */)
-
- mns, err := fs.NewMountNamespace(ctx, overlay)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- root := mns.Root()
- defer root.DecRef()
-
- ctx = &rootContext{
- Context: ctx,
- root: root,
- }
-
- for _, fileName := range []string{upperFileName, lowerFileName} {
- // Walk to the file.
- maxTraversals := uint(0)
- dirent, err := mns.FindInode(ctx, root, nil, fileName, &maxTraversals)
- if err != nil {
- t.Fatalf("FindInode(%q) failed: %v", fileName, err)
- }
-
- // Get a file from the dirent.
- file, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("GetFile() failed: %v", err)
- }
-
- // The dirent should have 3 refs, one from us, one from the
- // file, and one from the dirent cache.
- // dirent cache.
- if got, want := dirent.ReadRefs(), 3; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
-
- // Drop the file reference.
- file.DecRef()
-
- // Dirent should have 2 refs left.
- if got, want := dirent.ReadRefs(), 2; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
-
- // Flush the dirent cache.
- mns.FlushMountSourceRefs()
-
- // Dirent should have 1 ref left from the dirent cache.
- if got, want := dirent.ReadRefs(), 1; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
-
- // Drop our ref.
- dirent.DecRef()
-
- // We should be back to zero refs.
- if got, want := dirent.ReadRefs(), 0; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
- }
-
-}
-
-type dir struct {
- fs.InodeOperations
-
- // List of negative child names.
- negative []string
-
- // ReaddirCalled records whether Readdir was called on a file
- // corresponding to this inode.
- ReaddirCalled bool
-}
-
-// Getxattr implements InodeOperations.Getxattr.
-func (d *dir) Getxattr(inode *fs.Inode, name string) (string, error) {
- for _, n := range d.negative {
- if name == fs.XattrOverlayWhiteout(n) {
- return "y", nil
- }
- }
- return "", syserror.ENOATTR
-}
-
-// GetFile implements InodeOperations.GetFile.
-func (d *dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
- file, err := d.InodeOperations.GetFile(ctx, dirent, flags)
- if err != nil {
- return nil, err
- }
- defer file.DecRef()
- // Wrap the file's FileOperations in a dirFile.
- fops := &dirFile{
- FileOperations: file.FileOperations,
- inode: d,
- }
- return fs.NewFile(ctx, dirent, flags, fops), nil
-}
-
-type dirContent struct {
- name string
- dir bool
-}
-
-type dirFile struct {
- fs.FileOperations
- inode *dir
-}
-
-type inode struct {
- fsutil.InodeGenericChecker `state:"nosave"`
- fsutil.InodeNoExtendedAttributes `state:"nosave"`
- fsutil.InodeNoopRelease `state:"nosave"`
- fsutil.InodeNoopWriteOut `state:"nosave"`
- fsutil.InodeNotAllocatable `state:"nosave"`
- fsutil.InodeNotDirectory `state:"nosave"`
- fsutil.InodeNotMappable `state:"nosave"`
- fsutil.InodeNotSocket `state:"nosave"`
- fsutil.InodeNotSymlink `state:"nosave"`
- fsutil.InodeNotTruncatable `state:"nosave"`
- fsutil.InodeNotVirtual `state:"nosave"`
-
- fsutil.InodeSimpleAttributes
- fsutil.InodeStaticFileGetter
-}
-
-// Readdir implements fs.FileOperations.Readdir. It sets the ReaddirCalled
-// field on the inode.
-func (f *dirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) {
- f.inode.ReaddirCalled = true
- return f.FileOperations.Readdir(ctx, file, ser)
-}
-
-func newTestRamfsInode(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
- inode := fs.NewInode(ctx, &inode{
- InodeStaticFileGetter: fsutil.InodeStaticFileGetter{
- Contents: []byte("foobar"),
- },
- }, msrc, fs.StableAttr{Type: fs.RegularFile})
- return inode
-}
-
-func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode {
- msrc := fs.NewPseudoMountSource(ctx)
- contents := make(map[string]*fs.Inode)
- for _, c := range contains {
- if c.dir {
- contents[c.name] = newTestRamfsDir(ctx, nil, nil)
- } else {
- contents[c.name] = newTestRamfsInode(ctx, msrc)
- }
- }
- dops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermissions{
- User: fs.PermMask{Read: true, Execute: true},
- })
- return fs.NewInode(ctx, &dir{
- InodeOperations: dops,
- negative: negative,
- }, msrc, fs.StableAttr{Type: fs.Directory})
-}
diff --git a/pkg/sentry/fs/lock/BUILD b/pkg/sentry/fs/lock/BUILD
deleted file mode 100644
index 8d62642e7..000000000
--- a/pkg/sentry/fs/lock/BUILD
+++ /dev/null
@@ -1,59 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_template_instance(
- name = "lock_range",
- out = "lock_range.go",
- package = "lock",
- prefix = "Lock",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uint64",
- },
-)
-
-go_template_instance(
- name = "lock_set",
- out = "lock_set.go",
- consts = {
- "minDegree": "3",
- },
- package = "lock",
- prefix = "Lock",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "LockRange",
- "Value": "Lock",
- "Functions": "lockSetFunctions",
- },
-)
-
-go_library(
- name = "lock",
- srcs = [
- "lock.go",
- "lock_range.go",
- "lock_set.go",
- "lock_set_functions.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/lock",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/log",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "lock_test",
- size = "small",
- srcs = [
- "lock_range_test.go",
- "lock_test.go",
- ],
- embed = [":lock"],
-)
diff --git a/pkg/sentry/fs/lock/lock_range.go b/pkg/sentry/fs/lock/lock_range.go
new file mode 100755
index 000000000..7a6f77640
--- /dev/null
+++ b/pkg/sentry/fs/lock/lock_range.go
@@ -0,0 +1,62 @@
+package lock
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type LockRange struct {
+ // Start is the inclusive start of the range.
+ Start uint64
+
+ // End is the exclusive end of the range.
+ End uint64
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r LockRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r LockRange) Length() uint64 {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r LockRange) Contains(x uint64) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r LockRange) Overlaps(r2 LockRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r LockRange) IsSupersetOf(r2 LockRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r LockRange) Intersect(r2 LockRange) LockRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r LockRange) CanSplitAt(x uint64) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/sentry/fs/lock/lock_range_test.go b/pkg/sentry/fs/lock/lock_range_test.go
deleted file mode 100644
index 6221199d1..000000000
--- a/pkg/sentry/fs/lock/lock_range_test.go
+++ /dev/null
@@ -1,136 +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 lock
-
-import (
- "syscall"
- "testing"
-)
-
-func TestComputeRange(t *testing.T) {
- tests := []struct {
- // Description of test.
- name string
-
- // Requested start of the lock range.
- start int64
-
- // Requested length of the lock range,
- // can be negative :(
- length int64
-
- // Pre-computed file offset based on whence.
- // Will be added to start.
- offset int64
-
- // Expected error.
- err error
-
- // If error is nil, the expected LockRange.
- LockRange
- }{
- {
- name: "offset, start, and length all zero",
- LockRange: LockRange{Start: 0, End: LockEOF},
- },
- {
- name: "zero offset, zero start, positive length",
- start: 0,
- length: 4096,
- offset: 0,
- LockRange: LockRange{Start: 0, End: 4096},
- },
- {
- name: "zero offset, negative start",
- start: -4096,
- offset: 0,
- err: syscall.EINVAL,
- },
- {
- name: "large offset, negative start, positive length",
- start: -2048,
- length: 2048,
- offset: 4096,
- LockRange: LockRange{Start: 2048, End: 4096},
- },
- {
- name: "large offset, negative start, zero length",
- start: -2048,
- length: 0,
- offset: 4096,
- LockRange: LockRange{Start: 2048, End: LockEOF},
- },
- {
- name: "zero offset, zero start, negative length",
- start: 0,
- length: -4096,
- offset: 0,
- err: syscall.EINVAL,
- },
- {
- name: "large offset, zero start, negative length",
- start: 0,
- length: -4096,
- offset: 4096,
- LockRange: LockRange{Start: 0, End: 4096},
- },
- {
- name: "offset, start, and length equal, length is negative",
- start: 1024,
- length: -1024,
- offset: 1024,
- LockRange: LockRange{Start: 1024, End: 2048},
- },
- {
- name: "offset, start, and length equal, start is negative",
- start: -1024,
- length: 1024,
- offset: 1024,
- LockRange: LockRange{Start: 0, End: 1024},
- },
- {
- name: "offset, start, and length equal, offset is negative",
- start: 1024,
- length: 1024,
- offset: -1024,
- LockRange: LockRange{Start: 0, End: 1024},
- },
- {
- name: "offset, start, and length equal, all negative",
- start: -1024,
- length: -1024,
- offset: -1024,
- err: syscall.EINVAL,
- },
- {
- name: "offset, start, and length equal, all positive",
- start: 1024,
- length: 1024,
- offset: 1024,
- LockRange: LockRange{Start: 2048, End: 3072},
- },
- }
-
- for _, test := range tests {
- rng, err := ComputeRange(test.start, test.length, test.offset)
- if err != test.err {
- t.Errorf("%s: lockRange(%d, %d, %d) got error %v, want %v", test.name, test.start, test.length, test.offset, err, test.err)
- continue
- }
- if err == nil && rng != test.LockRange {
- t.Errorf("%s: lockRange(%d, %d, %d) got LockRange %v, want %v", test.name, test.start, test.length, test.offset, rng, test.LockRange)
- }
- }
-}
diff --git a/pkg/sentry/fs/lock/lock_set.go b/pkg/sentry/fs/lock/lock_set.go
new file mode 100755
index 000000000..2343ca0b4
--- /dev/null
+++ b/pkg/sentry/fs/lock/lock_set.go
@@ -0,0 +1,1270 @@
+package lock
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ LockminDegree = 3
+
+ LockmaxDegree = 2 * LockminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type LockSet struct {
+ root Locknode `state:".(*LockSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *LockSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *LockSet) IsEmptyRange(r LockRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *LockSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *LockSet) SpanRange(r LockRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *LockSet) FirstSegment() LockIterator {
+ if s.root.nrSegments == 0 {
+ return LockIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *LockSet) LastSegment() LockIterator {
+ if s.root.nrSegments == 0 {
+ return LockIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *LockSet) FirstGap() LockGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return LockGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *LockSet) LastGap() LockGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return LockGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *LockSet) Find(key uint64) (LockIterator, LockGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return LockIterator{n, i}, LockGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return LockIterator{}, LockGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *LockSet) FindSegment(key uint64) LockIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *LockSet) LowerBoundSegment(min uint64) LockIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *LockSet) UpperBoundSegment(max uint64) LockIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *LockSet) FindGap(key uint64) LockGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *LockSet) LowerBoundGap(min uint64) LockGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *LockSet) UpperBoundGap(max uint64) LockGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *LockSet) Add(r LockRange, val Lock) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *LockSet) AddWithoutMerging(r LockRange, val Lock) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *LockSet) Insert(gap LockGapIterator, r LockRange, val Lock) LockIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (lockSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (lockSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (lockSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *LockSet) InsertWithoutMerging(gap LockGapIterator, r LockRange, val Lock) LockIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *LockSet) InsertWithoutMergingUnchecked(gap LockGapIterator, r LockRange, val Lock) LockIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return LockIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *LockSet) Remove(seg LockIterator) LockGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ lockSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(LockGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *LockSet) RemoveAll() {
+ s.root = Locknode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *LockSet) RemoveRange(r LockRange) LockGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *LockSet) Merge(first, second LockIterator) LockIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *LockSet) MergeUnchecked(first, second LockIterator) LockIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (lockSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return LockIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *LockSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *LockSet) MergeRange(r LockRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *LockSet) MergeAdjacent(r LockRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *LockSet) Split(seg LockIterator, split uint64) (LockIterator, LockIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *LockSet) SplitUnchecked(seg LockIterator, split uint64) (LockIterator, LockIterator) {
+ val1, val2 := (lockSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), LockRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *LockSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *LockSet) Isolate(seg LockIterator, r LockRange) LockIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *LockSet) ApplyContiguous(r LockRange, fn func(seg LockIterator)) LockGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return LockGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return LockGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type Locknode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *Locknode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [LockmaxDegree - 1]LockRange
+ values [LockmaxDegree - 1]Lock
+ children [LockmaxDegree]*Locknode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Locknode) firstSegment() LockIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return LockIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Locknode) lastSegment() LockIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return LockIterator{n, n.nrSegments - 1}
+}
+
+func (n *Locknode) prevSibling() *Locknode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *Locknode) nextSibling() *Locknode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *Locknode) rebalanceBeforeInsert(gap LockGapIterator) LockGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < LockmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &Locknode{
+ nrSegments: LockminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &Locknode{
+ nrSegments: LockminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:LockminDegree-1], n.keys[:LockminDegree-1])
+ copy(left.values[:LockminDegree-1], n.values[:LockminDegree-1])
+ copy(right.keys[:LockminDegree-1], n.keys[LockminDegree:])
+ copy(right.values[:LockminDegree-1], n.values[LockminDegree:])
+ n.keys[0], n.values[0] = n.keys[LockminDegree-1], n.values[LockminDegree-1]
+ LockzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:LockminDegree], n.children[:LockminDegree])
+ copy(right.children[:LockminDegree], n.children[LockminDegree:])
+ LockzeroNodeSlice(n.children[2:])
+ for i := 0; i < LockminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < LockminDegree {
+ return LockGapIterator{left, gap.index}
+ }
+ return LockGapIterator{right, gap.index - LockminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[LockminDegree-1], n.values[LockminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &Locknode{
+ nrSegments: LockminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:LockminDegree-1], n.keys[LockminDegree:])
+ copy(sibling.values[:LockminDegree-1], n.values[LockminDegree:])
+ LockzeroValueSlice(n.values[LockminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:LockminDegree], n.children[LockminDegree:])
+ LockzeroNodeSlice(n.children[LockminDegree:])
+ for i := 0; i < LockminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = LockminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < LockminDegree {
+ return gap
+ }
+ return LockGapIterator{sibling, gap.index - LockminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *Locknode) rebalanceAfterRemove(gap LockGapIterator) LockGapIterator {
+ for {
+ if n.nrSegments >= LockminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= LockminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ lockSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return LockGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return LockGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= LockminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ lockSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return LockGapIterator{n, n.nrSegments}
+ }
+ return LockGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return LockGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return LockGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *Locknode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = LockGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ lockSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type LockIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *Locknode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg LockIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg LockIterator) Range() LockRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg LockIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg LockIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg LockIterator) SetRangeUnchecked(r LockRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg LockIterator) SetRange(r LockRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg LockIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg LockIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg LockIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg LockIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg LockIterator) Value() Lock {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg LockIterator) ValuePtr() *Lock {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg LockIterator) SetValue(val Lock) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg LockIterator) PrevSegment() LockIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return LockIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return LockIterator{}
+ }
+ return LocksegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg LockIterator) NextSegment() LockIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return LockIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return LockIterator{}
+ }
+ return LocksegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg LockIterator) PrevGap() LockGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return LockGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg LockIterator) NextGap() LockGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return LockGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg LockIterator) PrevNonEmpty() (LockIterator, LockGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return LockIterator{}, gap
+ }
+ return gap.PrevSegment(), LockGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg LockIterator) NextNonEmpty() (LockIterator, LockGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return LockIterator{}, gap
+ }
+ return gap.NextSegment(), LockGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type LockGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *Locknode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap LockGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap LockGapIterator) Range() LockRange {
+ return LockRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap LockGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return lockSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap LockGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return lockSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap LockGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap LockGapIterator) PrevSegment() LockIterator {
+ return LocksegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap LockGapIterator) NextSegment() LockIterator {
+ return LocksegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap LockGapIterator) PrevGap() LockGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return LockGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap LockGapIterator) NextGap() LockGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return LockGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func LocksegmentBeforePosition(n *Locknode, i int) LockIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return LockIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return LockIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func LocksegmentAfterPosition(n *Locknode, i int) LockIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return LockIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return LockIterator{n, i}
+}
+
+func LockzeroValueSlice(slice []Lock) {
+
+ for i := range slice {
+ lockSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func LockzeroNodeSlice(slice []*Locknode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *LockSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *Locknode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *Locknode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type LockSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []Lock
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *LockSet) ExportSortedSlices() *LockSegmentDataSlices {
+ var sds LockSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *LockSet) ImportSortedSlices(sds *LockSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := LockRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *LockSet) saveRoot() *LockSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *LockSet) loadRoot(sds *LockSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/lock/lock_state_autogen.go b/pkg/sentry/fs/lock/lock_state_autogen.go
new file mode 100755
index 000000000..cb69e2cd0
--- /dev/null
+++ b/pkg/sentry/fs/lock/lock_state_autogen.go
@@ -0,0 +1,106 @@
+// automatically generated by stateify.
+
+package lock
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Lock) beforeSave() {}
+func (x *Lock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Readers", &x.Readers)
+ m.Save("HasWriter", &x.HasWriter)
+ m.Save("Writer", &x.Writer)
+}
+
+func (x *Lock) afterLoad() {}
+func (x *Lock) load(m state.Map) {
+ m.Load("Readers", &x.Readers)
+ m.Load("HasWriter", &x.HasWriter)
+ m.Load("Writer", &x.Writer)
+}
+
+func (x *Locks) beforeSave() {}
+func (x *Locks) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.blockedQueue) { m.Failf("blockedQueue is %v, expected zero", x.blockedQueue) }
+ m.Save("locks", &x.locks)
+}
+
+func (x *Locks) afterLoad() {}
+func (x *Locks) load(m state.Map) {
+ m.Load("locks", &x.locks)
+}
+
+func (x *LockRange) beforeSave() {}
+func (x *LockRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *LockRange) afterLoad() {}
+func (x *LockRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func (x *LockSet) beforeSave() {}
+func (x *LockSet) save(m state.Map) {
+ x.beforeSave()
+ var root *LockSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *LockSet) afterLoad() {}
+func (x *LockSet) load(m state.Map) {
+ m.LoadValue("root", new(*LockSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*LockSegmentDataSlices)) })
+}
+
+func (x *Locknode) beforeSave() {}
+func (x *Locknode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *Locknode) afterLoad() {}
+func (x *Locknode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *LockSegmentDataSlices) beforeSave() {}
+func (x *LockSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *LockSegmentDataSlices) afterLoad() {}
+func (x *LockSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func init() {
+ state.Register("lock.Lock", (*Lock)(nil), state.Fns{Save: (*Lock).save, Load: (*Lock).load})
+ state.Register("lock.Locks", (*Locks)(nil), state.Fns{Save: (*Locks).save, Load: (*Locks).load})
+ state.Register("lock.LockRange", (*LockRange)(nil), state.Fns{Save: (*LockRange).save, Load: (*LockRange).load})
+ state.Register("lock.LockSet", (*LockSet)(nil), state.Fns{Save: (*LockSet).save, Load: (*LockSet).load})
+ state.Register("lock.Locknode", (*Locknode)(nil), state.Fns{Save: (*Locknode).save, Load: (*Locknode).load})
+ state.Register("lock.LockSegmentDataSlices", (*LockSegmentDataSlices)(nil), state.Fns{Save: (*LockSegmentDataSlices).save, Load: (*LockSegmentDataSlices).load})
+}
diff --git a/pkg/sentry/fs/lock/lock_test.go b/pkg/sentry/fs/lock/lock_test.go
deleted file mode 100644
index ba002aeb7..000000000
--- a/pkg/sentry/fs/lock/lock_test.go
+++ /dev/null
@@ -1,1059 +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 lock
-
-import (
- "reflect"
- "testing"
-)
-
-type entry struct {
- Lock
- LockRange
-}
-
-func equals(e0, e1 []entry) bool {
- if len(e0) != len(e1) {
- return false
- }
- for i := range e0 {
- for k := range e0[i].Lock.Readers {
- if !e1[i].Lock.Readers[k] {
- return false
- }
- }
- for k := range e1[i].Lock.Readers {
- if !e0[i].Lock.Readers[k] {
- return false
- }
- }
- if !reflect.DeepEqual(e0[i].LockRange, e1[i].LockRange) {
- return false
- }
- if e0[i].Lock.HasWriter != e1[i].Lock.HasWriter {
- return false
- }
- if e0[i].Lock.Writer != e1[i].Lock.Writer {
- return false
- }
- }
- return true
-}
-
-// fill a LockSet with consecutive region locks. Will panic if
-// LockRanges are not consecutive.
-func fill(entries []entry) LockSet {
- l := LockSet{}
- for _, e := range entries {
- gap := l.FindGap(e.LockRange.Start)
- if !gap.Ok() {
- panic("cannot insert into existing segment")
- }
- l.Insert(gap, e.LockRange, e.Lock)
- }
- return l
-}
-
-func TestCanLockEmpty(t *testing.T) {
- l := LockSet{}
-
- // Expect to be able to take any locks given that the set is empty.
- eof := l.FirstGap().End()
- r := LockRange{0, eof}
- if !l.canLock(1, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 1)
- }
- if !l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 2)
- }
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
- if !l.canLock(2, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 2)
- }
-}
-
-func TestCanLock(t *testing.T) {
- // + -------------- + ---------- + -------------- + --------- +
- // | Readers 1 & 2 | Readers 1 | Readers 1 & 3 | Writer 1 |
- // + ------------- + ---------- + -------------- + --------- +
- // 0 1024 2048 3072 4096
- l := fill([]entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 3: true}},
- LockRange: LockRange{2048, 3072},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 1},
- LockRange: LockRange{3072, 4096},
- },
- })
-
- // Now that we have a mildly interesting layout, try some checks on different
- // ranges, uids, and lock types.
- //
- // Expect to be able to extend the read lock, despite the writer lock, because
- // the writer has the same uid as the requested read lock.
- r := LockRange{0, 8192}
- if !l.canLock(1, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 1)
- }
- // Expect to *not* be able to extend the read lock since there is an overlapping
- // writer region locked by someone other than the uid.
- if l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got true, want false", ReadLock, r, 2)
- }
- // Expect to be able to extend the read lock if there are only other readers in
- // the way.
- r = LockRange{64, 3072}
- if !l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 2)
- }
- // Expect to be able to set a read lock beyond the range of any existing locks.
- r = LockRange{4096, 10240}
- if !l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 2)
- }
-
- // Expect to not be able to take a write lock with other readers in the way.
- r = LockRange{0, 8192}
- if l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got true, want false", WriteLock, r, 1)
- }
- // Expect to be able to extend the write lock for the same uid.
- r = LockRange{3072, 8192}
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
- // Expect to not be able to overlap a write lock for two different uids.
- if l.canLock(2, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got true, want false", WriteLock, r, 2)
- }
- // Expect to be able to set a write lock that is beyond the range of any
- // existing locks.
- r = LockRange{8192, 10240}
- if !l.canLock(2, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 2)
- }
- // Expect to be able to upgrade a read lock (any portion of it).
- r = LockRange{1024, 2048}
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
- r = LockRange{1080, 2000}
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
-}
-
-func TestSetLock(t *testing.T) {
- tests := []struct {
- // description of test.
- name string
-
- // LockSet entries to pre-fill.
- before []entry
-
- // Description of region to lock:
- //
- // start is the file offset of the lock.
- start uint64
- // end is the end file offset of the lock.
- end uint64
- // uid of lock attempter.
- uid UniqueID
- // lock type requested.
- lockType LockType
-
- // success is true if taking the above
- // lock should succeed.
- success bool
-
- // Expected layout of the set after locking
- // if success is true.
- after []entry
- }{
- {
- name: "set zero length ReadLock on empty set",
- start: 0,
- end: 0,
- uid: 0,
- lockType: ReadLock,
- success: true,
- },
- {
- name: "set zero length WriteLock on empty set",
- start: 0,
- end: 0,
- uid: 0,
- lockType: WriteLock,
- success: true,
- },
- {
- name: "set ReadLock on empty set",
- start: 0,
- end: LockEOF,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "set WriteLock on empty set",
- start: 0,
- end: LockEOF,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "set ReadLock on WriteLock same uid",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // + ----------- + --------------------------- +
- // | Readers 0 | Writer 0 |
- // + ----------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "set WriteLock on ReadLock same uid",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ----------- + --------------------------- +
- // | Writer 0 | Readers 0 |
- // + ----------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "set ReadLock on WriteLock different uid",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: ReadLock,
- success: false,
- },
- {
- name: "set WriteLock on ReadLock different uid",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: WriteLock,
- success: false,
- },
- {
- name: "split ReadLock for overlapping lock at start 0",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: ReadLock,
- success: true,
- // + -------------- + --------------------------- +
- // | Readers 0 & 1 | Readers 0 |
- // + -------------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "split ReadLock for overlapping lock at non-zero start",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 4096,
- end: 8192,
- uid: 1,
- lockType: ReadLock,
- success: true,
- // + ---------- + -------------- + ----------- +
- // | Readers 0 | Readers 0 & 1 | Readers 0 |
- // + ---------- + -------------- + ----------- +
- // 0 4096 8192 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, 8192},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{8192, LockEOF},
- },
- },
- },
- {
- name: "fill front gap with ReadLock",
- // + --------- + ---------------------------- +
- // | gap | Readers 0 |
- // + --------- + ---------------------------- +
- // 0 1024 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- start: 0,
- end: 8192,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "fill end gap with ReadLock",
- // + ---------------------------- +
- // | Readers 0 |
- // + ---------------------------- +
- // 0 4096
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- },
- start: 1024,
- end: LockEOF,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // Note that this is not merged after lock does a Split. This is
- // fine because the two locks will still *behave* as one. In other
- // words we can fragment any lock all we want and semantically it
- // makes no difference.
- //
- // + ----------- + --------------------------- +
- // | Readers 0 | Readers 0 |
- // + ----------- + --------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- },
- {
- name: "fill gap with ReadLock and split",
- // + --------- + ---------------------------- +
- // | gap | Readers 0 |
- // + --------- + ---------------------------- +
- // 0 1024 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: ReadLock,
- success: true,
- // + --------- + ------------- + ------------- +
- // | Reader 1 | Readers 0 & 1 | Reader 0 |
- // + ----------+ ------------- + ------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "upgrade ReadLock to WriteLock for single uid fill gap",
- // + ------------- + --------- + --- + ------------- +
- // | Readers 0 & 1 | Readers 0 | gap | Readers 0 & 2 |
- // + ------------- + --------- + --- + ------------- +
- // 0 1024 2048 4096 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 1024,
- end: 4096,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ------------- + -------- + ------------- +
- // | Readers 0 & 1 | Writer 0 | Readers 0 & 2 |
- // + ------------- + -------- + ------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "upgrade ReadLock to WriteLock for single uid keep gap",
- // + ------------- + --------- + --- + ------------- +
- // | Readers 0 & 1 | Readers 0 | gap | Readers 0 & 2 |
- // + ------------- + --------- + --- + ------------- +
- // 0 1024 2048 4096 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 1024,
- end: 3072,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ------------- + -------- + --- + ------------- +
- // | Readers 0 & 1 | Writer 0 | gap | Readers 0 & 2 |
- // + ------------- + -------- + --- + ------------- +
- // 0 1024 3072 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, 3072},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "fail to upgrade ReadLock to WriteLock with conflicting Reader",
- // + ------------- + --------- +
- // | Readers 0 & 1 | Readers 0 |
- // + ------------- + --------- +
- // 0 1024 2048
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- },
- start: 0,
- end: 2048,
- uid: 0,
- lockType: WriteLock,
- success: false,
- },
- {
- name: "take WriteLock on whole file if all uids are the same",
- // + ------------- + --------- + --------- + ---------- +
- // | Writer 0 | Readers 0 | Readers 0 | Readers 0 |
- // + ------------- + --------- + --------- + ---------- +
- // 0 1024 2048 4096 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{2048, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // We do not manually merge locks. Semantically a fragmented lock
- // held by the same uid will behave as one lock so it makes no difference.
- //
- // + ------------- + ---------------------------- +
- // | Writer 0 | Writer 0 |
- // + ------------- + ---------------------------- +
- // 0 1024 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- },
- }
-
- for _, test := range tests {
- l := fill(test.before)
-
- r := LockRange{Start: test.start, End: test.end}
- success := l.lock(test.uid, test.lockType, r)
- var got []entry
- for seg := l.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- got = append(got, entry{
- Lock: seg.Value(),
- LockRange: seg.Range(),
- })
- }
-
- if success != test.success {
- t.Errorf("%s: setlock(%v, %+v, %d, %d) got success %v, want %v", test.name, test.before, r, test.uid, test.lockType, success, test.success)
- continue
- }
-
- if success {
- if !equals(got, test.after) {
- t.Errorf("%s: got set %+v, want %+v", test.name, got, test.after)
- }
- }
- }
-}
-
-func TestUnlock(t *testing.T) {
- tests := []struct {
- // description of test.
- name string
-
- // LockSet entries to pre-fill.
- before []entry
-
- // Description of region to unlock:
- //
- // start is the file start of the lock.
- start uint64
- // end is the end file start of the lock.
- end uint64
- // uid of lock holder.
- uid UniqueID
-
- // Expected layout of the set after unlocking.
- after []entry
- }{
- {
- name: "unlock zero length on empty set",
- start: 0,
- end: 0,
- uid: 0,
- },
- {
- name: "unlock on empty set (no-op)",
- start: 0,
- end: LockEOF,
- uid: 0,
- },
- {
- name: "unlock uid not locked (no-op)",
- // + --------------------------- +
- // | Readers 1 & 2 |
- // + --------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 1024,
- end: 4096,
- uid: 0,
- // + --------------------------- +
- // | Readers 1 & 2 |
- // + --------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "unlock ReadLock over entire file",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- },
- {
- name: "unlock WriteLock over entire file",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- },
- {
- name: "unlock partial ReadLock (start)",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- // + ------ + --------------------------- +
- // | gap | Readers 0 |
- // +------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock partial WriteLock (start)",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- // + ------ + --------------------------- +
- // | gap | Writer 0 |
- // +------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock partial ReadLock (end)",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 4096,
- end: LockEOF,
- uid: 0,
- // + --------------------------- +
- // | Readers 0 |
- // +---------------------------- +
- // 0 4096
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- },
- },
- {
- name: "unlock partial WriteLock (end)",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 4096,
- end: LockEOF,
- uid: 0,
- // + --------------------------- +
- // | Writer 0 |
- // +---------------------------- +
- // 0 4096
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 4096},
- },
- },
- },
- {
- name: "unlock for single uid",
- // + ------------- + --------- + ------------------- +
- // | Readers 0 & 1 | Writer 0 | Readers 0 & 1 & 2 |
- // + ------------- + --------- + ------------------- +
- // 0 1024 4096 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- // + --------- + --- + --------------- +
- // | Readers 1 | gap | Readers 1 & 2 |
- // + --------- + --- + --------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock subsection locked",
- // + ------------------------------- +
- // | Readers 0 & 1 & 2 |
- // + ------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 1024,
- end: 4096,
- uid: 0,
- // + ----------------- + ------------- + ----------------- +
- // | Readers 0 & 1 & 2 | Readers 1 & 2 | Readers 0 & 1 & 2 |
- // + ----------------- + ------------- + ----------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock mid-gap to increase gap",
- // + --------- + ----- + ------------------- +
- // | Writer 0 | gap | Readers 0 & 1 |
- // + --------- + ----- + ------------------- +
- // 0 1024 4096 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 8,
- end: 2048,
- uid: 0,
- // + --------- + ----- + ------------------- +
- // | Writer 0 | gap | Readers 0 & 1 |
- // + --------- + ----- + ------------------- +
- // 0 8 4096 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 8},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock split region on uid mid-gap",
- // + --------- + ----- + ------------------- +
- // | Writer 0 | gap | Readers 0 & 1 |
- // + --------- + ----- + ------------------- +
- // 0 1024 4096 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 2048,
- end: 8192,
- uid: 0,
- // + --------- + ----- + --------- + ------------- +
- // | Writer 0 | gap | Readers 1 | Readers 0 & 1 |
- // + --------- + ----- + --------- + ------------- +
- // 0 1024 4096 8192 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{4096, 8192},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{8192, LockEOF},
- },
- },
- },
- }
-
- for _, test := range tests {
- l := fill(test.before)
-
- r := LockRange{Start: test.start, End: test.end}
- l.unlock(test.uid, r)
- var got []entry
- for seg := l.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- got = append(got, entry{
- Lock: seg.Value(),
- LockRange: seg.Range(),
- })
- }
- if !equals(got, test.after) {
- t.Errorf("%s: got set %+v, want %+v", test.name, got, test.after)
- }
- }
-}
diff --git a/pkg/sentry/fs/mount_test.go b/pkg/sentry/fs/mount_test.go
deleted file mode 100644
index 0b84732aa..000000000
--- a/pkg/sentry/fs/mount_test.go
+++ /dev/null
@@ -1,271 +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 fs
-
-import (
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
-)
-
-// cacheReallyContains iterates through the dirent cache to determine whether
-// it contains the given dirent.
-func cacheReallyContains(cache *DirentCache, d *Dirent) bool {
- for i := cache.list.Front(); i != nil; i = i.Next() {
- if i == d {
- return true
- }
- }
- return false
-}
-
-func mountPathsAre(root *Dirent, got []*Mount, want ...string) error {
- gotPaths := make(map[string]struct{}, len(got))
- gotStr := make([]string, len(got))
- for i, g := range got {
- groot := g.Root()
- name, _ := groot.FullName(root)
- groot.DecRef()
- gotStr[i] = name
- gotPaths[name] = struct{}{}
- }
- if len(got) != len(want) {
- return fmt.Errorf("mount paths are different, got: %q, want: %q", gotStr, want)
- }
- for _, w := range want {
- if _, ok := gotPaths[w]; !ok {
- return fmt.Errorf("no mount with path %q found", w)
- }
- }
- return nil
-}
-
-// TestMountSourceOnlyCachedOnce tests that a Dirent that is mounted over only ends
-// up in a single Dirent Cache. NOTE(b/63848693): Having a dirent in multiple
-// caches causes major consistency issues.
-func TestMountSourceOnlyCachedOnce(t *testing.T) {
- ctx := contexttest.Context(t)
-
- rootCache := NewDirentCache(100)
- rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{
- Type: Directory,
- })
- mm, err := NewMountNamespace(ctx, rootInode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- rootDirent := mm.Root()
- defer rootDirent.DecRef()
-
- // Get a child of the root which we will mount over. Note that the
- // MockInodeOperations causes Walk to always succeed.
- child, err := rootDirent.Walk(ctx, rootDirent, "child")
- if err != nil {
- t.Fatalf("failed to walk to child dirent: %v", err)
- }
- child.maybeExtendReference() // Cache.
-
- // Ensure that the root cache contains the child.
- if !cacheReallyContains(rootCache, child) {
- t.Errorf("wanted rootCache to contain child dirent, but it did not")
- }
-
- // Create a new cache and inode, and mount it over child.
- submountCache := NewDirentCache(100)
- submountInode := NewMockInode(ctx, NewMockMountSource(submountCache), StableAttr{
- Type: Directory,
- })
- if err := mm.Mount(ctx, child, submountInode); err != nil {
- t.Fatalf("failed to mount over child: %v", err)
- }
-
- // Walk to the child again.
- child2, err := rootDirent.Walk(ctx, rootDirent, "child")
- if err != nil {
- t.Fatalf("failed to walk to child dirent: %v", err)
- }
-
- // Should have a different Dirent than before.
- if child == child2 {
- t.Fatalf("expected %v not equal to %v, but they are the same", child, child2)
- }
-
- // Neither of the caches should no contain the child.
- if cacheReallyContains(rootCache, child) {
- t.Errorf("wanted rootCache not to contain child dirent, but it did")
- }
- if cacheReallyContains(submountCache, child) {
- t.Errorf("wanted submountCache not to contain child dirent, but it did")
- }
-}
-
-func TestAllMountsUnder(t *testing.T) {
- ctx := contexttest.Context(t)
-
- rootCache := NewDirentCache(100)
- rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{
- Type: Directory,
- })
- mm, err := NewMountNamespace(ctx, rootInode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- rootDirent := mm.Root()
- defer rootDirent.DecRef()
-
- // Add mounts at the following paths:
- paths := []string{
- "/foo",
- "/foo/bar",
- "/foo/bar/baz",
- "/foo/qux",
- "/waldo",
- }
-
- var maxTraversals uint
- for _, p := range paths {
- maxTraversals = 0
- d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", p, err)
- }
-
- submountInode := NewMockInode(ctx, NewMockMountSource(nil), StableAttr{
- Type: Directory,
- })
- if err := mm.Mount(ctx, d, submountInode); err != nil {
- t.Fatalf("could not mount at %q: %v", p, err)
- }
- d.DecRef()
- }
-
- // mm root should contain all submounts (and does not include the root mount).
- rootMnt := mm.FindMount(rootDirent)
- submounts := mm.AllMountsUnder(rootMnt)
- allPaths := append(paths, "/")
- if err := mountPathsAre(rootDirent, submounts, allPaths...); err != nil {
- t.Error(err)
- }
-
- // Each mount should have a unique ID.
- foundIDs := make(map[uint64]struct{})
- for _, m := range submounts {
- if _, ok := foundIDs[m.ID]; ok {
- t.Errorf("got multiple mounts with id %d", m.ID)
- }
- foundIDs[m.ID] = struct{}{}
- }
-
- // Root mount should have no parent.
- if p := rootMnt.ParentID; p != invalidMountID {
- t.Errorf("root.Parent got %v wanted nil", p)
- }
-
- // Check that "foo" mount has 3 children.
- maxTraversals = 0
- d, err := mm.FindLink(ctx, rootDirent, nil, "/foo", &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", "/foo", err)
- }
- defer d.DecRef()
- submounts = mm.AllMountsUnder(mm.FindMount(d))
- if err := mountPathsAre(rootDirent, submounts, "/foo", "/foo/bar", "/foo/qux", "/foo/bar/baz"); err != nil {
- t.Error(err)
- }
-
- // "waldo" mount should have no children.
- maxTraversals = 0
- waldo, err := mm.FindLink(ctx, rootDirent, nil, "/waldo", &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", "/waldo", err)
- }
- defer waldo.DecRef()
- submounts = mm.AllMountsUnder(mm.FindMount(waldo))
- if err := mountPathsAre(rootDirent, submounts, "/waldo"); err != nil {
- t.Error(err)
- }
-}
-
-func TestUnmount(t *testing.T) {
- ctx := contexttest.Context(t)
-
- rootCache := NewDirentCache(100)
- rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{
- Type: Directory,
- })
- mm, err := NewMountNamespace(ctx, rootInode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- rootDirent := mm.Root()
- defer rootDirent.DecRef()
-
- // Add mounts at the following paths:
- paths := []string{
- "/foo",
- "/foo/bar",
- "/foo/bar/goo",
- "/foo/bar/goo/abc",
- "/foo/abc",
- "/foo/def",
- "/waldo",
- "/wally",
- }
-
- var maxTraversals uint
- for _, p := range paths {
- maxTraversals = 0
- d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", p, err)
- }
-
- submountInode := NewMockInode(ctx, NewMockMountSource(nil), StableAttr{
- Type: Directory,
- })
- if err := mm.Mount(ctx, d, submountInode); err != nil {
- t.Fatalf("could not mount at %q: %v", p, err)
- }
- d.DecRef()
- }
-
- allPaths := make([]string, len(paths)+1)
- allPaths[0] = "/"
- copy(allPaths[1:], paths)
-
- rootMnt := mm.FindMount(rootDirent)
- for i := len(paths) - 1; i >= 0; i-- {
- maxTraversals = 0
- p := paths[i]
- d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", p, err)
- }
-
- if err := mm.Unmount(ctx, d, false); err != nil {
- t.Fatalf("could not unmount at %q: %v", p, err)
- }
- d.DecRef()
-
- // Remove the path that has been unmounted and the check that the remaining
- // mounts are still there.
- allPaths = allPaths[:len(allPaths)-1]
- submounts := mm.AllMountsUnder(rootMnt)
- if err := mountPathsAre(rootDirent, submounts, allPaths...); err != nil {
- t.Error(err)
- }
- }
-}
diff --git a/pkg/sentry/fs/mounts_test.go b/pkg/sentry/fs/mounts_test.go
deleted file mode 100644
index c4c771f2c..000000000
--- a/pkg/sentry/fs/mounts_test.go
+++ /dev/null
@@ -1,105 +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 fs_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
-)
-
-// Creates a new MountNamespace with filesystem:
-// / (root dir)
-// |-foo (dir)
-// |-bar (file)
-func createMountNamespace(ctx context.Context) (*fs.MountNamespace, error) {
- perms := fs.FilePermsFromMode(0777)
- m := fs.NewPseudoMountSource(ctx)
-
- barFile := fsutil.NewSimpleFileInode(ctx, fs.RootOwner, perms, 0)
- fooDir := ramfs.NewDir(ctx, map[string]*fs.Inode{
- "bar": fs.NewInode(ctx, barFile, m, fs.StableAttr{Type: fs.RegularFile}),
- }, fs.RootOwner, perms)
- rootDir := ramfs.NewDir(ctx, map[string]*fs.Inode{
- "foo": fs.NewInode(ctx, fooDir, m, fs.StableAttr{Type: fs.Directory}),
- }, fs.RootOwner, perms)
-
- return fs.NewMountNamespace(ctx, fs.NewInode(ctx, rootDir, m, fs.StableAttr{Type: fs.Directory}))
-}
-
-func TestFindLink(t *testing.T) {
- ctx := contexttest.Context(t)
- mm, err := createMountNamespace(ctx)
- if err != nil {
- t.Fatalf("createMountNamespace failed: %v", err)
- }
-
- root := mm.Root()
- defer root.DecRef()
- foo, err := root.Walk(ctx, root, "foo")
- if err != nil {
- t.Fatalf("Error walking to foo: %v", err)
- }
-
- // Positive cases.
- for _, tc := range []struct {
- findPath string
- wd *fs.Dirent
- wantPath string
- }{
- {".", root, "/"},
- {".", foo, "/foo"},
- {"..", foo, "/"},
- {"../../..", foo, "/"},
- {"///foo", foo, "/foo"},
- {"/foo", foo, "/foo"},
- {"/foo/bar", foo, "/foo/bar"},
- {"/foo/.///./bar", foo, "/foo/bar"},
- {"/foo///bar", foo, "/foo/bar"},
- {"/foo/../foo/bar", foo, "/foo/bar"},
- {"foo/bar", root, "/foo/bar"},
- {"foo////bar", root, "/foo/bar"},
- {"bar", foo, "/foo/bar"},
- } {
- wdPath, _ := tc.wd.FullName(root)
- maxTraversals := uint(0)
- if d, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err != nil {
- t.Errorf("FindLink(%q, wd=%q) failed: %v", tc.findPath, wdPath, err)
- } else if got, _ := d.FullName(root); got != tc.wantPath {
- t.Errorf("FindLink(%q, wd=%q) got dirent %q, want %q", tc.findPath, wdPath, got, tc.wantPath)
- }
- }
-
- // Negative cases.
- for _, tc := range []struct {
- findPath string
- wd *fs.Dirent
- }{
- {"bar", root},
- {"/bar", root},
- {"/foo/../../bar", root},
- {"foo", foo},
- } {
- wdPath, _ := tc.wd.FullName(root)
- maxTraversals := uint(0)
- if _, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err == nil {
- t.Errorf("FindLink(%q, wd=%q) did not return error", tc.findPath, wdPath)
- }
- }
-}
diff --git a/pkg/sentry/fs/path_test.go b/pkg/sentry/fs/path_test.go
deleted file mode 100644
index e6f57ebba..000000000
--- a/pkg/sentry/fs/path_test.go
+++ /dev/null
@@ -1,289 +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 fs
-
-import (
- "testing"
-)
-
-// TestSplitLast tests variants of path splitting.
-func TestSplitLast(t *testing.T) {
- cases := []struct {
- path string
- dir string
- file string
- }{
- {path: "/", dir: "/", file: "."},
- {path: "/.", dir: "/", file: "."},
- {path: "/./", dir: "/", file: "."},
- {path: "/./.", dir: "/.", file: "."},
- {path: "/././", dir: "/.", file: "."},
- {path: "/./..", dir: "/.", file: ".."},
- {path: "/./../", dir: "/.", file: ".."},
- {path: "/..", dir: "/", file: ".."},
- {path: "/../", dir: "/", file: ".."},
- {path: "/../.", dir: "/..", file: "."},
- {path: "/.././", dir: "/..", file: "."},
- {path: "/../..", dir: "/..", file: ".."},
- {path: "/../../", dir: "/..", file: ".."},
-
- {path: "", dir: ".", file: "."},
- {path: ".", dir: ".", file: "."},
- {path: "./", dir: ".", file: "."},
- {path: "./.", dir: ".", file: "."},
- {path: "././", dir: ".", file: "."},
- {path: "./..", dir: ".", file: ".."},
- {path: "./../", dir: ".", file: ".."},
- {path: "..", dir: ".", file: ".."},
- {path: "../", dir: ".", file: ".."},
- {path: "../.", dir: "..", file: "."},
- {path: ".././", dir: "..", file: "."},
- {path: "../..", dir: "..", file: ".."},
- {path: "../../", dir: "..", file: ".."},
-
- {path: "/foo", dir: "/", file: "foo"},
- {path: "/foo/", dir: "/", file: "foo"},
- {path: "/foo/.", dir: "/foo", file: "."},
- {path: "/foo/./", dir: "/foo", file: "."},
- {path: "/foo/./.", dir: "/foo/.", file: "."},
- {path: "/foo/./..", dir: "/foo/.", file: ".."},
- {path: "/foo/..", dir: "/foo", file: ".."},
- {path: "/foo/../", dir: "/foo", file: ".."},
- {path: "/foo/../.", dir: "/foo/..", file: "."},
- {path: "/foo/../..", dir: "/foo/..", file: ".."},
-
- {path: "/foo/bar", dir: "/foo", file: "bar"},
- {path: "/foo/bar/", dir: "/foo", file: "bar"},
- {path: "/foo/bar/.", dir: "/foo/bar", file: "."},
- {path: "/foo/bar/./", dir: "/foo/bar", file: "."},
- {path: "/foo/bar/./.", dir: "/foo/bar/.", file: "."},
- {path: "/foo/bar/./..", dir: "/foo/bar/.", file: ".."},
- {path: "/foo/bar/..", dir: "/foo/bar", file: ".."},
- {path: "/foo/bar/../", dir: "/foo/bar", file: ".."},
- {path: "/foo/bar/../.", dir: "/foo/bar/..", file: "."},
- {path: "/foo/bar/../..", dir: "/foo/bar/..", file: ".."},
-
- {path: "foo", dir: ".", file: "foo"},
- {path: "foo", dir: ".", file: "foo"},
- {path: "foo/", dir: ".", file: "foo"},
- {path: "foo/.", dir: "foo", file: "."},
- {path: "foo/./", dir: "foo", file: "."},
- {path: "foo/./.", dir: "foo/.", file: "."},
- {path: "foo/./..", dir: "foo/.", file: ".."},
- {path: "foo/..", dir: "foo", file: ".."},
- {path: "foo/../", dir: "foo", file: ".."},
- {path: "foo/../.", dir: "foo/..", file: "."},
- {path: "foo/../..", dir: "foo/..", file: ".."},
- {path: "foo/", dir: ".", file: "foo"},
- {path: "foo/.", dir: "foo", file: "."},
-
- {path: "foo/bar", dir: "foo", file: "bar"},
- {path: "foo/bar/", dir: "foo", file: "bar"},
- {path: "foo/bar/.", dir: "foo/bar", file: "."},
- {path: "foo/bar/./", dir: "foo/bar", file: "."},
- {path: "foo/bar/./.", dir: "foo/bar/.", file: "."},
- {path: "foo/bar/./..", dir: "foo/bar/.", file: ".."},
- {path: "foo/bar/..", dir: "foo/bar", file: ".."},
- {path: "foo/bar/../", dir: "foo/bar", file: ".."},
- {path: "foo/bar/../.", dir: "foo/bar/..", file: "."},
- {path: "foo/bar/../..", dir: "foo/bar/..", file: ".."},
- {path: "foo/bar/", dir: "foo", file: "bar"},
- {path: "foo/bar/.", dir: "foo/bar", file: "."},
- }
-
- for _, c := range cases {
- dir, file := SplitLast(c.path)
- if dir != c.dir || file != c.file {
- t.Errorf("SplitLast(%q) got (%q, %q), expected (%q, %q)", c.path, dir, file, c.dir, c.file)
- }
- }
-}
-
-// TestSplitFirst tests variants of path splitting.
-func TestSplitFirst(t *testing.T) {
- cases := []struct {
- path string
- first string
- remainder string
- }{
- {path: "/", first: "/", remainder: ""},
- {path: "/.", first: "/", remainder: "."},
- {path: "///.", first: "/", remainder: "//."},
- {path: "/.///", first: "/", remainder: "."},
- {path: "/./.", first: "/", remainder: "./."},
- {path: "/././", first: "/", remainder: "./."},
- {path: "/./..", first: "/", remainder: "./.."},
- {path: "/./../", first: "/", remainder: "./.."},
- {path: "/..", first: "/", remainder: ".."},
- {path: "/../", first: "/", remainder: ".."},
- {path: "/../.", first: "/", remainder: "../."},
- {path: "/.././", first: "/", remainder: "../."},
- {path: "/../..", first: "/", remainder: "../.."},
- {path: "/../../", first: "/", remainder: "../.."},
-
- {path: "", first: ".", remainder: ""},
- {path: ".", first: ".", remainder: ""},
- {path: "./", first: ".", remainder: ""},
- {path: ".///", first: ".", remainder: ""},
- {path: "./.", first: ".", remainder: "."},
- {path: "././", first: ".", remainder: "."},
- {path: "./..", first: ".", remainder: ".."},
- {path: "./../", first: ".", remainder: ".."},
- {path: "..", first: "..", remainder: ""},
- {path: "../", first: "..", remainder: ""},
- {path: "../.", first: "..", remainder: "."},
- {path: ".././", first: "..", remainder: "."},
- {path: "../..", first: "..", remainder: ".."},
- {path: "../../", first: "..", remainder: ".."},
-
- {path: "/foo", first: "/", remainder: "foo"},
- {path: "/foo/", first: "/", remainder: "foo"},
- {path: "/foo///", first: "/", remainder: "foo"},
- {path: "/foo/.", first: "/", remainder: "foo/."},
- {path: "/foo/./", first: "/", remainder: "foo/."},
- {path: "/foo/./.", first: "/", remainder: "foo/./."},
- {path: "/foo/./..", first: "/", remainder: "foo/./.."},
- {path: "/foo/..", first: "/", remainder: "foo/.."},
- {path: "/foo/../", first: "/", remainder: "foo/.."},
- {path: "/foo/../.", first: "/", remainder: "foo/../."},
- {path: "/foo/../..", first: "/", remainder: "foo/../.."},
-
- {path: "/foo/bar", first: "/", remainder: "foo/bar"},
- {path: "///foo/bar", first: "/", remainder: "//foo/bar"},
- {path: "/foo///bar", first: "/", remainder: "foo///bar"},
- {path: "/foo/bar/.", first: "/", remainder: "foo/bar/."},
- {path: "/foo/bar/./", first: "/", remainder: "foo/bar/."},
- {path: "/foo/bar/./.", first: "/", remainder: "foo/bar/./."},
- {path: "/foo/bar/./..", first: "/", remainder: "foo/bar/./.."},
- {path: "/foo/bar/..", first: "/", remainder: "foo/bar/.."},
- {path: "/foo/bar/../", first: "/", remainder: "foo/bar/.."},
- {path: "/foo/bar/../.", first: "/", remainder: "foo/bar/../."},
- {path: "/foo/bar/../..", first: "/", remainder: "foo/bar/../.."},
-
- {path: "foo", first: "foo", remainder: ""},
- {path: "foo", first: "foo", remainder: ""},
- {path: "foo/", first: "foo", remainder: ""},
- {path: "foo///", first: "foo", remainder: ""},
- {path: "foo/.", first: "foo", remainder: "."},
- {path: "foo/./", first: "foo", remainder: "."},
- {path: "foo/./.", first: "foo", remainder: "./."},
- {path: "foo/./..", first: "foo", remainder: "./.."},
- {path: "foo/..", first: "foo", remainder: ".."},
- {path: "foo/../", first: "foo", remainder: ".."},
- {path: "foo/../.", first: "foo", remainder: "../."},
- {path: "foo/../..", first: "foo", remainder: "../.."},
- {path: "foo/", first: "foo", remainder: ""},
- {path: "foo/.", first: "foo", remainder: "."},
-
- {path: "foo/bar", first: "foo", remainder: "bar"},
- {path: "foo///bar", first: "foo", remainder: "bar"},
- {path: "foo/bar/", first: "foo", remainder: "bar"},
- {path: "foo/bar/.", first: "foo", remainder: "bar/."},
- {path: "foo/bar/./", first: "foo", remainder: "bar/."},
- {path: "foo/bar/./.", first: "foo", remainder: "bar/./."},
- {path: "foo/bar/./..", first: "foo", remainder: "bar/./.."},
- {path: "foo/bar/..", first: "foo", remainder: "bar/.."},
- {path: "foo/bar/../", first: "foo", remainder: "bar/.."},
- {path: "foo/bar/../.", first: "foo", remainder: "bar/../."},
- {path: "foo/bar/../..", first: "foo", remainder: "bar/../.."},
- {path: "foo/bar/", first: "foo", remainder: "bar"},
- {path: "foo/bar/.", first: "foo", remainder: "bar/."},
- }
-
- for _, c := range cases {
- first, remainder := SplitFirst(c.path)
- if first != c.first || remainder != c.remainder {
- t.Errorf("SplitFirst(%q) got (%q, %q), expected (%q, %q)", c.path, first, remainder, c.first, c.remainder)
- }
- }
-}
-
-// TestIsSubpath tests the IsSubpath method.
-func TestIsSubpath(t *testing.T) {
- tcs := []struct {
- // Two absolute paths.
- pathA string
- pathB string
-
- // Whether pathA is a subpath of pathB.
- wantIsSubpath bool
-
- // Relative path from pathA to pathB. Only checked if
- // wantIsSubpath is true.
- wantRelpath string
- }{
- {
- pathA: "/foo/bar/baz",
- pathB: "/foo",
- wantIsSubpath: true,
- wantRelpath: "bar/baz",
- },
- {
- pathA: "/foo",
- pathB: "/foo/bar/baz",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/foo",
- wantIsSubpath: false,
- },
- {
- pathA: "/foobar",
- pathB: "/foo",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/foobar",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/foobar",
- wantIsSubpath: false,
- },
- {
- pathA: "/",
- pathB: "/foo",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/",
- wantIsSubpath: true,
- wantRelpath: "foo",
- },
- {
- pathA: "/foo/bar/../bar",
- pathB: "/foo",
- wantIsSubpath: true,
- wantRelpath: "bar",
- },
- {
- pathA: "/foo/bar",
- pathB: "/foo/../foo",
- wantIsSubpath: true,
- wantRelpath: "bar",
- },
- }
-
- for _, tc := range tcs {
- gotRelpath, gotIsSubpath := IsSubpath(tc.pathA, tc.pathB)
- if gotRelpath != tc.wantRelpath || gotIsSubpath != tc.wantIsSubpath {
- t.Errorf("IsSubpath(%q, %q) got %q %t, want %q %t", tc.pathA, tc.pathB, gotRelpath, gotIsSubpath, tc.wantRelpath, tc.wantIsSubpath)
- }
- }
-}
diff --git a/pkg/sentry/fs/proc/BUILD b/pkg/sentry/fs/proc/BUILD
deleted file mode 100644
index 75cbb0622..000000000
--- a/pkg/sentry/fs/proc/BUILD
+++ /dev/null
@@ -1,74 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "proc",
- srcs = [
- "cgroup.go",
- "cpuinfo.go",
- "exec_args.go",
- "fds.go",
- "filesystems.go",
- "fs.go",
- "inode.go",
- "loadavg.go",
- "meminfo.go",
- "mounts.go",
- "net.go",
- "proc.go",
- "rpcinet_proc.go",
- "stat.go",
- "sys.go",
- "sys_net.go",
- "sys_net_state.go",
- "task.go",
- "uid_gid_map.go",
- "uptime.go",
- "version.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/proc",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/proc/device",
- "//pkg/sentry/fs/proc/seqfile",
- "//pkg/sentry/fs/ramfs",
- "//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/rpcinet",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/tcpip/header",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "proc_test",
- size = "small",
- srcs = [
- "net_test.go",
- "sys_net_test.go",
- ],
- embed = [":proc"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/inet",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/proc/README.md b/pkg/sentry/fs/proc/README.md
deleted file mode 100644
index 5d4ec6c7b..000000000
--- a/pkg/sentry/fs/proc/README.md
+++ /dev/null
@@ -1,332 +0,0 @@
-This document tracks what is implemented in procfs. Refer to
-Documentation/filesystems/proc.txt in the Linux project for information about
-procfs generally.
-
-**NOTE**: This document is not guaranteed to be up to date. If you find an
-inconsistency, please file a bug.
-
-[TOC]
-
-## Kernel data
-
-The following files are implemented:
-
-| File /proc/ | Content |
-| :------------------------ | :---------------------------------------------------- |
-| [cpuinfo](#cpuinfo) | Info about the CPU |
-| [filesystems](#filesystems) | Supported filesystems |
-| [loadavg](#loadavg) | Load average of last 1, 5 & 15 minutes |
-| [meminfo](#meminfo) | Overall memory info |
-| [stat](#stat) | Overall kernel statistics |
-| [sys](#sys) | Change parameters within the kernel |
-| [uptime](#uptime) | Wall clock since boot, combined idle time of all cpus |
-| [version](#version) | Kernel version |
-
-### cpuinfo
-
-```bash
-$ cat /proc/cpuinfo
-processor : 0
-vendor_id : GenuineIntel
-cpu family : 6
-model : 45
-model name : unknown
-stepping : unknown
-cpu MHz : 1234.588
-fpu : yes
-fpu_exception : yes
-cpuid level : 13
-wp : yes
-flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx xsaveopt
-bogomips : 1234.59
-clflush size : 64
-cache_alignment : 64
-address sizes : 46 bits physical, 48 bits virtual
-power management:
-
-...
-```
-
-Notable divergences:
-
-Field name | Notes
-:--------------- | :---------------------------------------
-model name | Always unknown
-stepping | Always unknown
-fpu | Always yes
-fpu_exception | Always yes
-wp | Always yes
-bogomips | Bogus value (matches cpu MHz)
-clflush size | Always 64
-cache_alignment | Always 64
-address sizes | Always 46 bits physical, 48 bits virtual
-power management | Always blank
-
-Otherwise fields are derived from the sentry configuration.
-
-### filesystems
-
-```bash
-$ cat /proc/filesystems
-nodev 9p
-nodev devpts
-nodev devtmpfs
-nodev proc
-nodev sysfs
-nodev tmpfs
-```
-
-### loadavg
-
-```bash
-$ cat /proc/loadavg
-0.00 0.00 0.00 0/0 0
-```
-
-Column | Notes
-:------------------------------------ | :----------
-CPU.IO utilization in last 1 minute | Always zero
-CPU.IO utilization in last 5 minutes | Always zero
-CPU.IO utilization in last 10 minutes | Always zero
-Num currently running processes | Always zero
-Total num processes | Always zero
-
-TODO(b/62345059): Populate the columns with accurate statistics.
-
-### meminfo
-
-```bash
-$ cat /proc/meminfo
-MemTotal: 2097152 kB
-MemFree: 2083540 kB
-MemAvailable: 2083540 kB
-Buffers: 0 kB
-Cached: 4428 kB
-SwapCache: 0 kB
-Active: 10812 kB
-Inactive: 2216 kB
-Active(anon): 8600 kB
-Inactive(anon): 0 kB
-Active(file): 2212 kB
-Inactive(file): 2216 kB
-Unevictable: 0 kB
-Mlocked: 0 kB
-SwapTotal: 0 kB
-SwapFree: 0 kB
-Dirty: 0 kB
-Writeback: 0 kB
-AnonPages: 8600 kB
-Mapped: 4428 kB
-Shmem: 0 kB
-
-```
-
-Notable divergences:
-
-Field name | Notes
-:---------------- | :-----------------------------------------------------
-Buffers | Always zero, no block devices
-SwapCache | Always zero, no swap
-Inactive(anon) | Always zero, see SwapCache
-Unevictable | Always zero TODO(b/31823263)
-Mlocked | Always zero TODO(b/31823263)
-SwapTotal | Always zero, no swap
-SwapFree | Always zero, no swap
-Dirty | Always zero TODO(b/31823263)
-Writeback | Always zero TODO(b/31823263)
-MemAvailable | Uses the same value as MemFree since there is no swap.
-Slab | Missing
-SReclaimable | Missing
-SUnreclaim | Missing
-KernelStack | Missing
-PageTables | Missing
-NFS_Unstable | Missing
-Bounce | Missing
-WritebackTmp | Missing
-CommitLimit | Missing
-Committed_AS | Missing
-VmallocTotal | Missing
-VmallocUsed | Missing
-VmallocChunk | Missing
-HardwareCorrupted | Missing
-AnonHugePages | Missing
-ShmemHugePages | Missing
-ShmemPmdMapped | Missing
-HugePages_Total | Missing
-HugePages_Free | Missing
-HugePages_Rsvd | Missing
-HugePages_Surp | Missing
-Hugepagesize | Missing
-DirectMap4k | Missing
-DirectMap2M | Missing
-DirectMap1G | Missing
-
-### stat
-
-```bash
-$ cat /proc/stat
-cpu 0 0 0 0 0 0 0 0 0 0
-cpu0 0 0 0 0 0 0 0 0 0 0
-cpu1 0 0 0 0 0 0 0 0 0 0
-cpu2 0 0 0 0 0 0 0 0 0 0
-cpu3 0 0 0 0 0 0 0 0 0 0
-cpu4 0 0 0 0 0 0 0 0 0 0
-cpu5 0 0 0 0 0 0 0 0 0 0
-cpu6 0 0 0 0 0 0 0 0 0 0
-cpu7 0 0 0 0 0 0 0 0 0 0
-intr 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-ctxt 0
-btime 1504040968
-processes 0
-procs_running 0
-procs_blokkcked 0
-softirq 0 0 0 0 0 0 0 0 0 0 0
-```
-
-All fields except for `btime` are always zero.
-
-TODO(b/37226836): Populate with accurate fields.
-
-### sys
-
-```bash
-$ ls /proc/sys
-kernel vm
-```
-
-Directory | Notes
-:-------- | :----------------------------
-abi | Missing
-debug | Missing
-dev | Missing
-fs | Missing
-kernel | Contains hostname (only)
-net | Missing
-user | Missing
-vm | Contains mmap_min_addr (only)
-
-### uptime
-
-```bash
-$ cat /proc/uptime
-3204.62 0.00
-```
-
-Column | Notes
-:------------------------------- | :----------------------------
-Total num seconds system running | Time since procfs was mounted
-Number of seconds idle | Always zero
-
-### version
-
-```bash
-$ cat /proc/version
-Linux version 4.4 #1 SMP Sun Jan 10 15:06:54 PST 2016
-```
-
-## Process-specific data
-
-The following files are implemented:
-
-File /proc/PID | Content
-:---------------------- | :---------------------------------------------------
-[auxv](#auxv) | Copy of auxiliary vector for the process
-[cmdline](#cmdline) | Command line arguments
-[comm](#comm) | Command name associated with the process
-[environ](#environ) | Process environment
-[exe](#exe) | Symlink to the process's executable
-[fd](#fd) | Directory containing links to open file descriptors
-[fdinfo](#fdinfo) | Information associated with open file descriptors
-[gid_map](#gid_map) | Mappings for group IDs inside the user namespace
-[io](#io) | IO statistics
-[maps](#maps) | Memory mappings (anon, executables, library files)
-[mounts](#mounts) | Mounted filesystems
-[mountinfo](#mountinfo) | Information about mounts
-[ns](#ns) | Directory containing info about supported namespaces
-[stat](#stat) | Process statistics
-[statm](#statm) | Process memory statistics
-[status](#status) | Process status in human readable format
-[task](#task) | Directory containing info about running threads
-[uid_map](#uid_map) | Mappings for user IDs inside the user namespace
-
-### auxv
-
-TODO
-
-### cmdline
-
-TODO
-
-### comm
-
-TODO
-
-### environment
-
-TODO
-
-### exe
-
-TODO
-
-### fd
-
-TODO
-
-### fdinfo
-
-TODO
-
-### gid_map
-
-TODO
-
-### io
-
-Only has data for rchar, wchar, syscr, and syscw.
-
-TODO: add more detail.
-
-### maps
-
-TODO
-
-### mounts
-
-TODO
-
-### mountinfo
-
-TODO
-
-### ns
-
-TODO
-
-### stat
-
-Only has data for pid, comm, state, ppid, utime, stime, cutime, cstime,
-num_threads, and exit_signal.
-
-TODO: add more detail.
-
-### statm
-
-Only has data for vss and rss.
-
-TODO: add more detail.
-
-### status
-
-Contains data for Name, State, Tgid, Pid, Ppid, TracerPid, FDSize, VmSize,
-VmRSS, Threads, CapInh, CapPrm, CapEff, CapBnd, Seccomp.
-
-TODO: add more detail.
-
-### task
-
-TODO
-
-### uid_map
-
-TODO
diff --git a/pkg/sentry/fs/proc/device/BUILD b/pkg/sentry/fs/proc/device/BUILD
deleted file mode 100644
index 0394451d4..000000000
--- a/pkg/sentry/fs/proc/device/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "device",
- srcs = ["device.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/proc/device",
- visibility = ["//pkg/sentry:internal"],
- deps = ["//pkg/sentry/device"],
-)
diff --git a/pkg/sentry/fs/proc/device/device_state_autogen.go b/pkg/sentry/fs/proc/device/device_state_autogen.go
new file mode 100755
index 000000000..be407ac45
--- /dev/null
+++ b/pkg/sentry/fs/proc/device/device_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package device
+
diff --git a/pkg/sentry/fs/proc/net_test.go b/pkg/sentry/fs/proc/net_test.go
deleted file mode 100644
index f18681405..000000000
--- a/pkg/sentry/fs/proc/net_test.go
+++ /dev/null
@@ -1,74 +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 proc
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/inet"
-)
-
-func newIPv6TestStack() *inet.TestStack {
- s := inet.NewTestStack()
- s.SupportsIPv6Flag = true
- return s
-}
-
-func TestIfinet6NoAddresses(t *testing.T) {
- n := &ifinet6{s: newIPv6TestStack()}
- if got := n.contents(); got != nil {
- t.Errorf("Got n.contents() = %v, want = %v", got, nil)
- }
-}
-
-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{s: 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)
- }
-}
diff --git a/pkg/sentry/fs/proc/proc_state_autogen.go b/pkg/sentry/fs/proc/proc_state_autogen.go
new file mode 100755
index 000000000..eedd011a8
--- /dev/null
+++ b/pkg/sentry/fs/proc/proc_state_autogen.go
@@ -0,0 +1,717 @@
+// automatically generated by stateify.
+
+package proc
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *execArgInode) beforeSave() {}
+func (x *execArgInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("arg", &x.arg)
+ m.Save("t", &x.t)
+}
+
+func (x *execArgInode) afterLoad() {}
+func (x *execArgInode) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("arg", &x.arg)
+ m.Load("t", &x.t)
+}
+
+func (x *execArgFile) beforeSave() {}
+func (x *execArgFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("arg", &x.arg)
+ m.Save("t", &x.t)
+}
+
+func (x *execArgFile) afterLoad() {}
+func (x *execArgFile) load(m state.Map) {
+ m.Load("arg", &x.arg)
+ m.Load("t", &x.t)
+}
+
+func (x *fdDir) beforeSave() {}
+func (x *fdDir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+}
+
+func (x *fdDir) afterLoad() {}
+func (x *fdDir) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+}
+
+func (x *fdDirFile) beforeSave() {}
+func (x *fdDirFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("isInfoFile", &x.isInfoFile)
+ m.Save("t", &x.t)
+}
+
+func (x *fdDirFile) afterLoad() {}
+func (x *fdDirFile) load(m state.Map) {
+ m.Load("isInfoFile", &x.isInfoFile)
+ m.Load("t", &x.t)
+}
+
+func (x *fdInfoDir) beforeSave() {}
+func (x *fdInfoDir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+}
+
+func (x *fdInfoDir) afterLoad() {}
+func (x *fdInfoDir) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+}
+
+func (x *filesystemsData) beforeSave() {}
+func (x *filesystemsData) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystemsData) afterLoad() {}
+func (x *filesystemsData) load(m state.Map) {
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *taskOwnedInodeOps) beforeSave() {}
+func (x *taskOwnedInodeOps) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeOperations", &x.InodeOperations)
+ m.Save("t", &x.t)
+}
+
+func (x *taskOwnedInodeOps) afterLoad() {}
+func (x *taskOwnedInodeOps) load(m state.Map) {
+ m.Load("InodeOperations", &x.InodeOperations)
+ m.Load("t", &x.t)
+}
+
+func (x *staticFileInodeOps) beforeSave() {}
+func (x *staticFileInodeOps) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *staticFileInodeOps) afterLoad() {}
+func (x *staticFileInodeOps) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *loadavgData) beforeSave() {}
+func (x *loadavgData) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *loadavgData) afterLoad() {}
+func (x *loadavgData) load(m state.Map) {
+}
+
+func (x *meminfoData) beforeSave() {}
+func (x *meminfoData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *meminfoData) afterLoad() {}
+func (x *meminfoData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *mountInfoFile) beforeSave() {}
+func (x *mountInfoFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *mountInfoFile) afterLoad() {}
+func (x *mountInfoFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *mountsFile) beforeSave() {}
+func (x *mountsFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *mountsFile) afterLoad() {}
+func (x *mountsFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *ifinet6) beforeSave() {}
+func (x *ifinet6) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *ifinet6) afterLoad() {}
+func (x *ifinet6) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *netDev) beforeSave() {}
+func (x *netDev) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *netDev) afterLoad() {}
+func (x *netDev) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *netSnmp) beforeSave() {}
+func (x *netSnmp) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *netSnmp) afterLoad() {}
+func (x *netSnmp) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *netRoute) beforeSave() {}
+func (x *netRoute) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *netRoute) afterLoad() {}
+func (x *netRoute) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *netUnix) beforeSave() {}
+func (x *netUnix) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netUnix) afterLoad() {}
+func (x *netUnix) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *netTCP) beforeSave() {}
+func (x *netTCP) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netTCP) afterLoad() {}
+func (x *netTCP) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *netTCP6) beforeSave() {}
+func (x *netTCP6) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netTCP6) afterLoad() {}
+func (x *netTCP6) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *netUDP) beforeSave() {}
+func (x *netUDP) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netUDP) afterLoad() {}
+func (x *netUDP) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *proc) beforeSave() {}
+func (x *proc) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("k", &x.k)
+ m.Save("pidns", &x.pidns)
+ m.Save("cgroupControllers", &x.cgroupControllers)
+}
+
+func (x *proc) afterLoad() {}
+func (x *proc) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("k", &x.k)
+ m.Load("pidns", &x.pidns)
+ m.Load("cgroupControllers", &x.cgroupControllers)
+}
+
+func (x *self) beforeSave() {}
+func (x *self) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *self) afterLoad() {}
+func (x *self) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *threadSelf) beforeSave() {}
+func (x *threadSelf) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *threadSelf) afterLoad() {}
+func (x *threadSelf) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *rootProcFile) beforeSave() {}
+func (x *rootProcFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+}
+
+func (x *rootProcFile) afterLoad() {}
+func (x *rootProcFile) load(m state.Map) {
+ m.Load("iops", &x.iops)
+}
+
+func (x *statData) beforeSave() {}
+func (x *statData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *statData) afterLoad() {}
+func (x *statData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *mmapMinAddrData) beforeSave() {}
+func (x *mmapMinAddrData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *mmapMinAddrData) afterLoad() {}
+func (x *mmapMinAddrData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *overcommitMemory) beforeSave() {}
+func (x *overcommitMemory) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *overcommitMemory) afterLoad() {}
+func (x *overcommitMemory) load(m state.Map) {
+}
+
+func (x *hostname) beforeSave() {}
+func (x *hostname) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+}
+
+func (x *hostname) afterLoad() {}
+func (x *hostname) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+}
+
+func (x *hostnameFile) beforeSave() {}
+func (x *hostnameFile) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *hostnameFile) afterLoad() {}
+func (x *hostnameFile) load(m state.Map) {
+}
+
+func (x *tcpMemInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("dir", &x.dir)
+ m.Save("s", &x.s)
+ m.Save("size", &x.size)
+}
+
+func (x *tcpMemInode) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("dir", &x.dir)
+ m.LoadWait("s", &x.s)
+ m.Load("size", &x.size)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *tcpMemFile) beforeSave() {}
+func (x *tcpMemFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tcpMemInode", &x.tcpMemInode)
+}
+
+func (x *tcpMemFile) afterLoad() {}
+func (x *tcpMemFile) load(m state.Map) {
+ m.Load("tcpMemInode", &x.tcpMemInode)
+}
+
+func (x *tcpSack) beforeSave() {}
+func (x *tcpSack) save(m state.Map) {
+ x.beforeSave()
+ m.Save("stack", &x.stack)
+ m.Save("enabled", &x.enabled)
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+}
+
+func (x *tcpSack) load(m state.Map) {
+ m.LoadWait("stack", &x.stack)
+ m.Load("enabled", &x.enabled)
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *tcpSackFile) beforeSave() {}
+func (x *tcpSackFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tcpSack", &x.tcpSack)
+ m.Save("stack", &x.stack)
+}
+
+func (x *tcpSackFile) afterLoad() {}
+func (x *tcpSackFile) load(m state.Map) {
+ m.Load("tcpSack", &x.tcpSack)
+ m.LoadWait("stack", &x.stack)
+}
+
+func (x *taskDir) beforeSave() {}
+func (x *taskDir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *taskDir) afterLoad() {}
+func (x *taskDir) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *subtasks) beforeSave() {}
+func (x *subtasks) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+ m.Save("p", &x.p)
+}
+
+func (x *subtasks) afterLoad() {}
+func (x *subtasks) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+ m.Load("p", &x.p)
+}
+
+func (x *subtasksFile) beforeSave() {}
+func (x *subtasksFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *subtasksFile) afterLoad() {}
+func (x *subtasksFile) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *exe) beforeSave() {}
+func (x *exe) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("t", &x.t)
+}
+
+func (x *exe) afterLoad() {}
+func (x *exe) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("t", &x.t)
+}
+
+func (x *namespaceSymlink) beforeSave() {}
+func (x *namespaceSymlink) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("t", &x.t)
+}
+
+func (x *namespaceSymlink) afterLoad() {}
+func (x *namespaceSymlink) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("t", &x.t)
+}
+
+func (x *mapsData) beforeSave() {}
+func (x *mapsData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *mapsData) afterLoad() {}
+func (x *mapsData) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *smapsData) beforeSave() {}
+func (x *smapsData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *smapsData) afterLoad() {}
+func (x *smapsData) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *taskStatData) beforeSave() {}
+func (x *taskStatData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("tgstats", &x.tgstats)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *taskStatData) afterLoad() {}
+func (x *taskStatData) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("tgstats", &x.tgstats)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *statmData) beforeSave() {}
+func (x *statmData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *statmData) afterLoad() {}
+func (x *statmData) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *statusData) beforeSave() {}
+func (x *statusData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *statusData) afterLoad() {}
+func (x *statusData) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *ioData) beforeSave() {}
+func (x *ioData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ioUsage", &x.ioUsage)
+}
+
+func (x *ioData) afterLoad() {}
+func (x *ioData) load(m state.Map) {
+ m.Load("ioUsage", &x.ioUsage)
+}
+
+func (x *comm) beforeSave() {}
+func (x *comm) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("t", &x.t)
+}
+
+func (x *comm) afterLoad() {}
+func (x *comm) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("t", &x.t)
+}
+
+func (x *commFile) beforeSave() {}
+func (x *commFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *commFile) afterLoad() {}
+func (x *commFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *auxvec) beforeSave() {}
+func (x *auxvec) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("t", &x.t)
+}
+
+func (x *auxvec) afterLoad() {}
+func (x *auxvec) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("t", &x.t)
+}
+
+func (x *auxvecFile) beforeSave() {}
+func (x *auxvecFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *auxvecFile) afterLoad() {}
+func (x *auxvecFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *idMapInodeOperations) beforeSave() {}
+func (x *idMapInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("t", &x.t)
+ m.Save("gids", &x.gids)
+}
+
+func (x *idMapInodeOperations) afterLoad() {}
+func (x *idMapInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("t", &x.t)
+ m.Load("gids", &x.gids)
+}
+
+func (x *idMapFileOperations) beforeSave() {}
+func (x *idMapFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+}
+
+func (x *idMapFileOperations) afterLoad() {}
+func (x *idMapFileOperations) load(m state.Map) {
+ m.Load("iops", &x.iops)
+}
+
+func (x *uptime) beforeSave() {}
+func (x *uptime) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("startTime", &x.startTime)
+}
+
+func (x *uptime) afterLoad() {}
+func (x *uptime) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("startTime", &x.startTime)
+}
+
+func (x *uptimeFile) beforeSave() {}
+func (x *uptimeFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("startTime", &x.startTime)
+}
+
+func (x *uptimeFile) afterLoad() {}
+func (x *uptimeFile) load(m state.Map) {
+ m.Load("startTime", &x.startTime)
+}
+
+func (x *versionData) beforeSave() {}
+func (x *versionData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *versionData) afterLoad() {}
+func (x *versionData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func init() {
+ state.Register("proc.execArgInode", (*execArgInode)(nil), state.Fns{Save: (*execArgInode).save, Load: (*execArgInode).load})
+ state.Register("proc.execArgFile", (*execArgFile)(nil), state.Fns{Save: (*execArgFile).save, Load: (*execArgFile).load})
+ state.Register("proc.fdDir", (*fdDir)(nil), state.Fns{Save: (*fdDir).save, Load: (*fdDir).load})
+ state.Register("proc.fdDirFile", (*fdDirFile)(nil), state.Fns{Save: (*fdDirFile).save, Load: (*fdDirFile).load})
+ state.Register("proc.fdInfoDir", (*fdInfoDir)(nil), state.Fns{Save: (*fdInfoDir).save, Load: (*fdInfoDir).load})
+ state.Register("proc.filesystemsData", (*filesystemsData)(nil), state.Fns{Save: (*filesystemsData).save, Load: (*filesystemsData).load})
+ state.Register("proc.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("proc.taskOwnedInodeOps", (*taskOwnedInodeOps)(nil), state.Fns{Save: (*taskOwnedInodeOps).save, Load: (*taskOwnedInodeOps).load})
+ state.Register("proc.staticFileInodeOps", (*staticFileInodeOps)(nil), state.Fns{Save: (*staticFileInodeOps).save, Load: (*staticFileInodeOps).load})
+ state.Register("proc.loadavgData", (*loadavgData)(nil), state.Fns{Save: (*loadavgData).save, Load: (*loadavgData).load})
+ state.Register("proc.meminfoData", (*meminfoData)(nil), state.Fns{Save: (*meminfoData).save, Load: (*meminfoData).load})
+ state.Register("proc.mountInfoFile", (*mountInfoFile)(nil), state.Fns{Save: (*mountInfoFile).save, Load: (*mountInfoFile).load})
+ state.Register("proc.mountsFile", (*mountsFile)(nil), state.Fns{Save: (*mountsFile).save, Load: (*mountsFile).load})
+ state.Register("proc.ifinet6", (*ifinet6)(nil), state.Fns{Save: (*ifinet6).save, Load: (*ifinet6).load})
+ state.Register("proc.netDev", (*netDev)(nil), state.Fns{Save: (*netDev).save, Load: (*netDev).load})
+ state.Register("proc.netSnmp", (*netSnmp)(nil), state.Fns{Save: (*netSnmp).save, Load: (*netSnmp).load})
+ state.Register("proc.netRoute", (*netRoute)(nil), state.Fns{Save: (*netRoute).save, Load: (*netRoute).load})
+ state.Register("proc.netUnix", (*netUnix)(nil), state.Fns{Save: (*netUnix).save, Load: (*netUnix).load})
+ state.Register("proc.netTCP", (*netTCP)(nil), state.Fns{Save: (*netTCP).save, Load: (*netTCP).load})
+ state.Register("proc.netTCP6", (*netTCP6)(nil), state.Fns{Save: (*netTCP6).save, Load: (*netTCP6).load})
+ state.Register("proc.netUDP", (*netUDP)(nil), state.Fns{Save: (*netUDP).save, Load: (*netUDP).load})
+ state.Register("proc.proc", (*proc)(nil), state.Fns{Save: (*proc).save, Load: (*proc).load})
+ state.Register("proc.self", (*self)(nil), state.Fns{Save: (*self).save, Load: (*self).load})
+ state.Register("proc.threadSelf", (*threadSelf)(nil), state.Fns{Save: (*threadSelf).save, Load: (*threadSelf).load})
+ state.Register("proc.rootProcFile", (*rootProcFile)(nil), state.Fns{Save: (*rootProcFile).save, Load: (*rootProcFile).load})
+ state.Register("proc.statData", (*statData)(nil), state.Fns{Save: (*statData).save, Load: (*statData).load})
+ state.Register("proc.mmapMinAddrData", (*mmapMinAddrData)(nil), state.Fns{Save: (*mmapMinAddrData).save, Load: (*mmapMinAddrData).load})
+ state.Register("proc.overcommitMemory", (*overcommitMemory)(nil), state.Fns{Save: (*overcommitMemory).save, Load: (*overcommitMemory).load})
+ state.Register("proc.hostname", (*hostname)(nil), state.Fns{Save: (*hostname).save, Load: (*hostname).load})
+ state.Register("proc.hostnameFile", (*hostnameFile)(nil), state.Fns{Save: (*hostnameFile).save, Load: (*hostnameFile).load})
+ state.Register("proc.tcpMemInode", (*tcpMemInode)(nil), state.Fns{Save: (*tcpMemInode).save, Load: (*tcpMemInode).load})
+ state.Register("proc.tcpMemFile", (*tcpMemFile)(nil), state.Fns{Save: (*tcpMemFile).save, Load: (*tcpMemFile).load})
+ state.Register("proc.tcpSack", (*tcpSack)(nil), state.Fns{Save: (*tcpSack).save, Load: (*tcpSack).load})
+ state.Register("proc.tcpSackFile", (*tcpSackFile)(nil), state.Fns{Save: (*tcpSackFile).save, Load: (*tcpSackFile).load})
+ state.Register("proc.taskDir", (*taskDir)(nil), state.Fns{Save: (*taskDir).save, Load: (*taskDir).load})
+ state.Register("proc.subtasks", (*subtasks)(nil), state.Fns{Save: (*subtasks).save, Load: (*subtasks).load})
+ state.Register("proc.subtasksFile", (*subtasksFile)(nil), state.Fns{Save: (*subtasksFile).save, Load: (*subtasksFile).load})
+ state.Register("proc.exe", (*exe)(nil), state.Fns{Save: (*exe).save, Load: (*exe).load})
+ state.Register("proc.namespaceSymlink", (*namespaceSymlink)(nil), state.Fns{Save: (*namespaceSymlink).save, Load: (*namespaceSymlink).load})
+ state.Register("proc.mapsData", (*mapsData)(nil), state.Fns{Save: (*mapsData).save, Load: (*mapsData).load})
+ state.Register("proc.smapsData", (*smapsData)(nil), state.Fns{Save: (*smapsData).save, Load: (*smapsData).load})
+ state.Register("proc.taskStatData", (*taskStatData)(nil), state.Fns{Save: (*taskStatData).save, Load: (*taskStatData).load})
+ state.Register("proc.statmData", (*statmData)(nil), state.Fns{Save: (*statmData).save, Load: (*statmData).load})
+ state.Register("proc.statusData", (*statusData)(nil), state.Fns{Save: (*statusData).save, Load: (*statusData).load})
+ state.Register("proc.ioData", (*ioData)(nil), state.Fns{Save: (*ioData).save, Load: (*ioData).load})
+ state.Register("proc.comm", (*comm)(nil), state.Fns{Save: (*comm).save, Load: (*comm).load})
+ state.Register("proc.commFile", (*commFile)(nil), state.Fns{Save: (*commFile).save, Load: (*commFile).load})
+ state.Register("proc.auxvec", (*auxvec)(nil), state.Fns{Save: (*auxvec).save, Load: (*auxvec).load})
+ state.Register("proc.auxvecFile", (*auxvecFile)(nil), state.Fns{Save: (*auxvecFile).save, Load: (*auxvecFile).load})
+ state.Register("proc.idMapInodeOperations", (*idMapInodeOperations)(nil), state.Fns{Save: (*idMapInodeOperations).save, Load: (*idMapInodeOperations).load})
+ state.Register("proc.idMapFileOperations", (*idMapFileOperations)(nil), state.Fns{Save: (*idMapFileOperations).save, Load: (*idMapFileOperations).load})
+ state.Register("proc.uptime", (*uptime)(nil), state.Fns{Save: (*uptime).save, Load: (*uptime).load})
+ state.Register("proc.uptimeFile", (*uptimeFile)(nil), state.Fns{Save: (*uptimeFile).save, Load: (*uptimeFile).load})
+ state.Register("proc.versionData", (*versionData)(nil), state.Fns{Save: (*versionData).save, Load: (*versionData).load})
+}
diff --git a/pkg/sentry/fs/proc/seqfile/BUILD b/pkg/sentry/fs/proc/seqfile/BUILD
deleted file mode 100644
index fe7067be1..000000000
--- a/pkg/sentry/fs/proc/seqfile/BUILD
+++ /dev/null
@@ -1,36 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "seqfile",
- srcs = ["seqfile.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/proc/device",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "seqfile_test",
- size = "small",
- srcs = ["seqfile_test.go"],
- embed = [":seqfile"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go b/pkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go
new file mode 100755
index 000000000..db9f7ceb9
--- /dev/null
+++ b/pkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go
@@ -0,0 +1,58 @@
+// automatically generated by stateify.
+
+package seqfile
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *SeqData) beforeSave() {}
+func (x *SeqData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Buf", &x.Buf)
+ m.Save("Handle", &x.Handle)
+}
+
+func (x *SeqData) afterLoad() {}
+func (x *SeqData) load(m state.Map) {
+ m.Load("Buf", &x.Buf)
+ m.Load("Handle", &x.Handle)
+}
+
+func (x *SeqFile) beforeSave() {}
+func (x *SeqFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("SeqSource", &x.SeqSource)
+ m.Save("source", &x.source)
+ m.Save("generation", &x.generation)
+ m.Save("lastRead", &x.lastRead)
+}
+
+func (x *SeqFile) afterLoad() {}
+func (x *SeqFile) load(m state.Map) {
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("SeqSource", &x.SeqSource)
+ m.Load("source", &x.source)
+ m.Load("generation", &x.generation)
+ m.Load("lastRead", &x.lastRead)
+}
+
+func (x *seqFileOperations) beforeSave() {}
+func (x *seqFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("seqFile", &x.seqFile)
+}
+
+func (x *seqFileOperations) afterLoad() {}
+func (x *seqFileOperations) load(m state.Map) {
+ m.Load("seqFile", &x.seqFile)
+}
+
+func init() {
+ state.Register("seqfile.SeqData", (*SeqData)(nil), state.Fns{Save: (*SeqData).save, Load: (*SeqData).load})
+ state.Register("seqfile.SeqFile", (*SeqFile)(nil), state.Fns{Save: (*SeqFile).save, Load: (*SeqFile).load})
+ state.Register("seqfile.seqFileOperations", (*seqFileOperations)(nil), state.Fns{Save: (*seqFileOperations).save, Load: (*seqFileOperations).load})
+}
diff --git a/pkg/sentry/fs/proc/seqfile/seqfile_test.go b/pkg/sentry/fs/proc/seqfile/seqfile_test.go
deleted file mode 100644
index ebfeee835..000000000
--- a/pkg/sentry/fs/proc/seqfile/seqfile_test.go
+++ /dev/null
@@ -1,279 +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 seqfile
-
-import (
- "bytes"
- "fmt"
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-type seqTest struct {
- actual []SeqData
- update bool
-}
-
-func (s *seqTest) Init() {
- var sq []SeqData
- // Create some SeqData.
- for i := 0; i < 10; i++ {
- var b []byte
- for j := 0; j < 10; j++ {
- b = append(b, byte(i))
- }
- sq = append(sq, SeqData{
- Buf: b,
- Handle: &testHandle{i: i},
- })
- }
- s.actual = sq
-}
-
-// NeedsUpdate reports whether we need to update the data we've previously read.
-func (s *seqTest) NeedsUpdate(int64) bool {
- return s.update
-}
-
-// ReadSeqFiledata returns a slice of SeqData which contains elements
-// greater than the handle.
-func (s *seqTest) ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64) {
- if handle == nil {
- return s.actual, 0
- }
- h := *handle.(*testHandle)
- var ret []SeqData
- for _, b := range s.actual {
- // We want the next one.
- h2 := *b.Handle.(*testHandle)
- if h2.i > h.i {
- ret = append(ret, b)
- }
- }
- return ret, 0
-}
-
-// Flatten a slice of slices into one slice.
-func flatten(buf ...[]byte) []byte {
- var flat []byte
- for _, b := range buf {
- flat = append(flat, b...)
- }
- return flat
-}
-
-type testHandle struct {
- i int
-}
-
-type testTable struct {
- offset int64
- readBufferSize int
- expectedData []byte
- expectedError error
-}
-
-func runTableTests(ctx context.Context, table []testTable, dirent *fs.Dirent) error {
- for _, tt := range table {
- file, err := dirent.Inode.InodeOperations.GetFile(ctx, dirent, fs.FileFlags{Read: true})
- if err != nil {
- return fmt.Errorf("GetFile returned error: %v", err)
- }
-
- data := make([]byte, tt.readBufferSize)
- resultLen, err := file.Preadv(ctx, usermem.BytesIOSequence(data), tt.offset)
- if err != tt.expectedError {
- return fmt.Errorf("t.Preadv(len: %v, offset: %v) (error) => %v expected %v", tt.readBufferSize, tt.offset, err, tt.expectedError)
- }
- expectedLen := int64(len(tt.expectedData))
- if resultLen != expectedLen {
- // We make this just an error so we wall through and print the data below.
- return fmt.Errorf("t.Preadv(len: %v, offset: %v) (size) => %v expected %v", tt.readBufferSize, tt.offset, resultLen, expectedLen)
- }
- if !bytes.Equal(data[:expectedLen], tt.expectedData) {
- return fmt.Errorf("t.Preadv(len: %v, offset: %v) (data) => %v expected %v", tt.readBufferSize, tt.offset, data[:expectedLen], tt.expectedData)
- }
- }
- return nil
-}
-
-func TestSeqFile(t *testing.T) {
- testSource := &seqTest{}
- testSource.Init()
-
- // Create a file that can be R/W.
- ctx := contexttest.Context(t)
- m := fs.NewPseudoMountSource(ctx)
- contents := map[string]*fs.Inode{
- "foo": NewSeqFileInode(ctx, testSource, m),
- }
- root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777))
-
- // How about opening it?
- inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory})
- dirent2, err := root.Lookup(ctx, inode, "foo")
- if err != nil {
- t.Fatalf("failed to walk to foo for n2: %v", err)
- }
- n2 := dirent2.Inode.InodeOperations
- file2, err := n2.GetFile(ctx, dirent2, fs.FileFlags{Read: true, Write: true})
- if err != nil {
- t.Fatalf("GetFile returned error: %v", err)
- }
-
- // Writing?
- if _, err := file2.Writev(ctx, usermem.BytesIOSequence([]byte("test"))); err == nil {
- t.Fatalf("managed to write to n2: %v", err)
- }
-
- // How about reading?
- dirent3, err := root.Lookup(ctx, inode, "foo")
- if err != nil {
- t.Fatalf("failed to walk to foo: %v", err)
- }
- n3 := dirent3.Inode.InodeOperations
- if n2 != n3 {
- t.Error("got n2 != n3, want same")
- }
-
- testSource.update = true
-
- table := []testTable{
- // Read past the end.
- {100, 4, []byte{}, io.EOF},
- {110, 4, []byte{}, io.EOF},
- {200, 4, []byte{}, io.EOF},
- // Read a truncated first line.
- {0, 4, testSource.actual[0].Buf[:4], nil},
- // Read the whole first line.
- {0, 10, testSource.actual[0].Buf, nil},
- // Read the whole first line + 5 bytes of second line.
- {0, 15, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:5]), nil},
- // First 4 bytes of the second line.
- {10, 4, testSource.actual[1].Buf[:4], nil},
- // Read the two first lines.
- {0, 20, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf), nil},
- // Read three lines.
- {0, 30, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf), nil},
- // Read everything, but use a bigger buffer than necessary.
- {0, 150, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf, testSource.actual[3].Buf, testSource.actual[4].Buf, testSource.actual[5].Buf, testSource.actual[6].Buf, testSource.actual[7].Buf, testSource.actual[8].Buf, testSource.actual[9].Buf), nil},
- // Read the last 3 bytes.
- {97, 10, testSource.actual[9].Buf[7:], nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed with testSource.update = %v : %v", testSource.update, err)
- }
-
- // Disable updates and do it again.
- testSource.update = false
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed with testSource.update = %v: %v", testSource.update, err)
- }
-}
-
-// Test that we behave correctly when the file is updated.
-func TestSeqFileFileUpdated(t *testing.T) {
- testSource := &seqTest{}
- testSource.Init()
- testSource.update = true
-
- // Create a file that can be R/W.
- ctx := contexttest.Context(t)
- m := fs.NewPseudoMountSource(ctx)
- contents := map[string]*fs.Inode{
- "foo": NewSeqFileInode(ctx, testSource, m),
- }
- root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777))
-
- // How about opening it?
- inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory})
- dirent2, err := root.Lookup(ctx, inode, "foo")
- if err != nil {
- t.Fatalf("failed to walk to foo for dirent2: %v", err)
- }
-
- table := []testTable{
- {0, 16, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:6]), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed: %v", err)
- }
- // Delete the first entry.
- cut := testSource.actual[0].Buf
- testSource.actual = testSource.actual[1:]
-
- table = []testTable{
- // Try reading buffer 0 with an offset. This will not delete the old data.
- {1, 5, cut[1:6], nil},
- // Reset our file by reading at offset 0.
- {0, 10, testSource.actual[0].Buf, nil},
- {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil},
- // Read the same data a second time.
- {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil},
- // Read the following two lines.
- {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after removing first entry: %v", err)
- }
-
- // Add a new duplicate line in the middle (6666...)
- after := testSource.actual[5:]
- testSource.actual = testSource.actual[:4]
- // Note the list must be sorted.
- testSource.actual = append(testSource.actual, after[0])
- testSource.actual = append(testSource.actual, after...)
-
- table = []testTable{
- {50, 20, flatten(testSource.actual[4].Buf, testSource.actual[5].Buf), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after adding middle entry: %v", err)
- }
- // This will be used in a later test.
- oldTestData := testSource.actual
-
- // Delete everything.
- testSource.actual = testSource.actual[:0]
- table = []testTable{
- {20, 20, []byte{}, io.EOF},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after removing all entries: %v", err)
- }
- // Restore some of the data.
- testSource.actual = oldTestData[:1]
- table = []testTable{
- {6, 20, testSource.actual[0].Buf[6:], nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after adding first entry back: %v", err)
- }
-
- // Re-extend the data
- testSource.actual = oldTestData
- table = []testTable{
- {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after extending testSource: %v", err)
- }
-}
diff --git a/pkg/sentry/fs/proc/sys_net_test.go b/pkg/sentry/fs/proc/sys_net_test.go
deleted file mode 100644
index 6abae7a60..000000000
--- a/pkg/sentry/fs/proc/sys_net_test.go
+++ /dev/null
@@ -1,125 +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 proc
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/inet"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestQuerySendBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- s.TCPSendBufSize = inet.TCPBufferSize{100, 200, 300}
- tmi := &tcpMemInode{s: s, dir: tcpWMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- buf := make([]byte, 100)
- dst := usermem.BytesIOSequence(buf)
- n, err := tmf.Read(ctx, nil, dst, 0)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if got, want := string(buf[:n]), "100\t200\t300\n"; got != want {
- t.Fatalf("Bad string: got %v, want %v", got, want)
- }
-}
-
-func TestQueryRecvBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- s.TCPRecvBufSize = inet.TCPBufferSize{100, 200, 300}
- tmi := &tcpMemInode{s: s, dir: tcpRMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- buf := make([]byte, 100)
- dst := usermem.BytesIOSequence(buf)
- n, err := tmf.Read(ctx, nil, dst, 0)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if got, want := string(buf[:n]), "100\t200\t300\n"; got != want {
- t.Fatalf("Bad string: got %v, want %v", got, want)
- }
-}
-
-var cases = []struct {
- str string
- initial inet.TCPBufferSize
- final inet.TCPBufferSize
-}{
- {
- str: "",
- initial: inet.TCPBufferSize{1, 2, 3},
- final: inet.TCPBufferSize{1, 2, 3},
- },
- {
- str: "100\n",
- initial: inet.TCPBufferSize{1, 100, 200},
- final: inet.TCPBufferSize{100, 100, 200},
- },
- {
- str: "100 200 300\n",
- initial: inet.TCPBufferSize{1, 2, 3},
- final: inet.TCPBufferSize{100, 200, 300},
- },
-}
-
-func TestConfigureSendBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- for _, c := range cases {
- s.TCPSendBufSize = c.initial
- tmi := &tcpMemInode{s: s, dir: tcpWMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- // Write the values.
- src := usermem.BytesIOSequence([]byte(c.str))
- if n, err := tmf.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil {
- t.Errorf("Write, case = %q: got (%d, %v), wanted (%d, nil)", c.str, n, err, len(c.str))
- }
-
- // Read the values from the stack and check them.
- if s.TCPSendBufSize != c.final {
- t.Errorf("TCPSendBufferSize, case = %q: got %v, wanted %v", c.str, s.TCPSendBufSize, c.final)
- }
- }
-}
-
-func TestConfigureRecvBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- for _, c := range cases {
- s.TCPRecvBufSize = c.initial
- tmi := &tcpMemInode{s: s, dir: tcpRMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- // Write the values.
- src := usermem.BytesIOSequence([]byte(c.str))
- if n, err := tmf.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil {
- t.Errorf("Write, case = %q: got (%d, %v), wanted (%d, nil)", c.str, n, err, len(c.str))
- }
-
- // Read the values from the stack and check them.
- if s.TCPRecvBufSize != c.final {
- t.Errorf("TCPRecvBufferSize, case = %q: got %v, wanted %v", c.str, s.TCPRecvBufSize, c.final)
- }
- }
-}
diff --git a/pkg/sentry/fs/ramfs/BUILD b/pkg/sentry/fs/ramfs/BUILD
deleted file mode 100644
index 012cb3e44..000000000
--- a/pkg/sentry/fs/ramfs/BUILD
+++ /dev/null
@@ -1,38 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ramfs",
- srcs = [
- "dir.go",
- "socket.go",
- "symlink.go",
- "tree.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/ramfs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "ramfs_test",
- size = "small",
- srcs = ["tree_test.go"],
- embed = [":ramfs"],
- deps = [
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- ],
-)
diff --git a/pkg/sentry/fs/ramfs/ramfs_state_autogen.go b/pkg/sentry/fs/ramfs/ramfs_state_autogen.go
new file mode 100755
index 000000000..bc86a01a1
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/ramfs_state_autogen.go
@@ -0,0 +1,94 @@
+// automatically generated by stateify.
+
+package ramfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Dir) beforeSave() {}
+func (x *Dir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("children", &x.children)
+ m.Save("dentryMap", &x.dentryMap)
+}
+
+func (x *Dir) afterLoad() {}
+func (x *Dir) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("children", &x.children)
+ m.Load("dentryMap", &x.dentryMap)
+}
+
+func (x *dirFileOperations) beforeSave() {}
+func (x *dirFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("dirCursor", &x.dirCursor)
+ m.Save("dir", &x.dir)
+}
+
+func (x *dirFileOperations) afterLoad() {}
+func (x *dirFileOperations) load(m state.Map) {
+ m.Load("dirCursor", &x.dirCursor)
+ m.Load("dir", &x.dir)
+}
+
+func (x *Socket) beforeSave() {}
+func (x *Socket) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("ep", &x.ep)
+}
+
+func (x *Socket) afterLoad() {}
+func (x *Socket) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("ep", &x.ep)
+}
+
+func (x *socketFileOperations) beforeSave() {}
+func (x *socketFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *socketFileOperations) afterLoad() {}
+func (x *socketFileOperations) load(m state.Map) {
+}
+
+func (x *Symlink) beforeSave() {}
+func (x *Symlink) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("Target", &x.Target)
+}
+
+func (x *Symlink) afterLoad() {}
+func (x *Symlink) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("Target", &x.Target)
+}
+
+func (x *symlinkFileOperations) beforeSave() {}
+func (x *symlinkFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *symlinkFileOperations) afterLoad() {}
+func (x *symlinkFileOperations) load(m state.Map) {
+}
+
+func init() {
+ state.Register("ramfs.Dir", (*Dir)(nil), state.Fns{Save: (*Dir).save, Load: (*Dir).load})
+ state.Register("ramfs.dirFileOperations", (*dirFileOperations)(nil), state.Fns{Save: (*dirFileOperations).save, Load: (*dirFileOperations).load})
+ state.Register("ramfs.Socket", (*Socket)(nil), state.Fns{Save: (*Socket).save, Load: (*Socket).load})
+ state.Register("ramfs.socketFileOperations", (*socketFileOperations)(nil), state.Fns{Save: (*socketFileOperations).save, Load: (*socketFileOperations).load})
+ state.Register("ramfs.Symlink", (*Symlink)(nil), state.Fns{Save: (*Symlink).save, Load: (*Symlink).load})
+ state.Register("ramfs.symlinkFileOperations", (*symlinkFileOperations)(nil), state.Fns{Save: (*symlinkFileOperations).save, Load: (*symlinkFileOperations).load})
+}
diff --git a/pkg/sentry/fs/ramfs/tree_test.go b/pkg/sentry/fs/ramfs/tree_test.go
deleted file mode 100644
index 61a7e2900..000000000
--- a/pkg/sentry/fs/ramfs/tree_test.go
+++ /dev/null
@@ -1,80 +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 ramfs
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-func TestMakeDirectoryTree(t *testing.T) {
-
- for _, test := range []struct {
- name string
- subdirs []string
- }{
- {
- name: "abs paths",
- subdirs: []string{
- "/tmp",
- "/tmp/a/b",
- "/tmp/a/c/d",
- "/tmp/c",
- "/proc",
- "/dev/a/b",
- "/tmp",
- },
- },
- {
- name: "rel paths",
- subdirs: []string{
- "tmp",
- "tmp/a/b",
- "tmp/a/c/d",
- "tmp/c",
- "proc",
- "dev/a/b",
- "tmp",
- },
- },
- } {
- ctx := contexttest.Context(t)
- mount := fs.NewPseudoMountSource(ctx)
- tree, err := MakeDirectoryTree(ctx, mount, test.subdirs)
- if err != nil {
- t.Errorf("%s: failed to make ramfs tree, got error %v, want nil", test.name, err)
- continue
- }
-
- // Expect to be able to find each of the paths.
- mm, err := fs.NewMountNamespace(ctx, tree)
- if err != nil {
- t.Errorf("%s: failed to create mount manager: %v", test.name, err)
- continue
- }
- root := mm.Root()
- defer mm.DecRef()
-
- for _, p := range test.subdirs {
- maxTraversals := uint(0)
- if _, err := mm.FindInode(ctx, root, nil, p, &maxTraversals); err != nil {
- t.Errorf("%s: failed to find node %s: %v", test.name, p, err)
- break
- }
- }
- }
-}
diff --git a/pkg/sentry/fs/sys/BUILD b/pkg/sentry/fs/sys/BUILD
deleted file mode 100644
index 25f0f124e..000000000
--- a/pkg/sentry/fs/sys/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sys",
- srcs = [
- "device.go",
- "devices.go",
- "fs.go",
- "sys.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/sys",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/sys/sys_state_autogen.go b/pkg/sentry/fs/sys/sys_state_autogen.go
new file mode 100755
index 000000000..603057309
--- /dev/null
+++ b/pkg/sentry/fs/sys/sys_state_autogen.go
@@ -0,0 +1,34 @@
+// automatically generated by stateify.
+
+package sys
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *cpunum) beforeSave() {}
+func (x *cpunum) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *cpunum) afterLoad() {}
+func (x *cpunum) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func init() {
+ state.Register("sys.cpunum", (*cpunum)(nil), state.Fns{Save: (*cpunum).save, Load: (*cpunum).load})
+ state.Register("sys.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+}
diff --git a/pkg/sentry/fs/timerfd/BUILD b/pkg/sentry/fs/timerfd/BUILD
deleted file mode 100644
index a215c1b95..000000000
--- a/pkg/sentry/fs/timerfd/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "timerfd",
- srcs = ["timerfd.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/timerfd",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/timerfd/timerfd_state_autogen.go b/pkg/sentry/fs/timerfd/timerfd_state_autogen.go
new file mode 100755
index 000000000..e8d98af97
--- /dev/null
+++ b/pkg/sentry/fs/timerfd/timerfd_state_autogen.go
@@ -0,0 +1,25 @@
+// automatically generated by stateify.
+
+package timerfd
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *TimerOperations) beforeSave() {}
+func (x *TimerOperations) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.events) { m.Failf("events is %v, expected zero", x.events) }
+ m.Save("timer", &x.timer)
+ m.Save("val", &x.val)
+}
+
+func (x *TimerOperations) afterLoad() {}
+func (x *TimerOperations) load(m state.Map) {
+ m.Load("timer", &x.timer)
+ m.Load("val", &x.val)
+}
+
+func init() {
+ state.Register("timerfd.TimerOperations", (*TimerOperations)(nil), state.Fns{Save: (*TimerOperations).save, Load: (*TimerOperations).load})
+}
diff --git a/pkg/sentry/fs/tmpfs/BUILD b/pkg/sentry/fs/tmpfs/BUILD
deleted file mode 100644
index 59ce400c2..000000000
--- a/pkg/sentry/fs/tmpfs/BUILD
+++ /dev/null
@@ -1,51 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "tmpfs",
- srcs = [
- "device.go",
- "file_regular.go",
- "fs.go",
- "inode_file.go",
- "tmpfs.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/metric",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/pipe",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "tmpfs_test",
- size = "small",
- srcs = ["file_test.go"],
- embed = [":tmpfs"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/contexttest",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/tmpfs/file_test.go b/pkg/sentry/fs/tmpfs/file_test.go
deleted file mode 100644
index 0075ef023..000000000
--- a/pkg/sentry/fs/tmpfs/file_test.go
+++ /dev/null
@@ -1,72 +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 tmpfs
-
-import (
- "bytes"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usage"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func newFileInode(ctx context.Context) *fs.Inode {
- m := fs.NewCachingMountSource(ctx, &Filesystem{}, fs.MountSourceFlags{})
- iops := NewInMemoryFile(ctx, usage.Tmpfs, fs.WithCurrentTime(ctx, fs.UnstableAttr{}))
- return fs.NewInode(ctx, iops, m, fs.StableAttr{
- DeviceID: tmpfsDevice.DeviceID(),
- InodeID: tmpfsDevice.NextIno(),
- BlockSize: usermem.PageSize,
- Type: fs.RegularFile,
- })
-}
-
-func newFile(ctx context.Context) *fs.File {
- inode := newFileInode(ctx)
- f, _ := inode.GetFile(ctx, fs.NewDirent(ctx, inode, "stub"), fs.FileFlags{Read: true, Write: true})
- return f
-}
-
-// Allocate once, write twice.
-func TestGrow(t *testing.T) {
- ctx := contexttest.Context(t)
- f := newFile(ctx)
- defer f.DecRef()
-
- abuf := bytes.Repeat([]byte{'a'}, 68)
- n, err := f.Pwritev(ctx, usermem.BytesIOSequence(abuf), 0)
- if n != int64(len(abuf)) || err != nil {
- t.Fatalf("Pwritev got (%d, %v) want (%d, nil)", n, err, len(abuf))
- }
-
- bbuf := bytes.Repeat([]byte{'b'}, 856)
- n, err = f.Pwritev(ctx, usermem.BytesIOSequence(bbuf), 68)
- if n != int64(len(bbuf)) || err != nil {
- t.Fatalf("Pwritev got (%d, %v) want (%d, nil)", n, err, len(bbuf))
- }
-
- rbuf := make([]byte, len(abuf)+len(bbuf))
- n, err = f.Preadv(ctx, usermem.BytesIOSequence(rbuf), 0)
- if n != int64(len(rbuf)) || err != nil {
- t.Fatalf("Preadv got (%d, %v) want (%d, nil)", n, err, len(rbuf))
- }
-
- if want := append(abuf, bbuf...); !bytes.Equal(rbuf, want) {
- t.Fatalf("Read %v, want %v", rbuf, want)
- }
-}
diff --git a/pkg/sentry/fs/tmpfs/tmpfs_state_autogen.go b/pkg/sentry/fs/tmpfs/tmpfs_state_autogen.go
new file mode 100755
index 000000000..7d73d1c77
--- /dev/null
+++ b/pkg/sentry/fs/tmpfs/tmpfs_state_autogen.go
@@ -0,0 +1,108 @@
+// automatically generated by stateify.
+
+package tmpfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *regularFileOperations) beforeSave() {}
+func (x *regularFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+}
+
+func (x *regularFileOperations) afterLoad() {}
+func (x *regularFileOperations) load(m state.Map) {
+ m.Load("iops", &x.iops)
+}
+
+func (x *Filesystem) beforeSave() {}
+func (x *Filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *Filesystem) afterLoad() {}
+func (x *Filesystem) load(m state.Map) {
+}
+
+func (x *fileInodeOperations) beforeSave() {}
+func (x *fileInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("kernel", &x.kernel)
+ m.Save("memUsage", &x.memUsage)
+ m.Save("attr", &x.attr)
+ m.Save("mappings", &x.mappings)
+ m.Save("writableMappingPages", &x.writableMappingPages)
+ m.Save("data", &x.data)
+ m.Save("seals", &x.seals)
+}
+
+func (x *fileInodeOperations) afterLoad() {}
+func (x *fileInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("kernel", &x.kernel)
+ m.Load("memUsage", &x.memUsage)
+ m.Load("attr", &x.attr)
+ m.Load("mappings", &x.mappings)
+ m.Load("writableMappingPages", &x.writableMappingPages)
+ m.Load("data", &x.data)
+ m.Load("seals", &x.seals)
+}
+
+func (x *Dir) beforeSave() {}
+func (x *Dir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ramfsDir", &x.ramfsDir)
+ m.Save("kernel", &x.kernel)
+}
+
+func (x *Dir) load(m state.Map) {
+ m.Load("ramfsDir", &x.ramfsDir)
+ m.Load("kernel", &x.kernel)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *Symlink) beforeSave() {}
+func (x *Symlink) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+}
+
+func (x *Symlink) afterLoad() {}
+func (x *Symlink) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+}
+
+func (x *Socket) beforeSave() {}
+func (x *Socket) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Socket", &x.Socket)
+}
+
+func (x *Socket) afterLoad() {}
+func (x *Socket) load(m state.Map) {
+ m.Load("Socket", &x.Socket)
+}
+
+func (x *Fifo) beforeSave() {}
+func (x *Fifo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeOperations", &x.InodeOperations)
+}
+
+func (x *Fifo) afterLoad() {}
+func (x *Fifo) load(m state.Map) {
+ m.Load("InodeOperations", &x.InodeOperations)
+}
+
+func init() {
+ state.Register("tmpfs.regularFileOperations", (*regularFileOperations)(nil), state.Fns{Save: (*regularFileOperations).save, Load: (*regularFileOperations).load})
+ state.Register("tmpfs.Filesystem", (*Filesystem)(nil), state.Fns{Save: (*Filesystem).save, Load: (*Filesystem).load})
+ state.Register("tmpfs.fileInodeOperations", (*fileInodeOperations)(nil), state.Fns{Save: (*fileInodeOperations).save, Load: (*fileInodeOperations).load})
+ state.Register("tmpfs.Dir", (*Dir)(nil), state.Fns{Save: (*Dir).save, Load: (*Dir).load})
+ state.Register("tmpfs.Symlink", (*Symlink)(nil), state.Fns{Save: (*Symlink).save, Load: (*Symlink).load})
+ state.Register("tmpfs.Socket", (*Socket)(nil), state.Fns{Save: (*Socket).save, Load: (*Socket).load})
+ state.Register("tmpfs.Fifo", (*Fifo)(nil), state.Fns{Save: (*Fifo).save, Load: (*Fifo).load})
+}
diff --git a/pkg/sentry/fs/tty/BUILD b/pkg/sentry/fs/tty/BUILD
deleted file mode 100644
index 95ad98cb0..000000000
--- a/pkg/sentry/fs/tty/BUILD
+++ /dev/null
@@ -1,48 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "tty",
- srcs = [
- "dir.go",
- "fs.go",
- "line_discipline.go",
- "master.go",
- "queue.go",
- "slave.go",
- "terminal.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/tty",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/refs",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "tty_test",
- size = "small",
- srcs = ["tty_test.go"],
- embed = [":tty"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/tty/tty_state_autogen.go b/pkg/sentry/fs/tty/tty_state_autogen.go
new file mode 100755
index 000000000..676e531c7
--- /dev/null
+++ b/pkg/sentry/fs/tty/tty_state_autogen.go
@@ -0,0 +1,210 @@
+// automatically generated by stateify.
+
+package tty
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *dirInodeOperations) beforeSave() {}
+func (x *dirInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("msrc", &x.msrc)
+ m.Save("master", &x.master)
+ m.Save("slaves", &x.slaves)
+ m.Save("dentryMap", &x.dentryMap)
+ m.Save("next", &x.next)
+}
+
+func (x *dirInodeOperations) afterLoad() {}
+func (x *dirInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("msrc", &x.msrc)
+ m.Load("master", &x.master)
+ m.Load("slaves", &x.slaves)
+ m.Load("dentryMap", &x.dentryMap)
+ m.Load("next", &x.next)
+}
+
+func (x *dirFileOperations) beforeSave() {}
+func (x *dirFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("di", &x.di)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *dirFileOperations) afterLoad() {}
+func (x *dirFileOperations) load(m state.Map) {
+ m.Load("di", &x.di)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *superOperations) beforeSave() {}
+func (x *superOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *superOperations) afterLoad() {}
+func (x *superOperations) load(m state.Map) {
+}
+
+func (x *lineDiscipline) beforeSave() {}
+func (x *lineDiscipline) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.masterWaiter) { m.Failf("masterWaiter is %v, expected zero", x.masterWaiter) }
+ if !state.IsZeroValue(x.slaveWaiter) { m.Failf("slaveWaiter is %v, expected zero", x.slaveWaiter) }
+ m.Save("size", &x.size)
+ m.Save("inQueue", &x.inQueue)
+ m.Save("outQueue", &x.outQueue)
+ m.Save("termios", &x.termios)
+ m.Save("column", &x.column)
+}
+
+func (x *lineDiscipline) afterLoad() {}
+func (x *lineDiscipline) load(m state.Map) {
+ m.Load("size", &x.size)
+ m.Load("inQueue", &x.inQueue)
+ m.Load("outQueue", &x.outQueue)
+ m.Load("termios", &x.termios)
+ m.Load("column", &x.column)
+}
+
+func (x *outputQueueTransformer) beforeSave() {}
+func (x *outputQueueTransformer) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *outputQueueTransformer) afterLoad() {}
+func (x *outputQueueTransformer) load(m state.Map) {
+}
+
+func (x *inputQueueTransformer) beforeSave() {}
+func (x *inputQueueTransformer) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *inputQueueTransformer) afterLoad() {}
+func (x *inputQueueTransformer) load(m state.Map) {
+}
+
+func (x *masterInodeOperations) beforeSave() {}
+func (x *masterInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("InodeNoopTruncate", &x.InodeNoopTruncate)
+ m.Save("d", &x.d)
+}
+
+func (x *masterInodeOperations) afterLoad() {}
+func (x *masterInodeOperations) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("InodeNoopTruncate", &x.InodeNoopTruncate)
+ m.Load("d", &x.d)
+}
+
+func (x *masterFileOperations) beforeSave() {}
+func (x *masterFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("d", &x.d)
+ m.Save("t", &x.t)
+}
+
+func (x *masterFileOperations) afterLoad() {}
+func (x *masterFileOperations) load(m state.Map) {
+ m.Load("d", &x.d)
+ m.Load("t", &x.t)
+}
+
+func (x *queue) beforeSave() {}
+func (x *queue) save(m state.Map) {
+ x.beforeSave()
+ m.Save("readBuf", &x.readBuf)
+ m.Save("waitBuf", &x.waitBuf)
+ m.Save("waitBufLen", &x.waitBufLen)
+ m.Save("readable", &x.readable)
+ m.Save("transformer", &x.transformer)
+}
+
+func (x *queue) afterLoad() {}
+func (x *queue) load(m state.Map) {
+ m.Load("readBuf", &x.readBuf)
+ m.Load("waitBuf", &x.waitBuf)
+ m.Load("waitBufLen", &x.waitBufLen)
+ m.Load("readable", &x.readable)
+ m.Load("transformer", &x.transformer)
+}
+
+func (x *slaveInodeOperations) beforeSave() {}
+func (x *slaveInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("InodeNoopTruncate", &x.InodeNoopTruncate)
+ m.Save("d", &x.d)
+ m.Save("t", &x.t)
+}
+
+func (x *slaveInodeOperations) afterLoad() {}
+func (x *slaveInodeOperations) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("InodeNoopTruncate", &x.InodeNoopTruncate)
+ m.Load("d", &x.d)
+ m.Load("t", &x.t)
+}
+
+func (x *slaveFileOperations) beforeSave() {}
+func (x *slaveFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("si", &x.si)
+}
+
+func (x *slaveFileOperations) afterLoad() {}
+func (x *slaveFileOperations) load(m state.Map) {
+ m.Load("si", &x.si)
+}
+
+func (x *Terminal) beforeSave() {}
+func (x *Terminal) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("n", &x.n)
+ m.Save("d", &x.d)
+ m.Save("ld", &x.ld)
+ m.Save("masterKTTY", &x.masterKTTY)
+ m.Save("slaveKTTY", &x.slaveKTTY)
+}
+
+func (x *Terminal) afterLoad() {}
+func (x *Terminal) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("n", &x.n)
+ m.Load("d", &x.d)
+ m.Load("ld", &x.ld)
+ m.Load("masterKTTY", &x.masterKTTY)
+ m.Load("slaveKTTY", &x.slaveKTTY)
+}
+
+func init() {
+ state.Register("tty.dirInodeOperations", (*dirInodeOperations)(nil), state.Fns{Save: (*dirInodeOperations).save, Load: (*dirInodeOperations).load})
+ state.Register("tty.dirFileOperations", (*dirFileOperations)(nil), state.Fns{Save: (*dirFileOperations).save, Load: (*dirFileOperations).load})
+ state.Register("tty.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("tty.superOperations", (*superOperations)(nil), state.Fns{Save: (*superOperations).save, Load: (*superOperations).load})
+ state.Register("tty.lineDiscipline", (*lineDiscipline)(nil), state.Fns{Save: (*lineDiscipline).save, Load: (*lineDiscipline).load})
+ state.Register("tty.outputQueueTransformer", (*outputQueueTransformer)(nil), state.Fns{Save: (*outputQueueTransformer).save, Load: (*outputQueueTransformer).load})
+ state.Register("tty.inputQueueTransformer", (*inputQueueTransformer)(nil), state.Fns{Save: (*inputQueueTransformer).save, Load: (*inputQueueTransformer).load})
+ state.Register("tty.masterInodeOperations", (*masterInodeOperations)(nil), state.Fns{Save: (*masterInodeOperations).save, Load: (*masterInodeOperations).load})
+ state.Register("tty.masterFileOperations", (*masterFileOperations)(nil), state.Fns{Save: (*masterFileOperations).save, Load: (*masterFileOperations).load})
+ state.Register("tty.queue", (*queue)(nil), state.Fns{Save: (*queue).save, Load: (*queue).load})
+ state.Register("tty.slaveInodeOperations", (*slaveInodeOperations)(nil), state.Fns{Save: (*slaveInodeOperations).save, Load: (*slaveInodeOperations).load})
+ state.Register("tty.slaveFileOperations", (*slaveFileOperations)(nil), state.Fns{Save: (*slaveFileOperations).save, Load: (*slaveFileOperations).load})
+ state.Register("tty.Terminal", (*Terminal)(nil), state.Fns{Save: (*Terminal).save, Load: (*Terminal).load})
+}
diff --git a/pkg/sentry/fs/tty/tty_test.go b/pkg/sentry/fs/tty/tty_test.go
deleted file mode 100644
index 59f07ff8e..000000000
--- a/pkg/sentry/fs/tty/tty_test.go
+++ /dev/null
@@ -1,56 +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 tty
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestSimpleMasterToSlave(t *testing.T) {
- ld := newLineDiscipline(linux.DefaultSlaveTermios)
- 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)
- }
-}