201 lines
6.2 KiB
Go
201 lines
6.2 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 (
|
|
"io"
|
|
"math"
|
|
|
|
"gvisor.dev/gvisor/pkg/binary"
|
|
"gvisor.dev/gvisor/pkg/syserror"
|
|
)
|
|
|
|
const (
|
|
// numDirectBlks is the number of direct blocks in ext block map inodes.
|
|
numDirectBlks = 12
|
|
)
|
|
|
|
// blockMapFile is a type of regular file which uses direct/indirect block
|
|
// addressing to store file data. This was deprecated in ext4.
|
|
type blockMapFile struct {
|
|
regFile regularFile
|
|
|
|
// directBlks are the direct blocks numbers. The physical blocks pointed by
|
|
// these holds file data. Contains file blocks 0 to 11.
|
|
directBlks [numDirectBlks]uint32
|
|
|
|
// indirectBlk is the physical block which contains (blkSize/4) direct block
|
|
// numbers (as uint32 integers).
|
|
indirectBlk uint32
|
|
|
|
// doubleIndirectBlk is the physical block which contains (blkSize/4) indirect
|
|
// block numbers (as uint32 integers).
|
|
doubleIndirectBlk uint32
|
|
|
|
// tripleIndirectBlk is the physical block which contains (blkSize/4) doubly
|
|
// indirect block numbers (as uint32 integers).
|
|
tripleIndirectBlk uint32
|
|
|
|
// coverage at (i)th index indicates the amount of file data a node at
|
|
// height (i) covers. Height 0 is the direct block.
|
|
coverage [4]uint64
|
|
}
|
|
|
|
// Compiles only if blockMapFile implements io.ReaderAt.
|
|
var _ io.ReaderAt = (*blockMapFile)(nil)
|
|
|
|
// newBlockMapFile is the blockMapFile constructor. It initializes the file to
|
|
// physical blocks map with (at most) the first 12 (direct) blocks.
|
|
func newBlockMapFile(regFile regularFile) (*blockMapFile, error) {
|
|
file := &blockMapFile{regFile: regFile}
|
|
file.regFile.impl = file
|
|
|
|
for i := uint(0); i < 4; i++ {
|
|
file.coverage[i] = getCoverage(regFile.inode.blkSize, i)
|
|
}
|
|
|
|
blkMap := regFile.inode.diskInode.Data()
|
|
binary.Unmarshal(blkMap[:numDirectBlks*4], binary.LittleEndian, &file.directBlks)
|
|
binary.Unmarshal(blkMap[numDirectBlks*4:(numDirectBlks+1)*4], binary.LittleEndian, &file.indirectBlk)
|
|
binary.Unmarshal(blkMap[(numDirectBlks+1)*4:(numDirectBlks+2)*4], binary.LittleEndian, &file.doubleIndirectBlk)
|
|
binary.Unmarshal(blkMap[(numDirectBlks+2)*4:(numDirectBlks+3)*4], binary.LittleEndian, &file.tripleIndirectBlk)
|
|
return file, nil
|
|
}
|
|
|
|
// ReadAt implements io.ReaderAt.ReadAt.
|
|
func (f *blockMapFile) ReadAt(dst []byte, off int64) (int, error) {
|
|
if len(dst) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
if off < 0 {
|
|
return 0, syserror.EINVAL
|
|
}
|
|
|
|
offset := uint64(off)
|
|
size := f.regFile.inode.diskInode.Size()
|
|
if offset >= size {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
// dirBlksEnd is the file offset until which direct blocks cover file data.
|
|
// Direct blocks cover 0 <= file offset < dirBlksEnd.
|
|
dirBlksEnd := numDirectBlks * f.coverage[0]
|
|
|
|
// indirBlkEnd is the file offset until which the indirect block covers file
|
|
// data. The indirect block covers dirBlksEnd <= file offset < indirBlkEnd.
|
|
indirBlkEnd := dirBlksEnd + f.coverage[1]
|
|
|
|
// doubIndirBlkEnd is the file offset until which the double indirect block
|
|
// covers file data. The double indirect block covers the range
|
|
// indirBlkEnd <= file offset < doubIndirBlkEnd.
|
|
doubIndirBlkEnd := indirBlkEnd + f.coverage[2]
|
|
|
|
read := 0
|
|
toRead := len(dst)
|
|
if uint64(toRead)+offset > size {
|
|
toRead = int(size - offset)
|
|
}
|
|
for read < toRead {
|
|
var err error
|
|
var curR int
|
|
|
|
// Figure out which block to delegate the read to.
|
|
switch {
|
|
case offset < dirBlksEnd:
|
|
// Direct block.
|
|
curR, err = f.read(f.directBlks[offset/f.regFile.inode.blkSize], offset%f.regFile.inode.blkSize, 0, dst[read:])
|
|
case offset < indirBlkEnd:
|
|
// Indirect block.
|
|
curR, err = f.read(f.indirectBlk, offset-dirBlksEnd, 1, dst[read:])
|
|
case offset < doubIndirBlkEnd:
|
|
// Doubly indirect block.
|
|
curR, err = f.read(f.doubleIndirectBlk, offset-indirBlkEnd, 2, dst[read:])
|
|
default:
|
|
// Triply indirect block.
|
|
curR, err = f.read(f.tripleIndirectBlk, offset-doubIndirBlkEnd, 3, dst[read:])
|
|
}
|
|
|
|
read += curR
|
|
offset += uint64(curR)
|
|
if err != nil {
|
|
return read, err
|
|
}
|
|
}
|
|
|
|
if read < len(dst) {
|
|
return read, io.EOF
|
|
}
|
|
return read, nil
|
|
}
|
|
|
|
// read is the recursive step of the ReadAt function. It relies on knowing the
|
|
// current node's location on disk (curPhyBlk) and its height in the block map
|
|
// tree. A height of 0 shows that the current node is actually holding file
|
|
// data. relFileOff tells the offset from which we need to start to reading
|
|
// under the current node. It is completely relative to the current node.
|
|
func (f *blockMapFile) read(curPhyBlk uint32, relFileOff uint64, height uint, dst []byte) (int, error) {
|
|
curPhyBlkOff := int64(curPhyBlk) * int64(f.regFile.inode.blkSize)
|
|
if height == 0 {
|
|
toRead := int(f.regFile.inode.blkSize - relFileOff)
|
|
if len(dst) < toRead {
|
|
toRead = len(dst)
|
|
}
|
|
|
|
n, _ := f.regFile.inode.dev.ReadAt(dst[:toRead], curPhyBlkOff+int64(relFileOff))
|
|
if n < toRead {
|
|
return n, syserror.EIO
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
childCov := f.coverage[height-1]
|
|
startIdx := relFileOff / childCov
|
|
endIdx := f.regFile.inode.blkSize / 4 // This is exclusive.
|
|
wantEndIdx := (relFileOff + uint64(len(dst))) / childCov
|
|
wantEndIdx++ // Make this exclusive.
|
|
if wantEndIdx < endIdx {
|
|
endIdx = wantEndIdx
|
|
}
|
|
|
|
read := 0
|
|
curChildOff := relFileOff % childCov
|
|
for i := startIdx; i < endIdx; i++ {
|
|
var childPhyBlk uint32
|
|
err := readFromDisk(f.regFile.inode.dev, curPhyBlkOff+int64(i*4), &childPhyBlk)
|
|
if err != nil {
|
|
return read, err
|
|
}
|
|
|
|
n, err := f.read(childPhyBlk, curChildOff, height-1, dst[read:])
|
|
read += n
|
|
if err != nil {
|
|
return read, err
|
|
}
|
|
|
|
curChildOff = 0
|
|
}
|
|
|
|
return read, nil
|
|
}
|
|
|
|
// getCoverage returns the number of bytes a node at the given height covers.
|
|
// Height 0 is the file data block itself. Height 1 is the indirect block.
|
|
//
|
|
// Formula: blkSize * ((blkSize / 4)^height)
|
|
func getCoverage(blkSize uint64, height uint) uint64 {
|
|
return blkSize * uint64(math.Pow(float64(blkSize/4), float64(height)))
|
|
}
|