mirror of https://github.com/etcd-io/bbolt.git
Add meta page checksums.
This commit adds checksums to the meta pages on every write. When the database loads, it verifies the checksums on the meta pages and returns an error if either one is corrupt. In the future, it should fallback to the previous meta page but for right now it just hard fails. This is at least preferable to opening the database and getting a random error or further corruption. Fixes #25.pull/34/head
parent
20a1479c4c
commit
ca83d17125
2
db.go
2
db.go
|
@ -105,7 +105,7 @@ func Open(path string, mode os.FileMode) (*DB, error) {
|
|||
if _, err := db.file.ReadAt(buf[:], 0); err == nil {
|
||||
m := db.pageInBuffer(buf[:], 0).meta()
|
||||
if err := m.validate(); err != nil {
|
||||
return nil, fmt.Errorf("meta error: %s", err)
|
||||
return nil, fmt.Errorf("meta0 error: %s", err)
|
||||
}
|
||||
db.pageSize = int(m.pageSize)
|
||||
}
|
||||
|
|
35
db_test.go
35
db_test.go
|
@ -117,10 +117,43 @@ func TestDBCorruptMeta0(t *testing.T) {
|
|||
|
||||
// Open the database.
|
||||
_, err = Open(path, 0666)
|
||||
assert.Equal(t, err, errors.New("meta error: invalid database"))
|
||||
assert.Equal(t, err, errors.New("meta0 error: invalid database"))
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure that a corrupt meta page checksum causes the open to fail.
|
||||
func TestDBMetaChecksumError(t *testing.T) {
|
||||
for i := 0; i < 2; i++ {
|
||||
withTempPath(func(path string) {
|
||||
db, err := Open(path, 0600)
|
||||
pageSize := db.pageSize
|
||||
db.Update(func(tx *Tx) error {
|
||||
return tx.CreateBucket("widgets")
|
||||
})
|
||||
db.Update(func(tx *Tx) error {
|
||||
return tx.CreateBucket("woojits")
|
||||
})
|
||||
db.Close()
|
||||
|
||||
// Change a single byte in the meta page.
|
||||
f, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
|
||||
f.WriteAt([]byte{1}, int64((i*pageSize)+(pageHeaderSize+12)))
|
||||
f.Sync()
|
||||
f.Close()
|
||||
|
||||
// Reopen the database.
|
||||
_, err = Open(path, 0600)
|
||||
if assert.Error(t, err) {
|
||||
if i == 0 {
|
||||
assert.Equal(t, "meta0 error: checksum error", err.Error())
|
||||
} else {
|
||||
assert.Equal(t, "meta1 error: checksum error", err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a database cannot open a transaction when it's not open.
|
||||
func TestDBTxErrDatabaseNotOpen(t *testing.T) {
|
||||
var db DB
|
||||
|
|
28
meta.go
28
meta.go
|
@ -2,6 +2,8 @@ package bolt
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const magic uint32 = 0xED0CDAED
|
||||
|
@ -13,6 +15,9 @@ var (
|
|||
// ErrVersionMismatch is returned when the data file was created with a
|
||||
// different version of Bolt.
|
||||
ErrVersionMismatch = errors.New("version mismatch")
|
||||
|
||||
// ErrChecksum is returned when either meta page checksum does not match.
|
||||
ErrChecksum = errors.New("checksum error")
|
||||
)
|
||||
|
||||
type meta struct {
|
||||
|
@ -24,11 +29,14 @@ type meta struct {
|
|||
freelist pgid
|
||||
pgid pgid
|
||||
txid txid
|
||||
checksum uint64
|
||||
}
|
||||
|
||||
// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||
func (m *meta) validate() error {
|
||||
if m.magic != magic {
|
||||
if m.checksum != 0 && m.checksum != m.sum64() {
|
||||
return ErrChecksum
|
||||
} else if m.magic != magic {
|
||||
return ErrInvalid
|
||||
} else if m.version != version {
|
||||
return ErrVersionMismatch
|
||||
|
@ -38,13 +46,7 @@ func (m *meta) validate() error {
|
|||
|
||||
// copy copies one meta object to another.
|
||||
func (m *meta) copy(dest *meta) {
|
||||
dest.magic = m.magic
|
||||
dest.version = m.version
|
||||
dest.pageSize = m.pageSize
|
||||
dest.buckets = m.buckets
|
||||
dest.freelist = m.freelist
|
||||
dest.pgid = m.pgid
|
||||
dest.txid = m.txid
|
||||
*dest = *m
|
||||
}
|
||||
|
||||
// write writes the meta onto a page.
|
||||
|
@ -53,5 +55,15 @@ func (m *meta) write(p *page) {
|
|||
p.id = pgid(m.txid % 2)
|
||||
p.flags |= metaPageFlag
|
||||
|
||||
// Calculate the checksum.
|
||||
m.checksum = m.sum64()
|
||||
|
||||
m.copy(p.meta())
|
||||
}
|
||||
|
||||
// generates the checksum for the meta.
|
||||
func (m *meta) sum64() uint64 {
|
||||
var h = fnv.New64a()
|
||||
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||
return h.Sum64()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue