|
| 1 | +package bbolt_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "encoding/binary" |
| 6 | + "fmt" |
| 7 | + "math/rand" |
| 8 | + "testing" |
| 9 | + "unsafe" |
| 10 | + |
| 11 | + "github.com/stretchr/testify/require" |
| 12 | + |
| 13 | + "go.etcd.io/bbolt" |
| 14 | + "go.etcd.io/bbolt/internal/btesting" |
| 15 | + "go.etcd.io/bbolt/internal/common" |
| 16 | + "go.etcd.io/bbolt/internal/guts_cli" |
| 17 | +) |
| 18 | + |
| 19 | +func TestTx_Check_CorruptPage(t *testing.T) { |
| 20 | + bucketKey := "testBucket" |
| 21 | + |
| 22 | + t.Log("Creating db file.") |
| 23 | + db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: pageSize}) |
| 24 | + defer func() { |
| 25 | + require.NoError(t, db.Close()) |
| 26 | + }() |
| 27 | + |
| 28 | + uErr := db.Update(func(tx *bbolt.Tx) error { |
| 29 | + t.Logf("Creating bucket '%v'.", bucketKey) |
| 30 | + b, bErr := tx.CreateBucketIfNotExists([]byte(bucketKey)) |
| 31 | + require.NoError(t, bErr) |
| 32 | + t.Logf("Generating random data in bucket '%v'.", bucketKey) |
| 33 | + generateSampleDataInBucket(t, b, pageSize, 3) |
| 34 | + return nil |
| 35 | + }) |
| 36 | + require.NoError(t, uErr) |
| 37 | + |
| 38 | + t.Logf("Corrupting random leaf page in bucket '%v'.", bucketKey) |
| 39 | + victimPageId, validPageIds := corruptLeafPage(t, db.DB) |
| 40 | + |
| 41 | + t.Log("Running consistency check.") |
| 42 | + vErr := db.View(func(tx *bbolt.Tx) error { |
| 43 | + var cErrs []error |
| 44 | + |
| 45 | + t.Log("Check corrupted page.") |
| 46 | + errChan := tx.Check(bbolt.WithPageId(uint(victimPageId))) |
| 47 | + for cErr := range errChan { |
| 48 | + cErrs = append(cErrs, cErr) |
| 49 | + } |
| 50 | + require.Greater(t, len(cErrs), 0) |
| 51 | + |
| 52 | + t.Log("Check valid pages.") |
| 53 | + cErrs = cErrs[:0] |
| 54 | + for _, pgId := range validPageIds { |
| 55 | + errChan = tx.Check(bbolt.WithPageId(uint(pgId))) |
| 56 | + for cErr := range errChan { |
| 57 | + cErrs = append(cErrs, cErr) |
| 58 | + } |
| 59 | + require.Equal(t, 0, len(cErrs)) |
| 60 | + } |
| 61 | + return nil |
| 62 | + }) |
| 63 | + require.NoError(t, vErr) |
| 64 | +} |
| 65 | + |
| 66 | +// corruptLeafPage write an invalid leafPageElement into the victim page. |
| 67 | +func corruptLeafPage(t testing.TB, db *bbolt.DB) (victimPageId common.Pgid, validPageIds []common.Pgid) { |
| 68 | + t.Helper() |
| 69 | + victimPageId, validPageIds = findVictimPageId(t, db) |
| 70 | + victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId)) |
| 71 | + require.NoError(t, err) |
| 72 | + require.True(t, victimPage.IsLeafPage()) |
| 73 | + require.True(t, victimPage.Count() > 0) |
| 74 | + // Dumping random bytes in victim page for corruption. |
| 75 | + copy(victimBuf[32:], generateCorruptionBytes(t)) |
| 76 | + // Write the corrupt page to db file. |
| 77 | + err = guts_cli.WritePage(db.Path(), victimBuf) |
| 78 | + require.NoError(t, err) |
| 79 | + return victimPageId, validPageIds |
| 80 | +} |
| 81 | + |
| 82 | +// findVictimPageId finds all the leaf pages of a bucket and picks a random leaf page as a victim to be corrupted. |
| 83 | +func findVictimPageId(t testing.TB, db *bbolt.DB) (victimPageId common.Pgid, validPageIds []common.Pgid) { |
| 84 | + t.Helper() |
| 85 | + // Read DB's RootPage. |
| 86 | + rootPageId, _, err := guts_cli.GetRootPage(db.Path()) |
| 87 | + require.NoError(t, err) |
| 88 | + rootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(rootPageId)) |
| 89 | + require.NoError(t, err) |
| 90 | + require.True(t, rootPage.IsLeafPage()) |
| 91 | + require.Equal(t, 1, len(rootPage.LeafPageElements())) |
| 92 | + // Find Bucket's RootPage. |
| 93 | + lpe := rootPage.LeafPageElement(uint16(0)) |
| 94 | + require.Equal(t, uint32(common.BranchPageFlag), lpe.Flags()) |
| 95 | + k := lpe.Key() |
| 96 | + require.Equal(t, "testBucket", string(k)) |
| 97 | + bucketRootPageId := lpe.Bucket().RootPage() |
| 98 | + // Read Bucket's RootPage. |
| 99 | + bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId)) |
| 100 | + require.NoError(t, err) |
| 101 | + require.Equal(t, uint16(common.BranchPageFlag), bucketRootPage.Flags()) |
| 102 | + // Retrieve Bucket's PageIds |
| 103 | + var bucketPageIds []common.Pgid |
| 104 | + for _, bpe := range bucketRootPage.BranchPageElements() { |
| 105 | + bucketPageIds = append(bucketPageIds, bpe.Pgid()) |
| 106 | + } |
| 107 | + randomIdx := rand.Intn(len(bucketPageIds)) |
| 108 | + victimPageId = bucketPageIds[randomIdx] |
| 109 | + validPageIds = append(bucketPageIds[:randomIdx], bucketPageIds[randomIdx+1:]...) |
| 110 | + return victimPageId, validPageIds |
| 111 | +} |
| 112 | + |
| 113 | +// generateSampleDataInBucket fill in sample data into given bucket to create the given |
| 114 | +// number of leafPages. To control the number of leafPages, sample data are generated in order. |
| 115 | +func generateSampleDataInBucket(t testing.TB, bk *bbolt.Bucket, pageSize int, lPages int) { |
| 116 | + t.Helper() |
| 117 | + maxBytesInPage := int(bk.FillPercent * float64(pageSize)) |
| 118 | + currentKey := 1 |
| 119 | + currentVal := 100 |
| 120 | + for i := 0; i < lPages; i++ { |
| 121 | + currentSize := common.PageHeaderSize |
| 122 | + for { |
| 123 | + err := bk.Put([]byte(fmt.Sprintf("key_%d", currentKey)), []byte(fmt.Sprintf("val_%d", currentVal))) |
| 124 | + require.NoError(t, err) |
| 125 | + currentSize += common.LeafPageElementSize + unsafe.Sizeof(currentKey) + unsafe.Sizeof(currentVal) |
| 126 | + if int(currentSize) >= maxBytesInPage { |
| 127 | + break |
| 128 | + } |
| 129 | + currentKey++ |
| 130 | + currentVal++ |
| 131 | + } |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +// generateCorruptionBytes returns random bytes to corrupt a page. |
| 136 | +// It inserts a page element which violates the btree key order if no panic is expected. |
| 137 | +func generateCorruptionBytes(t testing.TB) []byte { |
| 138 | + t.Helper() |
| 139 | + invalidLPE := common.NewLeafPageElement(0, 0, 0, 0) |
| 140 | + var buf bytes.Buffer |
| 141 | + err := binary.Write(&buf, binary.BigEndian, invalidLPE) |
| 142 | + require.NoError(t, err) |
| 143 | + return buf.Bytes() |
| 144 | +} |
0 commit comments