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 }