diff --git a/db.go b/db.go index c43eb5c..ee3a24b 100644 --- a/db.go +++ b/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) } diff --git a/db_test.go b/db_test.go index df1aad3..e5b8c1a 100644 --- a/db_test.go +++ b/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 diff --git a/meta.go b/meta.go index cc62637..abb2a93 100644 --- a/meta.go +++ b/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() +}