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