gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/testutil/testutil.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 testutil provides common test utilities for kernfs-based 16 // filesystems. 17 package testutil 18 19 import ( 20 "fmt" 21 "io" 22 "strings" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "gvisor.dev/gvisor/pkg/abi/linux" 27 "gvisor.dev/gvisor/pkg/context" 28 "gvisor.dev/gvisor/pkg/fspath" 29 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 30 "gvisor.dev/gvisor/pkg/sentry/vfs" 31 "gvisor.dev/gvisor/pkg/sync" 32 "gvisor.dev/gvisor/pkg/usermem" 33 34 "gvisor.dev/gvisor/pkg/hostarch" 35 ) 36 37 // System represents the context for a single test. 38 // 39 // Test systems must be explicitly destroyed with System.Destroy. 40 type System struct { 41 t *testing.T 42 Ctx context.Context 43 Creds *auth.Credentials 44 VFS *vfs.VirtualFilesystem 45 Root vfs.VirtualDentry 46 MntNs *vfs.MountNamespace 47 } 48 49 // NewSystem constructs a System. 50 // 51 // Precondition: Caller must hold a reference on mns, whose ownership 52 // is transferred to the new System. 53 func NewSystem(ctx context.Context, t *testing.T, v *vfs.VirtualFilesystem, mns *vfs.MountNamespace) *System { 54 root := mns.Root(ctx) 55 s := &System{ 56 t: t, 57 Ctx: ctx, 58 Creds: auth.CredentialsFromContext(ctx), 59 VFS: v, 60 MntNs: mns, 61 Root: root, 62 } 63 return s 64 } 65 66 // WithSubtest creates a temporary test system with a new test harness, 67 // referencing all other resources from the original system. This is useful when 68 // a system is reused for multiple subtests, and the T needs to change for each 69 // case. Note that this is safe when test cases run in parallel, as all 70 // resources referenced by the system are immutable, or handle interior 71 // mutations in a thread-safe manner. 72 // 73 // The returned system must not outlive the original and should not be destroyed 74 // via System.Destroy. 75 func (s *System) WithSubtest(t *testing.T) *System { 76 return &System{ 77 t: t, 78 Ctx: s.Ctx, 79 Creds: s.Creds, 80 VFS: s.VFS, 81 MntNs: s.MntNs, 82 Root: s.Root, 83 } 84 } 85 86 // WithTemporaryContext constructs a temporary test system with a new context 87 // ctx. The temporary system borrows all resources and references from the 88 // original system. The returned temporary system must not outlive the original 89 // system, and should not be destroyed via System.Destroy. 90 func (s *System) WithTemporaryContext(ctx context.Context) *System { 91 return &System{ 92 t: s.t, 93 Ctx: ctx, 94 Creds: s.Creds, 95 VFS: s.VFS, 96 MntNs: s.MntNs, 97 Root: s.Root, 98 } 99 } 100 101 // Destroy release resources associated with a test system. 102 func (s *System) Destroy() { 103 s.Root.DecRef(s.Ctx) 104 s.MntNs.DecRef(s.Ctx) // Reference on MntNs passed to NewSystem. 105 } 106 107 // ReadToEnd reads the contents of fd until EOF to a string. 108 func (s *System) ReadToEnd(fd *vfs.FileDescription) (string, error) { 109 buf := make([]byte, hostarch.PageSize) 110 bufIOSeq := usermem.BytesIOSequence(buf) 111 opts := vfs.ReadOptions{} 112 113 var content strings.Builder 114 for { 115 n, err := fd.Read(s.Ctx, bufIOSeq, opts) 116 if n == 0 || err != nil { 117 if err == io.EOF { 118 err = nil 119 } 120 return content.String(), err 121 } 122 content.Write(buf[:n]) 123 } 124 } 125 126 // PathOpAtRoot constructs a PathOperation with the given path from 127 // the root of the filesystem. 128 func (s *System) PathOpAtRoot(path string) *vfs.PathOperation { 129 return &vfs.PathOperation{ 130 Root: s.Root, 131 Start: s.Root, 132 Path: fspath.Parse(path), 133 } 134 } 135 136 // GetDentryOrDie attempts to resolve a dentry referred to by the 137 // provided path operation. If unsuccessful, the test fails. 138 func (s *System) GetDentryOrDie(pop *vfs.PathOperation) vfs.VirtualDentry { 139 vd, err := s.VFS.GetDentryAt(s.Ctx, s.Creds, pop, &vfs.GetDentryOptions{}) 140 if err != nil { 141 s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err) 142 } 143 return vd 144 } 145 146 // DirentType is an alias for values for linux_dirent64.d_type. 147 type DirentType = uint8 148 149 // ListDirents lists the Dirents for a directory at pop. 150 func (s *System) ListDirents(pop *vfs.PathOperation) *DirentCollector { 151 fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{Flags: linux.O_RDONLY}) 152 if err != nil { 153 s.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) 154 } 155 defer fd.DecRef(s.Ctx) 156 157 collector := &DirentCollector{} 158 if err := fd.IterDirents(s.Ctx, collector); err != nil { 159 s.t.Fatalf("IterDirent failed: %v", err) 160 } 161 return collector 162 } 163 164 // AssertAllDirentTypes verifies that the set of dirents in collector contains 165 // exactly the specified set of expected entries. AssertAllDirentTypes respects 166 // collector.skipDots, and implicitly checks for "." and ".." accordingly. 167 func (s *System) AssertAllDirentTypes(collector *DirentCollector, expected map[string]DirentType) { 168 if expected == nil { 169 expected = make(map[string]DirentType) 170 } 171 // Also implicitly check for "." and "..", if enabled. 172 if !collector.skipDots { 173 expected["."] = linux.DT_DIR 174 expected[".."] = linux.DT_DIR 175 } 176 177 dentryTypes := make(map[string]DirentType) 178 collector.mu.Lock() 179 for _, dirent := range collector.dirents { 180 dentryTypes[dirent.Name] = dirent.Type 181 } 182 collector.mu.Unlock() 183 if diff := cmp.Diff(expected, dentryTypes); diff != "" { 184 s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff) 185 } 186 } 187 188 // AssertDirentOffsets verifies that collector contains at least the entries 189 // specified in expected, with the given NextOff field. Entries specified in 190 // expected but missing from collector result in failure. Extra entries in 191 // collector are ignored. AssertDirentOffsets respects collector.skipDots, and 192 // implicitly checks for "." and ".." accordingly. 193 func (s *System) AssertDirentOffsets(collector *DirentCollector, expected map[string]int64) { 194 // Also implicitly check for "." and "..", if enabled. 195 if !collector.skipDots { 196 expected["."] = 1 197 expected[".."] = 2 198 } 199 200 dentryNextOffs := make(map[string]int64) 201 collector.mu.Lock() 202 for _, dirent := range collector.dirents { 203 // Ignore extra entries in dentries that are not in expected. 204 if _, ok := expected[dirent.Name]; ok { 205 dentryNextOffs[dirent.Name] = dirent.NextOff 206 } 207 } 208 collector.mu.Unlock() 209 if diff := cmp.Diff(expected, dentryNextOffs); diff != "" { 210 s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff) 211 } 212 } 213 214 // DirentCollector provides an implementation for vfs.IterDirentsCallback for 215 // testing. It simply iterates to the end of a given directory FD and collects 216 // all dirents emitted by the callback. 217 type DirentCollector struct { 218 mu sync.Mutex 219 order []*vfs.Dirent 220 dirents map[string]*vfs.Dirent 221 // When the collector is used in various Assert* functions, should "." and 222 // ".." be implicitly checked? 223 skipDots bool 224 } 225 226 // SkipDotsChecks enables or disables the implicit checks on "." and ".." when 227 // the collector is used in various Assert* functions. Note that "." and ".." 228 // are still collected if passed to d.Handle, so the caller should only disable 229 // the checks when they aren't expected. 230 func (d *DirentCollector) SkipDotsChecks(value bool) { 231 d.skipDots = value 232 } 233 234 // Handle implements vfs.IterDirentsCallback.Handle. 235 func (d *DirentCollector) Handle(dirent vfs.Dirent) error { 236 d.mu.Lock() 237 if d.dirents == nil { 238 d.dirents = make(map[string]*vfs.Dirent) 239 } 240 d.order = append(d.order, &dirent) 241 d.dirents[dirent.Name] = &dirent 242 d.mu.Unlock() 243 return nil 244 } 245 246 // Count returns the number of dirents currently in the collector. 247 func (d *DirentCollector) Count() int { 248 d.mu.Lock() 249 defer d.mu.Unlock() 250 return len(d.dirents) 251 } 252 253 // Contains checks whether the collector has a dirent with the given name and 254 // type. 255 func (d *DirentCollector) Contains(name string, typ uint8) error { 256 d.mu.Lock() 257 defer d.mu.Unlock() 258 dirent, ok := d.dirents[name] 259 if !ok { 260 return fmt.Errorf("no dirent named %q found", name) 261 } 262 if dirent.Type != typ { 263 return fmt.Errorf("dirent named %q found, but was expecting type %s, got: %+v", name, linux.DirentType.Parse(uint64(typ)), dirent) 264 } 265 return nil 266 } 267 268 // Dirents returns all dirents discovered by this collector. 269 func (d *DirentCollector) Dirents() map[string]*vfs.Dirent { 270 d.mu.Lock() 271 dirents := make(map[string]*vfs.Dirent) 272 for n, d := range d.dirents { 273 dirents[n] = d 274 } 275 d.mu.Unlock() 276 return dirents 277 } 278 279 // OrderedDirents returns an ordered list of dirents as discovered by this 280 // collector. 281 func (d *DirentCollector) OrderedDirents() []*vfs.Dirent { 282 d.mu.Lock() 283 dirents := make([]*vfs.Dirent, len(d.order)) 284 copy(dirents, d.order) 285 d.mu.Unlock() 286 return dirents 287 }