github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/gofer/gofer_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 gofer
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"golang.org/x/sys/unix"
    23  	"github.com/SagerNet/gvisor/pkg/context"
    24  	"github.com/SagerNet/gvisor/pkg/p9"
    25  	"github.com/SagerNet/gvisor/pkg/p9/p9test"
    26  	"github.com/SagerNet/gvisor/pkg/sentry/contexttest"
    27  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    28  )
    29  
    30  // rootTest runs a test with a p9 mock and an fs.InodeOperations created from
    31  // the attached root directory. The root file will be closed and client
    32  // disconnected, but additional files must be closed manually.
    33  func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context, *p9test.Harness, *p9test.Mock, *fs.Inode)) {
    34  	t.Run(name, func(t *testing.T) {
    35  		h, c := p9test.NewHarness(t)
    36  		defer h.Finish()
    37  
    38  		// Create a new root. Note that we pass an empty, but non-nil
    39  		// map here. This allows tests to extend the root children
    40  		// dynamically.
    41  		root := h.NewDirectory(map[string]p9test.Generator{})(nil)
    42  
    43  		// Return this as the root.
    44  		h.Attacher.EXPECT().Attach().Return(root, nil).Times(1)
    45  
    46  		// ... and open via the client.
    47  		rootFile, err := c.Attach("/")
    48  		if err != nil {
    49  			t.Fatalf("unable to attach: %v", err)
    50  		}
    51  		defer rootFile.Close()
    52  
    53  		// Wrap an a session.
    54  		s := &session{
    55  			mounter:     fs.RootOwner,
    56  			cachePolicy: cp,
    57  			client:      c,
    58  		}
    59  
    60  		// ... and an INode, with only the mode being explicitly valid for now.
    61  		ctx := contexttest.Context(t)
    62  		sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{
    63  			file: rootFile,
    64  		}, root.QID, p9.AttrMaskAll(), root.Attr)
    65  		m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{})
    66  		rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr)
    67  
    68  		// Ensure that the cache is fully invalidated, so that any
    69  		// close actions actually take place before the full harness is
    70  		// torn down.
    71  		defer func() {
    72  			m.FlushDirentRefs()
    73  
    74  			// Wait for all resources to be released, otherwise the
    75  			// operations may fail after we close the rootFile.
    76  			fs.AsyncBarrier()
    77  		}()
    78  
    79  		// Execute the test.
    80  		fn(ctx, h, root, rootInode)
    81  	})
    82  }
    83  
    84  func TestLookup(t *testing.T) {
    85  	type lookupTest struct {
    86  		// Name of the test.
    87  		name string
    88  
    89  		// Expected return value.
    90  		want error
    91  	}
    92  
    93  	tests := []lookupTest{
    94  		{
    95  			name: "mock Walk passes (function succeeds)",
    96  			want: nil,
    97  		},
    98  		{
    99  			name: "mock Walk fails (function fails)",
   100  			want: unix.ENOENT,
   101  		},
   102  	}
   103  
   104  	const file = "file" // The walked target file.
   105  
   106  	for _, test := range tests {
   107  		rootTest(t, test.name, cacheNone, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
   108  			// Setup the appropriate result.
   109  			rootFile.WalkCallback = func() error {
   110  				return test.want
   111  			}
   112  			if test.want == nil {
   113  				// Set the contents of the root. We expect a
   114  				// normal file generator for ppp above. This is
   115  				// overriden by setting WalkErr in the mock.
   116  				rootFile.AddChild(file, h.NewFile())
   117  			}
   118  
   119  			// Call function.
   120  			dirent, err := rootInode.Lookup(ctx, file)
   121  
   122  			// Unwrap the InodeOperations.
   123  			var newInodeOperations fs.InodeOperations
   124  			if dirent != nil {
   125  				if dirent.IsNegative() {
   126  					err = unix.ENOENT
   127  				} else {
   128  					newInodeOperations = dirent.Inode.InodeOperations
   129  				}
   130  			}
   131  
   132  			// Check return values.
   133  			if err != test.want {
   134  				t.Errorf("Lookup got err %v, want %v", err, test.want)
   135  			}
   136  			if err == nil && newInodeOperations == nil {
   137  				t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil")
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func TestRevalidation(t *testing.T) {
   144  	type revalidationTest struct {
   145  		cachePolicy cachePolicy
   146  
   147  		// Whether dirent should be reloaded before any modifications.
   148  		preModificationWantReload bool
   149  
   150  		// Whether dirent should be reloaded after updating an unstable
   151  		// attribute on the remote fs.
   152  		postModificationWantReload bool
   153  
   154  		// Whether dirent unstable attributes should be updated after
   155  		// updating an attribute on the remote fs.
   156  		postModificationWantUpdatedAttrs bool
   157  
   158  		// Whether dirent should be reloaded after the remote has
   159  		// removed the file.
   160  		postRemovalWantReload bool
   161  	}
   162  
   163  	tests := []revalidationTest{
   164  		{
   165  			// Policy cacheNone causes Revalidate to always return
   166  			// true.
   167  			cachePolicy:                      cacheNone,
   168  			preModificationWantReload:        true,
   169  			postModificationWantReload:       true,
   170  			postModificationWantUpdatedAttrs: true,
   171  			postRemovalWantReload:            true,
   172  		},
   173  		{
   174  			// Policy cacheAll causes Revalidate to always return
   175  			// false.
   176  			cachePolicy:                      cacheAll,
   177  			preModificationWantReload:        false,
   178  			postModificationWantReload:       false,
   179  			postModificationWantUpdatedAttrs: false,
   180  			postRemovalWantReload:            false,
   181  		},
   182  		{
   183  			// Policy cacheAllWritethrough causes Revalidate to
   184  			// always return false.
   185  			cachePolicy:                      cacheAllWritethrough,
   186  			preModificationWantReload:        false,
   187  			postModificationWantReload:       false,
   188  			postModificationWantUpdatedAttrs: false,
   189  			postRemovalWantReload:            false,
   190  		},
   191  		{
   192  			// Policy cacheRemoteRevalidating causes Revalidate to
   193  			// return update cached unstable attrs, and returns
   194  			// true only when the remote inode itself has been
   195  			// removed or replaced.
   196  			cachePolicy:                      cacheRemoteRevalidating,
   197  			preModificationWantReload:        false,
   198  			postModificationWantReload:       false,
   199  			postModificationWantUpdatedAttrs: true,
   200  			postRemovalWantReload:            true,
   201  		},
   202  	}
   203  
   204  	const file = "file" // The file walked below.
   205  
   206  	for _, test := range tests {
   207  		name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy)
   208  		rootTest(t, name, test.cachePolicy, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
   209  			// Wrap in a dirent object.
   210  			rootDir := fs.NewDirent(ctx, rootInode, "root")
   211  
   212  			// Create a mock file a child of the root. We save when
   213  			// this is generated, so that when the time changed, we
   214  			// can update the original entry.
   215  			var origMocks []*p9test.Mock
   216  			rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
   217  				// Regular a regular file that has a consistent
   218  				// path number. This might be used by
   219  				// validation so we don't change it.
   220  				m := h.NewMock(parent, 0, p9.Attr{
   221  					Mode: p9.ModeRegular,
   222  				})
   223  				origMocks = append(origMocks, m)
   224  				return m
   225  			})
   226  
   227  			// Do the walk.
   228  			dirent, err := rootDir.Walk(ctx, rootDir, file)
   229  			if err != nil {
   230  				t.Fatalf("Lookup failed: %v", err)
   231  			}
   232  
   233  			// We must release the dirent, of the test will fail
   234  			// with a reference leak. This is tracked by p9test.
   235  			defer dirent.DecRef(ctx)
   236  
   237  			// Walk again. Depending on the cache policy, we may
   238  			// get a new dirent.
   239  			newDirent, err := rootDir.Walk(ctx, rootDir, file)
   240  			if err != nil {
   241  				t.Fatalf("Lookup failed: %v", err)
   242  			}
   243  			if test.preModificationWantReload && dirent == newDirent {
   244  				t.Errorf("Lookup with cachePolicy=%s got old dirent %+v, wanted a new dirent", test.cachePolicy, dirent)
   245  			}
   246  			if !test.preModificationWantReload && dirent != newDirent {
   247  				t.Errorf("Lookup with cachePolicy=%s got new dirent %+v, wanted old dirent %+v", test.cachePolicy, newDirent, dirent)
   248  			}
   249  			newDirent.DecRef(ctx) // See above.
   250  
   251  			// Modify the underlying mocked file's modification
   252  			// time for the next walk that occurs.
   253  			nowSeconds := time.Now().Unix()
   254  			rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
   255  				// Ensure that the path is the same as above,
   256  				// but we change only the modification time of
   257  				// the file.
   258  				return h.NewMock(parent, 0, p9.Attr{
   259  					Mode:         p9.ModeRegular,
   260  					MTimeSeconds: uint64(nowSeconds),
   261  				})
   262  			})
   263  
   264  			// We also modify the original time, so that GetAttr
   265  			// behaves as expected for the caching case.
   266  			for _, m := range origMocks {
   267  				m.Attr.MTimeSeconds = uint64(nowSeconds)
   268  			}
   269  
   270  			// Walk again. Depending on the cache policy, we may
   271  			// get a new dirent.
   272  			newDirent, err = rootDir.Walk(ctx, rootDir, file)
   273  			if err != nil {
   274  				t.Fatalf("Lookup failed: %v", err)
   275  			}
   276  			if test.postModificationWantReload && dirent == newDirent {
   277  				t.Errorf("Lookup with cachePolicy=%s got old dirent, wanted a new dirent", test.cachePolicy)
   278  			}
   279  			if !test.postModificationWantReload && dirent != newDirent {
   280  				t.Errorf("Lookup with cachePolicy=%s got new dirent, wanted old dirent", test.cachePolicy)
   281  			}
   282  			uattrs, err := newDirent.Inode.UnstableAttr(ctx)
   283  			if err != nil {
   284  				t.Fatalf("Error getting unstable attrs: %v", err)
   285  			}
   286  			gotModTimeSeconds := uattrs.ModificationTime.Seconds()
   287  			if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds {
   288  				t.Fatalf("Lookup with cachePolicy=%s got new modification time %v, wanted %v", test.cachePolicy, gotModTimeSeconds, nowSeconds)
   289  			}
   290  			newDirent.DecRef(ctx) // See above.
   291  
   292  			// Remove the file from the remote fs, subsequent walks
   293  			// should now fail to find anything.
   294  			rootFile.RemoveChild(file)
   295  
   296  			// Walk again. Depending on the cache policy, we may
   297  			// get ENOENT.
   298  			newDirent, err = rootDir.Walk(ctx, rootDir, file)
   299  			if test.postRemovalWantReload && err == nil {
   300  				t.Errorf("Lookup with cachePolicy=%s got nil error, wanted ENOENT", test.cachePolicy)
   301  			}
   302  			if !test.postRemovalWantReload && (err != nil || dirent != newDirent) {
   303  				t.Errorf("Lookup with cachePolicy=%s got new dirent and error %v, wanted old dirent and nil error", test.cachePolicy, err)
   304  			}
   305  			if err == nil {
   306  				newDirent.DecRef(ctx) // See above.
   307  			}
   308  		})
   309  	}
   310  }