2024-10-25 13:49:06 +01:00
|
|
|
package vfs
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
_ "embed"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
|
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
|
|
|
|
|
|
|
|
"github.com/ncruces/go-sqlite3/internal/util"
|
|
|
|
|
"github.com/ncruces/go-sqlite3/util/sql3util"
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func cksmWrapFile(file File, flags OpenFlag) File {
|
|
|
|
|
// Checksum only main databases.
|
|
|
|
|
if flags&OPEN_MAIN_DB == 0 {
|
2024-10-25 13:49:06 +01:00
|
|
|
return file
|
|
|
|
|
}
|
2025-08-16 00:57:58 +01:00
|
|
|
return &cksmFile{File: file}
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type cksmFile struct {
|
|
|
|
|
File
|
|
|
|
|
verifyCksm bool
|
2025-08-16 00:57:58 +01:00
|
|
|
computeCksm bool
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) ReadAt(p []byte, off int64) (n int, err error) {
|
2024-10-25 13:49:06 +01:00
|
|
|
n, err = c.File.ReadAt(p, off)
|
2025-02-24 12:59:50 +00:00
|
|
|
p = p[:n]
|
2024-10-25 13:49:06 +01:00
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
if isHeader(p, off) {
|
2025-02-24 12:59:50 +00:00
|
|
|
c.init((*[100]byte)(p))
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify checksums.
|
2025-08-16 00:57:58 +01:00
|
|
|
if c.verifyCksm && sql3util.ValidPageSize(len(p)) {
|
2024-10-25 13:49:06 +01:00
|
|
|
cksm1 := cksmCompute(p[:len(p)-8])
|
|
|
|
|
cksm2 := *(*[8]byte)(p[len(p)-8:])
|
|
|
|
|
if cksm1 != cksm2 {
|
|
|
|
|
return 0, _IOERR_DATA
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) WriteAt(p []byte, off int64) (n int, err error) {
|
|
|
|
|
if isHeader(p, off) {
|
2025-02-24 12:59:50 +00:00
|
|
|
c.init((*[100]byte)(p))
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute checksums.
|
2025-08-16 00:57:58 +01:00
|
|
|
if c.computeCksm && sql3util.ValidPageSize(len(p)) {
|
2024-10-25 13:49:06 +01:00
|
|
|
*(*[8]byte)(p[len(p)-8:]) = cksmCompute(p[:len(p)-8])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c.File.WriteAt(p, off)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) Pragma(name string, value string) (string, error) {
|
2024-10-25 13:49:06 +01:00
|
|
|
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":
|
2025-08-16 00:57:58 +01:00
|
|
|
if c.computeCksm && value != "" {
|
2024-10-25 13:49:06 +01:00
|
|
|
// Do not allow page size changes on a checksum database.
|
2025-08-16 00:57:58 +01:00
|
|
|
return "", nil
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "", _NOTFOUND
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) DeviceCharacteristics() DeviceCharacteristic {
|
2025-01-21 01:42:57 +00:00
|
|
|
ret := c.File.DeviceCharacteristics()
|
2024-11-26 11:39:31 +00:00
|
|
|
if c.verifyCksm {
|
2025-01-21 01:42:57 +00:00
|
|
|
ret &^= IOCAP_SUBPAGE_READ
|
2024-11-26 11:39:31 +00:00
|
|
|
}
|
2025-01-21 01:42:57 +00:00
|
|
|
return ret
|
2024-11-26 11:39:31 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg ptr_t) _ErrorCode {
|
|
|
|
|
if op == _FCNTL_PRAGMA {
|
2025-03-25 12:45:27 +00:00
|
|
|
rc := vfsFileControlImpl(ctx, mod, c, op, pArg)
|
|
|
|
|
if rc != _NOTFOUND {
|
|
|
|
|
return rc
|
|
|
|
|
}
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
return vfsFileControlImpl(ctx, mod, c.File, op, pArg)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) init(header *[100]byte) {
|
|
|
|
|
if r := header[20] == 8; r != c.computeCksm {
|
|
|
|
|
c.computeCksm = r
|
|
|
|
|
c.verifyCksm = r
|
2025-02-24 12:59:50 +00:00
|
|
|
}
|
2024-10-25 13:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:57:58 +01:00
|
|
|
func (c *cksmFile) SharedMemory() SharedMemory {
|
|
|
|
|
if f, ok := c.File.(FileSharedMemory); ok {
|
|
|
|
|
return f.SharedMemory()
|
2025-03-25 12:45:27 +00:00
|
|
|
}
|
2025-08-16 00:57:58 +01:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *cksmFile) Unwrap() File {
|
|
|
|
|
return c.File
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isHeader(p []byte, off int64) bool {
|
|
|
|
|
return off == 0 && len(p) >= 100 && bytes.HasPrefix(p, []byte("SQLite format 3\000"))
|
2025-03-25 12:45:27 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-25 13:49:06 +01:00
|
|
|
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
|
|
|
|
|
}
|