summaryrefslogtreecommitdiff
path: root/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go')
-rw-r--r--vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go149
1 files changed, 149 insertions, 0 deletions
diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go b/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go
new file mode 100644
index 000000000..900fa0952
--- /dev/null
+++ b/vendor/github.com/ncruces/go-sqlite3/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
+}