gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/vfs/file_description_impl_util_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 vfs
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"testing"
    22  
    23  	"gvisor.dev/gvisor/pkg/abi/linux"
    24  	"gvisor.dev/gvisor/pkg/atomicbitops"
    25  	"gvisor.dev/gvisor/pkg/context"
    26  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    27  	"gvisor.dev/gvisor/pkg/sentry/contexttest"
    28  	"gvisor.dev/gvisor/pkg/usermem"
    29  )
    30  
    31  // fileDescription is the common fd struct which a filesystem implementation
    32  // embeds in all of its file description implementations as required.
    33  type fileDescription struct {
    34  	vfsfd FileDescription
    35  	FileDescriptionDefaultImpl
    36  	NoLockFD
    37  }
    38  
    39  // genCount contains the number of times its DynamicBytesSource.Generate()
    40  // implementation has been called.
    41  type genCount struct {
    42  	count atomicbitops.Uint64
    43  }
    44  
    45  // Generate implements DynamicBytesSource.Generate.
    46  func (g *genCount) Generate(ctx context.Context, buf *bytes.Buffer) error {
    47  	fmt.Fprintf(buf, "%d", g.count.Add(1))
    48  	return nil
    49  }
    50  
    51  type storeData struct {
    52  	data string
    53  }
    54  
    55  var _ WritableDynamicBytesSource = (*storeData)(nil)
    56  
    57  // Generate implements DynamicBytesSource.
    58  func (d *storeData) Generate(ctx context.Context, buf *bytes.Buffer) error {
    59  	buf.WriteString(d.data)
    60  	return nil
    61  }
    62  
    63  // Generate implements WritableDynamicBytesSource.
    64  func (d *storeData) Write(ctx context.Context, _ *FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
    65  	buf := make([]byte, src.NumBytes())
    66  	n, err := src.CopyIn(ctx, buf)
    67  	if err != nil {
    68  		return 0, err
    69  	}
    70  
    71  	d.data = string(buf[:n])
    72  	return 0, nil
    73  }
    74  
    75  // testFD is a read-only FileDescriptionImpl representing a regular file.
    76  type testFD struct {
    77  	fileDescription
    78  	DynamicBytesFileDescriptionImpl
    79  	DentryMetadataFileDescriptionImpl
    80  
    81  	data DynamicBytesSource
    82  }
    83  
    84  func newTestFD(ctx context.Context, vfsObj *VirtualFilesystem, statusFlags uint32, data DynamicBytesSource) *FileDescription {
    85  	vd := vfsObj.NewAnonVirtualDentry("genCountFD")
    86  	defer vd.DecRef(ctx)
    87  	var fd testFD
    88  	fd.fileDescription.vfsfd.Init(&fd, statusFlags, vd.Mount(), vd.Dentry(), &FileDescriptionOptions{})
    89  	fd.DynamicBytesFileDescriptionImpl.Init(&fd.fileDescription.vfsfd, data)
    90  	return &fd.fileDescription.vfsfd
    91  }
    92  
    93  // Release implements FileDescriptionImpl.Release.
    94  func (fd *testFD) Release(context.Context) {
    95  }
    96  
    97  func TestGenCountFD(t *testing.T) {
    98  	ctx := contexttest.Context(t)
    99  
   100  	vfsObj := &VirtualFilesystem{}
   101  	if err := vfsObj.Init(ctx); err != nil {
   102  		t.Fatalf("VFS init: %v", err)
   103  	}
   104  	fd := newTestFD(ctx, vfsObj, linux.O_RDWR, &genCount{})
   105  	defer fd.DecRef(ctx)
   106  
   107  	// The first read causes Generate to be called to fill the FD's buffer.
   108  	buf := make([]byte, 2)
   109  	ioseq := usermem.BytesIOSequence(buf)
   110  	n, err := fd.Read(ctx, ioseq, ReadOptions{})
   111  	if n != 1 || (err != nil && err != io.EOF) {
   112  		t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err)
   113  	}
   114  	if want := byte('1'); buf[0] != want {
   115  		t.Errorf("first Read: got byte %c, wanted %c", buf[0], want)
   116  	}
   117  
   118  	// A second read without seeking is still at EOF.
   119  	n, err = fd.Read(ctx, ioseq, ReadOptions{})
   120  	if n != 0 || err != io.EOF {
   121  		t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err)
   122  	}
   123  
   124  	// Seeking to the beginning of the file causes it to be regenerated.
   125  	n, err = fd.Seek(ctx, 0, linux.SEEK_SET)
   126  	if n != 0 || err != nil {
   127  		t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err)
   128  	}
   129  	n, err = fd.Read(ctx, ioseq, ReadOptions{})
   130  	if n != 1 || (err != nil && err != io.EOF) {
   131  		t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err)
   132  	}
   133  	if want := byte('2'); buf[0] != want {
   134  		t.Errorf("Read after Seek: got byte %c, wanted %c", buf[0], want)
   135  	}
   136  
   137  	// PRead at the beginning of the file also causes it to be regenerated.
   138  	n, err = fd.PRead(ctx, ioseq, 0, ReadOptions{})
   139  	if n != 1 || (err != nil && err != io.EOF) {
   140  		t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err)
   141  	}
   142  	if want := byte('3'); buf[0] != want {
   143  		t.Errorf("PRead: got byte %c, wanted %c", buf[0], want)
   144  	}
   145  
   146  	// Write and PWrite fails.
   147  	if _, err := fd.Write(ctx, ioseq, WriteOptions{}); !linuxerr.Equals(linuxerr.EIO, err) {
   148  		t.Errorf("Write: got err %v, wanted %v", err, linuxerr.EIO)
   149  	}
   150  	if _, err := fd.PWrite(ctx, ioseq, 0, WriteOptions{}); !linuxerr.Equals(linuxerr.EIO, err) {
   151  		t.Errorf("Write: got err %v, wanted %v", err, linuxerr.EIO)
   152  	}
   153  }
   154  
   155  func TestWritable(t *testing.T) {
   156  	ctx := contexttest.Context(t)
   157  
   158  	vfsObj := &VirtualFilesystem{}
   159  	if err := vfsObj.Init(ctx); err != nil {
   160  		t.Fatalf("VFS init: %v", err)
   161  	}
   162  	fd := newTestFD(ctx, vfsObj, linux.O_RDWR, &storeData{data: "init"})
   163  	defer fd.DecRef(ctx)
   164  
   165  	buf := make([]byte, 10)
   166  	ioseq := usermem.BytesIOSequence(buf)
   167  	if n, err := fd.Read(ctx, ioseq, ReadOptions{}); n != 4 && err != io.EOF {
   168  		t.Fatalf("Read: got (%v, %v), wanted (4, EOF)", n, err)
   169  	}
   170  	if want := "init"; want == string(buf) {
   171  		t.Fatalf("Read: got %v, wanted %v", string(buf), want)
   172  	}
   173  
   174  	// Test PWrite.
   175  	want := "write"
   176  	writeIOSeq := usermem.BytesIOSequence([]byte(want))
   177  	if n, err := fd.PWrite(ctx, writeIOSeq, 0, WriteOptions{}); int(n) != len(want) && err != nil {
   178  		t.Errorf("PWrite: got err (%v, %v), wanted (%v, nil)", n, err, len(want))
   179  	}
   180  	if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF {
   181  		t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want))
   182  	}
   183  	if want == string(buf) {
   184  		t.Fatalf("PRead: got %v, wanted %v", string(buf), want)
   185  	}
   186  
   187  	// Test Seek to 0 followed by Write.
   188  	want = "write2"
   189  	writeIOSeq = usermem.BytesIOSequence([]byte(want))
   190  	if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 && err != nil {
   191  		t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err)
   192  	}
   193  	if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); int(n) != len(want) && err != nil {
   194  		t.Errorf("Write: got err (%v, %v), wanted (%v, nil)", n, err, len(want))
   195  	}
   196  	if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF {
   197  		t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want))
   198  	}
   199  	if want == string(buf) {
   200  		t.Fatalf("PRead: got %v, wanted %v", string(buf), want)
   201  	}
   202  
   203  	// Test failure if offset != 0.
   204  	if n, err := fd.Seek(ctx, 1, linux.SEEK_SET); n != 0 && err != nil {
   205  		t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err)
   206  	}
   207  	if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); n != 0 && !linuxerr.Equals(linuxerr.EINVAL, err) {
   208  		t.Errorf("Write: got err (%v, %v), wanted (0, EINVAL)", n, err)
   209  	}
   210  	if n, err := fd.PWrite(ctx, writeIOSeq, 2, WriteOptions{}); n != 0 && !linuxerr.Equals(linuxerr.EINVAL, err) {
   211  		t.Errorf("PWrite: got err (%v, %v), wanted (0, EINVAL)", n, err)
   212  	}
   213  }