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  }