gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/tmpfs/pipe_test.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tmpfs
    16  
    17  import (
    18  	"bytes"
    19  	"testing"
    20  
    21  	"gvisor.dev/gvisor/pkg/abi/linux"
    22  	"gvisor.dev/gvisor/pkg/context"
    23  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    24  	"gvisor.dev/gvisor/pkg/fspath"
    25  	"gvisor.dev/gvisor/pkg/sentry/contexttest"
    26  	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
    27  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    28  	"gvisor.dev/gvisor/pkg/usermem"
    29  )
    30  
    31  const fileName = "mypipe"
    32  
    33  func TestSeparateFDs(t *testing.T) {
    34  	ctx, creds, vfsObj, root := setup(t)
    35  	defer root.DecRef(ctx)
    36  
    37  	// Open the read side. This is done in a concurrently because opening
    38  	// One end the pipe blocks until the other end is opened.
    39  	pop := vfs.PathOperation{
    40  		Root:               root,
    41  		Start:              root,
    42  		Path:               fspath.Parse(fileName),
    43  		FollowFinalSymlink: true,
    44  	}
    45  	rfdchan := make(chan *vfs.FileDescription)
    46  	go func() {
    47  		openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY}
    48  		rfd, _ := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
    49  		rfdchan <- rfd
    50  	}()
    51  
    52  	// Open the write side.
    53  	openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY}
    54  	wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
    55  	if err != nil {
    56  		t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
    57  	}
    58  	defer wfd.DecRef(ctx)
    59  
    60  	rfd, ok := <-rfdchan
    61  	if !ok {
    62  		t.Fatalf("failed to open pipe for reading %q", fileName)
    63  	}
    64  	defer rfd.DecRef(ctx)
    65  
    66  	const msg = "vamos azul"
    67  	checkEmpty(ctx, t, rfd)
    68  	checkWrite(ctx, t, wfd, msg)
    69  	checkRead(ctx, t, rfd, msg)
    70  }
    71  
    72  func TestNonblockingRead(t *testing.T) {
    73  	ctx, creds, vfsObj, root := setup(t)
    74  	defer root.DecRef(ctx)
    75  
    76  	// Open the read side as nonblocking.
    77  	pop := vfs.PathOperation{
    78  		Root:               root,
    79  		Start:              root,
    80  		Path:               fspath.Parse(fileName),
    81  		FollowFinalSymlink: true,
    82  	}
    83  	openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_NONBLOCK}
    84  	rfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
    85  	if err != nil {
    86  		t.Fatalf("failed to open pipe for reading %q: %v", fileName, err)
    87  	}
    88  	defer rfd.DecRef(ctx)
    89  
    90  	// Open the write side.
    91  	openOpts = vfs.OpenOptions{Flags: linux.O_WRONLY}
    92  	wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
    93  	if err != nil {
    94  		t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
    95  	}
    96  	defer wfd.DecRef(ctx)
    97  
    98  	const msg = "geh blau"
    99  	checkEmpty(ctx, t, rfd)
   100  	checkWrite(ctx, t, wfd, msg)
   101  	checkRead(ctx, t, rfd, msg)
   102  }
   103  
   104  func TestNonblockingWriteError(t *testing.T) {
   105  	ctx, creds, vfsObj, root := setup(t)
   106  	defer root.DecRef(ctx)
   107  
   108  	// Open the write side as nonblocking, which should return ENXIO.
   109  	pop := vfs.PathOperation{
   110  		Root:               root,
   111  		Start:              root,
   112  		Path:               fspath.Parse(fileName),
   113  		FollowFinalSymlink: true,
   114  	}
   115  	openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY | linux.O_NONBLOCK}
   116  	_, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
   117  	if !linuxerr.Equals(linuxerr.ENXIO, err) {
   118  		t.Fatalf("expected ENXIO, but got error: %v", err)
   119  	}
   120  }
   121  
   122  func TestSingleFD(t *testing.T) {
   123  	ctx, creds, vfsObj, root := setup(t)
   124  	defer root.DecRef(ctx)
   125  
   126  	// Open the pipe as readable and writable.
   127  	pop := vfs.PathOperation{
   128  		Root:               root,
   129  		Start:              root,
   130  		Path:               fspath.Parse(fileName),
   131  		FollowFinalSymlink: true,
   132  	}
   133  	openOpts := vfs.OpenOptions{Flags: linux.O_RDWR}
   134  	fd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
   135  	if err != nil {
   136  		t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
   137  	}
   138  	defer fd.DecRef(ctx)
   139  
   140  	const msg = "forza blu"
   141  	checkEmpty(ctx, t, fd)
   142  	checkWrite(ctx, t, fd, msg)
   143  	checkRead(ctx, t, fd, msg)
   144  }
   145  
   146  // setup creates a VFS with a pipe in the root directory at path fileName. The
   147  // returned VirtualDentry must be DecRef()'d be the caller. It calls t.Fatal
   148  // upon failure.
   149  func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry) {
   150  	ctx := contexttest.Context(t)
   151  	creds := auth.CredentialsFromContext(ctx)
   152  
   153  	// Create VFS.
   154  	vfsObj := &vfs.VirtualFilesystem{}
   155  	if err := vfsObj.Init(ctx); err != nil {
   156  		t.Fatalf("VFS init: %v", err)
   157  	}
   158  	vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
   159  		AllowUserMount: true,
   160  	})
   161  	mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil)
   162  	if err != nil {
   163  		t.Fatalf("failed to create tmpfs root mount: %v", err)
   164  	}
   165  
   166  	// Create the pipe.
   167  	root := mntns.Root(ctx)
   168  	pop := vfs.PathOperation{
   169  		Root:  root,
   170  		Start: root,
   171  		Path:  fspath.Parse(fileName),
   172  	}
   173  	mknodOpts := vfs.MknodOptions{Mode: linux.ModeNamedPipe | 0644}
   174  	if err := vfsObj.MknodAt(ctx, creds, &pop, &mknodOpts); err != nil {
   175  		t.Fatalf("failed to create file %q: %v", fileName, err)
   176  	}
   177  
   178  	// Sanity check: the file pipe exists and has the correct mode.
   179  	stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
   180  		Root:               root,
   181  		Start:              root,
   182  		Path:               fspath.Parse(fileName),
   183  		FollowFinalSymlink: true,
   184  	}, &vfs.StatOptions{})
   185  	if err != nil {
   186  		t.Fatalf("stat(%q) failed: %v", fileName, err)
   187  	}
   188  	if stat.Mode&^linux.S_IFMT != 0644 {
   189  		t.Errorf("got wrong permissions (%0o)", stat.Mode)
   190  	}
   191  	if stat.Mode&linux.S_IFMT != linux.ModeNamedPipe {
   192  		t.Errorf("got wrong file type (%0o)", stat.Mode)
   193  	}
   194  
   195  	return ctx, creds, vfsObj, root
   196  }
   197  
   198  // checkEmpty calls t.Fatal if the pipe in fd is not empty.
   199  func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
   200  	readData := make([]byte, 1)
   201  	dst := usermem.BytesIOSequence(readData)
   202  	bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
   203  	if err != linuxerr.ErrWouldBlock {
   204  		t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err)
   205  	}
   206  	if bytesRead != 0 {
   207  		t.Fatalf("expected to read 0 bytes, but got %d", bytesRead)
   208  	}
   209  }
   210  
   211  // checkWrite calls t.Fatal if it fails to write all of msg to fd.
   212  func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
   213  	writeData := []byte(msg)
   214  	src := usermem.BytesIOSequence(writeData)
   215  	bytesWritten, err := fd.Write(ctx, src, vfs.WriteOptions{})
   216  	if err != nil {
   217  		t.Fatalf("error writing to pipe %q: %v", fileName, err)
   218  	}
   219  	if bytesWritten != int64(len(writeData)) {
   220  		t.Fatalf("expected to write %d bytes, but wrote %d", len(writeData), bytesWritten)
   221  	}
   222  }
   223  
   224  // checkRead calls t.Fatal if it fails to read msg from fd.
   225  func checkRead(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
   226  	readData := make([]byte, len(msg))
   227  	dst := usermem.BytesIOSequence(readData)
   228  	bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
   229  	if err != nil {
   230  		t.Fatalf("error reading from pipe %q: %v", fileName, err)
   231  	}
   232  	if bytesRead != int64(len(msg)) {
   233  		t.Fatalf("expected to read %d bytes, but got %d", len(msg), bytesRead)
   234  	}
   235  	if !bytes.Equal(readData, []byte(msg)) {
   236  		t.Fatalf("expected to read %q from pipe, but got %q", msg, string(readData))
   237  	}
   238  }