mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Delete VFS1 filesystem implementations.
Updates #1624 PiperOrigin-RevId: 488986080
This commit is contained in:
@@ -34,8 +34,8 @@ go_library(
|
||||
"//pkg/fspath",
|
||||
"//pkg/log",
|
||||
"//pkg/sentry/fdimport",
|
||||
"//pkg/sentry/fs/user",
|
||||
"//pkg/sentry/fsimpl/host",
|
||||
"//pkg/sentry/fsimpl/user",
|
||||
"//pkg/sentry/fsmetric",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fdimport"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/user"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/user"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/limits"
|
||||
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fdimport"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/user"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/host"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/user"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
|
||||
|
||||
+2
-41
@@ -1,4 +1,4 @@
|
||||
load("//tools:defs.bzl", "go_library", "go_test")
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
load("//tools/go_generics:defs.bzl", "go_template_instance")
|
||||
load("//pkg/sync/locking:locking.bzl", "declare_mutex")
|
||||
|
||||
@@ -60,7 +60,7 @@ go_library(
|
||||
"//pkg/secio",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/device",
|
||||
"//pkg/sentry/fs/lock",
|
||||
"//pkg/sentry/fsimpl/lock",
|
||||
"//pkg/sentry/fsmetric",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/kernel/time",
|
||||
@@ -123,42 +123,3 @@ declare_mutex(
|
||||
package = "fs",
|
||||
prefix = "inotify",
|
||||
)
|
||||
|
||||
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/context",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/sentry/fs/fsutil",
|
||||
"//pkg/sentry/fs/ramfs",
|
||||
"//pkg/sentry/fs/tmpfs",
|
||||
"//pkg/sentry/kernel/contexttest",
|
||||
"//pkg/sync",
|
||||
"//pkg/usermem",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "fs_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"dirent_cache_test.go",
|
||||
"dirent_refs_test.go",
|
||||
"mount_test.go",
|
||||
"path_test.go",
|
||||
],
|
||||
library = ":fs",
|
||||
deps = [
|
||||
"//pkg/context",
|
||||
"//pkg/sentry/contexttest",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "anon",
|
||||
srcs = [
|
||||
"anon.go",
|
||||
"device.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/context",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/sentry/device",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/sentry/fs/fsutil",
|
||||
],
|
||||
)
|
||||
@@ -1,42 +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 anon implements an anonymous inode, useful for implementing
|
||||
// inodes for pseudo filesystems.
|
||||
package anon
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
)
|
||||
|
||||
// NewInode constructs an anonymous Inode that is not associated
|
||||
// with any real filesystem. Some types depend on completely pseudo
|
||||
// "anon" inodes (eventfds, epollfds, etc).
|
||||
func NewInode(ctx context.Context) *fs.Inode {
|
||||
iops := &fsutil.SimpleFileInode{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermissions{
|
||||
User: fs.PermMask{Read: true, Write: true},
|
||||
}, linux.ANON_INODE_FS_MAGIC),
|
||||
}
|
||||
return fs.NewInode(ctx, iops, fs.NewPseudoMountSource(ctx), fs.StableAttr{
|
||||
Type: fs.Anonymous,
|
||||
DeviceID: PseudoDevice.DeviceID(),
|
||||
InodeID: PseudoDevice.NextIno(),
|
||||
BlockSize: hostarch.PageSize,
|
||||
})
|
||||
}
|
||||
@@ -1,22 +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 anon
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/device"
|
||||
)
|
||||
|
||||
// PseudoDevice is the device on which all anonymous inodes reside.
|
||||
var PseudoDevice = device.NewAnonDevice()
|
||||
@@ -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"
|
||||
"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/sync"
|
||||
"gvisor.dev/gvisor/pkg/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.Errorf("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(ctx)
|
||||
|
||||
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(ctx)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "dev",
|
||||
srcs = [
|
||||
"dev.go",
|
||||
"device.go",
|
||||
"fs.go",
|
||||
"full.go",
|
||||
"net_tun.go",
|
||||
"null.go",
|
||||
"random.go",
|
||||
"tty.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/bufferv2",
|
||||
"//pkg/context",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/rand",
|
||||
"//pkg/safemem",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/device",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/sentry/fs/fsutil",
|
||||
"//pkg/sentry/fs/ramfs",
|
||||
"//pkg/sentry/fs/tmpfs",
|
||||
"//pkg/sentry/inet",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/mm",
|
||||
"//pkg/sentry/pgalloc",
|
||||
"//pkg/sentry/socket/netstack",
|
||||
"//pkg/tcpip/link/tun",
|
||||
"//pkg/usermem",
|
||||
"//pkg/waiter",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -1,158 +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 dev provides a filesystem with simple devices.
|
||||
package dev
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/tmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/inet"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// Memory device numbers are from Linux's drivers/char/mem.c
|
||||
const (
|
||||
// Mem device major.
|
||||
memDevMajor uint16 = 1
|
||||
|
||||
// Mem device minors.
|
||||
nullDevMinor uint32 = 3
|
||||
zeroDevMinor uint32 = 5
|
||||
fullDevMinor uint32 = 7
|
||||
randomDevMinor uint32 = 8
|
||||
urandomDevMinor uint32 = 9
|
||||
)
|
||||
|
||||
// TTY major device number comes from include/uapi/linux/major.h.
|
||||
const (
|
||||
ttyDevMinor = 0
|
||||
ttyDevMajor = 5
|
||||
)
|
||||
|
||||
func newCharacterDevice(ctx context.Context, iops fs.InodeOperations, msrc *fs.MountSource, major uint16, minor uint32) *fs.Inode {
|
||||
return fs.NewInode(ctx, iops, msrc, fs.StableAttr{
|
||||
DeviceID: devDevice.DeviceID(),
|
||||
InodeID: devDevice.NextIno(),
|
||||
BlockSize: hostarch.PageSize,
|
||||
Type: fs.CharacterDevice,
|
||||
DeviceFileMajor: major,
|
||||
DeviceFileMinor: minor,
|
||||
})
|
||||
}
|
||||
|
||||
func newMemDevice(ctx context.Context, iops fs.InodeOperations, msrc *fs.MountSource, minor uint32) *fs.Inode {
|
||||
return fs.NewInode(ctx, iops, msrc, fs.StableAttr{
|
||||
DeviceID: devDevice.DeviceID(),
|
||||
InodeID: devDevice.NextIno(),
|
||||
BlockSize: hostarch.PageSize,
|
||||
Type: fs.CharacterDevice,
|
||||
DeviceFileMajor: memDevMajor,
|
||||
DeviceFileMinor: minor,
|
||||
})
|
||||
}
|
||||
|
||||
func newDirectory(ctx context.Context, contents map[string]*fs.Inode, msrc *fs.MountSource) *fs.Inode {
|
||||
iops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555))
|
||||
return fs.NewInode(ctx, iops, msrc, fs.StableAttr{
|
||||
DeviceID: devDevice.DeviceID(),
|
||||
InodeID: devDevice.NextIno(),
|
||||
BlockSize: hostarch.PageSize,
|
||||
Type: fs.Directory,
|
||||
})
|
||||
}
|
||||
|
||||
func newSymlink(ctx context.Context, target string, msrc *fs.MountSource) *fs.Inode {
|
||||
iops := ramfs.NewSymlink(ctx, fs.RootOwner, target)
|
||||
return fs.NewInode(ctx, iops, msrc, fs.StableAttr{
|
||||
DeviceID: devDevice.DeviceID(),
|
||||
InodeID: devDevice.NextIno(),
|
||||
BlockSize: hostarch.PageSize,
|
||||
Type: fs.Symlink,
|
||||
})
|
||||
}
|
||||
|
||||
// New returns the root node of a device filesystem.
|
||||
func New(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
|
||||
shm, err := tmpfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0777), msrc, nil /* parent */)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("tmpfs.NewDir failed: %v", err))
|
||||
}
|
||||
|
||||
contents := map[string]*fs.Inode{
|
||||
"fd": newSymlink(ctx, "/proc/self/fd", msrc),
|
||||
"stdin": newSymlink(ctx, "/proc/self/fd/0", msrc),
|
||||
"stdout": newSymlink(ctx, "/proc/self/fd/1", msrc),
|
||||
"stderr": newSymlink(ctx, "/proc/self/fd/2", msrc),
|
||||
|
||||
"null": newMemDevice(ctx, newNullDevice(ctx, fs.RootOwner, 0666), msrc, nullDevMinor),
|
||||
"zero": newMemDevice(ctx, newZeroDevice(ctx, fs.RootOwner, 0666), msrc, zeroDevMinor),
|
||||
"full": newMemDevice(ctx, newFullDevice(ctx, fs.RootOwner, 0666), msrc, fullDevMinor),
|
||||
|
||||
// This is not as good as /dev/random in linux because go
|
||||
// runtime uses sys_random and /dev/urandom internally.
|
||||
// According to 'man 4 random', this will be sufficient unless
|
||||
// application uses this to generate long-lived GPG/SSL/SSH
|
||||
// keys.
|
||||
"random": newMemDevice(ctx, newRandomDevice(ctx, fs.RootOwner, 0444), msrc, randomDevMinor),
|
||||
"urandom": newMemDevice(ctx, newRandomDevice(ctx, fs.RootOwner, 0444), msrc, urandomDevMinor),
|
||||
|
||||
"shm": shm,
|
||||
|
||||
// A devpts is typically mounted at /dev/pts to provide
|
||||
// pseudoterminal support. Place an empty directory there for
|
||||
// the devpts to be mounted over.
|
||||
"pts": newDirectory(ctx, nil, msrc),
|
||||
// Similarly, applications expect a ptmx device at /dev/ptmx
|
||||
// connected to the terminals provided by /dev/pts/. Rather
|
||||
// than creating a device directly (which requires a hairy
|
||||
// lookup on open to determine if a devpts exists), just create
|
||||
// a symlink to the ptmx provided by devpts. (The Linux devpts
|
||||
// documentation recommends this).
|
||||
//
|
||||
// If no devpts is mounted, this will simply be a dangling
|
||||
// symlink, which is fine.
|
||||
"ptmx": newSymlink(ctx, "pts/ptmx", msrc),
|
||||
|
||||
"tty": newCharacterDevice(ctx, newTTYDevice(ctx, fs.RootOwner, 0666), msrc, ttyDevMajor, ttyDevMinor),
|
||||
}
|
||||
|
||||
if isNetTunSupported(inet.StackFromContext(ctx)) {
|
||||
contents["net"] = newDirectory(ctx, map[string]*fs.Inode{
|
||||
"tun": newCharacterDevice(ctx, newNetTunDevice(ctx, fs.RootOwner, 0666), msrc, netTunDevMajor, netTunDevMinor),
|
||||
}, msrc)
|
||||
}
|
||||
|
||||
iops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555))
|
||||
return fs.NewInode(ctx, iops, msrc, fs.StableAttr{
|
||||
DeviceID: devDevice.DeviceID(),
|
||||
InodeID: devDevice.NextIno(),
|
||||
BlockSize: hostarch.PageSize,
|
||||
Type: fs.Directory,
|
||||
})
|
||||
}
|
||||
|
||||
// readZeros implements fs.FileOperations.Read with infinite null bytes.
|
||||
type readZeros struct{}
|
||||
|
||||
// Read implements fs.FileOperations.Read.
|
||||
func (*readZeros) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
|
||||
return dst.ZeroOut(ctx, math.MaxInt64)
|
||||
}
|
||||
@@ -1,20 +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 dev
|
||||
|
||||
import "gvisor.dev/gvisor/pkg/sentry/device"
|
||||
|
||||
// devDevice is the pseudo-filesystem device.
|
||||
var devDevice = device.NewAnonDevice()
|
||||
@@ -1,64 +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 dev
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
)
|
||||
|
||||
// filesystem is a devtmpfs.
|
||||
//
|
||||
// +stateify savable
|
||||
type filesystem struct{}
|
||||
|
||||
var _ fs.Filesystem = (*filesystem)(nil)
|
||||
|
||||
func init() {
|
||||
fs.RegisterFilesystem(&filesystem{})
|
||||
}
|
||||
|
||||
// FilesystemName is the name under which the filesystem is registered.
|
||||
// Name matches drivers/base/devtmpfs.c:dev_fs_type.name.
|
||||
const FilesystemName = "devtmpfs"
|
||||
|
||||
// Name is the name of the file system.
|
||||
func (*filesystem) Name() string {
|
||||
return FilesystemName
|
||||
}
|
||||
|
||||
// AllowUserMount allows users to mount(2) this file system.
|
||||
func (*filesystem) AllowUserMount() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// AllowUserList allows this filesystem to be listed in /proc/filesystems.
|
||||
func (*filesystem) AllowUserList() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Flags returns that there is nothing special about this file system.
|
||||
//
|
||||
// In Linux, devtmpfs does the same thing.
|
||||
func (*filesystem) Flags() fs.FilesystemFlags {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Mount returns a devtmpfs root that can be positioned in the vfs.
|
||||
func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ any) (*fs.Inode, error) {
|
||||
// devtmpfs backed by ramfs ignores bad options. See fs/ramfs/inode.c:ramfs_parse_options.
|
||||
// -> we should consider parsing the mode and backing devtmpfs by this.
|
||||
return New(ctx, fs.NewNonCachingMountSource(ctx, f, flags)), nil
|
||||
}
|
||||
@@ -1,81 +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 dev
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// fullDevice is used to implement /dev/full.
|
||||
//
|
||||
// +stateify savable
|
||||
type fullDevice struct {
|
||||
fsutil.InodeGenericChecker `state:"nosave"`
|
||||
fsutil.InodeNoExtendedAttributes `state:"nosave"`
|
||||
fsutil.InodeNoopAllocate `state:"nosave"`
|
||||
fsutil.InodeNoopRelease `state:"nosave"`
|
||||
fsutil.InodeNoopTruncate `state:"nosave"`
|
||||
fsutil.InodeNoopWriteOut `state:"nosave"`
|
||||
fsutil.InodeNotDirectory `state:"nosave"`
|
||||
fsutil.InodeNotMappable `state:"nosave"`
|
||||
fsutil.InodeNotSocket `state:"nosave"`
|
||||
fsutil.InodeNotSymlink `state:"nosave"`
|
||||
fsutil.InodeVirtual `state:"nosave"`
|
||||
|
||||
fsutil.InodeSimpleAttributes
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*fullDevice)(nil)
|
||||
|
||||
func newFullDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *fullDevice {
|
||||
f := &fullDevice{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// GetFile implements fs.InodeOperations.GetFile.
|
||||
func (f *fullDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
||||
flags.Pread = true
|
||||
return fs.NewFile(ctx, dirent, flags, &fullFileOperations{}), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type fullFileOperations struct {
|
||||
waiter.AlwaysReady `state:"nosave"`
|
||||
fsutil.FileGenericSeek `state:"nosave"`
|
||||
fsutil.FileNoIoctl `state:"nosave"`
|
||||
fsutil.FileNoMMap `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNoopRelease `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
readZeros `state:"nosave"`
|
||||
}
|
||||
|
||||
var _ fs.FileOperations = (*fullFileOperations)(nil)
|
||||
|
||||
// Write implements FileOperations.Write.
|
||||
func (*fullFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
|
||||
return 0, linuxerr.ENOSPC
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dev
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/bufferv2"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/inet"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
const (
|
||||
netTunDevMajor = 10
|
||||
netTunDevMinor = 200
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type netTunInodeOperations struct {
|
||||
fsutil.InodeGenericChecker `state:"nosave"`
|
||||
fsutil.InodeNoExtendedAttributes `state:"nosave"`
|
||||
fsutil.InodeNoopAllocate `state:"nosave"`
|
||||
fsutil.InodeNoopRelease `state:"nosave"`
|
||||
fsutil.InodeNoopTruncate `state:"nosave"`
|
||||
fsutil.InodeNoopWriteOut `state:"nosave"`
|
||||
fsutil.InodeNotDirectory `state:"nosave"`
|
||||
fsutil.InodeNotMappable `state:"nosave"`
|
||||
fsutil.InodeNotSocket `state:"nosave"`
|
||||
fsutil.InodeNotSymlink `state:"nosave"`
|
||||
fsutil.InodeVirtual `state:"nosave"`
|
||||
|
||||
fsutil.InodeSimpleAttributes
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*netTunInodeOperations)(nil)
|
||||
|
||||
func newNetTunDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *netTunInodeOperations {
|
||||
return &netTunInodeOperations{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
|
||||
}
|
||||
}
|
||||
|
||||
// GetFile implements fs.InodeOperations.GetFile.
|
||||
func (*netTunInodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
||||
return fs.NewFile(ctx, d, flags, &netTunFileOperations{}), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type netTunFileOperations struct {
|
||||
fsutil.FileNoSeek `state:"nosave"`
|
||||
fsutil.FileNoMMap `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
|
||||
device tun.Device
|
||||
}
|
||||
|
||||
var _ fs.FileOperations = (*netTunFileOperations)(nil)
|
||||
|
||||
// Release implements fs.FileOperations.Release.
|
||||
func (n *netTunFileOperations) Release(ctx context.Context) {
|
||||
n.device.Release(ctx)
|
||||
}
|
||||
|
||||
// Ioctl implements fs.FileOperations.Ioctl.
|
||||
func (n *netTunFileOperations) Ioctl(ctx context.Context, file *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
|
||||
request := args[1].Uint()
|
||||
data := args[2].Pointer()
|
||||
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
panic("Ioctl should be called from a task context")
|
||||
}
|
||||
|
||||
switch request {
|
||||
case linux.TUNSETIFF:
|
||||
if !t.HasCapability(linux.CAP_NET_ADMIN) {
|
||||
return 0, linuxerr.EPERM
|
||||
}
|
||||
stack, ok := t.NetworkContext().(*netstack.Stack)
|
||||
if !ok {
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
var req linux.IFReq
|
||||
if _, err := req.CopyIn(t, data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Validate flags.
|
||||
flags, err := netstack.LinuxToTUNFlags(hostarch.ByteOrder.Uint16(req.Data[:]))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, n.device.SetIff(stack.Stack, req.Name(), flags)
|
||||
|
||||
case linux.TUNGETIFF:
|
||||
var req linux.IFReq
|
||||
copy(req.IFName[:], n.device.Name())
|
||||
hostarch.ByteOrder.PutUint16(req.Data[:], netstack.TUNFlagsToLinux(n.device.Flags()))
|
||||
_, err := req.CopyOut(t, data)
|
||||
return 0, err
|
||||
|
||||
default:
|
||||
return 0, linuxerr.ENOTTY
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements fs.FileOperations.Write.
|
||||
func (n *netTunFileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
if src.NumBytes() == 0 {
|
||||
return 0, unix.EINVAL
|
||||
}
|
||||
data := bufferv2.NewView(int(src.NumBytes()))
|
||||
defer data.Release()
|
||||
if _, err := io.CopyN(data, src.Reader(ctx), src.NumBytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n.device.Write(data)
|
||||
}
|
||||
|
||||
// Read implements fs.FileOperations.Read.
|
||||
func (n *netTunFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
|
||||
data, err := n.device.Read()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer data.Release()
|
||||
dataSize := data.Size()
|
||||
bytesCopied, err := io.CopyN(dst.Writer(ctx), data, dst.NumBytes())
|
||||
if bytesCopied > 0 && bytesCopied < int64(dataSize) {
|
||||
// Not an error for partial copying. Packet truncated.
|
||||
err = nil
|
||||
}
|
||||
return int64(bytesCopied), err
|
||||
}
|
||||
|
||||
// Readiness implements watier.Waitable.Readiness.
|
||||
func (n *netTunFileOperations) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
return n.device.Readiness(mask)
|
||||
}
|
||||
|
||||
// EventRegister implements watier.Waitable.EventRegister.
|
||||
func (n *netTunFileOperations) EventRegister(e *waiter.Entry) error {
|
||||
n.device.EventRegister(e)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventUnregister implements watier.Waitable.EventUnregister.
|
||||
func (n *netTunFileOperations) EventUnregister(e *waiter.Entry) {
|
||||
n.device.EventUnregister(e)
|
||||
}
|
||||
|
||||
// isNetTunSupported returns whether /dev/net/tun device is supported for s.
|
||||
func isNetTunSupported(s inet.Stack) bool {
|
||||
_, ok := s.(*netstack.Stack)
|
||||
return ok
|
||||
}
|
||||
@@ -1,131 +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 dev
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/mm"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type nullDevice struct {
|
||||
fsutil.InodeGenericChecker `state:"nosave"`
|
||||
fsutil.InodeNoExtendedAttributes `state:"nosave"`
|
||||
fsutil.InodeNoopAllocate `state:"nosave"`
|
||||
fsutil.InodeNoopRelease `state:"nosave"`
|
||||
fsutil.InodeNoopTruncate `state:"nosave"`
|
||||
fsutil.InodeNoopWriteOut `state:"nosave"`
|
||||
fsutil.InodeNotDirectory `state:"nosave"`
|
||||
fsutil.InodeNotMappable `state:"nosave"`
|
||||
fsutil.InodeNotSocket `state:"nosave"`
|
||||
fsutil.InodeNotSymlink `state:"nosave"`
|
||||
fsutil.InodeVirtual `state:"nosave"`
|
||||
|
||||
fsutil.InodeSimpleAttributes
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*nullDevice)(nil)
|
||||
|
||||
func newNullDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *nullDevice {
|
||||
n := &nullDevice{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// GetFile implements fs.FileOperations.GetFile.
|
||||
func (n *nullDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
||||
flags.Pread = true
|
||||
flags.Pwrite = true
|
||||
|
||||
return fs.NewFile(ctx, dirent, flags, &nullFileOperations{}), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type nullFileOperations struct {
|
||||
fsutil.FileGenericSeek `state:"nosave"`
|
||||
fsutil.FileNoIoctl `state:"nosave"`
|
||||
fsutil.FileNoMMap `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNoopRead `state:"nosave"`
|
||||
fsutil.FileNoopRelease `state:"nosave"`
|
||||
fsutil.FileNoopWrite `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
waiter.AlwaysReady `state:"nosave"`
|
||||
}
|
||||
|
||||
var _ fs.FileOperations = (*nullFileOperations)(nil)
|
||||
|
||||
// +stateify savable
|
||||
type zeroDevice struct {
|
||||
nullDevice
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*zeroDevice)(nil)
|
||||
|
||||
func newZeroDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *zeroDevice {
|
||||
zd := &zeroDevice{
|
||||
nullDevice: nullDevice{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
|
||||
},
|
||||
}
|
||||
return zd
|
||||
}
|
||||
|
||||
// GetFile implements fs.FileOperations.GetFile.
|
||||
func (zd *zeroDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
||||
flags.Pread = true
|
||||
flags.Pwrite = true
|
||||
flags.NonSeekable = true
|
||||
|
||||
return fs.NewFile(ctx, dirent, flags, &zeroFileOperations{}), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type zeroFileOperations struct {
|
||||
fsutil.FileGenericSeek `state:"nosave"`
|
||||
fsutil.FileNoIoctl `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNoopRelease `state:"nosave"`
|
||||
fsutil.FileNoopWrite `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
waiter.AlwaysReady `state:"nosave"`
|
||||
readZeros `state:"nosave"`
|
||||
}
|
||||
|
||||
var _ fs.FileOperations = (*zeroFileOperations)(nil)
|
||||
|
||||
// ConfigureMMap implements fs.FileOperations.ConfigureMMap.
|
||||
func (*zeroFileOperations) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error {
|
||||
m, err := mm.NewSharedAnonMappable(opts.Length, pgalloc.MemoryFileProviderFromContext(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.MappingIdentity = m
|
||||
opts.Mappable = m
|
||||
return nil
|
||||
}
|
||||
@@ -1,79 +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 dev
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/rand"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type randomDevice struct {
|
||||
fsutil.InodeGenericChecker `state:"nosave"`
|
||||
fsutil.InodeNoExtendedAttributes `state:"nosave"`
|
||||
fsutil.InodeNoopAllocate `state:"nosave"`
|
||||
fsutil.InodeNoopRelease `state:"nosave"`
|
||||
fsutil.InodeNoopTruncate `state:"nosave"`
|
||||
fsutil.InodeNoopWriteOut `state:"nosave"`
|
||||
fsutil.InodeNotDirectory `state:"nosave"`
|
||||
fsutil.InodeNotMappable `state:"nosave"`
|
||||
fsutil.InodeNotSocket `state:"nosave"`
|
||||
fsutil.InodeNotSymlink `state:"nosave"`
|
||||
fsutil.InodeVirtual `state:"nosave"`
|
||||
|
||||
fsutil.InodeSimpleAttributes
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*randomDevice)(nil)
|
||||
|
||||
func newRandomDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *randomDevice {
|
||||
r := &randomDevice{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GetFile implements fs.InodeOperations.GetFile.
|
||||
func (*randomDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
||||
return fs.NewFile(ctx, dirent, flags, &randomFileOperations{}), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type randomFileOperations struct {
|
||||
fsutil.FileGenericSeek `state:"nosave"`
|
||||
fsutil.FileNoIoctl `state:"nosave"`
|
||||
fsutil.FileNoMMap `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNoopRelease `state:"nosave"`
|
||||
fsutil.FileNoopWrite `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
waiter.AlwaysReady `state:"nosave"`
|
||||
}
|
||||
|
||||
var _ fs.FileOperations = (*randomFileOperations)(nil)
|
||||
|
||||
// Read implements fs.FileOperations.Read.
|
||||
func (*randomFileOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) {
|
||||
return dst.CopyOutFrom(ctx, safemem.FromIOReader{rand.Reader})
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dev
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type ttyInodeOperations struct {
|
||||
fsutil.InodeGenericChecker `state:"nosave"`
|
||||
fsutil.InodeNoExtendedAttributes `state:"nosave"`
|
||||
fsutil.InodeNoopAllocate `state:"nosave"`
|
||||
fsutil.InodeNoopRelease `state:"nosave"`
|
||||
fsutil.InodeNoopTruncate `state:"nosave"`
|
||||
fsutil.InodeNoopWriteOut `state:"nosave"`
|
||||
fsutil.InodeNotDirectory `state:"nosave"`
|
||||
fsutil.InodeNotMappable `state:"nosave"`
|
||||
fsutil.InodeNotOpenable `state:"nosave"`
|
||||
fsutil.InodeNotSocket `state:"nosave"`
|
||||
fsutil.InodeNotSymlink `state:"nosave"`
|
||||
fsutil.InodeVirtual `state:"nosave"`
|
||||
|
||||
fsutil.InodeSimpleAttributes
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*ttyInodeOperations)(nil)
|
||||
|
||||
func newTTYDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *ttyInodeOperations {
|
||||
return &ttyInodeOperations{
|
||||
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
|
||||
}
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type ttyFileOperations struct {
|
||||
fsutil.FileNoSeek `state:"nosave"`
|
||||
fsutil.FileNoIoctl `state:"nosave"`
|
||||
fsutil.FileNoMMap `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNoopRelease `state:"nosave"`
|
||||
fsutil.FileNoopWrite `state:"nosave"`
|
||||
fsutil.FileNoopRead `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
waiter.AlwaysReady `state:"nosave"`
|
||||
}
|
||||
|
||||
var _ fs.FileOperations = (*ttyFileOperations)(nil)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/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(ctx)
|
||||
|
||||
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(ctx)
|
||||
|
||||
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 != unix.ENOENT {
|
||||
t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, unix.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(ctx)
|
||||
|
||||
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(ctx)
|
||||
|
||||
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 != unix.ENOENT {
|
||||
t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, unix.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(ctx)
|
||||
|
||||
test.root.flush(ctx)
|
||||
|
||||
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(ctx)
|
||||
newParent.flush(ctx)
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
load("//tools:defs.bzl", "go_library", "go_test")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "fdpipe",
|
||||
srcs = [
|
||||
"pipe.go",
|
||||
"pipe_opener.go",
|
||||
"pipe_state.go",
|
||||
],
|
||||
imports = ["gvisor.dev/gvisor/pkg/sentry/fs"],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/context",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fd",
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/log",
|
||||
"//pkg/safemem",
|
||||
"//pkg/secio",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/sentry/fs/fsutil",
|
||||
"//pkg/sync",
|
||||
"//pkg/usermem",
|
||||
"//pkg/waiter",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "fdpipe_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"pipe_opener_test.go",
|
||||
"pipe_test.go",
|
||||
],
|
||||
library = ":fdpipe",
|
||||
deps = [
|
||||
"//pkg/context",
|
||||
"//pkg/errors",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fd",
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/sentry/contexttest",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/usermem",
|
||||
"@com_github_google_uuid//:go_default_library",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user