gvisor/pkg/sentry/fsimpl/ext/block_map_test.go

157 lines
5.0 KiB
Go

// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ext
import (
"bytes"
"math/rand"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/binary"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
)
// These consts are for mocking the block map tree.
const (
mockBMBlkSize = uint32(16)
mockBMDiskSize = 2500
)
// TestBlockMapReader stress tests block map reader functionality. It performs
// random length reads from all possible positions in the block map structure.
func TestBlockMapReader(t *testing.T) {
mockBMFile, want := blockMapSetUp(t)
n := len(want)
for from := 0; from < n; from++ {
got := make([]byte, n-from)
if read, err := mockBMFile.ReadAt(got, int64(from)); err != nil {
t.Fatalf("file read operation from offset %d to %d only read %d bytes: %v", from, n, read, err)
}
if diff := cmp.Diff(got, want[from:]); diff != "" {
t.Fatalf("file data from offset %d to %d mismatched (-want +got):\n%s", from, n, diff)
}
}
}
// blkNumGen is a number generator which gives block numbers for building the
// block map file on disk. It gives unique numbers in a random order which
// facilitates in creating an extremely fragmented filesystem.
type blkNumGen struct {
nums []uint32
}
// newBlkNumGen is the blkNumGen constructor.
func newBlkNumGen() *blkNumGen {
blkNums := &blkNumGen{}
lim := mockBMDiskSize / mockBMBlkSize
blkNums.nums = make([]uint32, lim)
for i := uint32(0); i < lim; i++ {
blkNums.nums[i] = i
}
rand.Shuffle(int(lim), func(i, j int) {
blkNums.nums[i], blkNums.nums[j] = blkNums.nums[j], blkNums.nums[i]
})
return blkNums
}
// next returns the next random block number.
func (n *blkNumGen) next() uint32 {
ret := n.nums[0]
n.nums = n.nums[1:]
return ret
}
// blockMapSetUp creates a mock disk and a block map file. It initializes the
// block map file with 12 direct block, 1 indirect block, 1 double indirect
// block and 1 triple indirect block (basically fill it till the rim). It
// initializes the disk to reflect the inode. Also returns the file data that
// the inode covers and that is written to disk.
func blockMapSetUp(t *testing.T) (*blockMapFile, []byte) {
mockDisk := make([]byte, mockBMDiskSize)
var fileData []byte
blkNums := newBlkNumGen()
var data []byte
// Write the direct blocks.
for i := 0; i < numDirectBlks; i++ {
curBlkNum := blkNums.next()
data = binary.Marshal(data, binary.LittleEndian, curBlkNum)
fileData = append(fileData, writeFileDataToBlock(mockDisk, curBlkNum, 0, blkNums)...)
}
// Write to indirect block.
indirectBlk := blkNums.next()
data = binary.Marshal(data, binary.LittleEndian, indirectBlk)
fileData = append(fileData, writeFileDataToBlock(mockDisk, indirectBlk, 1, blkNums)...)
// Write to indirect block.
doublyIndirectBlk := blkNums.next()
data = binary.Marshal(data, binary.LittleEndian, doublyIndirectBlk)
fileData = append(fileData, writeFileDataToBlock(mockDisk, doublyIndirectBlk, 2, blkNums)...)
// Write to indirect block.
triplyIndirectBlk := blkNums.next()
data = binary.Marshal(data, binary.LittleEndian, triplyIndirectBlk)
fileData = append(fileData, writeFileDataToBlock(mockDisk, triplyIndirectBlk, 3, blkNums)...)
args := inodeArgs{
fs: &filesystem{
dev: bytes.NewReader(mockDisk),
},
diskInode: &disklayout.InodeNew{
InodeOld: disklayout.InodeOld{
SizeLo: getMockBMFileFize(),
},
},
blkSize: uint64(mockBMBlkSize),
}
copy(args.diskInode.Data(), data)
mockFile, err := newBlockMapFile(args)
if err != nil {
t.Fatalf("newBlockMapFile failed: %v", err)
}
return mockFile, fileData
}
// writeFileDataToBlock writes random bytes to the block on disk.
func writeFileDataToBlock(disk []byte, blkNum uint32, height uint, blkNums *blkNumGen) []byte {
if height == 0 {
start := blkNum * mockBMBlkSize
end := start + mockBMBlkSize
rand.Read(disk[start:end])
return disk[start:end]
}
var fileData []byte
for off := blkNum * mockBMBlkSize; off < (blkNum+1)*mockBMBlkSize; off += 4 {
curBlkNum := blkNums.next()
copy(disk[off:off+4], binary.Marshal(nil, binary.LittleEndian, curBlkNum))
fileData = append(fileData, writeFileDataToBlock(disk, curBlkNum, height-1, blkNums)...)
}
return fileData
}
// getMockBMFileFize gets the size of the mock block map file which is used for
// testing.
func getMockBMFileFize() uint32 {
return uint32(numDirectBlks*getCoverage(uint64(mockBMBlkSize), 0) + getCoverage(uint64(mockBMBlkSize), 1) + getCoverage(uint64(mockBMBlkSize), 2) + getCoverage(uint64(mockBMBlkSize), 3))
}