// 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 vfs import ( "bytes" "fmt" "io" "sync/atomic" "testing" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/sentry/contexttest" "gvisor.dev/gvisor/pkg/syserror" "gvisor.dev/gvisor/pkg/usermem" ) // fileDescription is the common fd struct which a filesystem implementation // embeds in all of its file description implementations as required. type fileDescription struct { vfsfd FileDescription FileDescriptionDefaultImpl } // genCount contains the number of times its DynamicBytesSource.Generate() // implementation has been called. type genCount struct { count uint64 // accessed using atomic memory ops } // Generate implements DynamicBytesSource.Generate. func (g *genCount) Generate(ctx context.Context, buf *bytes.Buffer) error { fmt.Fprintf(buf, "%d", atomic.AddUint64(&g.count, 1)) return nil } type storeData struct { data string } var _ WritableDynamicBytesSource = (*storeData)(nil) // Generate implements DynamicBytesSource. func (d *storeData) Generate(ctx context.Context, buf *bytes.Buffer) error { buf.WriteString(d.data) return nil } // Generate implements WritableDynamicBytesSource. func (d *storeData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { buf := make([]byte, src.NumBytes()) n, err := src.CopyIn(ctx, buf) if err != nil { return 0, err } d.data = string(buf[:n]) return 0, nil } // testFD is a read-only FileDescriptionImpl representing a regular file. type testFD struct { fileDescription DynamicBytesFileDescriptionImpl data DynamicBytesSource } func newTestFD(vfsObj *VirtualFilesystem, statusFlags uint32, data DynamicBytesSource) *FileDescription { vd := vfsObj.NewAnonVirtualDentry("genCountFD") defer vd.DecRef() var fd testFD fd.vfsfd.Init(&fd, statusFlags, vd.Mount(), vd.Dentry(), &FileDescriptionOptions{}) fd.DynamicBytesFileDescriptionImpl.SetDataSource(data) return &fd.vfsfd } // Release implements FileDescriptionImpl.Release. func (fd *testFD) Release() { } // SetStatusFlags implements FileDescriptionImpl.SetStatusFlags. // Stat implements FileDescriptionImpl.Stat. func (fd *testFD) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) { // Note that Statx.Mask == 0 in the return value. return linux.Statx{}, nil } // SetStat implements FileDescriptionImpl.SetStat. func (fd *testFD) SetStat(ctx context.Context, opts SetStatOptions) error { return syserror.EPERM } func TestGenCountFD(t *testing.T) { ctx := contexttest.Context(t) vfsObj := &VirtualFilesystem{} if err := vfsObj.Init(); err != nil { t.Fatalf("VFS init: %v", err) } fd := newTestFD(vfsObj, linux.O_RDWR, &genCount{}) defer fd.DecRef() // The first read causes Generate to be called to fill the FD's buffer. buf := make([]byte, 2) ioseq := usermem.BytesIOSequence(buf) n, err := fd.Read(ctx, ioseq, ReadOptions{}) if n != 1 || (err != nil && err != io.EOF) { t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err) } if want := byte('1'); buf[0] != want { t.Errorf("first Read: got byte %c, wanted %c", buf[0], want) } // A second read without seeking is still at EOF. n, err = fd.Read(ctx, ioseq, ReadOptions{}) if n != 0 || err != io.EOF { t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err) } // Seeking to the beginning of the file causes it to be regenerated. n, err = fd.Seek(ctx, 0, linux.SEEK_SET) if n != 0 || err != nil { t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err) } n, err = fd.Read(ctx, ioseq, ReadOptions{}) if n != 1 || (err != nil && err != io.EOF) { t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err) } if want := byte('2'); buf[0] != want { t.Errorf("Read after Seek: got byte %c, wanted %c", buf[0], want) } // PRead at the beginning of the file also causes it to be regenerated. n, err = fd.PRead(ctx, ioseq, 0, ReadOptions{}) if n != 1 || (err != nil && err != io.EOF) { t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err) } if want := byte('3'); buf[0] != want { t.Errorf("PRead: got byte %c, wanted %c", buf[0], want) } // Write and PWrite fails. if _, err := fd.Write(ctx, ioseq, WriteOptions{}); err != syserror.EINVAL { t.Errorf("Write: got err %v, wanted %v", err, syserror.EINVAL) } if _, err := fd.PWrite(ctx, ioseq, 0, WriteOptions{}); err != syserror.EINVAL { t.Errorf("Write: got err %v, wanted %v", err, syserror.EINVAL) } } func TestWritable(t *testing.T) { ctx := contexttest.Context(t) vfsObj := &VirtualFilesystem{} if err := vfsObj.Init(); err != nil { t.Fatalf("VFS init: %v", err) } fd := newTestFD(vfsObj, linux.O_RDWR, &storeData{data: "init"}) defer fd.DecRef() buf := make([]byte, 10) ioseq := usermem.BytesIOSequence(buf) if n, err := fd.Read(ctx, ioseq, ReadOptions{}); n != 4 && err != io.EOF { t.Fatalf("Read: got (%v, %v), wanted (4, EOF)", n, err) } if want := "init"; want == string(buf) { t.Fatalf("Read: got %v, wanted %v", string(buf), want) } // Test PWrite. want := "write" writeIOSeq := usermem.BytesIOSequence([]byte(want)) if n, err := fd.PWrite(ctx, writeIOSeq, 0, WriteOptions{}); int(n) != len(want) && err != nil { t.Errorf("PWrite: got err (%v, %v), wanted (%v, nil)", n, err, len(want)) } if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF { t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want)) } if want == string(buf) { t.Fatalf("PRead: got %v, wanted %v", string(buf), want) } // Test Seek to 0 followed by Write. want = "write2" writeIOSeq = usermem.BytesIOSequence([]byte(want)) if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 && err != nil { t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err) } if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); int(n) != len(want) && err != nil { t.Errorf("Write: got err (%v, %v), wanted (%v, nil)", n, err, len(want)) } if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF { t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want)) } if want == string(buf) { t.Fatalf("PRead: got %v, wanted %v", string(buf), want) } // Test failure if offset != 0. if n, err := fd.Seek(ctx, 1, linux.SEEK_SET); n != 0 && err != nil { t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err) } if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); n != 0 && err != syserror.EINVAL { t.Errorf("Write: got err (%v, %v), wanted (0, EINVAL)", n, err) } if n, err := fd.PWrite(ctx, writeIOSeq, 2, WriteOptions{}); n != 0 && err != syserror.EINVAL { t.Errorf("PWrite: got err (%v, %v), wanted (0, EINVAL)", n, err) } }