github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/ext/block_map_file.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  	"io"
    19  	"math"
    20  
    21  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    22  	"github.com/SagerNet/gvisor/pkg/marshal/primitive"
    23  	"github.com/SagerNet/gvisor/pkg/syserror"
    24  )
    25  
    26  const (
    27  	// numDirectBlks is the number of direct blocks in ext block map inodes.
    28  	numDirectBlks = 12
    29  )
    30  
    31  // blockMapFile is a type of regular file which uses direct/indirect block
    32  // addressing to store file data. This was deprecated in ext4.
    33  type blockMapFile struct {
    34  	regFile regularFile
    35  
    36  	// directBlks are the direct blocks numbers. The physical blocks pointed by
    37  	// these holds file data. Contains file blocks 0 to 11.
    38  	directBlks [numDirectBlks]primitive.Uint32
    39  
    40  	// indirectBlk is the physical block which contains (blkSize/4) direct block
    41  	// numbers (as uint32 integers).
    42  	indirectBlk primitive.Uint32
    43  
    44  	// doubleIndirectBlk is the physical block which contains (blkSize/4) indirect
    45  	// block numbers (as uint32 integers).
    46  	doubleIndirectBlk primitive.Uint32
    47  
    48  	// tripleIndirectBlk is the physical block which contains (blkSize/4) doubly
    49  	// indirect block numbers (as uint32 integers).
    50  	tripleIndirectBlk primitive.Uint32
    51  
    52  	// coverage at (i)th index indicates the amount of file data a node at
    53  	// height (i) covers. Height 0 is the direct block.
    54  	coverage [4]uint64
    55  }
    56  
    57  // Compiles only if blockMapFile implements io.ReaderAt.
    58  var _ io.ReaderAt = (*blockMapFile)(nil)
    59  
    60  // newBlockMapFile is the blockMapFile constructor. It initializes the file to
    61  // physical blocks map with (at most) the first 12 (direct) blocks.
    62  func newBlockMapFile(args inodeArgs) (*blockMapFile, error) {
    63  	file := &blockMapFile{}
    64  	file.regFile.impl = file
    65  	file.regFile.inode.init(args, &file.regFile)
    66  
    67  	for i := uint(0); i < 4; i++ {
    68  		file.coverage[i] = getCoverage(file.regFile.inode.blkSize, i)
    69  	}
    70  
    71  	blkMap := file.regFile.inode.diskInode.Data()
    72  	for i := 0; i < numDirectBlks; i++ {
    73  		file.directBlks[i].UnmarshalBytes(blkMap[i*4 : (i+1)*4])
    74  	}
    75  	file.indirectBlk.UnmarshalBytes(blkMap[numDirectBlks*4 : (numDirectBlks+1)*4])
    76  	file.doubleIndirectBlk.UnmarshalBytes(blkMap[(numDirectBlks+1)*4 : (numDirectBlks+2)*4])
    77  	file.tripleIndirectBlk.UnmarshalBytes(blkMap[(numDirectBlks+2)*4 : (numDirectBlks+3)*4])
    78  	return file, nil
    79  }
    80  
    81  // ReadAt implements io.ReaderAt.ReadAt.
    82  func (f *blockMapFile) ReadAt(dst []byte, off int64) (int, error) {
    83  	if len(dst) == 0 {
    84  		return 0, nil
    85  	}
    86  
    87  	if off < 0 {
    88  		return 0, linuxerr.EINVAL
    89  	}
    90  
    91  	offset := uint64(off)
    92  	size := f.regFile.inode.diskInode.Size()
    93  	if offset >= size {
    94  		return 0, io.EOF
    95  	}
    96  
    97  	// dirBlksEnd is the file offset until which direct blocks cover file data.
    98  	// Direct blocks cover 0 <= file offset < dirBlksEnd.
    99  	dirBlksEnd := numDirectBlks * f.coverage[0]
   100  
   101  	// indirBlkEnd is the file offset until which the indirect block covers file
   102  	// data. The indirect block covers dirBlksEnd <= file offset < indirBlkEnd.
   103  	indirBlkEnd := dirBlksEnd + f.coverage[1]
   104  
   105  	// doubIndirBlkEnd is the file offset until which the double indirect block
   106  	// covers file data. The double indirect block covers the range
   107  	// indirBlkEnd <= file offset < doubIndirBlkEnd.
   108  	doubIndirBlkEnd := indirBlkEnd + f.coverage[2]
   109  
   110  	read := 0
   111  	toRead := len(dst)
   112  	if uint64(toRead)+offset > size {
   113  		toRead = int(size - offset)
   114  	}
   115  	for read < toRead {
   116  		var err error
   117  		var curR int
   118  
   119  		// Figure out which block to delegate the read to.
   120  		switch {
   121  		case offset < dirBlksEnd:
   122  			// Direct block.
   123  			curR, err = f.read(uint32(f.directBlks[offset/f.regFile.inode.blkSize]), offset%f.regFile.inode.blkSize, 0, dst[read:])
   124  		case offset < indirBlkEnd:
   125  			// Indirect block.
   126  			curR, err = f.read(uint32(f.indirectBlk), offset-dirBlksEnd, 1, dst[read:])
   127  		case offset < doubIndirBlkEnd:
   128  			// Doubly indirect block.
   129  			curR, err = f.read(uint32(f.doubleIndirectBlk), offset-indirBlkEnd, 2, dst[read:])
   130  		default:
   131  			// Triply indirect block.
   132  			curR, err = f.read(uint32(f.tripleIndirectBlk), offset-doubIndirBlkEnd, 3, dst[read:])
   133  		}
   134  
   135  		read += curR
   136  		offset += uint64(curR)
   137  		if err != nil {
   138  			return read, err
   139  		}
   140  	}
   141  
   142  	if read < len(dst) {
   143  		return read, io.EOF
   144  	}
   145  	return read, nil
   146  }
   147  
   148  // read is the recursive step of the ReadAt function. It relies on knowing the
   149  // current node's location on disk (curPhyBlk) and its height in the block map
   150  // tree. A height of 0 shows that the current node is actually holding file
   151  // data. relFileOff tells the offset from which we need to start to reading
   152  // under the current node. It is completely relative to the current node.
   153  func (f *blockMapFile) read(curPhyBlk uint32, relFileOff uint64, height uint, dst []byte) (int, error) {
   154  	curPhyBlkOff := int64(curPhyBlk) * int64(f.regFile.inode.blkSize)
   155  	if height == 0 {
   156  		toRead := int(f.regFile.inode.blkSize - relFileOff)
   157  		if len(dst) < toRead {
   158  			toRead = len(dst)
   159  		}
   160  
   161  		n, _ := f.regFile.inode.fs.dev.ReadAt(dst[:toRead], curPhyBlkOff+int64(relFileOff))
   162  		if n < toRead {
   163  			return n, syserror.EIO
   164  		}
   165  		return n, nil
   166  	}
   167  
   168  	childCov := f.coverage[height-1]
   169  	startIdx := relFileOff / childCov
   170  	endIdx := f.regFile.inode.blkSize / 4 // This is exclusive.
   171  	wantEndIdx := (relFileOff + uint64(len(dst))) / childCov
   172  	wantEndIdx++ // Make this exclusive.
   173  	if wantEndIdx < endIdx {
   174  		endIdx = wantEndIdx
   175  	}
   176  
   177  	read := 0
   178  	curChildOff := relFileOff % childCov
   179  	for i := startIdx; i < endIdx; i++ {
   180  		var childPhyBlk primitive.Uint32
   181  		err := readFromDisk(f.regFile.inode.fs.dev, curPhyBlkOff+int64(i*4), &childPhyBlk)
   182  		if err != nil {
   183  			return read, err
   184  		}
   185  
   186  		n, err := f.read(uint32(childPhyBlk), curChildOff, height-1, dst[read:])
   187  		read += n
   188  		if err != nil {
   189  			return read, err
   190  		}
   191  
   192  		curChildOff = 0
   193  	}
   194  
   195  	return read, nil
   196  }
   197  
   198  // getCoverage returns the number of bytes a node at the given height covers.
   199  // Height 0 is the file data block itself. Height 1 is the indirect block.
   200  //
   201  // Formula: blkSize * ((blkSize / 4)^height)
   202  func getCoverage(blkSize uint64, height uint) uint64 {
   203  	return blkSize * uint64(math.Pow(float64(blkSize/4), float64(height)))
   204  }