github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/inode_overlay_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 "testing" 19 20 "github.com/SagerNet/gvisor/pkg/context" 21 "github.com/SagerNet/gvisor/pkg/errors/linuxerr" 22 "github.com/SagerNet/gvisor/pkg/sentry/fs" 23 "github.com/SagerNet/gvisor/pkg/sentry/fs/fsutil" 24 "github.com/SagerNet/gvisor/pkg/sentry/fs/ramfs" 25 "github.com/SagerNet/gvisor/pkg/sentry/kernel/contexttest" 26 ) 27 28 func TestLookup(t *testing.T) { 29 ctx := contexttest.Context(t) 30 for _, test := range []struct { 31 // Test description. 32 desc string 33 34 // Lookup parameters. 35 dir *fs.Inode 36 name string 37 38 // Want from lookup. 39 found bool 40 hasUpper bool 41 hasLower bool 42 }{ 43 { 44 desc: "no upper, lower has name", 45 dir: fs.NewTestOverlayDir(ctx, 46 nil, /* upper */ 47 newTestRamfsDir(ctx, []dirContent{ 48 { 49 name: "a", 50 dir: false, 51 }, 52 }, nil), /* lower */ 53 false /* revalidate */), 54 name: "a", 55 found: true, 56 hasUpper: false, 57 hasLower: true, 58 }, 59 { 60 desc: "no lower, upper has name", 61 dir: fs.NewTestOverlayDir(ctx, 62 newTestRamfsDir(ctx, []dirContent{ 63 { 64 name: "a", 65 dir: false, 66 }, 67 }, nil), /* upper */ 68 nil, /* lower */ 69 false /* revalidate */), 70 name: "a", 71 found: true, 72 hasUpper: true, 73 hasLower: false, 74 }, 75 { 76 desc: "upper and lower, only lower has name", 77 dir: fs.NewTestOverlayDir(ctx, 78 newTestRamfsDir(ctx, []dirContent{ 79 { 80 name: "b", 81 dir: false, 82 }, 83 }, nil), /* upper */ 84 newTestRamfsDir(ctx, []dirContent{ 85 { 86 name: "a", 87 dir: false, 88 }, 89 }, nil), /* lower */ 90 false /* revalidate */), 91 name: "a", 92 found: true, 93 hasUpper: false, 94 hasLower: true, 95 }, 96 { 97 desc: "upper and lower, only upper has name", 98 dir: fs.NewTestOverlayDir(ctx, 99 newTestRamfsDir(ctx, []dirContent{ 100 { 101 name: "a", 102 dir: false, 103 }, 104 }, nil), /* upper */ 105 newTestRamfsDir(ctx, []dirContent{ 106 { 107 name: "b", 108 dir: false, 109 }, 110 }, nil), /* lower */ 111 false /* revalidate */), 112 name: "a", 113 found: true, 114 hasUpper: true, 115 hasLower: false, 116 }, 117 { 118 desc: "upper and lower, both have file", 119 dir: fs.NewTestOverlayDir(ctx, 120 newTestRamfsDir(ctx, []dirContent{ 121 { 122 name: "a", 123 dir: false, 124 }, 125 }, nil), /* upper */ 126 newTestRamfsDir(ctx, []dirContent{ 127 { 128 name: "a", 129 dir: false, 130 }, 131 }, nil), /* lower */ 132 false /* revalidate */), 133 name: "a", 134 found: true, 135 hasUpper: true, 136 hasLower: false, 137 }, 138 { 139 desc: "upper and lower, both have directory", 140 dir: fs.NewTestOverlayDir(ctx, 141 newTestRamfsDir(ctx, []dirContent{ 142 { 143 name: "a", 144 dir: true, 145 }, 146 }, nil), /* upper */ 147 newTestRamfsDir(ctx, []dirContent{ 148 { 149 name: "a", 150 dir: true, 151 }, 152 }, nil), /* lower */ 153 false /* revalidate */), 154 name: "a", 155 found: true, 156 hasUpper: true, 157 hasLower: true, 158 }, 159 { 160 desc: "upper and lower, upper negative masks lower file", 161 dir: fs.NewTestOverlayDir(ctx, 162 newTestRamfsDir(ctx, nil, []string{"a"}), /* upper */ 163 newTestRamfsDir(ctx, []dirContent{ 164 { 165 name: "a", 166 dir: false, 167 }, 168 }, nil), /* lower */ 169 false /* revalidate */), 170 name: "a", 171 found: false, 172 hasUpper: false, 173 hasLower: false, 174 }, 175 { 176 desc: "upper and lower, upper negative does not mask lower file", 177 dir: fs.NewTestOverlayDir(ctx, 178 newTestRamfsDir(ctx, nil, []string{"b"}), /* upper */ 179 newTestRamfsDir(ctx, []dirContent{ 180 { 181 name: "a", 182 dir: false, 183 }, 184 }, nil), /* lower */ 185 false /* revalidate */), 186 name: "a", 187 found: true, 188 hasUpper: false, 189 hasLower: true, 190 }, 191 } { 192 t.Run(test.desc, func(t *testing.T) { 193 dirent, err := test.dir.Lookup(ctx, test.name) 194 if test.found && (linuxerr.Equals(linuxerr.ENOENT, err) || dirent.IsNegative()) { 195 t.Fatalf("lookup %q expected to find positive dirent, got dirent %v err %v", test.name, dirent, err) 196 } 197 if !test.found { 198 if !linuxerr.Equals(linuxerr.ENOENT, err) && !dirent.IsNegative() { 199 t.Errorf("lookup %q expected to return ENOENT or negative dirent, got dirent %v err %v", test.name, dirent, err) 200 } 201 // Nothing more to check. 202 return 203 } 204 if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper { 205 t.Fatalf("lookup got upper filesystem %v, want %v", hasUpper, test.hasUpper) 206 } 207 if hasLower := dirent.Inode.TestHasLowerFS(); hasLower != test.hasLower { 208 t.Errorf("lookup got lower filesystem %v, want %v", hasLower, test.hasLower) 209 } 210 }) 211 } 212 } 213 214 func TestLookupRevalidation(t *testing.T) { 215 // File name used in the tests. 216 fileName := "foofile" 217 ctx := contexttest.Context(t) 218 for _, tc := range []struct { 219 // Test description. 220 desc string 221 222 // Upper and lower fs for the overlay. 223 upper *fs.Inode 224 lower *fs.Inode 225 226 // Whether the upper requires revalidation. 227 revalidate bool 228 229 // Whether we should get the same dirent on second lookup. 230 wantSame bool 231 }{ 232 { 233 desc: "file from upper with no revalidation", 234 upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 235 lower: newTestRamfsDir(ctx, nil, nil), 236 revalidate: false, 237 wantSame: true, 238 }, 239 { 240 desc: "file from upper with revalidation", 241 upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 242 lower: newTestRamfsDir(ctx, nil, nil), 243 revalidate: true, 244 wantSame: false, 245 }, 246 { 247 desc: "file from lower with no revalidation", 248 upper: newTestRamfsDir(ctx, nil, nil), 249 lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 250 revalidate: false, 251 wantSame: true, 252 }, 253 { 254 desc: "file from lower with revalidation", 255 upper: newTestRamfsDir(ctx, nil, nil), 256 lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 257 revalidate: true, 258 // The file does not exist in the upper, so we do not 259 // need to revalidate it. 260 wantSame: true, 261 }, 262 { 263 desc: "file from upper and lower with no revalidation", 264 upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 265 lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 266 revalidate: false, 267 wantSame: true, 268 }, 269 { 270 desc: "file from upper and lower with revalidation", 271 upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 272 lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), 273 revalidate: true, 274 wantSame: false, 275 }, 276 } { 277 t.Run(tc.desc, func(t *testing.T) { 278 root := fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root") 279 ctx = &rootContext{ 280 Context: ctx, 281 root: root, 282 } 283 overlay := fs.NewDirent(ctx, fs.NewTestOverlayDir(ctx, tc.upper, tc.lower, tc.revalidate), "overlay") 284 // Lookup the file twice through the overlay. 285 first, err := overlay.Walk(ctx, root, fileName) 286 if err != nil { 287 t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err) 288 } 289 second, err := overlay.Walk(ctx, root, fileName) 290 if err != nil { 291 t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err) 292 } 293 294 if tc.wantSame && first != second { 295 t.Errorf("dirent lookup got different dirents, wanted same\nfirst=%+v\nsecond=%+v", first, second) 296 } else if !tc.wantSame && first == second { 297 t.Errorf("dirent lookup got the same dirent, wanted different: %+v", first) 298 } 299 }) 300 } 301 } 302 303 func TestCacheFlush(t *testing.T) { 304 ctx := contexttest.Context(t) 305 306 // Upper and lower each have a file. 307 upperFileName := "file-from-upper" 308 lowerFileName := "file-from-lower" 309 upper := newTestRamfsDir(ctx, []dirContent{{name: upperFileName}}, nil) 310 lower := newTestRamfsDir(ctx, []dirContent{{name: lowerFileName}}, nil) 311 312 overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */) 313 314 mns, err := fs.NewMountNamespace(ctx, overlay) 315 if err != nil { 316 t.Fatalf("NewMountNamespace failed: %v", err) 317 } 318 root := mns.Root() 319 defer root.DecRef(ctx) 320 321 ctx = &rootContext{ 322 Context: ctx, 323 root: root, 324 } 325 326 for _, fileName := range []string{upperFileName, lowerFileName} { 327 // Walk to the file. 328 maxTraversals := uint(0) 329 dirent, err := mns.FindInode(ctx, root, nil, fileName, &maxTraversals) 330 if err != nil { 331 t.Fatalf("FindInode(%q) failed: %v", fileName, err) 332 } 333 334 // Get a file from the dirent. 335 file, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true}) 336 if err != nil { 337 t.Fatalf("GetFile() failed: %v", err) 338 } 339 340 // The dirent should have 3 refs, one from us, one from the 341 // file, and one from the dirent cache. 342 // dirent cache. 343 if got, want := dirent.ReadRefs(), 3; int(got) != want { 344 t.Errorf("dirent.ReadRefs() got %d want %d", got, want) 345 } 346 347 // Drop the file reference. 348 file.DecRef(ctx) 349 350 // Dirent should have 2 refs left. 351 if got, want := dirent.ReadRefs(), 2; int(got) != want { 352 t.Errorf("dirent.ReadRefs() got %d want %d", got, want) 353 } 354 355 // Flush the dirent cache. 356 mns.FlushMountSourceRefs() 357 358 // Dirent should have 1 ref left from the dirent cache. 359 if got, want := dirent.ReadRefs(), 1; int(got) != want { 360 t.Errorf("dirent.ReadRefs() got %d want %d", got, want) 361 } 362 363 // Drop our ref. 364 dirent.DecRef(ctx) 365 366 // We should be back to zero refs. 367 if got, want := dirent.ReadRefs(), 0; int(got) != want { 368 t.Errorf("dirent.ReadRefs() got %d want %d", got, want) 369 } 370 } 371 372 } 373 374 type dir struct { 375 fs.InodeOperations 376 377 // List of negative child names. 378 negative []string 379 380 // ReaddirCalled records whether Readdir was called on a file 381 // corresponding to this inode. 382 ReaddirCalled bool 383 } 384 385 // GetXattr implements InodeOperations.GetXattr. 386 func (d *dir) GetXattr(_ context.Context, _ *fs.Inode, name string, _ uint64) (string, error) { 387 for _, n := range d.negative { 388 if name == fs.XattrOverlayWhiteout(n) { 389 return "y", nil 390 } 391 } 392 return "", linuxerr.ENOATTR 393 } 394 395 // GetFile implements InodeOperations.GetFile. 396 func (d *dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { 397 file, err := d.InodeOperations.GetFile(ctx, dirent, flags) 398 if err != nil { 399 return nil, err 400 } 401 defer file.DecRef(ctx) 402 // Wrap the file's FileOperations in a dirFile. 403 fops := &dirFile{ 404 FileOperations: file.FileOperations, 405 inode: d, 406 } 407 return fs.NewFile(ctx, dirent, flags, fops), nil 408 } 409 410 type dirContent struct { 411 name string 412 dir bool 413 } 414 415 type dirFile struct { 416 fs.FileOperations 417 inode *dir 418 } 419 420 type inode struct { 421 fsutil.InodeGenericChecker `state:"nosave"` 422 fsutil.InodeNoExtendedAttributes `state:"nosave"` 423 fsutil.InodeNoopRelease `state:"nosave"` 424 fsutil.InodeNoopWriteOut `state:"nosave"` 425 fsutil.InodeNotAllocatable `state:"nosave"` 426 fsutil.InodeNotDirectory `state:"nosave"` 427 fsutil.InodeNotMappable `state:"nosave"` 428 fsutil.InodeNotSocket `state:"nosave"` 429 fsutil.InodeNotSymlink `state:"nosave"` 430 fsutil.InodeNotTruncatable `state:"nosave"` 431 fsutil.InodeNotVirtual `state:"nosave"` 432 433 fsutil.InodeSimpleAttributes 434 fsutil.InodeStaticFileGetter 435 } 436 437 // Readdir implements fs.FileOperations.Readdir. It sets the ReaddirCalled 438 // field on the inode. 439 func (f *dirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) { 440 f.inode.ReaddirCalled = true 441 return f.FileOperations.Readdir(ctx, file, ser) 442 } 443 444 func newTestRamfsInode(ctx context.Context, msrc *fs.MountSource) *fs.Inode { 445 inode := fs.NewInode(ctx, &inode{ 446 InodeStaticFileGetter: fsutil.InodeStaticFileGetter{ 447 Contents: []byte("foobar"), 448 }, 449 }, msrc, fs.StableAttr{Type: fs.RegularFile}) 450 return inode 451 } 452 453 func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode { 454 msrc := fs.NewPseudoMountSource(ctx) 455 contents := make(map[string]*fs.Inode) 456 for _, c := range contains { 457 if c.dir { 458 contents[c.name] = newTestRamfsDir(ctx, nil, nil) 459 } else { 460 contents[c.name] = newTestRamfsInode(ctx, msrc) 461 } 462 } 463 dops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermissions{ 464 User: fs.PermMask{Read: true, Execute: true}, 465 }) 466 return fs.NewInode(ctx, &dir{ 467 InodeOperations: dops, 468 negative: negative, 469 }, msrc, fs.StableAttr{Type: fs.Directory}) 470 }