gvisor/pkg/sentry/fsimpl/tmpfs/pipe_test.go

239 lines
7.2 KiB
Go

// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tmpfs
import (
"bytes"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/contexttest"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
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.VirtualFilesystem{}
if err := vfsObj.Init(); err != nil {
t.Fatalf("VFS init: %v", err)
}
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))
}
}