github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/ext/ext_test.go (about) 1 // Copyright 2019 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 ext 16 17 import ( 18 "fmt" 19 "io" 20 "os" 21 "path" 22 "sort" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 "github.com/SagerNet/gvisor/pkg/abi/linux" 28 "github.com/SagerNet/gvisor/pkg/context" 29 "github.com/SagerNet/gvisor/pkg/errors/linuxerr" 30 "github.com/SagerNet/gvisor/pkg/fspath" 31 "github.com/SagerNet/gvisor/pkg/sentry/contexttest" 32 "github.com/SagerNet/gvisor/pkg/sentry/fsimpl/ext/disklayout" 33 "github.com/SagerNet/gvisor/pkg/sentry/kernel/auth" 34 "github.com/SagerNet/gvisor/pkg/sentry/vfs" 35 "github.com/SagerNet/gvisor/pkg/test/testutil" 36 "github.com/SagerNet/gvisor/pkg/usermem" 37 ) 38 39 const ( 40 assetsDir = "pkg/sentry/fsimpl/ext/assets" 41 ) 42 43 var ( 44 ext2ImagePath = path.Join(assetsDir, "tiny.ext2") 45 ext3ImagePath = path.Join(assetsDir, "tiny.ext3") 46 ext4ImagePath = path.Join(assetsDir, "tiny.ext4") 47 ) 48 49 // setUp opens imagePath as an ext Filesystem and returns all necessary 50 // elements required to run tests. If error is non-nil, it also returns a tear 51 // down function which must be called after the test is run for clean up. 52 func setUp(t *testing.T, imagePath string) (context.Context, *vfs.VirtualFilesystem, *vfs.VirtualDentry, func(), error) { 53 localImagePath, err := testutil.FindFile(imagePath) 54 if err != nil { 55 return nil, nil, nil, nil, fmt.Errorf("failed to open local image at path %s: %v", imagePath, err) 56 } 57 58 f, err := os.Open(localImagePath) 59 if err != nil { 60 return nil, nil, nil, nil, err 61 } 62 63 ctx := contexttest.Context(t) 64 creds := auth.CredentialsFromContext(ctx) 65 66 // Create VFS. 67 vfsObj := &vfs.VirtualFilesystem{} 68 if err := vfsObj.Init(ctx); err != nil { 69 t.Fatalf("VFS init: %v", err) 70 } 71 vfsObj.MustRegisterFilesystemType("extfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ 72 AllowUserMount: true, 73 }) 74 mntns, err := vfsObj.NewMountNamespace(ctx, creds, localImagePath, "extfs", &vfs.MountOptions{ 75 GetFilesystemOptions: vfs.GetFilesystemOptions{ 76 InternalData: int(f.Fd()), 77 }, 78 }) 79 if err != nil { 80 f.Close() 81 return nil, nil, nil, nil, err 82 } 83 84 root := mntns.Root() 85 root.IncRef() 86 87 tearDown := func() { 88 root.DecRef(ctx) 89 90 if err := f.Close(); err != nil { 91 t.Fatalf("tearDown failed: %v", err) 92 } 93 } 94 return ctx, vfsObj, &root, tearDown, nil 95 } 96 97 // TODO(b/134676337): Test vfs.FilesystemImpl.ReadlinkAt and 98 // vfs.FilesystemImpl.StatFSAt which are not implemented in 99 // vfs.VirtualFilesystem yet. 100 101 // TestSeek tests vfs.FileDescriptionImpl.Seek functionality. 102 func TestSeek(t *testing.T) { 103 type seekTest struct { 104 name string 105 image string 106 path string 107 } 108 109 tests := []seekTest{ 110 { 111 name: "ext4 root dir seek", 112 image: ext4ImagePath, 113 path: "/", 114 }, 115 { 116 name: "ext3 root dir seek", 117 image: ext3ImagePath, 118 path: "/", 119 }, 120 { 121 name: "ext2 root dir seek", 122 image: ext2ImagePath, 123 path: "/", 124 }, 125 { 126 name: "ext4 reg file seek", 127 image: ext4ImagePath, 128 path: "/file.txt", 129 }, 130 { 131 name: "ext3 reg file seek", 132 image: ext3ImagePath, 133 path: "/file.txt", 134 }, 135 { 136 name: "ext2 reg file seek", 137 image: ext2ImagePath, 138 path: "/file.txt", 139 }, 140 } 141 142 for _, test := range tests { 143 t.Run(test.name, func(t *testing.T) { 144 ctx, vfsfs, root, tearDown, err := setUp(t, test.image) 145 if err != nil { 146 t.Fatalf("setUp failed: %v", err) 147 } 148 defer tearDown() 149 150 fd, err := vfsfs.OpenAt( 151 ctx, 152 auth.CredentialsFromContext(ctx), 153 &vfs.PathOperation{Root: *root, Start: *root, Path: fspath.Parse(test.path)}, 154 &vfs.OpenOptions{}, 155 ) 156 if err != nil { 157 t.Fatalf("vfsfs.OpenAt failed: %v", err) 158 } 159 160 if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 || err != nil { 161 t.Errorf("expected seek position 0, got %d and error %v", n, err) 162 } 163 164 stat, err := fd.Stat(ctx, vfs.StatOptions{}) 165 if err != nil { 166 t.Errorf("fd.stat failed for file %s in image %s: %v", test.path, test.image, err) 167 } 168 169 // We should be able to seek beyond the end of file. 170 size := int64(stat.Size) 171 if n, err := fd.Seek(ctx, size, linux.SEEK_SET); n != size || err != nil { 172 t.Errorf("expected seek position %d, got %d and error %v", size, n, err) 173 } 174 175 // EINVAL should be returned if the resulting offset is negative. 176 if _, err := fd.Seek(ctx, -1, linux.SEEK_SET); !linuxerr.Equals(linuxerr.EINVAL, err) { 177 t.Errorf("expected error EINVAL but got %v", err) 178 } 179 180 if n, err := fd.Seek(ctx, 3, linux.SEEK_CUR); n != size+3 || err != nil { 181 t.Errorf("expected seek position %d, got %d and error %v", size+3, n, err) 182 } 183 184 // Make sure negative offsets work with SEEK_CUR. 185 if n, err := fd.Seek(ctx, -2, linux.SEEK_CUR); n != size+1 || err != nil { 186 t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err) 187 } 188 189 // EINVAL should be returned if the resulting offset is negative. 190 if _, err := fd.Seek(ctx, -(size + 2), linux.SEEK_CUR); !linuxerr.Equals(linuxerr.EINVAL, err) { 191 t.Errorf("expected error EINVAL but got %v", err) 192 } 193 194 // Make sure SEEK_END works with regular files. 195 if _, ok := fd.Impl().(*regularFileFD); ok { 196 // Seek back to 0. 197 if n, err := fd.Seek(ctx, -size, linux.SEEK_END); n != 0 || err != nil { 198 t.Errorf("expected seek position %d, got %d and error %v", 0, n, err) 199 } 200 201 // Seek forward beyond EOF. 202 if n, err := fd.Seek(ctx, 1, linux.SEEK_END); n != size+1 || err != nil { 203 t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err) 204 } 205 206 // EINVAL should be returned if the resulting offset is negative. 207 if _, err := fd.Seek(ctx, -(size + 1), linux.SEEK_END); !linuxerr.Equals(linuxerr.EINVAL, err) { 208 t.Errorf("expected error EINVAL but got %v", err) 209 } 210 } 211 }) 212 } 213 } 214 215 // TestStatAt tests filesystem.StatAt functionality. 216 func TestStatAt(t *testing.T) { 217 type statAtTest struct { 218 name string 219 image string 220 path string 221 want linux.Statx 222 } 223 224 tests := []statAtTest{ 225 { 226 name: "ext4 statx small file", 227 image: ext4ImagePath, 228 path: "/file.txt", 229 want: linux.Statx{ 230 Blksize: 0x400, 231 Nlink: 1, 232 UID: 0, 233 GID: 0, 234 Mode: 0644 | linux.ModeRegular, 235 Size: 13, 236 }, 237 }, 238 { 239 name: "ext3 statx small file", 240 image: ext3ImagePath, 241 path: "/file.txt", 242 want: linux.Statx{ 243 Blksize: 0x400, 244 Nlink: 1, 245 UID: 0, 246 GID: 0, 247 Mode: 0644 | linux.ModeRegular, 248 Size: 13, 249 }, 250 }, 251 { 252 name: "ext2 statx small file", 253 image: ext2ImagePath, 254 path: "/file.txt", 255 want: linux.Statx{ 256 Blksize: 0x400, 257 Nlink: 1, 258 UID: 0, 259 GID: 0, 260 Mode: 0644 | linux.ModeRegular, 261 Size: 13, 262 }, 263 }, 264 { 265 name: "ext4 statx big file", 266 image: ext4ImagePath, 267 path: "/bigfile.txt", 268 want: linux.Statx{ 269 Blksize: 0x400, 270 Nlink: 1, 271 UID: 0, 272 GID: 0, 273 Mode: 0644 | linux.ModeRegular, 274 Size: 13042, 275 }, 276 }, 277 { 278 name: "ext3 statx big file", 279 image: ext3ImagePath, 280 path: "/bigfile.txt", 281 want: linux.Statx{ 282 Blksize: 0x400, 283 Nlink: 1, 284 UID: 0, 285 GID: 0, 286 Mode: 0644 | linux.ModeRegular, 287 Size: 13042, 288 }, 289 }, 290 { 291 name: "ext2 statx big file", 292 image: ext2ImagePath, 293 path: "/bigfile.txt", 294 want: linux.Statx{ 295 Blksize: 0x400, 296 Nlink: 1, 297 UID: 0, 298 GID: 0, 299 Mode: 0644 | linux.ModeRegular, 300 Size: 13042, 301 }, 302 }, 303 { 304 name: "ext4 statx symlink file", 305 image: ext4ImagePath, 306 path: "/symlink.txt", 307 want: linux.Statx{ 308 Blksize: 0x400, 309 Nlink: 1, 310 UID: 0, 311 GID: 0, 312 Mode: 0777 | linux.ModeSymlink, 313 Size: 8, 314 }, 315 }, 316 { 317 name: "ext3 statx symlink file", 318 image: ext3ImagePath, 319 path: "/symlink.txt", 320 want: linux.Statx{ 321 Blksize: 0x400, 322 Nlink: 1, 323 UID: 0, 324 GID: 0, 325 Mode: 0777 | linux.ModeSymlink, 326 Size: 8, 327 }, 328 }, 329 { 330 name: "ext2 statx symlink file", 331 image: ext2ImagePath, 332 path: "/symlink.txt", 333 want: linux.Statx{ 334 Blksize: 0x400, 335 Nlink: 1, 336 UID: 0, 337 GID: 0, 338 Mode: 0777 | linux.ModeSymlink, 339 Size: 8, 340 }, 341 }, 342 } 343 344 // Ignore the fields that are not supported by filesystem.StatAt yet and 345 // those which are likely to change as the image does. 346 ignoredFields := map[string]bool{ 347 "Attributes": true, 348 "AttributesMask": true, 349 "Atime": true, 350 "Blocks": true, 351 "Btime": true, 352 "Ctime": true, 353 "DevMajor": true, 354 "DevMinor": true, 355 "Ino": true, 356 "Mask": true, 357 "Mtime": true, 358 "RdevMajor": true, 359 "RdevMinor": true, 360 } 361 362 for _, test := range tests { 363 t.Run(test.name, func(t *testing.T) { 364 ctx, vfsfs, root, tearDown, err := setUp(t, test.image) 365 if err != nil { 366 t.Fatalf("setUp failed: %v", err) 367 } 368 defer tearDown() 369 370 got, err := vfsfs.StatAt(ctx, 371 auth.CredentialsFromContext(ctx), 372 &vfs.PathOperation{Root: *root, Start: *root, Path: fspath.Parse(test.path)}, 373 &vfs.StatOptions{}, 374 ) 375 if err != nil { 376 t.Fatalf("vfsfs.StatAt failed for file %s in image %s: %v", test.path, test.image, err) 377 } 378 379 cmpIgnoreFields := cmp.FilterPath(func(p cmp.Path) bool { 380 _, ok := ignoredFields[p.String()] 381 return ok 382 }, cmp.Ignore()) 383 if diff := cmp.Diff(got, test.want, cmpIgnoreFields, cmpopts.IgnoreUnexported(linux.Statx{})); diff != "" { 384 t.Errorf("stat mismatch (-want +got):\n%s", diff) 385 } 386 }) 387 } 388 } 389 390 // TestRead tests the read functionality for vfs file descriptions. 391 func TestRead(t *testing.T) { 392 type readTest struct { 393 name string 394 image string 395 absPath string 396 } 397 398 tests := []readTest{ 399 { 400 name: "ext4 read small file", 401 image: ext4ImagePath, 402 absPath: "/file.txt", 403 }, 404 { 405 name: "ext3 read small file", 406 image: ext3ImagePath, 407 absPath: "/file.txt", 408 }, 409 { 410 name: "ext2 read small file", 411 image: ext2ImagePath, 412 absPath: "/file.txt", 413 }, 414 { 415 name: "ext4 read big file", 416 image: ext4ImagePath, 417 absPath: "/bigfile.txt", 418 }, 419 { 420 name: "ext3 read big file", 421 image: ext3ImagePath, 422 absPath: "/bigfile.txt", 423 }, 424 { 425 name: "ext2 read big file", 426 image: ext2ImagePath, 427 absPath: "/bigfile.txt", 428 }, 429 } 430 431 for _, test := range tests { 432 t.Run(test.name, func(t *testing.T) { 433 ctx, vfsfs, root, tearDown, err := setUp(t, test.image) 434 if err != nil { 435 t.Fatalf("setUp failed: %v", err) 436 } 437 defer tearDown() 438 439 fd, err := vfsfs.OpenAt( 440 ctx, 441 auth.CredentialsFromContext(ctx), 442 &vfs.PathOperation{Root: *root, Start: *root, Path: fspath.Parse(test.absPath)}, 443 &vfs.OpenOptions{}, 444 ) 445 if err != nil { 446 t.Fatalf("vfsfs.OpenAt failed: %v", err) 447 } 448 449 // Get a local file descriptor and compare its functionality with a vfs file 450 // description for the same file. 451 localFile, err := testutil.FindFile(path.Join(assetsDir, test.absPath)) 452 if err != nil { 453 t.Fatalf("testutil.FindFile failed for %s: %v", test.absPath, err) 454 } 455 456 f, err := os.Open(localFile) 457 if err != nil { 458 t.Fatalf("os.Open failed for %s: %v", localFile, err) 459 } 460 defer f.Close() 461 462 // Read the entire file by reading one byte repeatedly. Doing this stress 463 // tests the underlying file reader implementation. 464 got := make([]byte, 1) 465 want := make([]byte, 1) 466 for { 467 n, err := f.Read(want) 468 fd.Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}) 469 470 if diff := cmp.Diff(got, want); diff != "" { 471 t.Errorf("file data mismatch (-want +got):\n%s", diff) 472 } 473 474 // Make sure there is no more file data left after getting EOF. 475 if n == 0 || err == io.EOF { 476 if n, _ := fd.Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}); n != 0 { 477 t.Errorf("extra unexpected file data in file %s in image %s", test.absPath, test.image) 478 } 479 480 break 481 } 482 483 if err != nil { 484 t.Fatalf("read failed: %v", err) 485 } 486 } 487 }) 488 } 489 } 490 491 // iterDirentsCb is a simple callback which just keeps adding the dirents to an 492 // internal list. Implements vfs.IterDirentsCallback. 493 type iterDirentsCb struct { 494 dirents []vfs.Dirent 495 } 496 497 // Compiles only if iterDirentCb implements vfs.IterDirentsCallback. 498 var _ vfs.IterDirentsCallback = (*iterDirentsCb)(nil) 499 500 // newIterDirentsCb is the iterDirent 501 func newIterDirentCb() *iterDirentsCb { 502 return &iterDirentsCb{dirents: make([]vfs.Dirent, 0)} 503 } 504 505 // Handle implements vfs.IterDirentsCallback.Handle. 506 func (cb *iterDirentsCb) Handle(dirent vfs.Dirent) error { 507 cb.dirents = append(cb.dirents, dirent) 508 return nil 509 } 510 511 // TestIterDirents tests the FileDescriptionImpl.IterDirents functionality. 512 func TestIterDirents(t *testing.T) { 513 type iterDirentTest struct { 514 name string 515 image string 516 path string 517 want []vfs.Dirent 518 } 519 520 wantDirents := []vfs.Dirent{ 521 { 522 Name: ".", 523 Type: linux.DT_DIR, 524 }, 525 { 526 Name: "..", 527 Type: linux.DT_DIR, 528 }, 529 { 530 Name: "lost+found", 531 Type: linux.DT_DIR, 532 }, 533 { 534 Name: "file.txt", 535 Type: linux.DT_REG, 536 }, 537 { 538 Name: "bigfile.txt", 539 Type: linux.DT_REG, 540 }, 541 { 542 Name: "symlink.txt", 543 Type: linux.DT_LNK, 544 }, 545 } 546 tests := []iterDirentTest{ 547 { 548 name: "ext4 root dir iteration", 549 image: ext4ImagePath, 550 path: "/", 551 want: wantDirents, 552 }, 553 { 554 name: "ext3 root dir iteration", 555 image: ext3ImagePath, 556 path: "/", 557 want: wantDirents, 558 }, 559 { 560 name: "ext2 root dir iteration", 561 image: ext2ImagePath, 562 path: "/", 563 want: wantDirents, 564 }, 565 } 566 567 for _, test := range tests { 568 t.Run(test.name, func(t *testing.T) { 569 ctx, vfsfs, root, tearDown, err := setUp(t, test.image) 570 if err != nil { 571 t.Fatalf("setUp failed: %v", err) 572 } 573 defer tearDown() 574 575 fd, err := vfsfs.OpenAt( 576 ctx, 577 auth.CredentialsFromContext(ctx), 578 &vfs.PathOperation{Root: *root, Start: *root, Path: fspath.Parse(test.path)}, 579 &vfs.OpenOptions{}, 580 ) 581 if err != nil { 582 t.Fatalf("vfsfs.OpenAt failed: %v", err) 583 } 584 585 cb := &iterDirentsCb{} 586 if err = fd.IterDirents(ctx, cb); err != nil { 587 t.Fatalf("dir fd.IterDirents() failed: %v", err) 588 } 589 590 sort.Slice(cb.dirents, func(i int, j int) bool { return cb.dirents[i].Name < cb.dirents[j].Name }) 591 sort.Slice(test.want, func(i int, j int) bool { return test.want[i].Name < test.want[j].Name }) 592 593 // Ignore the inode number and offset of dirents because those are likely to 594 // change as the underlying image changes. 595 cmpIgnoreFields := cmp.FilterPath(func(p cmp.Path) bool { 596 return p.String() == "Ino" || p.String() == "NextOff" 597 }, cmp.Ignore()) 598 if diff := cmp.Diff(cb.dirents, test.want, cmpIgnoreFields); diff != "" { 599 t.Errorf("dirents mismatch (-want +got):\n%s", diff) 600 } 601 }) 602 } 603 } 604 605 // TestRootDir tests that the root directory inode is correctly initialized and 606 // returned from setUp. 607 func TestRootDir(t *testing.T) { 608 type inodeProps struct { 609 Mode linux.FileMode 610 UID auth.KUID 611 GID auth.KGID 612 Size uint64 613 InodeSize uint16 614 Links uint16 615 Flags disklayout.InodeFlags 616 } 617 618 type rootDirTest struct { 619 name string 620 image string 621 wantInode inodeProps 622 } 623 624 tests := []rootDirTest{ 625 { 626 name: "ext4 root dir", 627 image: ext4ImagePath, 628 wantInode: inodeProps{ 629 Mode: linux.ModeDirectory | 0755, 630 Size: 0x400, 631 InodeSize: 0x80, 632 Links: 3, 633 Flags: disklayout.InodeFlags{Extents: true}, 634 }, 635 }, 636 { 637 name: "ext3 root dir", 638 image: ext3ImagePath, 639 wantInode: inodeProps{ 640 Mode: linux.ModeDirectory | 0755, 641 Size: 0x400, 642 InodeSize: 0x80, 643 Links: 3, 644 }, 645 }, 646 { 647 name: "ext2 root dir", 648 image: ext2ImagePath, 649 wantInode: inodeProps{ 650 Mode: linux.ModeDirectory | 0755, 651 Size: 0x400, 652 InodeSize: 0x80, 653 Links: 3, 654 }, 655 }, 656 } 657 658 for _, test := range tests { 659 t.Run(test.name, func(t *testing.T) { 660 _, _, vd, tearDown, err := setUp(t, test.image) 661 if err != nil { 662 t.Fatalf("setUp failed: %v", err) 663 } 664 defer tearDown() 665 666 d, ok := vd.Dentry().Impl().(*dentry) 667 if !ok { 668 t.Fatalf("ext dentry of incorrect type: %T", vd.Dentry().Impl()) 669 } 670 671 // Offload inode contents into local structs for comparison. 672 gotInode := inodeProps{ 673 Mode: d.inode.diskInode.Mode(), 674 UID: d.inode.diskInode.UID(), 675 GID: d.inode.diskInode.GID(), 676 Size: d.inode.diskInode.Size(), 677 InodeSize: d.inode.diskInode.InodeSize(), 678 Links: d.inode.diskInode.LinksCount(), 679 Flags: d.inode.diskInode.Flags(), 680 } 681 682 if diff := cmp.Diff(gotInode, test.wantInode); diff != "" { 683 t.Errorf("inode mismatch (-want +got):\n%s", diff) 684 } 685 }) 686 } 687 } 688 689 // TestFilesystemInit tests that the filesystem superblock and block group 690 // descriptors are correctly read in and initialized. 691 func TestFilesystemInit(t *testing.T) { 692 // sb only contains the immutable properties of the superblock. 693 type sb struct { 694 InodesCount uint32 695 BlocksCount uint64 696 MaxMountCount uint16 697 FirstDataBlock uint32 698 BlockSize uint64 699 BlocksPerGroup uint32 700 ClusterSize uint64 701 ClustersPerGroup uint32 702 InodeSize uint16 703 InodesPerGroup uint32 704 BgDescSize uint16 705 Magic uint16 706 Revision disklayout.SbRevision 707 CompatFeatures disklayout.CompatFeatures 708 IncompatFeatures disklayout.IncompatFeatures 709 RoCompatFeatures disklayout.RoCompatFeatures 710 } 711 712 // bg only contains the immutable properties of the block group descriptor. 713 type bg struct { 714 InodeTable uint64 715 BlockBitmap uint64 716 InodeBitmap uint64 717 ExclusionBitmap uint64 718 Flags disklayout.BGFlags 719 } 720 721 type fsInitTest struct { 722 name string 723 image string 724 wantSb sb 725 wantBgs []bg 726 } 727 728 tests := []fsInitTest{ 729 { 730 name: "ext4 filesystem init", 731 image: ext4ImagePath, 732 wantSb: sb{ 733 InodesCount: 0x10, 734 BlocksCount: 0x40, 735 MaxMountCount: 0xffff, 736 FirstDataBlock: 0x1, 737 BlockSize: 0x400, 738 BlocksPerGroup: 0x2000, 739 ClusterSize: 0x400, 740 ClustersPerGroup: 0x2000, 741 InodeSize: 0x80, 742 InodesPerGroup: 0x10, 743 BgDescSize: 0x40, 744 Magic: linux.EXT_SUPER_MAGIC, 745 Revision: disklayout.DynamicRev, 746 CompatFeatures: disklayout.CompatFeatures{ 747 ExtAttr: true, 748 ResizeInode: true, 749 DirIndex: true, 750 }, 751 IncompatFeatures: disklayout.IncompatFeatures{ 752 DirentFileType: true, 753 Extents: true, 754 Is64Bit: true, 755 FlexBg: true, 756 }, 757 RoCompatFeatures: disklayout.RoCompatFeatures{ 758 Sparse: true, 759 LargeFile: true, 760 HugeFile: true, 761 DirNlink: true, 762 ExtraIsize: true, 763 MetadataCsum: true, 764 }, 765 }, 766 wantBgs: []bg{ 767 { 768 InodeTable: 0x23, 769 BlockBitmap: 0x3, 770 InodeBitmap: 0x13, 771 Flags: disklayout.BGFlags{ 772 InodeZeroed: true, 773 }, 774 }, 775 }, 776 }, 777 { 778 name: "ext3 filesystem init", 779 image: ext3ImagePath, 780 wantSb: sb{ 781 InodesCount: 0x10, 782 BlocksCount: 0x40, 783 MaxMountCount: 0xffff, 784 FirstDataBlock: 0x1, 785 BlockSize: 0x400, 786 BlocksPerGroup: 0x2000, 787 ClusterSize: 0x400, 788 ClustersPerGroup: 0x2000, 789 InodeSize: 0x80, 790 InodesPerGroup: 0x10, 791 BgDescSize: 0x20, 792 Magic: linux.EXT_SUPER_MAGIC, 793 Revision: disklayout.DynamicRev, 794 CompatFeatures: disklayout.CompatFeatures{ 795 ExtAttr: true, 796 ResizeInode: true, 797 DirIndex: true, 798 }, 799 IncompatFeatures: disklayout.IncompatFeatures{ 800 DirentFileType: true, 801 }, 802 RoCompatFeatures: disklayout.RoCompatFeatures{ 803 Sparse: true, 804 LargeFile: true, 805 }, 806 }, 807 wantBgs: []bg{ 808 { 809 InodeTable: 0x5, 810 BlockBitmap: 0x3, 811 InodeBitmap: 0x4, 812 Flags: disklayout.BGFlags{ 813 InodeZeroed: true, 814 }, 815 }, 816 }, 817 }, 818 { 819 name: "ext2 filesystem init", 820 image: ext2ImagePath, 821 wantSb: sb{ 822 InodesCount: 0x10, 823 BlocksCount: 0x40, 824 MaxMountCount: 0xffff, 825 FirstDataBlock: 0x1, 826 BlockSize: 0x400, 827 BlocksPerGroup: 0x2000, 828 ClusterSize: 0x400, 829 ClustersPerGroup: 0x2000, 830 InodeSize: 0x80, 831 InodesPerGroup: 0x10, 832 BgDescSize: 0x20, 833 Magic: linux.EXT_SUPER_MAGIC, 834 Revision: disklayout.DynamicRev, 835 CompatFeatures: disklayout.CompatFeatures{ 836 ExtAttr: true, 837 ResizeInode: true, 838 DirIndex: true, 839 }, 840 IncompatFeatures: disklayout.IncompatFeatures{ 841 DirentFileType: true, 842 }, 843 RoCompatFeatures: disklayout.RoCompatFeatures{ 844 Sparse: true, 845 LargeFile: true, 846 }, 847 }, 848 wantBgs: []bg{ 849 { 850 InodeTable: 0x5, 851 BlockBitmap: 0x3, 852 InodeBitmap: 0x4, 853 Flags: disklayout.BGFlags{ 854 InodeZeroed: true, 855 }, 856 }, 857 }, 858 }, 859 } 860 861 for _, test := range tests { 862 t.Run(test.name, func(t *testing.T) { 863 _, _, vd, tearDown, err := setUp(t, test.image) 864 if err != nil { 865 t.Fatalf("setUp failed: %v", err) 866 } 867 defer tearDown() 868 869 fs, ok := vd.Mount().Filesystem().Impl().(*filesystem) 870 if !ok { 871 t.Fatalf("ext filesystem of incorrect type: %T", vd.Mount().Filesystem().Impl()) 872 } 873 874 // Offload superblock and block group descriptors contents into 875 // local structs for comparison. 876 totalFreeInodes := uint32(0) 877 totalFreeBlocks := uint64(0) 878 gotSb := sb{ 879 InodesCount: fs.sb.InodesCount(), 880 BlocksCount: fs.sb.BlocksCount(), 881 MaxMountCount: fs.sb.MaxMountCount(), 882 FirstDataBlock: fs.sb.FirstDataBlock(), 883 BlockSize: fs.sb.BlockSize(), 884 BlocksPerGroup: fs.sb.BlocksPerGroup(), 885 ClusterSize: fs.sb.ClusterSize(), 886 ClustersPerGroup: fs.sb.ClustersPerGroup(), 887 InodeSize: fs.sb.InodeSize(), 888 InodesPerGroup: fs.sb.InodesPerGroup(), 889 BgDescSize: fs.sb.BgDescSize(), 890 Magic: fs.sb.Magic(), 891 Revision: fs.sb.Revision(), 892 CompatFeatures: fs.sb.CompatibleFeatures(), 893 IncompatFeatures: fs.sb.IncompatibleFeatures(), 894 RoCompatFeatures: fs.sb.ReadOnlyCompatibleFeatures(), 895 } 896 gotNumBgs := len(fs.bgs) 897 gotBgs := make([]bg, gotNumBgs) 898 for i := 0; i < gotNumBgs; i++ { 899 gotBgs[i].InodeTable = fs.bgs[i].InodeTable() 900 gotBgs[i].BlockBitmap = fs.bgs[i].BlockBitmap() 901 gotBgs[i].InodeBitmap = fs.bgs[i].InodeBitmap() 902 gotBgs[i].ExclusionBitmap = fs.bgs[i].ExclusionBitmap() 903 gotBgs[i].Flags = fs.bgs[i].Flags() 904 905 totalFreeInodes += fs.bgs[i].FreeInodesCount() 906 totalFreeBlocks += uint64(fs.bgs[i].FreeBlocksCount()) 907 } 908 909 if diff := cmp.Diff(gotSb, test.wantSb); diff != "" { 910 t.Errorf("superblock mismatch (-want +got):\n%s", diff) 911 } 912 913 if diff := cmp.Diff(gotBgs, test.wantBgs); diff != "" { 914 t.Errorf("block group descriptors mismatch (-want +got):\n%s", diff) 915 } 916 917 if diff := cmp.Diff(totalFreeInodes, fs.sb.FreeInodesCount()); diff != "" { 918 t.Errorf("total free inodes mismatch (-want +got):\n%s", diff) 919 } 920 921 if diff := cmp.Diff(totalFreeBlocks, fs.sb.FreeBlocksCount()); diff != "" { 922 t.Errorf("total free blocks mismatch (-want +got):\n%s", diff) 923 } 924 }) 925 } 926 }