Files
sqlite3/vfs/cksmvfs/api.go

76 lines
1.8 KiB
Go
Raw Normal View History

2024-10-25 00:12:29 +01:00
// Package cksmvfs wraps an SQLite VFS to help detect database corruption.
//
// The "cksmvfs" [vfs.VFS] wraps the default VFS adding an 8-byte checksum
// to the end of every page in an SQLite database.
// The checksum is added as each page is written
// and verified as each page is read.
// The checksum is intended to help detect database corruption
// caused by random bit-flips in the mass storage device.
//
// This implementation is compatible with SQLite's
// [Checksum VFS Shim].
//
// [Checksum VFS Shim]: https://sqlite.org/cksumvfs.html
package cksmvfs
import (
"fmt"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/vfs"
)
func init() {
vfs.Register("cksmvfs", Wrap(vfs.Find("")))
}
// Wrap wraps a base VFS to create a checksumming VFS.
func Wrap(base vfs.VFS) vfs.VFS {
return &cksmVFS{VFS: base}
}
// EnableChecksums enables checksums on a database.
func EnableChecksums(db *sqlite3.Conn, schema string) error {
if f, ok := db.Filename("").DatabaseFile().(*cksmFile); !ok {
return fmt.Errorf("cksmvfs: incorrect type: %T", f)
}
r, err := db.FileControl(schema, sqlite3.FCNTL_RESERVE_BYTES)
if err != nil {
return err
}
if r == 8 {
// Correct value, enabled.
return nil
}
if r == 0 {
// Default value, enable.
_, err = db.FileControl(schema, sqlite3.FCNTL_RESERVE_BYTES, 8)
if err != nil {
return err
}
r, err = db.FileControl(schema, sqlite3.FCNTL_RESERVE_BYTES)
if err != nil {
return err
}
}
if r != 8 {
// Invalid value.
return fmt.Errorf("cksmvfs: reserve bytes must be 8, is: %d", r)
}
// VACUUM the database.
if schema != "" {
err = db.Exec(`VACUUM ` + sqlite3.QuoteIdentifier(schema))
} else {
err = db.Exec(`VACUUM`)
}
if err != nil {
return err
}
// Checkpoint the WAL.
_, _, err = db.WALCheckpoint(schema, sqlite3.CHECKPOINT_RESTART)
return err
}