-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
499 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Go `cksmvfs` SQLite VFS | ||
|
||
This package wraps an SQLite VFS to help detect database corruption. | ||
|
||
The `"cksmvfs"` VFS wraps the default SQLite 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](https://sqlite.org/cksumvfs.html). | ||
|
||
> [!IMPORTANT] | ||
> [Checksums](https://en.wikipedia.org/wiki/Checksum) | ||
> are meant to protect against _silent data corruption_ (bit rot). | ||
> They do not offer _authenticity_ (i.e. protect against _forgery_), | ||
> nor prevent _silent loss of durability_. | ||
> Checkpoint WAL mode databases to improve durabiliy. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package cksmvfs_test | ||
|
||
import ( | ||
_ "embed" | ||
"log" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/ncruces/go-sqlite3" | ||
"github.com/ncruces/go-sqlite3/driver" | ||
_ "github.com/ncruces/go-sqlite3/embed" | ||
_ "github.com/ncruces/go-sqlite3/internal/testcfg" | ||
"github.com/ncruces/go-sqlite3/util/ioutil" | ||
"github.com/ncruces/go-sqlite3/vfs" | ||
"github.com/ncruces/go-sqlite3/vfs/cksmvfs" | ||
"github.com/ncruces/go-sqlite3/vfs/memdb" | ||
"github.com/ncruces/go-sqlite3/vfs/readervfs" | ||
) | ||
|
||
//go:embed testdata/cksm.db | ||
var cksmDB string | ||
|
||
func Test_fileformat(t *testing.T) { | ||
readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(cksmDB))) | ||
vfs.Register("rcksm", cksmvfs.Wrap(vfs.Find("reader"))) | ||
|
||
db, err := driver.Open("file:test.db?vfs=rcksm") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer db.Close() | ||
|
||
var enabled bool | ||
err = db.QueryRow(`PRAGMA checksum_verification`).Scan(&enabled) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !enabled { | ||
t.Error("want true") | ||
} | ||
|
||
db.SetMaxIdleConns(0) // Clears the page cache. | ||
|
||
_, err = db.Exec(`PRAGMA integrity_check`) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
//go:embed testdata/test.db | ||
var testDB []byte | ||
|
||
func Test_enable(t *testing.T) { | ||
memdb.Create("nockpt.db", testDB) | ||
vfs.Register("mcksm", cksmvfs.Wrap(vfs.Find("memdb"))) | ||
|
||
db, err := driver.Open("file:/nockpt.db?vfs=mcksm", | ||
func(db *sqlite3.Conn) error { | ||
return cksmvfs.EnableChecksums(db, "") | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer db.Close() | ||
|
||
var enabled bool | ||
err = db.QueryRow(`PRAGMA checksum_verification`).Scan(&enabled) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !enabled { | ||
t.Error("want true") | ||
} | ||
|
||
db.SetMaxIdleConns(0) // Clears the page cache. | ||
|
||
_, err = db.Exec(`PRAGMA integrity_check`) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func Test_new(t *testing.T) { | ||
if !vfs.SupportsFileLocking { | ||
t.Skip("skipping without locks") | ||
} | ||
|
||
name := "file:" + | ||
filepath.ToSlash(filepath.Join(t.TempDir(), "test.db")) + | ||
"?vfs=cksmvfs&_pragma=journal_mode(wal)" | ||
|
||
db, err := driver.Open(name) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer db.Close() | ||
|
||
var enabled bool | ||
err = db.QueryRow(`PRAGMA checksum_verification`).Scan(&enabled) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !enabled { | ||
t.Error("want true") | ||
} | ||
|
||
var size int | ||
err = db.QueryRow(`PRAGMA page_size=1024`).Scan(&size) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if size != 4096 { | ||
t.Errorf("got %d, want 4096", size) | ||
} | ||
|
||
_, err = db.Exec(`CREATE TABLE users (id INT, name VARCHAR(10))`) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
_, err = db.Exec(`INSERT INTO users (id, name) VALUES (0, 'go'), (1, 'zig'), (2, 'whatever')`) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
db.SetMaxIdleConns(0) // Clears the page cache. | ||
|
||
_, err = db.Exec(`PRAGMA integrity_check`) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
Oops, something went wrong.