mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Convert memfs into proto-tmpfs.
- Renamed memfs to tmpfs. - Copied fileRangeSet bits from fs/fsutil/ to fsimpl/tmpfs/ - Changed tmpfs to be backed by filemem instead of byte slice. - regularFileReadWriter uses a sync.Pool, similar to gofer client. PiperOrigin-RevId: 288356380
This commit is contained in:
committed by
gVisor bot
parent
354a15a234
commit
51f3ab85e0
@@ -0,0 +1,92 @@
|
||||
load("//tools/go_stateify:defs.bzl", "go_library")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
load("//tools/go_generics:defs.bzl", "go_template_instance")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_template_instance(
|
||||
name = "dentry_list",
|
||||
out = "dentry_list.go",
|
||||
package = "tmpfs",
|
||||
prefix = "dentry",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*dentry",
|
||||
"Linker": "*dentry",
|
||||
},
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "tmpfs",
|
||||
srcs = [
|
||||
"dentry_list.go",
|
||||
"directory.go",
|
||||
"filesystem.go",
|
||||
"named_pipe.go",
|
||||
"regular_file.go",
|
||||
"symlink.go",
|
||||
"tmpfs.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs",
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/amutex",
|
||||
"//pkg/fspath",
|
||||
"//pkg/log",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/sentry/fs/fsutil",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/kernel/pipe",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/pgalloc",
|
||||
"//pkg/sentry/platform",
|
||||
"//pkg/sentry/safemem",
|
||||
"//pkg/sentry/usage",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "benchmark_test",
|
||||
size = "small",
|
||||
srcs = ["benchmark_test.go"],
|
||||
deps = [
|
||||
":tmpfs",
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/fspath",
|
||||
"//pkg/refs",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/context/contexttest",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/sentry/fs/tmpfs",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "tmpfs_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"pipe_test.go",
|
||||
"regular_file_test.go",
|
||||
],
|
||||
embed = [":tmpfs"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/fspath",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/context/contexttest",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/kernel/contexttest",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,487 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package benchmark_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/refs"
|
||||
"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/tmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// Differences from stat_benchmark:
|
||||
//
|
||||
// - Syscall interception, CopyInPath, copyOutStat, and overlayfs overheads are
|
||||
// not included.
|
||||
//
|
||||
// - *MountStat benchmarks use a tmpfs root mount and a tmpfs submount at /tmp.
|
||||
// Non-MountStat benchmarks use a tmpfs root mount and no submounts.
|
||||
// stat_benchmark uses a varying root mount, a tmpfs submount at /tmp, and a
|
||||
// subdirectory /tmp/<top_dir> (assuming TEST_TMPDIR == "/tmp"). Thus
|
||||
// stat_benchmark at depth 1 does a comparable amount of work to *MountStat
|
||||
// benchmarks at depth 2, and non-MountStat benchmarks at depth 3.
|
||||
var depths = []int{1, 2, 3, 8, 64, 100}
|
||||
|
||||
const (
|
||||
mountPointName = "tmp"
|
||||
filename = "gvisor_test_temp_0_1557494568"
|
||||
)
|
||||
|
||||
// This is copied from syscalls/linux/sys_file.go, with the dependency on
|
||||
// kernel.Task stripped out.
|
||||
func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent) error) error {
|
||||
var (
|
||||
d *fs.Dirent // The file.
|
||||
rel *fs.Dirent // The relative directory for search (if required.)
|
||||
err error
|
||||
)
|
||||
|
||||
// Extract the working directory (maybe).
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
// Absolute path; rel can be nil.
|
||||
} else if dirFD == linux.AT_FDCWD {
|
||||
// Need to reference the working directory.
|
||||
rel = wd
|
||||
} else {
|
||||
// Need to extract the given FD.
|
||||
return syserror.EBADF
|
||||
}
|
||||
|
||||
// Lookup the node.
|
||||
remainingTraversals := uint(linux.MaxSymlinkTraversals)
|
||||
if resolve {
|
||||
d, err = mntns.FindInode(ctx, root, rel, path, &remainingTraversals)
|
||||
} else {
|
||||
d, err = mntns.FindLink(ctx, root, rel, path, &remainingTraversals)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = fn(root, d)
|
||||
d.DecRef()
|
||||
return err
|
||||
}
|
||||
|
||||
func BenchmarkVFS1TmpfsStat(b *testing.B) {
|
||||
for _, depth := range depths {
|
||||
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
|
||||
ctx := contexttest.Context(b)
|
||||
|
||||
// Create VFS.
|
||||
tmpfsFS, ok := fs.FindFilesystem("tmpfs")
|
||||
if !ok {
|
||||
b.Fatalf("failed to find tmpfs filesystem type")
|
||||
}
|
||||
rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
mntns, err := fs.NewMountNamespace(ctx, rootInode)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create mount namespace: %v", err)
|
||||
}
|
||||
defer mntns.DecRef()
|
||||
|
||||
var filePathBuilder strings.Builder
|
||||
filePathBuilder.WriteByte('/')
|
||||
|
||||
// Create nested directories with given depth.
|
||||
root := mntns.Root()
|
||||
defer root.DecRef()
|
||||
d := root
|
||||
d.IncRef()
|
||||
defer d.DecRef()
|
||||
for i := depth; i > 0; i-- {
|
||||
name := fmt.Sprintf("%d", i)
|
||||
if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
|
||||
b.Fatalf("failed to create directory %q: %v", name, err)
|
||||
}
|
||||
next, err := d.Walk(ctx, root, name)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to directory %q: %v", name, err)
|
||||
}
|
||||
d.DecRef()
|
||||
d = next
|
||||
filePathBuilder.WriteString(name)
|
||||
filePathBuilder.WriteByte('/')
|
||||
}
|
||||
|
||||
// Create the file that will be stat'd.
|
||||
file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create file %q: %v", filename, err)
|
||||
}
|
||||
file.DecRef()
|
||||
filePathBuilder.WriteString(filename)
|
||||
filePath := filePathBuilder.String()
|
||||
|
||||
dirPath := false
|
||||
runtime.GC()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
|
||||
if dirPath && !fs.IsDir(d.Inode.StableAttr) {
|
||||
return syserror.ENOTDIR
|
||||
}
|
||||
uattr, err := d.Inode.UnstableAttr(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Sanity check.
|
||||
if uattr.Perms.User.Execute {
|
||||
b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("stat(%q) failed: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
// Don't include deferred cleanup in benchmark time.
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVFS2MemfsStat(b *testing.B) {
|
||||
for _, depth := range depths {
|
||||
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
|
||||
ctx := contexttest.Context(b)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
// Create VFS.
|
||||
vfsObj := vfs.New()
|
||||
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
defer mntns.DecRef(vfsObj)
|
||||
|
||||
var filePathBuilder strings.Builder
|
||||
filePathBuilder.WriteByte('/')
|
||||
|
||||
// Create nested directories with given depth.
|
||||
root := mntns.Root()
|
||||
defer root.DecRef()
|
||||
vd := root
|
||||
vd.IncRef()
|
||||
for i := depth; i > 0; i-- {
|
||||
name := fmt.Sprintf("%d", i)
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: vd,
|
||||
Path: fspath.Parse(name),
|
||||
}
|
||||
if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
|
||||
Mode: 0755,
|
||||
}); err != nil {
|
||||
b.Fatalf("failed to create directory %q: %v", name, err)
|
||||
}
|
||||
nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to directory %q: %v", name, err)
|
||||
}
|
||||
vd.DecRef()
|
||||
vd = nextVD
|
||||
filePathBuilder.WriteString(name)
|
||||
filePathBuilder.WriteByte('/')
|
||||
}
|
||||
|
||||
// Create the file that will be stat'd.
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: vd,
|
||||
Path: fspath.Parse(filename),
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
|
||||
Mode: 0644,
|
||||
})
|
||||
vd.DecRef()
|
||||
vd = vfs.VirtualDentry{}
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create file %q: %v", filename, err)
|
||||
}
|
||||
defer fd.DecRef()
|
||||
filePathBuilder.WriteString(filename)
|
||||
filePath := filePathBuilder.String()
|
||||
|
||||
runtime.GC()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(filePath),
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.StatOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("stat(%q) failed: %v", filePath, err)
|
||||
}
|
||||
// Sanity check.
|
||||
if stat.Mode&^linux.S_IFMT != 0644 {
|
||||
b.Fatalf("got wrong permissions (%0o)", stat.Mode)
|
||||
}
|
||||
}
|
||||
// Don't include deferred cleanup in benchmark time.
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVFS1TmpfsMountStat(b *testing.B) {
|
||||
for _, depth := range depths {
|
||||
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
|
||||
ctx := contexttest.Context(b)
|
||||
|
||||
// Create VFS.
|
||||
tmpfsFS, ok := fs.FindFilesystem("tmpfs")
|
||||
if !ok {
|
||||
b.Fatalf("failed to find tmpfs filesystem type")
|
||||
}
|
||||
rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
mntns, err := fs.NewMountNamespace(ctx, rootInode)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create mount namespace: %v", err)
|
||||
}
|
||||
defer mntns.DecRef()
|
||||
|
||||
var filePathBuilder strings.Builder
|
||||
filePathBuilder.WriteByte('/')
|
||||
|
||||
// Create and mount the submount.
|
||||
root := mntns.Root()
|
||||
defer root.DecRef()
|
||||
if err := root.Inode.CreateDirectory(ctx, root, mountPointName, fs.FilePermsFromMode(0755)); err != nil {
|
||||
b.Fatalf("failed to create mount point: %v", err)
|
||||
}
|
||||
mountPoint, err := root.Walk(ctx, root, mountPointName)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to mount point: %v", err)
|
||||
}
|
||||
defer mountPoint.DecRef()
|
||||
submountInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create tmpfs submount: %v", err)
|
||||
}
|
||||
if err := mntns.Mount(ctx, mountPoint, submountInode); err != nil {
|
||||
b.Fatalf("failed to mount tmpfs submount: %v", err)
|
||||
}
|
||||
filePathBuilder.WriteString(mountPointName)
|
||||
filePathBuilder.WriteByte('/')
|
||||
|
||||
// Create nested directories with given depth.
|
||||
d, err := root.Walk(ctx, root, mountPointName)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to mount root: %v", err)
|
||||
}
|
||||
defer d.DecRef()
|
||||
for i := depth; i > 0; i-- {
|
||||
name := fmt.Sprintf("%d", i)
|
||||
if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
|
||||
b.Fatalf("failed to create directory %q: %v", name, err)
|
||||
}
|
||||
next, err := d.Walk(ctx, root, name)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to directory %q: %v", name, err)
|
||||
}
|
||||
d.DecRef()
|
||||
d = next
|
||||
filePathBuilder.WriteString(name)
|
||||
filePathBuilder.WriteByte('/')
|
||||
}
|
||||
|
||||
// Create the file that will be stat'd.
|
||||
file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create file %q: %v", filename, err)
|
||||
}
|
||||
file.DecRef()
|
||||
filePathBuilder.WriteString(filename)
|
||||
filePath := filePathBuilder.String()
|
||||
|
||||
dirPath := false
|
||||
runtime.GC()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
|
||||
if dirPath && !fs.IsDir(d.Inode.StableAttr) {
|
||||
return syserror.ENOTDIR
|
||||
}
|
||||
uattr, err := d.Inode.UnstableAttr(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Sanity check.
|
||||
if uattr.Perms.User.Execute {
|
||||
b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("stat(%q) failed: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
// Don't include deferred cleanup in benchmark time.
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVFS2MemfsMountStat(b *testing.B) {
|
||||
for _, depth := range depths {
|
||||
b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
|
||||
ctx := contexttest.Context(b)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
// Create VFS.
|
||||
vfsObj := vfs.New()
|
||||
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
defer mntns.DecRef(vfsObj)
|
||||
|
||||
var filePathBuilder strings.Builder
|
||||
filePathBuilder.WriteByte('/')
|
||||
|
||||
// Create the mount point.
|
||||
root := mntns.Root()
|
||||
defer root.DecRef()
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(mountPointName),
|
||||
}
|
||||
if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
|
||||
Mode: 0755,
|
||||
}); err != nil {
|
||||
b.Fatalf("failed to create mount point: %v", err)
|
||||
}
|
||||
// Save the mount point for later use.
|
||||
mountPoint, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to mount point: %v", err)
|
||||
}
|
||||
defer mountPoint.DecRef()
|
||||
// Create and mount the submount.
|
||||
if err := vfsObj.MountAt(ctx, creds, "", &pop, "tmpfs", &vfs.MountOptions{}); err != nil {
|
||||
b.Fatalf("failed to mount tmpfs submount: %v", err)
|
||||
}
|
||||
filePathBuilder.WriteString(mountPointName)
|
||||
filePathBuilder.WriteByte('/')
|
||||
|
||||
// Create nested directories with given depth.
|
||||
vd, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to mount root: %v", err)
|
||||
}
|
||||
for i := depth; i > 0; i-- {
|
||||
name := fmt.Sprintf("%d", i)
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: vd,
|
||||
Path: fspath.Parse(name),
|
||||
}
|
||||
if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
|
||||
Mode: 0755,
|
||||
}); err != nil {
|
||||
b.Fatalf("failed to create directory %q: %v", name, err)
|
||||
}
|
||||
nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to walk to directory %q: %v", name, err)
|
||||
}
|
||||
vd.DecRef()
|
||||
vd = nextVD
|
||||
filePathBuilder.WriteString(name)
|
||||
filePathBuilder.WriteByte('/')
|
||||
}
|
||||
|
||||
// Verify that we didn't create any directories under the mount
|
||||
// point (i.e. they were all created on the submount).
|
||||
firstDirName := fmt.Sprintf("%d", depth)
|
||||
if child := mountPoint.Dentry().Child(firstDirName); child != nil {
|
||||
b.Fatalf("created directory %q under root mount, not submount", firstDirName)
|
||||
}
|
||||
|
||||
// Create the file that will be stat'd.
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: vd,
|
||||
Path: fspath.Parse(filename),
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
|
||||
Mode: 0644,
|
||||
})
|
||||
vd.DecRef()
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create file %q: %v", filename, err)
|
||||
}
|
||||
fd.DecRef()
|
||||
filePathBuilder.WriteString(filename)
|
||||
filePath := filePathBuilder.String()
|
||||
|
||||
runtime.GC()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(filePath),
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.StatOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("stat(%q) failed: %v", filePath, err)
|
||||
}
|
||||
// Sanity check.
|
||||
if stat.Mode&^linux.S_IFMT != 0644 {
|
||||
b.Fatalf("got wrong permissions (%0o)", stat.Mode)
|
||||
}
|
||||
}
|
||||
// Don't include deferred cleanup in benchmark time.
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Turn off reference leak checking for a fair comparison between vfs1 and
|
||||
// vfs2.
|
||||
refs.SetLeakMode(refs.NoLeakChecking)
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
type directory struct {
|
||||
inode inode
|
||||
|
||||
// childList is a list containing (1) child Dentries and (2) fake Dentries
|
||||
// (with inode == nil) that represent the iteration position of
|
||||
// directoryFDs. childList is used to support directoryFD.IterDirents()
|
||||
// efficiently. childList is protected by filesystem.mu.
|
||||
childList dentryList
|
||||
}
|
||||
|
||||
func (fs *filesystem) newDirectory(creds *auth.Credentials, mode linux.FileMode) *inode {
|
||||
dir := &directory{}
|
||||
dir.inode.init(dir, fs, creds, mode)
|
||||
dir.inode.nlink = 2 // from "." and parent directory or ".." for root
|
||||
return &dir.inode
|
||||
}
|
||||
|
||||
func (i *inode) isDir() bool {
|
||||
_, ok := i.impl.(*directory)
|
||||
return ok
|
||||
}
|
||||
|
||||
type directoryFD struct {
|
||||
fileDescription
|
||||
vfs.DirectoryFileDescriptionDefaultImpl
|
||||
|
||||
// Protected by filesystem.mu.
|
||||
iter *dentry
|
||||
off int64
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *directoryFD) Release() {
|
||||
if fd.iter != nil {
|
||||
fs := fd.filesystem()
|
||||
dir := fd.inode().impl.(*directory)
|
||||
fs.mu.Lock()
|
||||
dir.childList.Remove(fd.iter)
|
||||
fs.mu.Unlock()
|
||||
fd.iter = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
|
||||
func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
|
||||
fs := fd.filesystem()
|
||||
vfsd := fd.vfsfd.VirtualDentry().Dentry()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
if fd.off == 0 {
|
||||
if !cb.Handle(vfs.Dirent{
|
||||
Name: ".",
|
||||
Type: linux.DT_DIR,
|
||||
Ino: vfsd.Impl().(*dentry).inode.ino,
|
||||
NextOff: 1,
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
if fd.off == 1 {
|
||||
parentInode := vfsd.ParentOrSelf().Impl().(*dentry).inode
|
||||
if !cb.Handle(vfs.Dirent{
|
||||
Name: "..",
|
||||
Type: parentInode.direntType(),
|
||||
Ino: parentInode.ino,
|
||||
NextOff: 2,
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
|
||||
dir := vfsd.Impl().(*dentry).inode.impl.(*directory)
|
||||
var child *dentry
|
||||
if fd.iter == nil {
|
||||
// Start iteration at the beginning of dir.
|
||||
child = dir.childList.Front()
|
||||
fd.iter = &dentry{}
|
||||
} else {
|
||||
// Continue iteration from where we left off.
|
||||
child = fd.iter.Next()
|
||||
dir.childList.Remove(fd.iter)
|
||||
}
|
||||
for child != nil {
|
||||
// Skip other directoryFD iterators.
|
||||
if child.inode != nil {
|
||||
if !cb.Handle(vfs.Dirent{
|
||||
Name: child.vfsd.Name(),
|
||||
Type: child.inode.direntType(),
|
||||
Ino: child.inode.ino,
|
||||
NextOff: fd.off + 1,
|
||||
}) {
|
||||
dir.childList.InsertBefore(child, fd.iter)
|
||||
return nil
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
child = child.Next()
|
||||
}
|
||||
dir.childList.PushBack(fd.iter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
fs := fd.filesystem()
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
switch whence {
|
||||
case linux.SEEK_SET:
|
||||
// Use offset as given.
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
default:
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
// If the offset isn't changing (e.g. due to lseek(0, SEEK_CUR)), don't
|
||||
// seek even if doing so might reposition the iterator due to concurrent
|
||||
// mutation of the directory. Compare fs/libfs.c:dcache_dir_lseek().
|
||||
if fd.off == offset {
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
fd.off = offset
|
||||
// Compensate for "." and "..".
|
||||
remChildren := int64(0)
|
||||
if offset >= 2 {
|
||||
remChildren = offset - 2
|
||||
}
|
||||
|
||||
dir := fd.inode().impl.(*directory)
|
||||
|
||||
// Ensure that fd.iter exists and is not linked into dir.childList.
|
||||
if fd.iter == nil {
|
||||
fd.iter = &dentry{}
|
||||
} else {
|
||||
dir.childList.Remove(fd.iter)
|
||||
}
|
||||
// Insert fd.iter before the remChildren'th child, or at the end of the
|
||||
// list if remChildren >= number of children.
|
||||
child := dir.childList.Front()
|
||||
for child != nil {
|
||||
// Skip other directoryFD iterators.
|
||||
if child.inode != nil {
|
||||
if remChildren == 0 {
|
||||
dir.childList.InsertBefore(child, fd.iter)
|
||||
return offset, nil
|
||||
}
|
||||
remChildren--
|
||||
}
|
||||
child = child.Next()
|
||||
}
|
||||
dir.childList.PushBack(fd.iter)
|
||||
return offset, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
type namedPipe struct {
|
||||
inode inode
|
||||
|
||||
pipe *pipe.VFSPipe
|
||||
}
|
||||
|
||||
// Preconditions:
|
||||
// * fs.mu must be locked.
|
||||
// * rp.Mount().CheckBeginWrite() has been called successfully.
|
||||
func (fs *filesystem) newNamedPipe(creds *auth.Credentials, mode linux.FileMode) *inode {
|
||||
file := &namedPipe{pipe: pipe.NewVFSPipe(pipe.DefaultPipeSize, usermem.PageSize)}
|
||||
file.inode.init(file, fs, creds, mode)
|
||||
file.inode.nlink = 1 // Only the parent has a link.
|
||||
return &file.inode
|
||||
}
|
||||
|
||||
// namedPipeFD implements vfs.FileDescriptionImpl. Methods are implemented
|
||||
// entirely via struct embedding.
|
||||
type namedPipeFD struct {
|
||||
fileDescription
|
||||
|
||||
*pipe.VFSPipeFD
|
||||
}
|
||||
|
||||
func newNamedPipeFD(ctx context.Context, np *namedPipe, rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
var err error
|
||||
var fd namedPipeFD
|
||||
fd.VFSPipeFD, err = np.pipe.NewVFSPipeFD(ctx, rp, vfsd, &fd.vfsfd, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mnt := rp.Mount()
|
||||
fd.vfsfd.Init(&fd, flags, mnt, vfsd, &vfs.FileDescriptionOptions{})
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
const fileName = "mypipe"
|
||||
|
||||
func TestSeparateFDs(t *testing.T) {
|
||||
ctx, creds, vfsObj, root := setup(t)
|
||||
defer root.DecRef()
|
||||
|
||||
// Open the read side. This is done in a concurrently because opening
|
||||
// One end the pipe blocks until the other end is opened.
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(fileName),
|
||||
FollowFinalSymlink: true,
|
||||
}
|
||||
rfdchan := make(chan *vfs.FileDescription)
|
||||
go func() {
|
||||
openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY}
|
||||
rfd, _ := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
|
||||
rfdchan <- rfd
|
||||
}()
|
||||
|
||||
// Open the write side.
|
||||
openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY}
|
||||
wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
|
||||
}
|
||||
defer wfd.DecRef()
|
||||
|
||||
rfd, ok := <-rfdchan
|
||||
if !ok {
|
||||
t.Fatalf("failed to open pipe for reading %q", fileName)
|
||||
}
|
||||
defer rfd.DecRef()
|
||||
|
||||
const msg = "vamos azul"
|
||||
checkEmpty(ctx, t, rfd)
|
||||
checkWrite(ctx, t, wfd, msg)
|
||||
checkRead(ctx, t, rfd, msg)
|
||||
}
|
||||
|
||||
func TestNonblockingRead(t *testing.T) {
|
||||
ctx, creds, vfsObj, root := setup(t)
|
||||
defer root.DecRef()
|
||||
|
||||
// Open the read side as nonblocking.
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(fileName),
|
||||
FollowFinalSymlink: true,
|
||||
}
|
||||
openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_NONBLOCK}
|
||||
rfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open pipe for reading %q: %v", fileName, err)
|
||||
}
|
||||
defer rfd.DecRef()
|
||||
|
||||
// Open the write side.
|
||||
openOpts = vfs.OpenOptions{Flags: linux.O_WRONLY}
|
||||
wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
|
||||
}
|
||||
defer wfd.DecRef()
|
||||
|
||||
const msg = "geh blau"
|
||||
checkEmpty(ctx, t, rfd)
|
||||
checkWrite(ctx, t, wfd, msg)
|
||||
checkRead(ctx, t, rfd, msg)
|
||||
}
|
||||
|
||||
func TestNonblockingWriteError(t *testing.T) {
|
||||
ctx, creds, vfsObj, root := setup(t)
|
||||
defer root.DecRef()
|
||||
|
||||
// Open the write side as nonblocking, which should return ENXIO.
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(fileName),
|
||||
FollowFinalSymlink: true,
|
||||
}
|
||||
openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY | linux.O_NONBLOCK}
|
||||
_, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
|
||||
if err != syserror.ENXIO {
|
||||
t.Fatalf("expected ENXIO, but got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingleFD(t *testing.T) {
|
||||
ctx, creds, vfsObj, root := setup(t)
|
||||
defer root.DecRef()
|
||||
|
||||
// Open the pipe as readable and writable.
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(fileName),
|
||||
FollowFinalSymlink: true,
|
||||
}
|
||||
openOpts := vfs.OpenOptions{Flags: linux.O_RDWR}
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
|
||||
}
|
||||
defer fd.DecRef()
|
||||
|
||||
const msg = "forza blu"
|
||||
checkEmpty(ctx, t, fd)
|
||||
checkWrite(ctx, t, fd, msg)
|
||||
checkRead(ctx, t, fd, msg)
|
||||
}
|
||||
|
||||
// setup creates a VFS with a pipe in the root directory at path fileName. The
|
||||
// returned VirtualDentry must be DecRef()'d be the caller. It calls t.Fatal
|
||||
// upon failure.
|
||||
func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry) {
|
||||
ctx := contexttest.Context(t)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
// Create VFS.
|
||||
vfsObj := vfs.New()
|
||||
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
|
||||
// Create the pipe.
|
||||
root := mntns.Root()
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(fileName),
|
||||
}
|
||||
mknodOpts := vfs.MknodOptions{Mode: linux.ModeNamedPipe | 0644}
|
||||
if err := vfsObj.MknodAt(ctx, creds, &pop, &mknodOpts); err != nil {
|
||||
t.Fatalf("failed to create file %q: %v", fileName, err)
|
||||
}
|
||||
|
||||
// Sanity check: the file pipe exists and has the correct mode.
|
||||
stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(fileName),
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.StatOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("stat(%q) failed: %v", fileName, err)
|
||||
}
|
||||
if stat.Mode&^linux.S_IFMT != 0644 {
|
||||
t.Errorf("got wrong permissions (%0o)", stat.Mode)
|
||||
}
|
||||
if stat.Mode&linux.S_IFMT != linux.ModeNamedPipe {
|
||||
t.Errorf("got wrong file type (%0o)", stat.Mode)
|
||||
}
|
||||
|
||||
return ctx, creds, vfsObj, root
|
||||
}
|
||||
|
||||
// checkEmpty calls t.Fatal if the pipe in fd is not empty.
|
||||
func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
|
||||
readData := make([]byte, 1)
|
||||
dst := usermem.BytesIOSequence(readData)
|
||||
bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
|
||||
if err != syserror.ErrWouldBlock {
|
||||
t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err)
|
||||
}
|
||||
if bytesRead != 0 {
|
||||
t.Fatalf("expected to read 0 bytes, but got %d", bytesRead)
|
||||
}
|
||||
}
|
||||
|
||||
// checkWrite calls t.Fatal if it fails to write all of msg to fd.
|
||||
func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
|
||||
writeData := []byte(msg)
|
||||
src := usermem.BytesIOSequence(writeData)
|
||||
bytesWritten, err := fd.Write(ctx, src, vfs.WriteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error writing to pipe %q: %v", fileName, err)
|
||||
}
|
||||
if bytesWritten != int64(len(writeData)) {
|
||||
t.Fatalf("expected to write %d bytes, but wrote %d", len(writeData), bytesWritten)
|
||||
}
|
||||
}
|
||||
|
||||
// checkRead calls t.Fatal if it fails to read msg from fd.
|
||||
func checkRead(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
|
||||
readData := make([]byte, len(msg))
|
||||
dst := usermem.BytesIOSequence(readData)
|
||||
bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from pipe %q: %v", fileName, err)
|
||||
}
|
||||
if bytesRead != int64(len(msg)) {
|
||||
t.Fatalf("expected to read %d bytes, but got %d", len(msg), bytesRead)
|
||||
}
|
||||
if !bytes.Equal(readData, []byte(msg)) {
|
||||
t.Fatalf("expected to read %q from pipe, but got %q", msg, string(readData))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
type regularFile struct {
|
||||
inode inode
|
||||
|
||||
// memFile is a platform.File used to allocate pages to this regularFile.
|
||||
memFile *pgalloc.MemoryFile
|
||||
|
||||
// mu protects the fields below.
|
||||
mu sync.RWMutex
|
||||
|
||||
// data maps offsets into the file to offsets into memFile that store
|
||||
// the file's data.
|
||||
data fsutil.FileRangeSet
|
||||
|
||||
// size is the size of data, but accessed using atomic memory
|
||||
// operations to avoid locking in inode.stat().
|
||||
size uint64
|
||||
|
||||
// seals represents file seals on this inode.
|
||||
seals uint32
|
||||
}
|
||||
|
||||
func (fs *filesystem) newRegularFile(creds *auth.Credentials, mode linux.FileMode) *inode {
|
||||
file := ®ularFile{
|
||||
memFile: fs.memFile,
|
||||
}
|
||||
file.inode.init(file, fs, creds, mode)
|
||||
file.inode.nlink = 1 // from parent directory
|
||||
return &file.inode
|
||||
}
|
||||
|
||||
type regularFileFD struct {
|
||||
fileDescription
|
||||
|
||||
// These are immutable.
|
||||
readable bool
|
||||
writable bool
|
||||
|
||||
// off is the file offset. off is accessed using atomic memory operations.
|
||||
// offMu serializes operations that may mutate off.
|
||||
off int64
|
||||
offMu sync.Mutex
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *regularFileFD) Release() {
|
||||
if fd.writable {
|
||||
fd.vfsfd.VirtualDentry().Mount().EndWrite()
|
||||
}
|
||||
}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
if !fd.readable {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if dst.NumBytes() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
f := fd.inode().impl.(*regularFile)
|
||||
rw := getRegularFileReadWriter(f, offset)
|
||||
n, err := dst.CopyOutFrom(ctx, rw)
|
||||
putRegularFileReadWriter(rw)
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
n, err := fd.PRead(ctx, dst, fd.off, opts)
|
||||
fd.off += n
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
if !fd.writable {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
srclen := src.NumBytes()
|
||||
if srclen == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
f := fd.inode().impl.(*regularFile)
|
||||
end := offset + srclen
|
||||
if end < offset {
|
||||
// Overflow.
|
||||
return 0, syserror.EFBIG
|
||||
}
|
||||
rw := getRegularFileReadWriter(f, offset)
|
||||
n, err := src.CopyInTo(ctx, rw)
|
||||
putRegularFileReadWriter(rw)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
n, err := fd.PWrite(ctx, src, fd.off, opts)
|
||||
fd.off += n
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
defer fd.offMu.Unlock()
|
||||
switch whence {
|
||||
case linux.SEEK_SET:
|
||||
// use offset as specified
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
case linux.SEEK_END:
|
||||
offset += int64(atomic.LoadUint64(&fd.inode().impl.(*regularFile).size))
|
||||
default:
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
fd.off = offset
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// Sync implements vfs.FileDescriptionImpl.Sync.
|
||||
func (fd *regularFileFD) Sync(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// regularFileReadWriter implements safemem.Reader and Safemem.Writer.
|
||||
type regularFileReadWriter struct {
|
||||
file *regularFile
|
||||
|
||||
// Offset into the file to read/write at. Note that this may be
|
||||
// different from the FD offset if PRead/PWrite is used.
|
||||
off uint64
|
||||
}
|
||||
|
||||
var regularFileReadWriterPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return ®ularFileReadWriter{}
|
||||
},
|
||||
}
|
||||
|
||||
func getRegularFileReadWriter(file *regularFile, offset int64) *regularFileReadWriter {
|
||||
rw := regularFileReadWriterPool.Get().(*regularFileReadWriter)
|
||||
rw.file = file
|
||||
rw.off = uint64(offset)
|
||||
return rw
|
||||
}
|
||||
|
||||
func putRegularFileReadWriter(rw *regularFileReadWriter) {
|
||||
rw.file = nil
|
||||
regularFileReadWriterPool.Put(rw)
|
||||
}
|
||||
|
||||
// ReadToBlocks implements safemem.Reader.ReadToBlocks.
|
||||
func (rw *regularFileReadWriter) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
|
||||
rw.file.mu.RLock()
|
||||
|
||||
// Compute the range to read (limited by file size and overflow-checked).
|
||||
if rw.off >= rw.file.size {
|
||||
rw.file.mu.RUnlock()
|
||||
return 0, io.EOF
|
||||
}
|
||||
end := rw.file.size
|
||||
if rend := rw.off + dsts.NumBytes(); rend > rw.off && rend < end {
|
||||
end = rend
|
||||
}
|
||||
|
||||
var done uint64
|
||||
seg, gap := rw.file.data.Find(uint64(rw.off))
|
||||
for rw.off < end {
|
||||
mr := memmap.MappableRange{uint64(rw.off), uint64(end)}
|
||||
switch {
|
||||
case seg.Ok():
|
||||
// Get internal mappings.
|
||||
ims, err := rw.file.memFile.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), usermem.Read)
|
||||
if err != nil {
|
||||
rw.file.mu.RUnlock()
|
||||
return done, err
|
||||
}
|
||||
|
||||
// Copy from internal mappings.
|
||||
n, err := safemem.CopySeq(dsts, ims)
|
||||
done += n
|
||||
rw.off += uint64(n)
|
||||
dsts = dsts.DropFirst64(n)
|
||||
if err != nil {
|
||||
rw.file.mu.RUnlock()
|
||||
return done, err
|
||||
}
|
||||
|
||||
// Continue.
|
||||
seg, gap = seg.NextNonEmpty()
|
||||
|
||||
case gap.Ok():
|
||||
// Tmpfs holes are zero-filled.
|
||||
gapmr := gap.Range().Intersect(mr)
|
||||
dst := dsts.TakeFirst64(gapmr.Length())
|
||||
n, err := safemem.ZeroSeq(dst)
|
||||
done += n
|
||||
rw.off += uint64(n)
|
||||
dsts = dsts.DropFirst64(n)
|
||||
if err != nil {
|
||||
rw.file.mu.RUnlock()
|
||||
return done, err
|
||||
}
|
||||
|
||||
// Continue.
|
||||
seg, gap = gap.NextSegment(), fsutil.FileRangeGapIterator{}
|
||||
}
|
||||
}
|
||||
rw.file.mu.RUnlock()
|
||||
return done, nil
|
||||
}
|
||||
|
||||
// WriteFromBlocks implements safemem.Writer.WriteFromBlocks.
|
||||
func (rw *regularFileReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
|
||||
rw.file.mu.Lock()
|
||||
|
||||
// Compute the range to write (overflow-checked).
|
||||
end := rw.off + srcs.NumBytes()
|
||||
if end <= rw.off {
|
||||
end = math.MaxInt64
|
||||
}
|
||||
|
||||
// Check if seals prevent either file growth or all writes.
|
||||
switch {
|
||||
case rw.file.seals&linux.F_SEAL_WRITE != 0: // Write sealed
|
||||
rw.file.mu.Unlock()
|
||||
return 0, syserror.EPERM
|
||||
case end > rw.file.size && rw.file.seals&linux.F_SEAL_GROW != 0: // Grow sealed
|
||||
// When growth is sealed, Linux effectively allows writes which would
|
||||
// normally grow the file to partially succeed up to the current EOF,
|
||||
// rounded down to the page boundary before the EOF.
|
||||
//
|
||||
// This happens because writes (and thus the growth check) for tmpfs
|
||||
// files proceed page-by-page on Linux, and the final write to the page
|
||||
// containing EOF fails, resulting in a partial write up to the start of
|
||||
// that page.
|
||||
//
|
||||
// To emulate this behaviour, artifically truncate the write to the
|
||||
// start of the page containing the current EOF.
|
||||
//
|
||||
// See Linux, mm/filemap.c:generic_perform_write() and
|
||||
// mm/shmem.c:shmem_write_begin().
|
||||
if pgstart := uint64(usermem.Addr(rw.file.size).RoundDown()); end > pgstart {
|
||||
end = pgstart
|
||||
}
|
||||
if end <= rw.off {
|
||||
// Truncation would result in no data being written.
|
||||
rw.file.mu.Unlock()
|
||||
return 0, syserror.EPERM
|
||||
}
|
||||
}
|
||||
|
||||
// Page-aligned mr for when we need to allocate memory. RoundUp can't
|
||||
// overflow since end is an int64.
|
||||
pgstartaddr := usermem.Addr(rw.off).RoundDown()
|
||||
pgendaddr, _ := usermem.Addr(end).RoundUp()
|
||||
pgMR := memmap.MappableRange{uint64(pgstartaddr), uint64(pgendaddr)}
|
||||
|
||||
var (
|
||||
done uint64
|
||||
retErr error
|
||||
)
|
||||
seg, gap := rw.file.data.Find(uint64(rw.off))
|
||||
for rw.off < end {
|
||||
mr := memmap.MappableRange{uint64(rw.off), uint64(end)}
|
||||
switch {
|
||||
case seg.Ok():
|
||||
// Get internal mappings.
|
||||
ims, err := rw.file.memFile.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), usermem.Write)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
goto exitLoop
|
||||
}
|
||||
|
||||
// Copy to internal mappings.
|
||||
n, err := safemem.CopySeq(ims, srcs)
|
||||
done += n
|
||||
rw.off += uint64(n)
|
||||
srcs = srcs.DropFirst64(n)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
goto exitLoop
|
||||
}
|
||||
|
||||
// Continue.
|
||||
seg, gap = seg.NextNonEmpty()
|
||||
|
||||
case gap.Ok():
|
||||
// Allocate memory for the write.
|
||||
gapMR := gap.Range().Intersect(pgMR)
|
||||
fr, err := rw.file.memFile.Allocate(gapMR.Length(), usage.Tmpfs)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
goto exitLoop
|
||||
}
|
||||
|
||||
// Write to that memory as usual.
|
||||
seg, gap = rw.file.data.Insert(gap, gapMR, fr.Start), fsutil.FileRangeGapIterator{}
|
||||
}
|
||||
}
|
||||
exitLoop:
|
||||
// If the write ends beyond the file's previous size, it causes the
|
||||
// file to grow.
|
||||
if rw.off > rw.file.size {
|
||||
atomic.StoreUint64(&rw.file.size, rw.off)
|
||||
}
|
||||
|
||||
rw.file.mu.Unlock()
|
||||
return done, retErr
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
// newFileFD creates a new file in a new tmpfs mount, and returns the FD. If
|
||||
// the returned err is not nil, then cleanup should be called when the FD is no
|
||||
// longer needed.
|
||||
func newFileFD(ctx context.Context, filename string) (*vfs.FileDescription, func(), error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
vfsObj := vfs.New()
|
||||
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
root := mntns.Root()
|
||||
|
||||
// Create the file that will be write/read.
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(filename),
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
|
||||
Mode: 0644,
|
||||
})
|
||||
if err != nil {
|
||||
root.DecRef()
|
||||
mntns.DecRef(vfsObj)
|
||||
return nil, nil, fmt.Errorf("failed to create file %q: %v", filename, err)
|
||||
}
|
||||
|
||||
return fd, func() {
|
||||
root.DecRef()
|
||||
mntns.DecRef(vfsObj)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Test that we can write some data to a file and read it back.`
|
||||
func TestSimpleWriteRead(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fd, cleanup, err := newFileFD(ctx, "simpleReadWrite")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Write.
|
||||
data := []byte("foobarbaz")
|
||||
n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("fd.Write failed: %v", err)
|
||||
}
|
||||
if n != int64(len(data)) {
|
||||
t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
|
||||
}
|
||||
if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
|
||||
t.Errorf("fd.Write left offset at %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// Seek back to beginning.
|
||||
if _, err := fd.Seek(ctx, 0, linux.SEEK_SET); err != nil {
|
||||
t.Fatalf("fd.Seek failed: %v", err)
|
||||
}
|
||||
if got, want := fd.Impl().(*regularFileFD).off, int64(0); got != want {
|
||||
t.Errorf("fd.Seek(0) left offset at %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// Read.
|
||||
buf := make([]byte, len(data))
|
||||
n, err = fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("fd.Read failed: %v", err)
|
||||
}
|
||||
if n != int64(len(data)) {
|
||||
t.Errorf("fd.Read got short read length %d, want %d", n, len(data))
|
||||
}
|
||||
if got, want := string(buf), string(data); got != want {
|
||||
t.Errorf("Read got %q want %s", got, want)
|
||||
}
|
||||
if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
|
||||
t.Errorf("fd.Write left offset at %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPWrite(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fd, cleanup, err := newFileFD(ctx, "PRead")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Fill file with 1k 'a's.
|
||||
data := bytes.Repeat([]byte{'a'}, 1000)
|
||||
n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("fd.Write failed: %v", err)
|
||||
}
|
||||
if n != int64(len(data)) {
|
||||
t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
|
||||
}
|
||||
|
||||
// Write "gVisor is awesome" at various offsets.
|
||||
buf := []byte("gVisor is awesome")
|
||||
offsets := []int{0, 1, 2, 10, 20, 50, 100, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
|
||||
for _, offset := range offsets {
|
||||
name := fmt.Sprintf("PWrite offset=%d", offset)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
n, err := fd.PWrite(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.WriteOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("fd.PWrite got err %v want nil", err)
|
||||
}
|
||||
if n != int64(len(buf)) {
|
||||
t.Errorf("fd.PWrite got %d bytes want %d", n, len(buf))
|
||||
}
|
||||
|
||||
// Update data to reflect expected file contents.
|
||||
if len(data) < offset+len(buf) {
|
||||
data = append(data, make([]byte, (offset+len(buf))-len(data))...)
|
||||
}
|
||||
copy(data[offset:], buf)
|
||||
|
||||
// Read the whole file and compare with data.
|
||||
readBuf := make([]byte, len(data))
|
||||
n, err = fd.PRead(ctx, usermem.BytesIOSequence(readBuf), 0, vfs.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("fd.PRead failed: %v", err)
|
||||
}
|
||||
if n != int64(len(data)) {
|
||||
t.Errorf("fd.PRead got short read length %d, want %d", n, len(data))
|
||||
}
|
||||
if got, want := string(readBuf), string(data); got != want {
|
||||
t.Errorf("PRead got %q want %s", got, want)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRead(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fd, cleanup, err := newFileFD(ctx, "PRead")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Write 100 sequences of 'gVisor is awesome'.
|
||||
data := bytes.Repeat([]byte("gVisor is awsome"), 100)
|
||||
n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("fd.Write failed: %v", err)
|
||||
}
|
||||
if n != int64(len(data)) {
|
||||
t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
|
||||
}
|
||||
|
||||
// Read various sizes from various offsets.
|
||||
sizes := []int{0, 1, 2, 10, 20, 50, 100, 1000}
|
||||
offsets := []int{0, 1, 2, 10, 20, 50, 100, 1000, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
|
||||
|
||||
for _, size := range sizes {
|
||||
for _, offset := range offsets {
|
||||
name := fmt.Sprintf("PRead offset=%d size=%d", offset, size)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var (
|
||||
wantRead []byte
|
||||
wantErr error
|
||||
)
|
||||
if offset < len(data) {
|
||||
wantRead = data[offset:]
|
||||
} else if size > 0 {
|
||||
wantErr = io.EOF
|
||||
}
|
||||
if offset+size < len(data) {
|
||||
wantRead = wantRead[:size]
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.ReadOptions{})
|
||||
if err != wantErr {
|
||||
t.Errorf("fd.PRead got err %v want %v", err, wantErr)
|
||||
}
|
||||
if n != int64(len(wantRead)) {
|
||||
t.Errorf("fd.PRead got %d bytes want %d", n, len(wantRead))
|
||||
}
|
||||
if got := string(buf[:n]); got != string(wantRead) {
|
||||
t.Errorf("fd.PRead got %q want %q", got, string(wantRead))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
)
|
||||
|
||||
type symlink struct {
|
||||
inode inode
|
||||
target string // immutable
|
||||
}
|
||||
|
||||
func (fs *filesystem) newSymlink(creds *auth.Credentials, target string) *inode {
|
||||
link := &symlink{
|
||||
target: target,
|
||||
}
|
||||
link.inode.init(link, fs, creds, 0777)
|
||||
link.inode.nlink = 1 // from parent directory
|
||||
return &link.inode
|
||||
}
|
||||
|
||||
// O_PATH is unimplemented, so there's no way to get a FileDescription
|
||||
// representing a symlink yet.
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package tmpfs provides a filesystem implementation that behaves like tmpfs:
|
||||
// the Dentry tree is the sole source of truth for the state of the filesystem.
|
||||
//
|
||||
// Lock order:
|
||||
//
|
||||
// filesystem.mu
|
||||
// regularFileFD.offMu
|
||||
// regularFile.mu
|
||||
// inode.mu
|
||||
package tmpfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// FilesystemType implements vfs.FilesystemType.
|
||||
type FilesystemType struct{}
|
||||
|
||||
// filesystem implements vfs.FilesystemImpl.
|
||||
type filesystem struct {
|
||||
vfsfs vfs.Filesystem
|
||||
|
||||
// memFile is used to allocate pages to for regular files.
|
||||
memFile *pgalloc.MemoryFile
|
||||
|
||||
// mu serializes changes to the Dentry tree.
|
||||
mu sync.RWMutex
|
||||
|
||||
nextInoMinusOne uint64 // accessed using atomic memory operations
|
||||
}
|
||||
|
||||
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
|
||||
func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
|
||||
memFileProvider := pgalloc.MemoryFileProviderFromContext(ctx)
|
||||
if memFileProvider == nil {
|
||||
panic("MemoryFileProviderFromContext returned nil")
|
||||
}
|
||||
fs := filesystem{
|
||||
memFile: memFileProvider.MemoryFile(),
|
||||
}
|
||||
fs.vfsfs.Init(vfsObj, &fs)
|
||||
root := fs.newDentry(fs.newDirectory(creds, 01777))
|
||||
return &fs.vfsfs, &root.vfsd, nil
|
||||
}
|
||||
|
||||
// Release implements vfs.FilesystemImpl.Release.
|
||||
func (fs *filesystem) Release() {
|
||||
}
|
||||
|
||||
// dentry implements vfs.DentryImpl.
|
||||
type dentry struct {
|
||||
vfsd vfs.Dentry
|
||||
|
||||
// inode is the inode represented by this dentry. Multiple Dentries may
|
||||
// share a single non-directory inode (with hard links). inode is
|
||||
// immutable.
|
||||
inode *inode
|
||||
|
||||
// tmpfs doesn't count references on dentries; because the dentry tree is
|
||||
// the sole source of truth, it is by definition always consistent with the
|
||||
// state of the filesystem. However, it does count references on inodes,
|
||||
// because inode resources are released when all references are dropped.
|
||||
// (tmpfs doesn't really have resources to release, but we implement
|
||||
// reference counting because tmpfs regular files will.)
|
||||
|
||||
// dentryEntry (ugh) links dentries into their parent directory.childList.
|
||||
dentryEntry
|
||||
}
|
||||
|
||||
func (fs *filesystem) newDentry(inode *inode) *dentry {
|
||||
d := &dentry{
|
||||
inode: inode,
|
||||
}
|
||||
d.vfsd.Init(d)
|
||||
return d
|
||||
}
|
||||
|
||||
// IncRef implements vfs.DentryImpl.IncRef.
|
||||
func (d *dentry) IncRef() {
|
||||
d.inode.incRef()
|
||||
}
|
||||
|
||||
// TryIncRef implements vfs.DentryImpl.TryIncRef.
|
||||
func (d *dentry) TryIncRef() bool {
|
||||
return d.inode.tryIncRef()
|
||||
}
|
||||
|
||||
// DecRef implements vfs.DentryImpl.DecRef.
|
||||
func (d *dentry) DecRef() {
|
||||
d.inode.decRef()
|
||||
}
|
||||
|
||||
// inode represents a filesystem object.
|
||||
type inode struct {
|
||||
// refs is a reference count. refs is accessed using atomic memory
|
||||
// operations.
|
||||
//
|
||||
// A reference is held on all inodes that are reachable in the filesystem
|
||||
// tree. For non-directories (which may have multiple hard links), this
|
||||
// means that a reference is dropped when nlink reaches 0. For directories,
|
||||
// nlink never reaches 0 due to the "." entry; instead,
|
||||
// filesystem.RmdirAt() drops the reference.
|
||||
refs int64
|
||||
|
||||
// Inode metadata; protected by mu and accessed using atomic memory
|
||||
// operations unless otherwise specified.
|
||||
mu sync.RWMutex
|
||||
mode uint32 // excluding file type bits, which are based on impl
|
||||
nlink uint32 // protected by filesystem.mu instead of inode.mu
|
||||
uid uint32 // auth.KUID, but stored as raw uint32 for sync/atomic
|
||||
gid uint32 // auth.KGID, but ...
|
||||
ino uint64 // immutable
|
||||
|
||||
impl interface{} // immutable
|
||||
}
|
||||
|
||||
const maxLinks = math.MaxUint32
|
||||
|
||||
func (i *inode) init(impl interface{}, fs *filesystem, creds *auth.Credentials, mode linux.FileMode) {
|
||||
i.refs = 1
|
||||
i.mode = uint32(mode)
|
||||
i.uid = uint32(creds.EffectiveKUID)
|
||||
i.gid = uint32(creds.EffectiveKGID)
|
||||
i.ino = atomic.AddUint64(&fs.nextInoMinusOne, 1)
|
||||
// i.nlink initialized by caller
|
||||
i.impl = impl
|
||||
}
|
||||
|
||||
// incLinksLocked increments i's link count.
|
||||
//
|
||||
// Preconditions: filesystem.mu must be locked for writing. i.nlink != 0.
|
||||
// i.nlink < maxLinks.
|
||||
func (i *inode) incLinksLocked() {
|
||||
if i.nlink == 0 {
|
||||
panic("tmpfs.inode.incLinksLocked() called with no existing links")
|
||||
}
|
||||
if i.nlink == maxLinks {
|
||||
panic("memfs.inode.incLinksLocked() called with maximum link count")
|
||||
}
|
||||
atomic.AddUint32(&i.nlink, 1)
|
||||
}
|
||||
|
||||
// decLinksLocked decrements i's link count.
|
||||
//
|
||||
// Preconditions: filesystem.mu must be locked for writing. i.nlink != 0.
|
||||
func (i *inode) decLinksLocked() {
|
||||
if i.nlink == 0 {
|
||||
panic("tmpfs.inode.decLinksLocked() called with no existing links")
|
||||
}
|
||||
atomic.AddUint32(&i.nlink, ^uint32(0))
|
||||
}
|
||||
|
||||
func (i *inode) incRef() {
|
||||
if atomic.AddInt64(&i.refs, 1) <= 1 {
|
||||
panic("tmpfs.inode.incRef() called without holding a reference")
|
||||
}
|
||||
}
|
||||
|
||||
func (i *inode) tryIncRef() bool {
|
||||
for {
|
||||
refs := atomic.LoadInt64(&i.refs)
|
||||
if refs == 0 {
|
||||
return false
|
||||
}
|
||||
if atomic.CompareAndSwapInt64(&i.refs, refs, refs+1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *inode) decRef() {
|
||||
if refs := atomic.AddInt64(&i.refs, -1); refs == 0 {
|
||||
// This is unnecessary; it's mostly to simulate what tmpfs would do.
|
||||
if regFile, ok := i.impl.(*regularFile); ok {
|
||||
regFile.mu.Lock()
|
||||
regFile.data.DropAll(regFile.memFile)
|
||||
atomic.StoreUint64(®File.size, 0)
|
||||
regFile.mu.Unlock()
|
||||
}
|
||||
} else if refs < 0 {
|
||||
panic("tmpfs.inode.decRef() called without holding a reference")
|
||||
}
|
||||
}
|
||||
|
||||
func (i *inode) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes, isDir bool) error {
|
||||
return vfs.GenericCheckPermissions(creds, ats, isDir, uint16(atomic.LoadUint32(&i.mode)), auth.KUID(atomic.LoadUint32(&i.uid)), auth.KGID(atomic.LoadUint32(&i.gid)))
|
||||
}
|
||||
|
||||
// Go won't inline this function, and returning linux.Statx (which is quite
|
||||
// big) means spending a lot of time in runtime.duffcopy(), so instead it's an
|
||||
// output parameter.
|
||||
func (i *inode) statTo(stat *linux.Statx) {
|
||||
stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_NLINK | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO
|
||||
stat.Blksize = 1 // usermem.PageSize in tmpfs
|
||||
stat.Nlink = atomic.LoadUint32(&i.nlink)
|
||||
stat.UID = atomic.LoadUint32(&i.uid)
|
||||
stat.GID = atomic.LoadUint32(&i.gid)
|
||||
stat.Mode = uint16(atomic.LoadUint32(&i.mode))
|
||||
stat.Ino = i.ino
|
||||
// TODO: device number
|
||||
switch impl := i.impl.(type) {
|
||||
case *regularFile:
|
||||
stat.Mode |= linux.S_IFREG
|
||||
stat.Mask |= linux.STATX_SIZE | linux.STATX_BLOCKS
|
||||
stat.Size = uint64(atomic.LoadUint64(&impl.size))
|
||||
// In tmpfs, this will be FileRangeSet.Span() / 512 (but also cached in
|
||||
// a uint64 accessed using atomic memory operations to avoid taking
|
||||
// locks).
|
||||
stat.Blocks = allocatedBlocksForSize(stat.Size)
|
||||
case *directory:
|
||||
stat.Mode |= linux.S_IFDIR
|
||||
case *symlink:
|
||||
stat.Mode |= linux.S_IFLNK
|
||||
stat.Mask |= linux.STATX_SIZE | linux.STATX_BLOCKS
|
||||
stat.Size = uint64(len(impl.target))
|
||||
stat.Blocks = allocatedBlocksForSize(stat.Size)
|
||||
case *namedPipe:
|
||||
stat.Mode |= linux.S_IFIFO
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown inode type: %T", i.impl))
|
||||
}
|
||||
}
|
||||
|
||||
// allocatedBlocksForSize returns the number of 512B blocks needed to
|
||||
// accommodate the given size in bytes, as appropriate for struct
|
||||
// stat::st_blocks and struct statx::stx_blocks. (Note that this 512B block
|
||||
// size is independent of the "preferred block size for I/O", struct
|
||||
// stat::st_blksize and struct statx::stx_blksize.)
|
||||
func allocatedBlocksForSize(size uint64) uint64 {
|
||||
return (size + 511) / 512
|
||||
}
|
||||
|
||||
func (i *inode) direntType() uint8 {
|
||||
switch i.impl.(type) {
|
||||
case *regularFile:
|
||||
return linux.DT_REG
|
||||
case *directory:
|
||||
return linux.DT_DIR
|
||||
case *symlink:
|
||||
return linux.DT_LNK
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown inode type: %T", i.impl))
|
||||
}
|
||||
}
|
||||
|
||||
// fileDescription is embedded by tmpfs implementations of
|
||||
// vfs.FileDescriptionImpl.
|
||||
type fileDescription struct {
|
||||
vfsfd vfs.FileDescription
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
}
|
||||
|
||||
func (fd *fileDescription) filesystem() *filesystem {
|
||||
return fd.vfsfd.Mount().Filesystem().Impl().(*filesystem)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) inode() *inode {
|
||||
return fd.vfsfd.Dentry().Impl().(*dentry).inode
|
||||
}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
var stat linux.Statx
|
||||
fd.inode().statTo(&stat)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
if opts.Stat.Mask == 0 {
|
||||
return nil
|
||||
}
|
||||
// TODO: implement inode.setStat
|
||||
return syserror.EPERM
|
||||
}
|
||||
Reference in New Issue
Block a user