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  }