1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
package surgeon
import (
"fmt"
"go.etcd.io/bbolt/internal/common"
"go.etcd.io/bbolt/internal/guts_cli"
)
func CopyPage(path string, srcPage common.Pgid, target common.Pgid) error {
p1, d1, err1 := guts_cli.ReadPage(path, uint64(srcPage))
if err1 != nil {
return err1
}
p1.SetId(target)
return guts_cli.WritePage(path, d1)
}
func ClearPage(path string, pgId common.Pgid) (bool, error) {
return ClearPageElements(path, pgId, 0, -1, false)
}
// ClearPageElements supports clearing elements in both branch and leaf
// pages. Note if the ${abandonFreelist} is true, the freelist may be cleaned
// in the meta pages in the following two cases, and bbolt needs to scan the
// db to reconstruct free list. It may cause some delay on next startup,
// depending on the db size.
// 1. Any branch elements are cleared;
// 2. An object saved in overflow pages is cleared;
//
// Usually ${abandonFreelist} defaults to false, it means it will not clear the
// freelist in meta pages automatically. Users will receive a warning message
// to remind them to explicitly execute `bbolt surgery abandom-freelist`
// afterwards; the first return parameter will be true in such case. But if
// the freelist isn't synced at all, no warning message will be displayed.
func ClearPageElements(path string, pgId common.Pgid, start, end int, abandonFreelist bool) (bool, error) {
// Read the page
p, buf, err := guts_cli.ReadPage(path, uint64(pgId))
if err != nil {
return false, fmt.Errorf("ReadPage failed: %w", err)
}
if !p.IsLeafPage() && !p.IsBranchPage() {
return false, fmt.Errorf("can't clear elements in %q page", p.Typ())
}
elementCnt := int(p.Count())
if elementCnt == 0 {
return false, nil
}
if start < 0 || start >= elementCnt {
return false, fmt.Errorf("the start index (%d) is out of range [0, %d)", start, elementCnt)
}
if (end < 0 || end > elementCnt) && end != -1 {
return false, fmt.Errorf("the end index (%d) is out of range [0, %d]", end, elementCnt)
}
if start > end && end != -1 {
return false, fmt.Errorf("the start index (%d) is bigger than the end index (%d)", start, end)
}
if start == end {
return false, fmt.Errorf("invalid: the start index (%d) is equal to the end index (%d)", start, end)
}
preOverflow := p.Overflow()
var (
dataWritten uint32
)
if end == int(p.Count()) || end == -1 {
inodes := common.ReadInodeFromPage(p)
inodes = inodes[:start]
p.SetCount(uint16(start))
// no need to write inode & data again, we just need to get
// the data size which will be kept.
dataWritten = common.UsedSpaceInPage(inodes, p)
} else {
inodes := common.ReadInodeFromPage(p)
inodes = append(inodes[:start], inodes[end:]...)
p.SetCount(uint16(len(inodes)))
dataWritten = common.WriteInodeToPage(inodes, p)
}
pageSize, _, err := guts_cli.ReadPageAndHWMSize(path)
if err != nil {
return false, fmt.Errorf("ReadPageAndHWMSize failed: %w", err)
}
if dataWritten%uint32(pageSize) == 0 {
p.SetOverflow(dataWritten/uint32(pageSize) - 1)
} else {
p.SetOverflow(dataWritten / uint32(pageSize))
}
datasz := pageSize * (uint64(p.Overflow()) + 1)
if err := guts_cli.WritePage(path, buf[0:datasz]); err != nil {
return false, fmt.Errorf("WritePage failed: %w", err)
}
if preOverflow != p.Overflow() || p.IsBranchPage() {
if abandonFreelist {
return false, ClearFreelist(path)
}
return true, nil
}
return false, nil
}
func ClearFreelist(path string) error {
if err := clearFreelistInMetaPage(path, 0); err != nil {
return fmt.Errorf("clearFreelist on meta page 0 failed: %w", err)
}
if err := clearFreelistInMetaPage(path, 1); err != nil {
return fmt.Errorf("clearFreelist on meta page 1 failed: %w", err)
}
return nil
}
func clearFreelistInMetaPage(path string, pageId uint64) error {
_, buf, err := guts_cli.ReadPage(path, pageId)
if err != nil {
return fmt.Errorf("ReadPage %d failed: %w", pageId, err)
}
meta := common.LoadPageMeta(buf)
meta.SetFreelist(common.PgidNoFreelist)
meta.SetChecksum(meta.Sum64())
if err := guts_cli.WritePage(path, buf); err != nil {
return fmt.Errorf("WritePage %d failed: %w", pageId, err)
}
return nil
}
// RevertMetaPage replaces the newer metadata page with the older.
// It usually means that one transaction is being lost. But frequently
// data corruption happens on the last transaction pages and the
// previous state is consistent.
func RevertMetaPage(path string) error {
_, activeMetaPage, err := guts_cli.GetRootPage(path)
if err != nil {
return err
}
if activeMetaPage == 0 {
return CopyPage(path, 1, 0)
} else {
return CopyPage(path, 0, 1)
}
}
|