160 lines
5.0 KiB
Go
160 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)
|
|
regFile := regularFile{
|
|
inode: inode{
|
|
fs: &filesystem{
|
|
dev: bytes.NewReader(mockDisk),
|
|
},
|
|
diskInode: &disklayout.InodeNew{
|
|
InodeOld: disklayout.InodeOld{
|
|
SizeLo: getMockBMFileFize(),
|
|
},
|
|
},
|
|
blkSize: uint64(mockBMBlkSize),
|
|
},
|
|
}
|
|
|
|
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)...)
|
|
|
|
copy(regFile.inode.diskInode.Data(), data)
|
|
|
|
mockFile, err := newBlockMapFile(regFile)
|
|
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))
|
|
}
|