From 69e5cf706bad50014b841db9fad0a098b51535a3 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Fri, 25 Oct 2024 13:49:06 +0100 Subject: [PATCH] Checksums in default VFS. (#177) --- config.go | 44 +++++ tests/cksm_test.go | 75 ++++++++ {vfs/cksmvfs => tests}/testdata/cksm.db | Bin vfs/README.md | 23 ++- vfs/adiantum/adiantum_test.go | 2 + vfs/api.go | 5 + vfs/cksm.go | 149 +++++++++++++++ vfs/cksmvfs/README.md | 20 -- vfs/cksmvfs/api.go | 75 -------- vfs/cksmvfs/api_test.go | 133 -------------- vfs/cksmvfs/cksmvfs.go | 234 ------------------------ vfs/cksmvfs/empty.db | Bin 4096 -> 0 bytes vfs/cksmvfs/testdata/test.db | Bin 1024 -> 0 bytes vfs/const.go | 1 + vfs/memdb/memdb_test.go | 2 + vfs/vfs.go | 7 + vfs/xts/aes_test.go | 2 + 17 files changed, 305 insertions(+), 467 deletions(-) create mode 100644 tests/cksm_test.go rename {vfs/cksmvfs => tests}/testdata/cksm.db (100%) create mode 100644 vfs/cksm.go delete mode 100644 vfs/cksmvfs/README.md delete mode 100644 vfs/cksmvfs/api.go delete mode 100644 vfs/cksmvfs/api_test.go delete mode 100644 vfs/cksmvfs/cksmvfs.go delete mode 100644 vfs/cksmvfs/empty.db delete mode 100644 vfs/cksmvfs/testdata/test.db diff --git a/config.go b/config.go index 1d155a7..cf72cbd 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package sqlite3 import ( "context" + "strconv" "github.com/tetratelabs/wazero/api" @@ -327,3 +328,46 @@ func (c *Conn) SoftHeapLimit(n int64) int64 { func (c *Conn) HardHeapLimit(n int64) int64 { return int64(c.call("sqlite3_hard_heap_limit64", uint64(n))) } + +// EnableChecksums enables checksums on a database. +// +// https://sqlite.org/cksumvfs.html +func (c *Conn) EnableChecksums(schema string) error { + r, err := c.FileControl(schema, FCNTL_RESERVE_BYTES) + if err != nil { + return err + } + if r == 8 { + // Correct value, enabled. + return nil + } + if r == 0 { + // Default value, enable. + _, err = c.FileControl(schema, FCNTL_RESERVE_BYTES, 8) + if err != nil { + return err + } + r, err = c.FileControl(schema, FCNTL_RESERVE_BYTES) + if err != nil { + return err + } + } + if r != 8 { + // Invalid value. + return util.ErrorString("sqlite3: reserve bytes must be 8, is: " + strconv.Itoa(r.(int))) + } + + // VACUUM the database. + if schema != "" { + err = c.Exec(`VACUUM ` + QuoteIdentifier(schema)) + } else { + err = c.Exec(`VACUUM`) + } + if err != nil { + return err + } + + // Checkpoint the WAL. + _, _, err = c.WALCheckpoint(schema, CHECKPOINT_RESTART) + return err +} diff --git a/tests/cksm_test.go b/tests/cksm_test.go new file mode 100644 index 0000000..7c84c38 --- /dev/null +++ b/tests/cksm_test.go @@ -0,0 +1,75 @@ +package tests + +import ( + _ "embed" + "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/memdb" + "github.com/ncruces/go-sqlite3/vfs/readervfs" +) + +//go:embed testdata/cksm.db +var cksmDB string + +func Test_fileformat(t *testing.T) { + t.Parallel() + + readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(cksmDB))) + + db, err := driver.Open("file:test.db?vfs=reader") + 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_enable(t *testing.T) { + t.Parallel() + + db, err := driver.Open(memdb.TestDB(t), + func(db *sqlite3.Conn) error { + return db.EnableChecksums("main") + }) + 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) + } +} diff --git a/vfs/cksmvfs/testdata/cksm.db b/tests/testdata/cksm.db similarity index 100% rename from vfs/cksmvfs/testdata/cksm.db rename to tests/testdata/cksm.db diff --git a/vfs/README.md b/vfs/README.md index 08447e3..7799148 100644 --- a/vfs/README.md +++ b/vfs/README.md @@ -16,12 +16,12 @@ The main differences are [file locking](#file-locking) and [WAL mode](#write-ahe POSIX advisory locks, which SQLite uses on Unix, are [broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161). -On Linux and macOS, this module uses +On Linux and macOS, this package uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html) to synchronize access to database files. OFD locks are fully compatible with POSIX advisory locks. -This module can also use +This package can also use [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2), albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`). On BSD, macOS, and illumos, BSD locks are fully compatible with POSIX advisory locks; @@ -30,7 +30,7 @@ elsewhere, they are very likely broken. BSD locks are the default on BSD and illumos, but you can opt into them with the `sqlite3_flock` build tag. -On Windows, this module uses `LockFileEx` and `UnlockFileEx`, +On Windows, this package uses `LockFileEx` and `UnlockFileEx`, like SQLite. Otherwise, file locking is not supported, and you must use @@ -46,7 +46,7 @@ to check if your build supports file locking. ### Write-Ahead Logging -On little-endian Unix, this module uses `mmap` to implement +On little-endian Unix, this package uses `mmap` to implement [shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index), like SQLite. @@ -67,9 +67,22 @@ to check if your build supports shared memory. ### Batch-Atomic Write -On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714) +On 64-bit Linux, this package supports +[batch-atomic writes](https://sqlite.org/cgi/src/technote/714) on the F2FS filesystem. +### Checksums + +This package can be [configured](https://pkg.go.dev/github.com/ncruces/go-sqlite3#Conn.EnableChecksums) +to add 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. + +The implementation is compatible with SQLite's +[Checksum VFS Shim](https://sqlite.org/cksumvfs.html). + ### Build Tags The VFS can be customized with a few build tags: diff --git a/vfs/adiantum/adiantum_test.go b/vfs/adiantum/adiantum_test.go index 6e76773..319742e 100644 --- a/vfs/adiantum/adiantum_test.go +++ b/vfs/adiantum/adiantum_test.go @@ -20,6 +20,8 @@ import ( var testDB string func Test_fileformat(t *testing.T) { + t.Parallel() + readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(testDB))) vfs.Register("radiantum", adiantum.Wrap(vfs.Find("reader"), nil)) diff --git a/vfs/api.go b/vfs/api.go index 4fa310e..330e8a2 100644 --- a/vfs/api.go +++ b/vfs/api.go @@ -186,3 +186,8 @@ type blockingSharedMemory interface { SharedMemory shmEnableBlocking(block bool) } + +type fileControl interface { + File + fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg uint32) _ErrorCode +} diff --git a/vfs/cksm.go b/vfs/cksm.go new file mode 100644 index 0000000..900fa09 --- /dev/null +++ b/vfs/cksm.go @@ -0,0 +1,149 @@ +package vfs + +import ( + "bytes" + "context" + _ "embed" + "encoding/binary" + "strconv" + + "github.com/tetratelabs/wazero/api" + + "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/go-sqlite3/util/sql3util" +) + +func cksmWrapFile(name *Filename, flags OpenFlag, file File) File { + // Checksum only main databases and WALs. + if flags&(OPEN_MAIN_DB|OPEN_WAL) == 0 { + return file + } + + cksm := cksmFile{File: file} + + if flags&OPEN_WAL != 0 { + main, _ := name.DatabaseFile().(cksmFile) + cksm.cksmFlags = main.cksmFlags + } else { + cksm.cksmFlags = new(cksmFlags) + cksm.isDB = true + } + + return cksm +} + +type cksmFile struct { + File + *cksmFlags + isDB bool +} + +type cksmFlags struct { + computeCksm bool + verifyCksm bool + inCkpt bool + pageSize int +} + +func (c cksmFile) ReadAt(p []byte, off int64) (n int, err error) { + n, err = c.File.ReadAt(p, off) + + // SQLite is reading the header of a database file. + if c.isDB && off == 0 && len(p) >= 100 && + bytes.HasPrefix(p, []byte("SQLite format 3\000")) { + c.init(p) + } + + // Verify checksums. + if c.verifyCksm && !c.inCkpt && len(p) == c.pageSize { + cksm1 := cksmCompute(p[:len(p)-8]) + cksm2 := *(*[8]byte)(p[len(p)-8:]) + if cksm1 != cksm2 { + return 0, _IOERR_DATA + } + } + return n, err +} + +func (c cksmFile) WriteAt(p []byte, off int64) (n int, err error) { + // SQLite is writing the first page of a database file. + if c.isDB && off == 0 && len(p) >= 100 && + bytes.HasPrefix(p, []byte("SQLite format 3\000")) { + c.init(p) + } + + // Compute checksums. + if c.computeCksm && !c.inCkpt && len(p) == c.pageSize { + *(*[8]byte)(p[len(p)-8:]) = cksmCompute(p[:len(p)-8]) + } + + return c.File.WriteAt(p, off) +} + +func (c cksmFile) Pragma(name string, value string) (string, error) { + switch name { + case "checksum_verification": + b, ok := sql3util.ParseBool(value) + if ok { + c.verifyCksm = b && c.computeCksm + } + if !c.verifyCksm { + return "0", nil + } + return "1", nil + + case "page_size": + if c.computeCksm { + // Do not allow page size changes on a checksum database. + return strconv.Itoa(c.pageSize), nil + } + } + return "", _NOTFOUND +} + +func (c cksmFile) fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg uint32) _ErrorCode { + switch op { + case _FCNTL_CKPT_START: + c.inCkpt = true + case _FCNTL_CKPT_DONE: + c.inCkpt = false + } + if rc := vfsFileControlImpl(ctx, mod, c, op, pArg); rc != _NOTFOUND { + return rc + } + return vfsFileControlImpl(ctx, mod, c.File, op, pArg) +} + +func (f *cksmFlags) init(header []byte) { + f.pageSize = 256 * int(binary.LittleEndian.Uint16(header[16:18])) + if r := header[20] == 8; r != f.computeCksm { + f.computeCksm = r + f.verifyCksm = r + } +} + +func cksmCompute(a []byte) (cksm [8]byte) { + var s1, s2 uint32 + for len(a) >= 8 { + s1 += binary.LittleEndian.Uint32(a[0:4]) + s2 + s2 += binary.LittleEndian.Uint32(a[4:8]) + s1 + a = a[8:] + } + if len(a) != 0 { + panic(util.AssertErr()) + } + binary.LittleEndian.PutUint32(cksm[0:4], s1) + binary.LittleEndian.PutUint32(cksm[4:8], s2) + return +} + +func (c cksmFile) SharedMemory() SharedMemory { + if f, ok := c.File.(FileSharedMemory); ok { + return f.SharedMemory() + } + return nil +} + +func (c cksmFile) Unwrap() File { + return c.File +} diff --git a/vfs/cksmvfs/README.md b/vfs/cksmvfs/README.md deleted file mode 100644 index dd4b26e..0000000 --- a/vfs/cksmvfs/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# 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. \ No newline at end of file diff --git a/vfs/cksmvfs/api.go b/vfs/cksmvfs/api.go deleted file mode 100644 index 087022f..0000000 --- a/vfs/cksmvfs/api.go +++ /dev/null @@ -1,75 +0,0 @@ -// 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 -} diff --git a/vfs/cksmvfs/api_test.go b/vfs/cksmvfs/api_test.go deleted file mode 100644 index 04d4f39..0000000 --- a/vfs/cksmvfs/api_test.go +++ /dev/null @@ -1,133 +0,0 @@ -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) - } -} diff --git a/vfs/cksmvfs/cksmvfs.go b/vfs/cksmvfs/cksmvfs.go deleted file mode 100644 index 82db696..0000000 --- a/vfs/cksmvfs/cksmvfs.go +++ /dev/null @@ -1,234 +0,0 @@ -package cksmvfs - -import ( - "bytes" - _ "embed" - "encoding/binary" - "io" - "runtime" - "strconv" - - "github.com/ncruces/go-sqlite3" - "github.com/ncruces/go-sqlite3/internal/util" - "github.com/ncruces/go-sqlite3/util/sql3util" - "github.com/ncruces/go-sqlite3/util/vfsutil" - "github.com/ncruces/go-sqlite3/vfs" -) - -type cksmVFS struct { - vfs.VFS -} - -func (c *cksmVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) { - // notest // OpenFilename is called instead - return nil, 0, sqlite3.CANTOPEN -} - -func (c *cksmVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) { - // Prevent accidental wrapping. - if pc, _, _, ok := runtime.Caller(1); ok { - if fn := runtime.FuncForPC(pc); fn != nil { - if fn.Name() != "github.com/ncruces/go-sqlite3/vfs.vfsOpen" { - return nil, 0, sqlite3.CANTOPEN - } - } - } - - file, flags, err = vfsutil.WrapOpenFilename(c.VFS, name, flags) - - // Checksum only main databases and WALs. - if err != nil || flags&(vfs.OPEN_MAIN_DB|vfs.OPEN_WAL) == 0 { - return file, flags, err - } - - cksm := cksmFile{File: file} - - if flags&vfs.OPEN_WAL != 0 { - main, _ := name.DatabaseFile().(*cksmFile) - cksm.cksmFlags = main.cksmFlags - } else { - cksm.isDB = true - cksm.cksmFlags = new(cksmFlags) - } - const createDB = vfs.OPEN_CREATE | vfs.OPEN_READWRITE | vfs.OPEN_MAIN_DB - cksm.createDB = flags&createDB == createDB - - return &cksm, flags, err -} - -type cksmFile struct { - vfs.File - *cksmFlags - isDB bool - createDB bool -} - -type cksmFlags struct { - computeCksm bool - verifyCksm bool - inCkpt bool - pageSize int -} - -//go:embed empty.db -var empty string - -func (c *cksmFile) ReadAt(p []byte, off int64) (n int, err error) { - n, err = c.File.ReadAt(p, off) - - // SQLite is trying to read from the first page of an empty database file. - // Instead, read from an empty database that had checksums enabled, - // so checksums are enabled by default. - if c.createDB && n == 0 && err == io.EOF && off < 100 { - n = copy(p, empty[off:]) - if n < len(p) { - clear(p[n:]) - } - err = nil - } - - // SQLite is reading the header of a database file. - if c.isDB && off == 0 && len(p) >= 100 && - bytes.HasPrefix(p, []byte("SQLite format 3\000")) { - c.updateFlags(p) - } - - // Verify checksums. - if c.verifyCksm && !c.inCkpt && len(p) == c.pageSize { - cksm1 := cksmCompute(p[:len(p)-8]) - cksm2 := *(*[8]byte)(p[len(p)-8:]) - if cksm1 != cksm2 { - return 0, sqlite3.IOERR_DATA - } - } - return n, err -} - -func (c *cksmFile) WriteAt(p []byte, off int64) (n int, err error) { - // SQLite is writing the first page of a database file. - if c.isDB && off == 0 && len(p) >= 100 && - bytes.HasPrefix(p, []byte("SQLite format 3\000")) { - c.updateFlags(p) - } - - // Compute checksums. - if c.computeCksm && !c.inCkpt && len(p) == c.pageSize { - *(*[8]byte)(p[len(p)-8:]) = cksmCompute(p[:len(p)-8]) - } - - return c.File.WriteAt(p, off) -} - -func (c *cksmFile) updateFlags(header []byte) { - c.pageSize = 256 * int(binary.LittleEndian.Uint16(header[16:18])) - if r := header[20] == 8; r != c.computeCksm { - c.computeCksm = r - c.verifyCksm = r - } -} - -func (c *cksmFile) CheckpointStart() { - c.inCkpt = true -} - -func (c *cksmFile) CheckpointDone() { - c.inCkpt = false -} - -func (c *cksmFile) Pragma(name string, value string) (string, error) { - switch name { - case "checksum_verification": - b, ok := sql3util.ParseBool(value) - if ok { - c.verifyCksm = b && c.computeCksm - } - if !c.verifyCksm { - return "0", nil - } - return "1", nil - - case "page_size": - if c.computeCksm { - // Do not allow page size changes on a checksum database. - return strconv.Itoa(c.pageSize), nil - } - } - return vfsutil.WrapPragma(c.File, name, value) -} - -func cksmCompute(a []byte) (cksm [8]byte) { - var s1, s2 uint32 - for len(a) >= 8 { - s1 += binary.LittleEndian.Uint32(a[0:4]) + s2 - s2 += binary.LittleEndian.Uint32(a[4:8]) + s1 - a = a[8:] - } - if len(a) != 0 { - panic(util.AssertErr()) - } - binary.LittleEndian.PutUint32(cksm[0:4], s1) - binary.LittleEndian.PutUint32(cksm[4:8], s2) - return -} - -func (c *cksmFile) Unwrap() vfs.File { - return c.File -} - -func (c *cksmFile) SharedMemory() vfs.SharedMemory { - return vfsutil.WrapSharedMemory(c.File) -} - -// Wrap optional methods. - -func (c *cksmFile) LockState() vfs.LockLevel { - return vfsutil.WrapLockState(c.File) // notest -} - -func (c *cksmFile) PersistentWAL() bool { - return vfsutil.WrapPersistentWAL(c.File) // notest -} - -func (c *cksmFile) SetPersistentWAL(keepWAL bool) { - vfsutil.WrapSetPersistentWAL(c.File, keepWAL) // notest -} - -func (c *cksmFile) PowersafeOverwrite() bool { - return vfsutil.WrapPowersafeOverwrite(c.File) // notest -} - -func (c *cksmFile) SetPowersafeOverwrite(psow bool) { - vfsutil.WrapSetPowersafeOverwrite(c.File, psow) // notest -} - -func (c *cksmFile) ChunkSize(size int) { - vfsutil.WrapChunkSize(c.File, size) // notest -} - -func (c *cksmFile) SizeHint(size int64) error { - return vfsutil.WrapSizeHint(c.File, size) // notest -} - -func (c *cksmFile) HasMoved() (bool, error) { - return vfsutil.WrapHasMoved(c.File) // notest -} - -func (c *cksmFile) Overwrite() error { - return vfsutil.WrapOverwrite(c.File) // notest -} - -func (c *cksmFile) CommitPhaseTwo() error { - return vfsutil.WrapCommitPhaseTwo(c.File) // notest -} - -func (c *cksmFile) BeginAtomicWrite() error { - return vfsutil.WrapBeginAtomicWrite(c.File) // notest -} - -func (c *cksmFile) CommitAtomicWrite() error { - return vfsutil.WrapCommitAtomicWrite(c.File) // notest -} - -func (c *cksmFile) RollbackAtomicWrite() error { - return vfsutil.WrapRollbackAtomicWrite(c.File) // notest -} diff --git a/vfs/cksmvfs/empty.db b/vfs/cksmvfs/empty.db deleted file mode 100644 index 1093329c8cfb1a46f01c07a023913b96a9e0baf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}WTQP*7lCU|@t|AO!{>KB<6_LC^UBFGv^v4^s7x zsvix3(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@RpfCj3T&5~WyqfX|02=ZO Ai2wiq diff --git a/vfs/cksmvfs/testdata/test.db b/vfs/cksmvfs/testdata/test.db deleted file mode 100644 index bd97e0053ce89a8c9523fb57a0627a59ff3d3395..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmWFz^vNtqRY=P(%1ta$FlJz4U}R))P*7lC05TyMNPz)}&jMwGC`KeUE+!L$URgIU zNHODX1|S**)C&P;MmBMAamMtL#H5_m(&E&jVlctv9OUX4;;Inh=;Y(702Wfv$V^f2 z^b65Z$V<#kRS0toa`tcx(l9j8)C9#1Gvi|h#-EHIfz&7%ej&ia%*-guRGyJol3JEp U#Ky$TDO{DA&dSKlA();I02EgyJOBUy diff --git a/vfs/const.go b/vfs/const.go index 2fc934f..e80437b 100644 --- a/vfs/const.go +++ b/vfs/const.go @@ -51,6 +51,7 @@ const ( _IOERR_BEGIN_ATOMIC _ErrorCode = util.IOERR_BEGIN_ATOMIC _IOERR_COMMIT_ATOMIC _ErrorCode = util.IOERR_COMMIT_ATOMIC _IOERR_ROLLBACK_ATOMIC _ErrorCode = util.IOERR_ROLLBACK_ATOMIC + _IOERR_DATA _ErrorCode = util.IOERR_DATA _BUSY_SNAPSHOT _ErrorCode = util.BUSY_SNAPSHOT _CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH _CANTOPEN_ISDIR _ErrorCode = util.CANTOPEN_ISDIR diff --git a/vfs/memdb/memdb_test.go b/vfs/memdb/memdb_test.go index 31dc1ce..b8fd3b4 100644 --- a/vfs/memdb/memdb_test.go +++ b/vfs/memdb/memdb_test.go @@ -13,6 +13,8 @@ import ( var walDB []byte func Test_wal(t *testing.T) { + t.Parallel() + Create("test.db", walDB) db, err := sqlite3.Open("file:/test.db?vfs=memdb") diff --git a/vfs/vfs.go b/vfs/vfs.go index 277146a..83c95d0 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -159,6 +159,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, fla if pOutFlags != 0 { util.WriteUint32(mod, pOutFlags, uint32(flags)) } + file = cksmWrapFile(name, flags, file) vfsFileRegister(ctx, mod, pFile, file) return _OK } @@ -237,7 +238,13 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) + if file, ok := file.(fileControl); ok { + return file.fileControl(ctx, mod, op, pArg) + } + return vfsFileControlImpl(ctx, mod, file, op, pArg) +} +func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _FcntlOpcode, pArg uint32) _ErrorCode { switch op { case _FCNTL_LOCKSTATE: if file, ok := file.(FileLockState); ok { diff --git a/vfs/xts/aes_test.go b/vfs/xts/aes_test.go index f412a93..880fd5e 100644 --- a/vfs/xts/aes_test.go +++ b/vfs/xts/aes_test.go @@ -20,6 +20,8 @@ import ( var testDB string func Test_fileformat(t *testing.T) { + t.Parallel() + readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(testDB))) vfs.Register("rxts", xts.Wrap(vfs.Find("reader"), nil))