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  }