github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/ext/extent_test.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 "bytes" 19 "math/rand" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 "github.com/SagerNet/gvisor/pkg/sentry/fsimpl/ext/disklayout" 25 ) 26 27 const ( 28 // mockExtentBlkSize is the mock block size used for testing. 29 // No block has more than 1 header + 4 entries. 30 mockExtentBlkSize = uint64(64) 31 ) 32 33 // The tree described below looks like: 34 // 35 // 0.{Head}[Idx][Idx] 36 // / \ 37 // / \ 38 // 1.{Head}[Ext][Ext] 2.{Head}[Idx] 39 // / | \ 40 // [Phy] [Phy, Phy] 3.{Head}[Ext] 41 // | 42 // [Phy, Phy, Phy] 43 // 44 // Legend: 45 // - Head = ExtentHeader 46 // - Idx = ExtentIdx 47 // - Ext = Extent 48 // - Phy = Physical Block 49 // 50 // Please note that ext4 might not construct extent trees looking like this. 51 // This is purely for testing the tree traversal logic. 52 var ( 53 node3 = &disklayout.ExtentNode{ 54 Header: disklayout.ExtentHeader{ 55 Magic: disklayout.ExtentMagic, 56 NumEntries: 1, 57 MaxEntries: 4, 58 Height: 0, 59 }, 60 Entries: []disklayout.ExtentEntryPair{ 61 { 62 Entry: &disklayout.Extent{ 63 FirstFileBlock: 3, 64 Length: 3, 65 StartBlockLo: 6, 66 }, 67 Node: nil, 68 }, 69 }, 70 } 71 72 node2 = &disklayout.ExtentNode{ 73 Header: disklayout.ExtentHeader{ 74 Magic: disklayout.ExtentMagic, 75 NumEntries: 1, 76 MaxEntries: 4, 77 Height: 1, 78 }, 79 Entries: []disklayout.ExtentEntryPair{ 80 { 81 Entry: &disklayout.ExtentIdx{ 82 FirstFileBlock: 3, 83 ChildBlockLo: 2, 84 }, 85 Node: node3, 86 }, 87 }, 88 } 89 90 node1 = &disklayout.ExtentNode{ 91 Header: disklayout.ExtentHeader{ 92 Magic: disklayout.ExtentMagic, 93 NumEntries: 2, 94 MaxEntries: 4, 95 Height: 0, 96 }, 97 Entries: []disklayout.ExtentEntryPair{ 98 { 99 Entry: &disklayout.Extent{ 100 FirstFileBlock: 0, 101 Length: 1, 102 StartBlockLo: 3, 103 }, 104 Node: nil, 105 }, 106 { 107 Entry: &disklayout.Extent{ 108 FirstFileBlock: 1, 109 Length: 2, 110 StartBlockLo: 4, 111 }, 112 Node: nil, 113 }, 114 }, 115 } 116 117 node0 = &disklayout.ExtentNode{ 118 Header: disklayout.ExtentHeader{ 119 Magic: disklayout.ExtentMagic, 120 NumEntries: 2, 121 MaxEntries: 4, 122 Height: 2, 123 }, 124 Entries: []disklayout.ExtentEntryPair{ 125 { 126 Entry: &disklayout.ExtentIdx{ 127 FirstFileBlock: 0, 128 ChildBlockLo: 0, 129 }, 130 Node: node1, 131 }, 132 { 133 Entry: &disklayout.ExtentIdx{ 134 FirstFileBlock: 3, 135 ChildBlockLo: 1, 136 }, 137 Node: node2, 138 }, 139 }, 140 } 141 ) 142 143 // TestExtentReader stress tests extentReader functionality. It performs random 144 // length reads from all possible positions in the extent tree. 145 func TestExtentReader(t *testing.T) { 146 mockExtentFile, want := extentTreeSetUp(t, node0) 147 n := len(want) 148 149 for from := 0; from < n; from++ { 150 got := make([]byte, n-from) 151 152 if read, err := mockExtentFile.ReadAt(got, int64(from)); err != nil { 153 t.Fatalf("file read operation from offset %d to %d only read %d bytes: %v", from, n, read, err) 154 } 155 156 if diff := cmp.Diff(got, want[from:]); diff != "" { 157 t.Fatalf("file data from offset %d to %d mismatched (-want +got):\n%s", from, n, diff) 158 } 159 } 160 } 161 162 // TestBuildExtentTree tests the extent tree building logic. 163 func TestBuildExtentTree(t *testing.T) { 164 mockExtentFile, _ := extentTreeSetUp(t, node0) 165 166 opt := cmpopts.IgnoreUnexported(disklayout.ExtentIdx{}, disklayout.ExtentHeader{}) 167 if diff := cmp.Diff(&mockExtentFile.root, node0, opt); diff != "" { 168 t.Errorf("extent tree mismatch (-want +got):\n%s", diff) 169 } 170 } 171 172 // extentTreeSetUp writes the passed extent tree to a mock disk as an extent 173 // tree. It also constucts a mock extent file with the same tree built in it. 174 // It also writes random data file data and returns it. 175 func extentTreeSetUp(t *testing.T, root *disklayout.ExtentNode) (*extentFile, []byte) { 176 t.Helper() 177 178 mockDisk := make([]byte, mockExtentBlkSize*10) 179 mockExtentFile := &extentFile{} 180 args := inodeArgs{ 181 fs: &filesystem{ 182 dev: bytes.NewReader(mockDisk), 183 }, 184 diskInode: &disklayout.InodeNew{ 185 InodeOld: disklayout.InodeOld{ 186 SizeLo: uint32(mockExtentBlkSize) * getNumPhyBlks(root), 187 }, 188 }, 189 blkSize: mockExtentBlkSize, 190 } 191 mockExtentFile.regFile.inode.init(args, &mockExtentFile.regFile) 192 193 fileData := writeTree(&mockExtentFile.regFile.inode, mockDisk, node0, mockExtentBlkSize) 194 195 if err := mockExtentFile.buildExtTree(); err != nil { 196 t.Fatalf("inode.buildExtTree failed: %v", err) 197 } 198 return mockExtentFile, fileData 199 } 200 201 // writeTree writes the tree represented by `root` to the inode and disk. It 202 // also writes random file data on disk. 203 func writeTree(in *inode, disk []byte, root *disklayout.ExtentNode, mockExtentBlkSize uint64) []byte { 204 rootData := in.diskInode.Data() 205 root.Header.MarshalBytes(rootData) 206 off := root.Header.SizeBytes() 207 for _, ep := range root.Entries { 208 ep.Entry.MarshalBytes(rootData[off:]) 209 off += ep.Entry.SizeBytes() 210 } 211 212 var fileData []byte 213 for _, ep := range root.Entries { 214 if root.Header.Height == 0 { 215 fileData = append(fileData, writeFileDataToExtent(disk, ep.Entry.(*disklayout.Extent))...) 216 } else { 217 fileData = append(fileData, writeTreeToDisk(disk, ep)...) 218 } 219 } 220 return fileData 221 } 222 223 // writeTreeToDisk is the recursive step for writeTree which writes the tree 224 // on the disk only. Also writes random file data on disk. 225 func writeTreeToDisk(disk []byte, curNode disklayout.ExtentEntryPair) []byte { 226 nodeData := disk[curNode.Entry.PhysicalBlock()*mockExtentBlkSize:] 227 curNode.Node.Header.MarshalBytes(nodeData) 228 off := curNode.Node.Header.SizeBytes() 229 for _, ep := range curNode.Node.Entries { 230 ep.Entry.MarshalBytes(nodeData[off:]) 231 off += ep.Entry.SizeBytes() 232 } 233 234 var fileData []byte 235 for _, ep := range curNode.Node.Entries { 236 if curNode.Node.Header.Height == 0 { 237 fileData = append(fileData, writeFileDataToExtent(disk, ep.Entry.(*disklayout.Extent))...) 238 } else { 239 fileData = append(fileData, writeTreeToDisk(disk, ep)...) 240 } 241 } 242 return fileData 243 } 244 245 // writeFileDataToExtent writes random bytes to the blocks on disk that the 246 // passed extent points to. 247 func writeFileDataToExtent(disk []byte, ex *disklayout.Extent) []byte { 248 phyExStartBlk := ex.PhysicalBlock() 249 phyExStartOff := phyExStartBlk * mockExtentBlkSize 250 phyExEndOff := phyExStartOff + uint64(ex.Length)*mockExtentBlkSize 251 rand.Read(disk[phyExStartOff:phyExEndOff]) 252 return disk[phyExStartOff:phyExEndOff] 253 } 254 255 // getNumPhyBlks returns the number of physical blocks covered under the node. 256 func getNumPhyBlks(node *disklayout.ExtentNode) uint32 { 257 var res uint32 258 for _, ep := range node.Entries { 259 if node.Header.Height == 0 { 260 res += uint32(ep.Entry.(*disklayout.Extent).Length) 261 } else { 262 res += getNumPhyBlks(ep.Node) 263 } 264 } 265 return res 266 }