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 }