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
|
package guts_cli
// Low level access to pages / data-structures of the bbolt file.
import (
"errors"
"fmt"
"io"
"os"
"go.etcd.io/bbolt/internal/common"
)
var (
// ErrCorrupt is returned when a checking a data file finds errors.
ErrCorrupt = errors.New("invalid value")
)
// ReadPage reads Page info & full Page data from a path.
// This is not transactionally safe.
func ReadPage(path string, pageID uint64) (*common.Page, []byte, error) {
// Find Page size.
pageSize, hwm, err := ReadPageAndHWMSize(path)
if err != nil {
return nil, nil, fmt.Errorf("read Page size: %s", err)
}
// Open database file.
f, err := os.Open(path)
if err != nil {
return nil, nil, err
}
defer f.Close()
// Read one block into buffer.
buf := make([]byte, pageSize)
if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
return nil, nil, err
} else if n != len(buf) {
return nil, nil, io.ErrUnexpectedEOF
}
// Determine total number of blocks.
p := common.LoadPage(buf)
if p.Id() != common.Pgid(pageID) {
return nil, nil, fmt.Errorf("error: %w due to unexpected Page id: %d != %d", ErrCorrupt, p.Id(), pageID)
}
overflowN := p.Overflow()
if overflowN >= uint32(hwm)-3 { // we exclude 2 Meta pages and the current Page.
return nil, nil, fmt.Errorf("error: %w, Page claims to have %d overflow pages (>=hwm=%d). Interrupting to avoid risky OOM", ErrCorrupt, overflowN, hwm)
}
if overflowN == 0 {
return p, buf, nil
}
// Re-read entire Page (with overflow) into buffer.
buf = make([]byte, (uint64(overflowN)+1)*pageSize)
if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
return nil, nil, err
} else if n != len(buf) {
return nil, nil, io.ErrUnexpectedEOF
}
p = common.LoadPage(buf)
if p.Id() != common.Pgid(pageID) {
return nil, nil, fmt.Errorf("error: %w due to unexpected Page id: %d != %d", ErrCorrupt, p.Id(), pageID)
}
return p, buf, nil
}
func WritePage(path string, pageBuf []byte) error {
page := common.LoadPage(pageBuf)
pageSize, _, err := ReadPageAndHWMSize(path)
if err != nil {
return err
}
expectedLen := pageSize * (uint64(page.Overflow()) + 1)
if expectedLen != uint64(len(pageBuf)) {
return fmt.Errorf("WritePage: len(buf):%d != pageSize*(overflow+1):%d", len(pageBuf), expectedLen)
}
f, err := os.OpenFile(path, os.O_WRONLY, 0)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteAt(pageBuf, int64(page.Id())*int64(pageSize))
return err
}
// ReadPageAndHWMSize reads Page size and HWM (id of the last+1 Page).
// This is not transactionally safe.
func ReadPageAndHWMSize(path string) (uint64, common.Pgid, error) {
// Open database file.
f, err := os.Open(path)
if err != nil {
return 0, 0, err
}
defer f.Close()
// Read 4KB chunk.
buf := make([]byte, 4096)
if _, err := io.ReadFull(f, buf); err != nil {
return 0, 0, err
}
// Read Page size from metadata.
m := common.LoadPageMeta(buf)
if m.Magic() != common.Magic {
return 0, 0, fmt.Errorf("the Meta Page has wrong (unexpected) magic")
}
return uint64(m.PageSize()), common.Pgid(m.Pgid()), nil
}
// GetRootPage returns the root-page (according to the most recent transaction).
func GetRootPage(path string) (root common.Pgid, activeMeta common.Pgid, err error) {
m, id, err := GetActiveMetaPage(path)
if err != nil {
return 0, id, err
}
return m.RootBucket().RootPage(), id, nil
}
// GetActiveMetaPage returns the active meta page and its page ID (0 or 1).
func GetActiveMetaPage(path string) (*common.Meta, common.Pgid, error) {
_, buf0, err0 := ReadPage(path, 0)
if err0 != nil {
return nil, 0, err0
}
m0 := common.LoadPageMeta(buf0)
_, buf1, err1 := ReadPage(path, 1)
if err1 != nil {
return nil, 1, err1
}
m1 := common.LoadPageMeta(buf1)
if m0.Txid() < m1.Txid() {
return m1, 1, nil
} else {
return m0, 0, nil
}
}
|