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 }