summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/copy_up_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fs/copy_up_test.go')
-rw-r--r--pkg/sentry/fs/copy_up_test.go182
1 files changed, 182 insertions, 0 deletions
diff --git a/pkg/sentry/fs/copy_up_test.go b/pkg/sentry/fs/copy_up_test.go
new file mode 100644
index 000000000..c3c9d963d
--- /dev/null
+++ b/pkg/sentry/fs/copy_up_test.go
@@ -0,0 +1,182 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs_test
+
+import (
+ "bytes"
+ "crypto/rand"
+ "fmt"
+ "io"
+ "sync"
+ "testing"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ _ "gvisor.googlesource.com/gvisor/pkg/sentry/fs/tmpfs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
+)
+
+const (
+ // origFileSize is the original file size. This many bytes should be
+ // copied up before the test file is modified.
+ origFileSize = 4096
+
+ // truncatedFileSize is the size to truncate all test files.
+ truncateFileSize = 10
+)
+
+// TestConcurrentCopyUp is a copy up stress test for an overlay.
+//
+// It creates a 64-level deep directory tree in the lower filesystem and
+// populates the last subdirectory with 64 files containing random content:
+//
+// /lower
+// /sudir0/.../subdir63/
+// /file0
+// ...
+// /file63
+//
+// The files are truncated concurrently by 4 goroutines per file.
+// These goroutines contend with copying up all parent 64 subdirectories
+// as well as the final file content.
+//
+// At the end of the test, we assert that the files respect the new truncated
+// size and contain the content we expect.
+func TestConcurrentCopyUp(t *testing.T) {
+ ctx := contexttest.Context(t)
+ files := makeOverlayTestFiles(t)
+
+ var wg sync.WaitGroup
+ for _, file := range files {
+ for i := 0; i < 4; i++ {
+ wg.Add(1)
+ go func(o *overlayTestFile) {
+ if err := o.File.Dirent.Inode.Truncate(ctx, o.File.Dirent, truncateFileSize); err != nil {
+ t.Fatalf("failed to copy up: %v", err)
+ }
+ wg.Done()
+ }(file)
+ }
+ }
+ wg.Wait()
+
+ for _, file := range files {
+ got := make([]byte, origFileSize)
+ n, err := file.File.Readv(ctx, usermem.BytesIOSequence(got))
+ if int(n) != truncateFileSize {
+ t.Fatalf("read %d bytes from file, want %d", n, truncateFileSize)
+ }
+ if err != nil && err != io.EOF {
+ t.Fatalf("read got error %v, want nil", err)
+ }
+ if !bytes.Equal(got[:n], file.content[:truncateFileSize]) {
+ t.Fatalf("file content is %v, want %v", got[:n], file.content[:truncateFileSize])
+ }
+ }
+}
+
+type overlayTestFile struct {
+ File *fs.File
+ name string
+ content []byte
+}
+
+func makeOverlayTestFiles(t *testing.T) []*overlayTestFile {
+ ctx := contexttest.Context(t)
+
+ // Create a lower tmpfs mount.
+ fsys, _ := fs.FindFilesystem("tmpfs")
+ lower, err := fsys.Mount(contexttest.Context(t), "", fs.MountSourceFlags{}, "")
+ if err != nil {
+ t.Fatalf("failed to mount tmpfs: %v", err)
+ }
+ lowerRoot := fs.NewDirent(lower, "")
+
+ // Make a deep set of subdirectories that everyone shares.
+ next := lowerRoot
+ for i := 0; i < 64; i++ {
+ name := fmt.Sprintf("subdir%d", i)
+ err := next.CreateDirectory(ctx, lowerRoot, name, fs.FilePermsFromMode(0777))
+ if err != nil {
+ t.Fatalf("failed to create dir %q: %v", name, err)
+ }
+ next, err = next.Walk(ctx, lowerRoot, name)
+ if err != nil {
+ t.Fatalf("failed to walk to %q: %v", name, err)
+ }
+ }
+
+ // Make a bunch of files in the last directory.
+ var files []*overlayTestFile
+ for i := 0; i < 64; i++ {
+ name := fmt.Sprintf("file%d", i)
+ f, err := next.Create(ctx, next, name, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
+ if err != nil {
+ t.Fatalf("failed to create file %q: %v", name, err)
+ }
+ defer f.DecRef()
+
+ relname, _ := f.Dirent.FullName(lowerRoot)
+
+ o := &overlayTestFile{
+ name: relname,
+ content: make([]byte, origFileSize),
+ }
+
+ if _, err := rand.Read(o.content); err != nil {
+ t.Fatalf("failed to read from /dev/urandom: %v", err)
+ }
+
+ if _, err := f.Writev(ctx, usermem.BytesIOSequence(o.content)); err != nil {
+ t.Fatalf("failed to write content to file %q: %v", name, err)
+ }
+
+ files = append(files, o)
+ }
+
+ // Create an empty upper tmpfs mount which we will copy up into.
+ upper, err := fsys.Mount(ctx, "", fs.MountSourceFlags{}, "")
+ if err != nil {
+ t.Fatalf("failed to mount tmpfs: %v", err)
+ }
+
+ // Construct an overlay root.
+ overlay, err := fs.NewOverlayRoot(ctx, upper, lower, fs.MountSourceFlags{})
+ if err != nil {
+ t.Fatalf("failed to construct overlay root: %v", err)
+ }
+
+ // Create a MountNamespace to traverse the file system.
+ mns, err := fs.NewMountNamespace(ctx, overlay)
+ if err != nil {
+ t.Fatalf("failed to construct mount manager: %v", err)
+ }
+
+ // Walk to all of the files in the overlay, open them readable.
+ for _, f := range files {
+ d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, 0)
+ if err != nil {
+ t.Fatalf("failed to find %q: %v", f.name, err)
+ }
+ defer d.DecRef()
+
+ f.File, err = d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
+ if err != nil {
+ t.Fatalf("failed to open file %q readable: %v", f.name, err)
+ }
+ }
+
+ return files
+}