github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/ext/block_map_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 "bytes" 19 "math/rand" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/SagerNet/gvisor/pkg/marshal/primitive" 24 "github.com/SagerNet/gvisor/pkg/sentry/fsimpl/ext/disklayout" 25 ) 26 27 // These consts are for mocking the block map tree. 28 const ( 29 mockBMBlkSize = uint32(16) 30 mockBMDiskSize = 2500 31 ) 32 33 // TestBlockMapReader stress tests block map reader functionality. It performs 34 // random length reads from all possible positions in the block map structure. 35 func TestBlockMapReader(t *testing.T) { 36 mockBMFile, want := blockMapSetUp(t) 37 n := len(want) 38 39 for from := 0; from < n; from++ { 40 got := make([]byte, n-from) 41 42 if read, err := mockBMFile.ReadAt(got, int64(from)); err != nil { 43 t.Fatalf("file read operation from offset %d to %d only read %d bytes: %v", from, n, read, err) 44 } 45 46 if diff := cmp.Diff(got, want[from:]); diff != "" { 47 t.Fatalf("file data from offset %d to %d mismatched (-want +got):\n%s", from, n, diff) 48 } 49 } 50 } 51 52 // blkNumGen is a number generator which gives block numbers for building the 53 // block map file on disk. It gives unique numbers in a random order which 54 // facilitates in creating an extremely fragmented filesystem. 55 type blkNumGen struct { 56 nums []uint32 57 } 58 59 // newBlkNumGen is the blkNumGen constructor. 60 func newBlkNumGen() *blkNumGen { 61 blkNums := &blkNumGen{} 62 lim := mockBMDiskSize / mockBMBlkSize 63 blkNums.nums = make([]uint32, lim) 64 for i := uint32(0); i < lim; i++ { 65 blkNums.nums[i] = i 66 } 67 68 rand.Shuffle(int(lim), func(i, j int) { 69 blkNums.nums[i], blkNums.nums[j] = blkNums.nums[j], blkNums.nums[i] 70 }) 71 return blkNums 72 } 73 74 // next returns the next random block number. 75 func (n *blkNumGen) next() uint32 { 76 ret := n.nums[0] 77 n.nums = n.nums[1:] 78 return ret 79 } 80 81 // blockMapSetUp creates a mock disk and a block map file. It initializes the 82 // block map file with 12 direct block, 1 indirect block, 1 double indirect 83 // block and 1 triple indirect block (basically fill it till the rim). It 84 // initializes the disk to reflect the inode. Also returns the file data that 85 // the inode covers and that is written to disk. 86 func blockMapSetUp(t *testing.T) (*blockMapFile, []byte) { 87 mockDisk := make([]byte, mockBMDiskSize) 88 var fileData []byte 89 blkNums := newBlkNumGen() 90 off := 0 91 data := make([]byte, (numDirectBlks+3)*(*primitive.Uint32)(nil).SizeBytes()) 92 93 // Write the direct blocks. 94 for i := 0; i < numDirectBlks; i++ { 95 curBlkNum := primitive.Uint32(blkNums.next()) 96 curBlkNum.MarshalBytes(data[off:]) 97 off += curBlkNum.SizeBytes() 98 fileData = append(fileData, writeFileDataToBlock(mockDisk, uint32(curBlkNum), 0, blkNums)...) 99 } 100 101 // Write to indirect block. 102 indirectBlk := primitive.Uint32(blkNums.next()) 103 indirectBlk.MarshalBytes(data[off:]) 104 off += indirectBlk.SizeBytes() 105 fileData = append(fileData, writeFileDataToBlock(mockDisk, uint32(indirectBlk), 1, blkNums)...) 106 107 // Write to double indirect block. 108 doublyIndirectBlk := primitive.Uint32(blkNums.next()) 109 doublyIndirectBlk.MarshalBytes(data[off:]) 110 off += doublyIndirectBlk.SizeBytes() 111 fileData = append(fileData, writeFileDataToBlock(mockDisk, uint32(doublyIndirectBlk), 2, blkNums)...) 112 113 // Write to triple indirect block. 114 triplyIndirectBlk := primitive.Uint32(blkNums.next()) 115 triplyIndirectBlk.MarshalBytes(data[off:]) 116 fileData = append(fileData, writeFileDataToBlock(mockDisk, uint32(triplyIndirectBlk), 3, blkNums)...) 117 118 args := inodeArgs{ 119 fs: &filesystem{ 120 dev: bytes.NewReader(mockDisk), 121 }, 122 diskInode: &disklayout.InodeNew{ 123 InodeOld: disklayout.InodeOld{ 124 SizeLo: getMockBMFileFize(), 125 }, 126 }, 127 blkSize: uint64(mockBMBlkSize), 128 } 129 copy(args.diskInode.Data(), data) 130 131 mockFile, err := newBlockMapFile(args) 132 if err != nil { 133 t.Fatalf("newBlockMapFile failed: %v", err) 134 } 135 return mockFile, fileData 136 } 137 138 // writeFileDataToBlock writes random bytes to the block on disk. 139 func writeFileDataToBlock(disk []byte, blkNum uint32, height uint, blkNums *blkNumGen) []byte { 140 if height == 0 { 141 start := blkNum * mockBMBlkSize 142 end := start + mockBMBlkSize 143 rand.Read(disk[start:end]) 144 return disk[start:end] 145 } 146 147 var fileData []byte 148 for off := blkNum * mockBMBlkSize; off < (blkNum+1)*mockBMBlkSize; off += 4 { 149 curBlkNum := primitive.Uint32(blkNums.next()) 150 curBlkNum.MarshalBytes(disk[off : off+4]) 151 fileData = append(fileData, writeFileDataToBlock(disk, uint32(curBlkNum), height-1, blkNums)...) 152 } 153 return fileData 154 } 155 156 // getMockBMFileFize gets the size of the mock block map file which is used for 157 // testing. 158 func getMockBMFileFize() uint32 { 159 return uint32(numDirectBlks*getCoverage(uint64(mockBMBlkSize), 0) + getCoverage(uint64(mockBMBlkSize), 1) + getCoverage(uint64(mockBMBlkSize), 2) + getCoverage(uint64(mockBMBlkSize), 3)) 160 }