|
| 1 | +package bbolt_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "math/rand" |
| 6 | + "testing" |
| 7 | + |
| 8 | + "github.com/stretchr/testify/require" |
| 9 | + |
| 10 | + "go.etcd.io/bbolt" |
| 11 | + "go.etcd.io/bbolt/internal/btesting" |
| 12 | + "go.etcd.io/bbolt/internal/common" |
| 13 | + "go.etcd.io/bbolt/internal/guts_cli" |
| 14 | +) |
| 15 | + |
| 16 | +func TestTx_Check_CorruptPage(t *testing.T) { |
| 17 | + t.Log("Creating db file.") |
| 18 | + db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: 4096}) |
| 19 | + |
| 20 | + // Each page can hold roughly 20 key/values pair, so 100 such |
| 21 | + // key/value pairs will consume about 5 leaf pages. |
| 22 | + err := db.Fill([]byte("data"), 1, 100, |
| 23 | + func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) }, |
| 24 | + func(tx int, k int) []byte { return make([]byte, 100) }, |
| 25 | + ) |
| 26 | + require.NoError(t, err) |
| 27 | + |
| 28 | + t.Log("Corrupting random leaf page.") |
| 29 | + victimPageId, validPageIds := corruptRandomLeafPage(t, db.DB) |
| 30 | + |
| 31 | + t.Log("Running consistency check.") |
| 32 | + vErr := db.View(func(tx *bbolt.Tx) error { |
| 33 | + var cErrs []error |
| 34 | + |
| 35 | + t.Log("Check corrupted page.") |
| 36 | + errChan := tx.Check(bbolt.WithPageId(uint(victimPageId))) |
| 37 | + for cErr := range errChan { |
| 38 | + cErrs = append(cErrs, cErr) |
| 39 | + } |
| 40 | + require.Greater(t, len(cErrs), 0) |
| 41 | + |
| 42 | + t.Log("Check valid pages.") |
| 43 | + cErrs = cErrs[:0] |
| 44 | + for _, pgId := range validPageIds { |
| 45 | + errChan = tx.Check(bbolt.WithPageId(uint(pgId))) |
| 46 | + for cErr := range errChan { |
| 47 | + cErrs = append(cErrs, cErr) |
| 48 | + } |
| 49 | + require.Equal(t, 0, len(cErrs)) |
| 50 | + } |
| 51 | + return nil |
| 52 | + }) |
| 53 | + require.NoError(t, vErr) |
| 54 | + t.Log("All check passed") |
| 55 | + |
| 56 | + // Manually close the db, otherwise the PostTestCleanup will |
| 57 | + // check the db again and accordingly fail the test. |
| 58 | + db.MustClose() |
| 59 | +} |
| 60 | + |
| 61 | +// corruptRandomLeafPage corrupts one random leaf page. |
| 62 | +func corruptRandomLeafPage(t testing.TB, db *bbolt.DB) (victimPageId common.Pgid, validPageIds []common.Pgid) { |
| 63 | + victimPageId, validPageIds = pickupRandomLeafPage(t, db) |
| 64 | + victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId)) |
| 65 | + require.NoError(t, err) |
| 66 | + require.True(t, victimPage.IsLeafPage()) |
| 67 | + require.True(t, victimPage.Count() > 1) |
| 68 | + |
| 69 | + // intentionally make the second key < the first key. |
| 70 | + element := victimPage.LeafPageElement(1) |
| 71 | + key := element.Key() |
| 72 | + key[0] = 0 |
| 73 | + |
| 74 | + // Write the corrupt page to db file. |
| 75 | + err = guts_cli.WritePage(db.Path(), victimBuf) |
| 76 | + require.NoError(t, err) |
| 77 | + return victimPageId, validPageIds |
| 78 | +} |
| 79 | + |
| 80 | +// pickupRandomLeafPage picks up a random leaf page. |
| 81 | +func pickupRandomLeafPage(t testing.TB, db *bbolt.DB) (victimPageId common.Pgid, validPageIds []common.Pgid) { |
| 82 | + // Read DB's RootPage, which should be a leaf page. |
| 83 | + rootPageId, _, err := guts_cli.GetRootPage(db.Path()) |
| 84 | + require.NoError(t, err) |
| 85 | + rootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(rootPageId)) |
| 86 | + require.NoError(t, err) |
| 87 | + require.True(t, rootPage.IsLeafPage()) |
| 88 | + |
| 89 | + // The leaf page contains only one item, namely the bucket |
| 90 | + require.Equal(t, uint16(1), rootPage.Count()) |
| 91 | + lpe := rootPage.LeafPageElement(uint16(0)) |
| 92 | + require.True(t, lpe.IsBucketEntry()) |
| 93 | + |
| 94 | + // The bucket should be pointing to a branch page |
| 95 | + bucketRootPageId := lpe.Bucket().RootPage() |
| 96 | + bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId)) |
| 97 | + require.NoError(t, err) |
| 98 | + require.True(t, bucketRootPage.IsBranchPage()) |
| 99 | + |
| 100 | + // Retrieve all the leaf pages included in the branch page, and pick up random one from them. |
| 101 | + var bucketPageIds []common.Pgid |
| 102 | + for _, bpe := range bucketRootPage.BranchPageElements() { |
| 103 | + bucketPageIds = append(bucketPageIds, bpe.Pgid()) |
| 104 | + } |
| 105 | + randomIdx := rand.Intn(len(bucketPageIds)) |
| 106 | + victimPageId = bucketPageIds[randomIdx] |
| 107 | + validPageIds = append(bucketPageIds[:randomIdx], bucketPageIds[randomIdx+1:]...) |
| 108 | + return victimPageId, validPageIds |
| 109 | +} |
0 commit comments