github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/copy_up_test.go (about)

     1  // Copyright 2018 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 fs_test
    16  
    17  import (
    18  	"bytes"
    19  	"crypto/rand"
    20  	"fmt"
    21  	"io"
    22  	"testing"
    23  
    24  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    25  	_ "github.com/SagerNet/gvisor/pkg/sentry/fs/tmpfs"
    26  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/contexttest"
    27  	"github.com/SagerNet/gvisor/pkg/sync"
    28  	"github.com/SagerNet/gvisor/pkg/usermem"
    29  )
    30  
    31  const (
    32  	// origFileSize is the original file size. This many bytes should be
    33  	// copied up before the test file is modified.
    34  	origFileSize = 4096
    35  
    36  	// truncatedFileSize is the size to truncate all test files.
    37  	truncateFileSize = 10
    38  )
    39  
    40  // TestConcurrentCopyUp is a copy up stress test for an overlay.
    41  //
    42  // It creates a 64-level deep directory tree in the lower filesystem and
    43  // populates the last subdirectory with 64 files containing random content:
    44  //
    45  //    /lower
    46  //      /sudir0/.../subdir63/
    47  //                     /file0
    48  //                     ...
    49  //                     /file63
    50  //
    51  // The files are truncated concurrently by 4 goroutines per file.
    52  // These goroutines contend with copying up all parent 64 subdirectories
    53  // as well as the final file content.
    54  //
    55  // At the end of the test, we assert that the files respect the new truncated
    56  // size and contain the content we expect.
    57  func TestConcurrentCopyUp(t *testing.T) {
    58  	ctx := contexttest.Context(t)
    59  	files := makeOverlayTestFiles(t)
    60  
    61  	var wg sync.WaitGroup
    62  	for _, file := range files {
    63  		for i := 0; i < 4; i++ {
    64  			wg.Add(1)
    65  			go func(o *overlayTestFile) {
    66  				if err := o.File.Dirent.Inode.Truncate(ctx, o.File.Dirent, truncateFileSize); err != nil {
    67  					t.Errorf("failed to copy up: %v", err)
    68  				}
    69  				wg.Done()
    70  			}(file)
    71  		}
    72  	}
    73  	wg.Wait()
    74  
    75  	for _, file := range files {
    76  		got := make([]byte, origFileSize)
    77  		n, err := file.File.Readv(ctx, usermem.BytesIOSequence(got))
    78  		if int(n) != truncateFileSize {
    79  			t.Fatalf("read %d bytes from file, want %d", n, truncateFileSize)
    80  		}
    81  		if err != nil && err != io.EOF {
    82  			t.Fatalf("read got error %v, want nil", err)
    83  		}
    84  		if !bytes.Equal(got[:n], file.content[:truncateFileSize]) {
    85  			t.Fatalf("file content is %v, want %v", got[:n], file.content[:truncateFileSize])
    86  		}
    87  	}
    88  }
    89  
    90  type overlayTestFile struct {
    91  	File    *fs.File
    92  	name    string
    93  	content []byte
    94  }
    95  
    96  func makeOverlayTestFiles(t *testing.T) []*overlayTestFile {
    97  	ctx := contexttest.Context(t)
    98  
    99  	// Create a lower tmpfs mount.
   100  	fsys, _ := fs.FindFilesystem("tmpfs")
   101  	lower, err := fsys.Mount(contexttest.Context(t), "", fs.MountSourceFlags{}, "", nil)
   102  	if err != nil {
   103  		t.Fatalf("failed to mount tmpfs: %v", err)
   104  	}
   105  	lowerRoot := fs.NewDirent(ctx, lower, "")
   106  
   107  	// Make a deep set of subdirectories that everyone shares.
   108  	next := lowerRoot
   109  	for i := 0; i < 64; i++ {
   110  		name := fmt.Sprintf("subdir%d", i)
   111  		err := next.CreateDirectory(ctx, lowerRoot, name, fs.FilePermsFromMode(0777))
   112  		if err != nil {
   113  			t.Fatalf("failed to create dir %q: %v", name, err)
   114  		}
   115  		next, err = next.Walk(ctx, lowerRoot, name)
   116  		if err != nil {
   117  			t.Fatalf("failed to walk to %q: %v", name, err)
   118  		}
   119  	}
   120  
   121  	// Make a bunch of files in the last directory.
   122  	var files []*overlayTestFile
   123  	for i := 0; i < 64; i++ {
   124  		name := fmt.Sprintf("file%d", i)
   125  		f, err := next.Create(ctx, next, name, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
   126  		if err != nil {
   127  			t.Fatalf("failed to create file %q: %v", name, err)
   128  		}
   129  		defer f.DecRef(ctx)
   130  
   131  		relname, _ := f.Dirent.FullName(lowerRoot)
   132  
   133  		o := &overlayTestFile{
   134  			name:    relname,
   135  			content: make([]byte, origFileSize),
   136  		}
   137  
   138  		if _, err := rand.Read(o.content); err != nil {
   139  			t.Fatalf("failed to read from /dev/urandom: %v", err)
   140  		}
   141  
   142  		if _, err := f.Writev(ctx, usermem.BytesIOSequence(o.content)); err != nil {
   143  			t.Fatalf("failed to write content to file %q: %v", name, err)
   144  		}
   145  
   146  		files = append(files, o)
   147  	}
   148  
   149  	// Create an empty upper tmpfs mount which we will copy up into.
   150  	upper, err := fsys.Mount(ctx, "", fs.MountSourceFlags{}, "", nil)
   151  	if err != nil {
   152  		t.Fatalf("failed to mount tmpfs: %v", err)
   153  	}
   154  
   155  	// Construct an overlay root.
   156  	overlay, err := fs.NewOverlayRoot(ctx, upper, lower, fs.MountSourceFlags{})
   157  	if err != nil {
   158  		t.Fatalf("failed to construct overlay root: %v", err)
   159  	}
   160  
   161  	// Create a MountNamespace to traverse the file system.
   162  	mns, err := fs.NewMountNamespace(ctx, overlay)
   163  	if err != nil {
   164  		t.Fatalf("failed to construct mount manager: %v", err)
   165  	}
   166  
   167  	// Walk to all of the files in the overlay, open them readable.
   168  	for _, f := range files {
   169  		maxTraversals := uint(0)
   170  		d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, &maxTraversals)
   171  		if err != nil {
   172  			t.Fatalf("failed to find %q: %v", f.name, err)
   173  		}
   174  		defer d.DecRef(ctx)
   175  
   176  		f.File, err = d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
   177  		if err != nil {
   178  			t.Fatalf("failed to open file %q readable: %v", f.name, err)
   179  		}
   180  	}
   181  
   182  	return files
   183  }