Checksums in default VFS. (#177)

This commit is contained in:
Nuno Cruces
2024-10-25 13:49:06 +01:00
committed by GitHub
parent 75c1dbb052
commit 69e5cf706b
17 changed files with 305 additions and 467 deletions

View File

@@ -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
}

75
tests/cksm_test.go Normal file
View File

@@ -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)
}
}

View File

@@ -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:

View File

@@ -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))

View File

@@ -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
}

149
vfs/cksm.go Normal file
View File

@@ -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
}

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

Binary file not shown.

Binary file not shown.

View File

@@ -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

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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))