package sqlite3
import "github.com/tetratelabs/wazero/api"
func newConn(module api.Module) *Conn {
getFun := func(name string) api.Function {
f := module.ExportedFunction(name)
if f == nil {
panic(noFuncErr + errorString(name))
}
return f
}
global := module.ExportedGlobal("malloc_destructor")
if global == nil {
panic(noGlobalErr + "malloc_destructor")
}
destructor := uint32(global.Get())
if destructor == 0 {
panic(noGlobalErr + "malloc_destructor")
}
destructor, ok := module.Memory().ReadUint32Le(destructor)
if !ok {
panic(noGlobalErr + "malloc_destructor")
}
return &Conn{
module: module,
memory: module.Memory(),
api: sqliteAPI{
malloc: getFun("malloc"),
free: getFun("free"),
destructor: uint64(destructor),
errcode: getFun("sqlite3_errcode"),
errstr: getFun("sqlite3_errstr"),
errmsg: getFun("sqlite3_errmsg"),
erroff: getFun("sqlite3_error_offset"),
open: getFun("sqlite3_open_v2"),
close: getFun("sqlite3_close"),
prepare: getFun("sqlite3_prepare_v3"),
finalize: getFun("sqlite3_finalize"),
reset: getFun("sqlite3_reset"),
step: getFun("sqlite3_step"),
exec: getFun("sqlite3_exec"),
clearBindings: getFun("sqlite3_clear_bindings"),
bindInteger: getFun("sqlite3_bind_int64"),
bindFloat: getFun("sqlite3_bind_double"),
bindText: getFun("sqlite3_bind_text64"),
bindBlob: getFun("sqlite3_bind_blob64"),
bindZeroBlob: getFun("sqlite3_bind_zeroblob64"),
bindNull: getFun("sqlite3_bind_null"),
columnInteger: getFun("sqlite3_column_int64"),
columnFloat: getFun("sqlite3_column_double"),
columnText: getFun("sqlite3_column_text"),
columnBlob: getFun("sqlite3_column_blob"),
columnBytes: getFun("sqlite3_column_bytes"),
columnType: getFun("sqlite3_column_type"),
},
}
}
type sqliteAPI struct {
malloc api.Function
free api.Function
destructor uint64
errcode api.Function
errstr api.Function
errmsg api.Function
erroff api.Function
open api.Function
close api.Function
prepare api.Function
finalize api.Function
reset api.Function
step api.Function
exec api.Function
clearBindings api.Function
bindInteger api.Function
bindFloat api.Function
bindText api.Function
bindBlob api.Function
bindZeroBlob api.Function
bindNull api.Function
columnInteger api.Function
columnFloat api.Function
columnText api.Function
columnBlob api.Function
columnBytes api.Function
columnType api.Function
}
package sqlite3
import (
"context"
"os"
"sync"
"sync/atomic"
"github.com/tetratelabs/wazero"
)
// Configure SQLite.
var (
Binary []byte // Binary to load.
Path string // Path to load the binary from.
)
var (
once sync.Once
wasm wazero.Runtime
module wazero.CompiledModule
counter atomic.Uint64
)
func compile() {
ctx := context.Background()
wasm = wazero.NewRuntime(ctx)
if err := vfsInstantiate(ctx, wasm); err != nil {
panic(err)
}
if Binary == nil && Path != "" {
if bin, err := os.ReadFile(Path); err != nil {
panic(err)
} else {
Binary = bin
}
}
if m, err := wasm.CompileModule(ctx, Binary); err != nil {
panic(err)
} else {
module = m
}
}
package sqlite3
import (
"bytes"
"context"
"math"
"strconv"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
type Conn struct {
ctx context.Context
handle uint32
module api.Module
memory api.Memory
api sqliteAPI
}
func Open(filename string) (conn *Conn, err error) {
return OpenFlags(filename, OPEN_READWRITE|OPEN_CREATE)
}
func OpenFlags(filename string, flags OpenFlag) (conn *Conn, err error) {
once.Do(compile)
ctx := context.Background()
cfg := wazero.NewModuleConfig().
WithName("sqlite3-" + strconv.FormatUint(counter.Add(1), 10))
module, err := wasm.InstantiateModule(ctx, module, cfg)
if err != nil {
return nil, err
}
defer func() {
if conn == nil {
module.Close(ctx)
}
}()
c := newConn(module)
c.ctx = context.Background()
namePtr := c.newString(filename)
connPtr := c.new(ptrlen)
defer c.free(namePtr)
defer c.free(connPtr)
r, err := c.api.open.Call(c.ctx, uint64(namePtr), uint64(connPtr), uint64(flags), 0)
if err != nil {
return nil, err
}
c.handle, _ = c.memory.ReadUint32Le(connPtr)
if err := c.error(r[0]); err != nil {
return nil, err
}
return c, nil
}
func (c *Conn) Close() error {
r, err := c.api.close.Call(c.ctx, uint64(c.handle))
if err != nil {
return err
}
if err := c.error(r[0]); err != nil {
return err
}
return c.module.Close(c.ctx)
}
func (c *Conn) Exec(sql string) error {
sqlPtr := c.newString(sql)
defer c.free(sqlPtr)
r, err := c.api.exec.Call(c.ctx, uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
if err != nil {
return err
}
return c.error(r[0])
}
func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
return c.PrepareFlags(sql, 0)
}
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
sqlPtr := c.newString(sql)
stmtPtr := c.new(ptrlen)
tailPtr := c.new(ptrlen)
defer c.free(sqlPtr)
defer c.free(stmtPtr)
defer c.free(tailPtr)
r, err := c.api.prepare.Call(c.ctx, uint64(c.handle),
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
uint64(stmtPtr), uint64(tailPtr))
if err != nil {
return nil, "", err
}
stmt = &Stmt{c: c}
stmt.handle, _ = c.memory.ReadUint32Le(stmtPtr)
i, _ := c.memory.ReadUint32Le(tailPtr)
tail = sql[i-sqlPtr:]
if err := c.error(r[0]); err != nil {
return nil, "", err
}
if stmt.handle == 0 {
return nil, "", nil
}
return
}
func (c *Conn) error(rc uint64) error {
if rc == _OK {
return nil
}
err := Error{
Code: ErrorCode(rc),
ExtendedCode: ExtendedErrorCode(rc),
}
if err.Code == NOMEM || err.ExtendedCode == IOERR_NOMEM {
panic(oomErr)
}
var r []uint64
// Do this first, sqlite3_errmsg is guaranteed to never change the value of the error code.
r, _ = c.api.errmsg.Call(c.ctx, uint64(c.handle))
if r != nil {
err.msg = c.getString(uint32(r[0]), 512)
}
r, _ = c.api.errstr.Call(c.ctx, rc)
if r != nil {
err.str = c.getString(uint32(r[0]), 512)
}
if err.msg == err.str {
err.msg = ""
}
return &err
}
func (c *Conn) free(ptr uint32) {
if ptr == 0 {
return
}
_, err := c.api.free.Call(c.ctx, uint64(ptr))
if err != nil {
panic(err)
}
}
func (c *Conn) new(len uint32) uint32 {
r, err := c.api.malloc.Call(c.ctx, uint64(len))
if err != nil {
panic(err)
}
ptr := uint32(r[0])
if ptr == 0 || ptr >= c.memory.Size() {
panic(oomErr)
}
return ptr
}
func (c *Conn) newBytes(s []byte) uint32 {
if s == nil {
return 0
}
siz := uint32(len(s))
ptr := c.new(siz)
mem, ok := c.memory.Read(ptr, siz)
if !ok {
c.api.free.Call(c.ctx, uint64(ptr))
panic(rangeErr)
}
copy(mem, s)
return ptr
}
func (c *Conn) newString(s string) uint32 {
siz := uint32(len(s) + 1)
ptr := c.new(siz)
mem, ok := c.memory.Read(ptr, siz)
if !ok {
c.api.free.Call(c.ctx, uint64(ptr))
panic(rangeErr)
}
mem[len(s)] = 0
copy(mem, s)
return ptr
}
func (c *Conn) getString(ptr, maxlen uint32) string {
return getString(c.memory, ptr, maxlen)
}
func getString(memory api.Memory, ptr, maxlen uint32) string {
if ptr == 0 {
panic(nilErr)
}
switch maxlen {
case 0:
return ""
case math.MaxUint32:
//
default:
maxlen = maxlen + 1
}
mem, ok := memory.Read(ptr, maxlen)
if !ok {
mem, ok = memory.Read(ptr, memory.Size()-ptr)
if !ok {
panic(rangeErr)
}
}
if i := bytes.IndexByte(mem, 0); i < 0 {
panic(noNulErr)
} else {
return string(mem[:i])
}
}
package sqlite3
import (
"strconv"
"strings"
)
type Error struct {
Code ErrorCode
ExtendedCode ExtendedErrorCode
str string
msg string
}
func (e *Error) Error() string {
var b strings.Builder
b.WriteString("sqlite3: ")
if e.str != "" {
b.WriteString(e.str)
} else {
b.WriteString(strconv.Itoa(int(e.Code)))
}
if e.msg != "" {
b.WriteByte(':')
b.WriteByte(' ')
b.WriteString(e.msg)
}
return b.String()
}
type errorString string
func (e errorString) Error() string { return string(e) }
const (
nilErr = errorString("sqlite3: invalid memory address or null pointer dereference")
oomErr = errorString("sqlite3: out of memory")
rangeErr = errorString("sqlite3: index out of range")
noNulErr = errorString("sqlite3: missing NUL terminator")
noGlobalErr = errorString("sqlite3: could not find global: ")
noFuncErr = errorString("sqlite3: could not find function: ")
assertErr = errorString("sqlite3: assertion failed")
)
package sqlite3
import (
"math"
)
type Stmt struct {
c *Conn
handle uint32
err error
}
func (s *Stmt) Close() error {
r, err := s.c.api.finalize.Call(s.c.ctx, uint64(s.handle))
if err != nil {
return err
}
s.handle = 0
return s.c.error(r[0])
}
func (s *Stmt) Reset() error {
r, err := s.c.api.reset.Call(s.c.ctx, uint64(s.handle))
if err != nil {
return err
}
return s.c.error(r[0])
}
func (s *Stmt) Step() bool {
r, err := s.c.api.step.Call(s.c.ctx, uint64(s.handle))
if err != nil {
s.err = err
return false
}
if r[0] == _ROW {
return true
}
if r[0] == _DONE {
s.err = nil
} else {
s.err = s.c.error(r[0])
}
return false
}
func (s *Stmt) Err() error {
return s.err
}
func (s *Stmt) BindBool(param int, value bool) error {
if value {
return s.BindInt64(param, 1)
}
return s.BindInt64(param, 0)
}
func (s *Stmt) BindInt(param int, value int) error {
return s.BindInt64(param, int64(value))
}
func (s *Stmt) BindInt64(param int, value int64) error {
r, err := s.c.api.bindInteger.Call(s.c.ctx,
uint64(s.handle), uint64(param), uint64(value))
if err != nil {
return err
}
return s.c.error(r[0])
}
func (s *Stmt) BindFloat(param int, value float64) error {
r, err := s.c.api.bindFloat.Call(s.c.ctx,
uint64(s.handle), uint64(param), math.Float64bits(value))
if err != nil {
return err
}
return s.c.error(r[0])
}
func (s *Stmt) BindText(param int, value string) error {
ptr := s.c.newString(value)
r, err := s.c.api.bindText.Call(s.c.ctx,
uint64(s.handle), uint64(param),
uint64(ptr), uint64(len(value)),
s.c.api.destructor, _UTF8)
if err != nil {
return err
}
return s.c.error(r[0])
}
func (s *Stmt) BindBlob(param int, value []byte) error {
ptr := s.c.newBytes(value)
r, err := s.c.api.bindBlob.Call(s.c.ctx,
uint64(s.handle), uint64(param),
uint64(ptr), uint64(len(value)),
s.c.api.destructor)
if err != nil {
return err
}
return s.c.error(r[0])
}
func (s *Stmt) BindNull(param int) error {
r, err := s.c.api.bindNull.Call(s.c.ctx,
uint64(s.handle), uint64(param))
if err != nil {
return err
}
return s.c.error(r[0])
}
func (s *Stmt) ColumnBool(col int) bool {
if i := s.ColumnInt64(col); i != 0 {
return true
}
return false
}
func (s *Stmt) ColumnInt(col int) int {
return int(s.ColumnInt64(col))
}
func (s *Stmt) ColumnInt64(col int) int64 {
r, err := s.c.api.columnInteger.Call(s.c.ctx,
uint64(s.handle), uint64(col))
if err != nil {
panic(err)
}
return int64(r[0])
}
func (s *Stmt) ColumnFloat(col int) float64 {
r, err := s.c.api.columnInteger.Call(s.c.ctx,
uint64(s.handle), uint64(col))
if err != nil {
panic(err)
}
return math.Float64frombits(r[0])
}
func (s *Stmt) ColumnText(col int) string {
r, err := s.c.api.columnText.Call(s.c.ctx,
uint64(s.handle), uint64(col))
if err != nil {
panic(err)
}
ptr := uint32(r[0])
if ptr == 0 {
r, err = s.c.api.errcode.Call(s.c.ctx, uint64(s.handle))
if err != nil {
panic(err)
}
s.err = s.c.error(r[0])
return ""
}
r, err = s.c.api.columnBytes.Call(s.c.ctx,
uint64(s.handle), uint64(col))
if err != nil {
panic(err)
}
mem, ok := s.c.memory.Read(ptr, uint32(r[0]))
if !ok {
panic(rangeErr)
}
return string(mem)
}
func (s *Stmt) ColumnBlob(col int, buf []byte) []byte {
r, err := s.c.api.columnBlob.Call(s.c.ctx,
uint64(s.handle), uint64(col))
if err != nil {
panic(err)
}
ptr := uint32(r[0])
if ptr == 0 {
r, err = s.c.api.errcode.Call(s.c.ctx, uint64(s.handle))
if err != nil {
panic(err)
}
s.err = s.c.error(r[0])
return nil
}
r, err = s.c.api.columnBytes.Call(s.c.ctx,
uint64(s.handle), uint64(col))
if err != nil {
panic(err)
}
mem, ok := s.c.memory.Read(ptr, uint32(r[0]))
if !ok {
panic(rangeErr)
}
return append(buf[0:0], mem...)
}
package sqlite3
import (
"context"
"errors"
"io"
"io/fs"
"math/rand"
"os"
"path/filepath"
"syscall"
"time"
"github.com/ncruces/julianday"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/sys"
)
func vfsInstantiate(ctx context.Context, r wazero.Runtime) (err error) {
wasi := r.NewHostModuleBuilder("wasi_snapshot_preview1")
wasi.NewFunctionBuilder().WithFunc(vfsExit).Export("proc_exit")
_, err = wasi.Instantiate(ctx)
if err != nil {
return err
}
env := r.NewHostModuleBuilder("env")
env.NewFunctionBuilder().WithFunc(vfsLocaltime).Export("go_localtime")
env.NewFunctionBuilder().WithFunc(vfsRandomness).Export("go_randomness")
env.NewFunctionBuilder().WithFunc(vfsSleep).Export("go_sleep")
env.NewFunctionBuilder().WithFunc(vfsCurrentTime).Export("go_current_time")
env.NewFunctionBuilder().WithFunc(vfsCurrentTime64).Export("go_current_time_64")
env.NewFunctionBuilder().WithFunc(vfsFullPathname).Export("go_full_pathname")
env.NewFunctionBuilder().WithFunc(vfsDelete).Export("go_delete")
env.NewFunctionBuilder().WithFunc(vfsAccess).Export("go_access")
env.NewFunctionBuilder().WithFunc(vfsOpen).Export("go_open")
env.NewFunctionBuilder().WithFunc(vfsClose).Export("go_close")
env.NewFunctionBuilder().WithFunc(vfsRead).Export("go_read")
env.NewFunctionBuilder().WithFunc(vfsWrite).Export("go_write")
env.NewFunctionBuilder().WithFunc(vfsTruncate).Export("go_truncate")
env.NewFunctionBuilder().WithFunc(vfsSync).Export("go_sync")
env.NewFunctionBuilder().WithFunc(vfsFileSize).Export("go_file_size")
env.NewFunctionBuilder().WithFunc(vfsLock).Export("go_lock")
env.NewFunctionBuilder().WithFunc(vfsUnlock).Export("go_unlock")
env.NewFunctionBuilder().WithFunc(vfsCheckReservedLock).Export("go_check_reserved_lock")
_, err = env.Instantiate(ctx)
return err
}
func vfsExit(ctx context.Context, mod api.Module, exitCode uint32) {
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)
// Prevent any code from executing after this function.
panic(sys.NewExitError(mod.Name(), exitCode))
}
func vfsLocaltime(ctx context.Context, mod api.Module, t uint64, pTm uint32) uint32 {
tm := time.Unix(int64(t), 0)
var isdst int
if tm.IsDST() {
isdst = 1
}
if pTm == 0 {
panic(nilErr)
}
// https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
if mem := mod.Memory(); true &&
mem.WriteUint32Le(pTm+0*ptrlen, uint32(tm.Second())) &&
mem.WriteUint32Le(pTm+1*ptrlen, uint32(tm.Minute())) &&
mem.WriteUint32Le(pTm+2*ptrlen, uint32(tm.Hour())) &&
mem.WriteUint32Le(pTm+3*ptrlen, uint32(tm.Day())) &&
mem.WriteUint32Le(pTm+4*ptrlen, uint32(tm.Month()-time.January)) &&
mem.WriteUint32Le(pTm+5*ptrlen, uint32(tm.Year()-1900)) &&
mem.WriteUint32Le(pTm+6*ptrlen, uint32(tm.Weekday()-time.Sunday)) &&
mem.WriteUint32Le(pTm+7*ptrlen, uint32(tm.YearDay()-1)) &&
mem.WriteUint32Le(pTm+8*ptrlen, uint32(isdst)) {
return _OK
}
panic(rangeErr)
}
func vfsRandomness(ctx context.Context, mod api.Module, pVfs, nByte, zByte uint32) uint32 {
if zByte == 0 {
panic(nilErr)
}
mem, ok := mod.Memory().Read(zByte, nByte)
if !ok {
panic(rangeErr)
}
n, _ := rand.Read(mem)
return uint32(n)
}
func vfsSleep(ctx context.Context, pVfs, nMicro uint32) uint32 {
time.Sleep(time.Duration(nMicro) * time.Microsecond)
return _OK
}
func vfsCurrentTime(ctx context.Context, mod api.Module, pVfs, prNow uint32) uint32 {
day := julianday.Float(time.Now())
if prNow == 0 {
panic(nilErr)
}
if ok := mod.Memory().WriteFloat64Le(prNow, day); !ok {
panic(rangeErr)
}
return _OK
}
func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) uint32 {
day, nsec := julianday.Date(time.Now())
msec := day*86_400_000 + nsec/1_000_000
if piNow == 0 {
panic(nilErr)
}
if ok := mod.Memory().WriteUint64Le(piNow, uint64(msec)); !ok {
panic(rangeErr)
}
return _OK
}
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) uint32 {
rel := getString(mod.Memory(), zRelative, _MAX_PATHNAME)
abs, err := filepath.Abs(rel)
if err != nil {
return uint32(IOERR)
}
// Consider either using [filepath.EvalSymlinks] to canonicalize the path (as the Unix VFS does).
// Or using [os.Readlink] to resolve a symbolic link (as the Unix VFS did).
// This might be buggy on Windows (the Windows VFS doesn't try).
siz := uint32(len(abs) + 1)
if siz > nFull {
return uint32(CANTOPEN_FULLPATH)
}
if zFull == 0 {
panic(nilErr)
}
mem, ok := mod.Memory().Read(zFull, siz)
if !ok {
panic(rangeErr)
}
mem[len(abs)] = 0
copy(mem, abs)
return _OK
}
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) uint32 {
path := getString(mod.Memory(), zPath, _MAX_PATHNAME)
err := os.Remove(path)
if errors.Is(err, fs.ErrNotExist) {
return _OK
}
if err != nil {
return uint32(IOERR_DELETE)
}
if syncDir != 0 {
f, err := os.Open(filepath.Dir(path))
if err == nil {
err = f.Sync()
f.Close()
}
if err != nil {
return uint32(IOERR_DELETE)
}
}
return _OK
}
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags AccessFlag, pResOut uint32) uint32 {
// Consider using [syscall.Access] for [ACCESS_READWRITE]/[ACCESS_READ]
// (as the Unix VFS does).
path := getString(mod.Memory(), zPath, _MAX_PATHNAME)
fi, err := os.Stat(path)
var res uint32
switch {
case flags == ACCESS_EXISTS:
switch {
case err == nil:
res = 1
case errors.Is(err, fs.ErrNotExist):
res = 0
default:
return uint32(IOERR_ACCESS)
}
case err == nil:
var want fs.FileMode = syscall.S_IRUSR
if flags == ACCESS_READWRITE {
want |= syscall.S_IWUSR
}
if fi.IsDir() {
want |= syscall.S_IXUSR
}
if fi.Mode()&want == want {
res = 1
} else {
res = 0
}
case errors.Is(err, fs.ErrPermission):
res = 0
default:
return uint32(IOERR_ACCESS)
}
if pResOut == 0 {
panic(nilErr)
}
if ok := mod.Memory().WriteUint32Le(pResOut, res); !ok {
panic(rangeErr)
}
return _OK
}
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zName, pFile uint32, flags OpenFlag, pOutFlags uint32) uint32 {
var oflags int
if flags&OPEN_EXCLUSIVE != 0 {
oflags |= os.O_EXCL
}
if flags&OPEN_CREATE != 0 {
oflags |= os.O_CREATE
}
if flags&OPEN_READONLY != 0 {
oflags |= os.O_RDONLY
}
if flags&OPEN_READWRITE != 0 {
oflags |= os.O_RDWR
}
var err error
var file *os.File
if zName == 0 {
file, err = os.CreateTemp("", "*.db")
} else {
name := getString(mod.Memory(), zName, _MAX_PATHNAME)
file, err = os.OpenFile(name, oflags, 0600)
}
if err != nil {
return uint32(CANTOPEN)
}
if flags&OPEN_DELETEONCLOSE != 0 {
deleteOnClose(file)
}
info, err := file.Stat()
if err != nil {
return uint32(CANTOPEN)
}
if info.IsDir() {
return uint32(CANTOPEN_ISDIR)
}
id := vfsGetOpenFileID(file, info)
vfsFilePtr{mod, pFile}.SetID(id).SetLock(_NO_LOCK)
if pOutFlags == 0 {
return _OK
}
if ok := mod.Memory().WriteUint32Le(pOutFlags, uint32(flags)); !ok {
panic(rangeErr)
}
return _OK
}
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) uint32 {
id := vfsFilePtr{mod, pFile}.ID()
err := vfsReleaseOpenFile(id)
if err != nil {
return uint32(IOERR_CLOSE)
}
return _OK
}
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst uint64) uint32 {
if zBuf == 0 {
panic(nilErr)
}
buf, ok := mod.Memory().Read(zBuf, iAmt)
if !ok {
panic(rangeErr)
}
file := vfsFilePtr{mod, pFile}.OSFile()
n, err := file.ReadAt(buf, int64(iOfst))
if n == int(iAmt) {
return _OK
}
if n == 0 && err != io.EOF {
return uint32(IOERR_READ)
}
for i := range buf[n:] {
buf[i] = 0
}
return uint32(IOERR_SHORT_READ)
}
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst uint64) uint32 {
if zBuf == 0 {
panic(nilErr)
}
buf, ok := mod.Memory().Read(zBuf, iAmt)
if !ok {
panic(rangeErr)
}
file := vfsFilePtr{mod, pFile}.OSFile()
_, err := file.WriteAt(buf, int64(iOfst))
if err != nil {
return uint32(IOERR_WRITE)
}
return _OK
}
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte uint64) uint32 {
file := vfsFilePtr{mod, pFile}.OSFile()
err := file.Truncate(int64(nByte))
if err != nil {
return uint32(IOERR_TRUNCATE)
}
return _OK
}
func vfsSync(ctx context.Context, mod api.Module, pFile, flags uint32) uint32 {
file := vfsFilePtr{mod, pFile}.OSFile()
err := file.Sync()
if err != nil {
return uint32(IOERR_FSYNC)
}
return _OK
}
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) uint32 {
// This uses [file.Seek] because we don't care about the offset for reading/writing.
// But consider using [file.Stat] instead (as other VFSes do).
file := vfsFilePtr{mod, pFile}.OSFile()
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return uint32(IOERR_SEEK)
}
if pSize == 0 {
panic(nilErr)
}
if ok := mod.Memory().WriteUint64Le(pSize, uint64(off)); !ok {
panic(rangeErr)
}
return _OK
}
package sqlite3
import (
"os"
"sync"
"github.com/tetratelabs/wazero/api"
)
type vfsOpenFile struct {
file *os.File
info os.FileInfo
nref int
shared int
vfsLocker
}
var (
vfsOpenFiles []*vfsOpenFile
vfsOpenFilesMtx sync.Mutex
)
func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 {
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
// Reuse an already opened file.
for id, of := range vfsOpenFiles {
if of == nil {
continue
}
if os.SameFile(info, of.info) {
of.nref++
_ = file.Close()
return uint32(id)
}
}
of := &vfsOpenFile{
file: file,
info: info,
nref: 1,
vfsLocker: &vfsFileLocker{file, _NO_LOCK},
}
// Find an empty slot.
for id, ptr := range vfsOpenFiles {
if ptr == nil {
vfsOpenFiles[id] = of
return uint32(id)
}
}
// Add a new slot.
id := len(vfsOpenFiles)
vfsOpenFiles = append(vfsOpenFiles, of)
return uint32(id)
}
func vfsReleaseOpenFile(id uint32) error {
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[id]
if of.nref--; of.nref > 0 {
return nil
}
err := of.file.Close()
vfsOpenFiles[id] = nil
return err
}
type vfsFilePtr struct {
api.Module
ptr uint32
}
func (p vfsFilePtr) OSFile() *os.File {
id := p.ID()
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
return vfsOpenFiles[id].file
}
func (p vfsFilePtr) ID() uint32 {
if p.ptr == 0 {
panic(nilErr)
}
id, ok := p.Memory().ReadUint32Le(p.ptr + ptrlen)
if !ok {
panic(rangeErr)
}
return id
}
func (p vfsFilePtr) Lock() vfsLockState {
if p.ptr == 0 {
panic(nilErr)
}
lk, ok := p.Memory().ReadUint32Le(p.ptr + 2*ptrlen)
if !ok {
panic(rangeErr)
}
return vfsLockState(lk)
}
func (p vfsFilePtr) SetID(id uint32) vfsFilePtr {
if p.ptr == 0 {
panic(nilErr)
}
if ok := p.Memory().WriteUint32Le(p.ptr+ptrlen, id); !ok {
panic(rangeErr)
}
return p
}
func (p vfsFilePtr) SetLock(lock vfsLockState) vfsFilePtr {
if p.ptr == 0 {
panic(nilErr)
}
if ok := p.Memory().WriteUint32Le(p.ptr+2*ptrlen, uint32(lock)); !ok {
panic(rangeErr)
}
return p
}
package sqlite3
import (
"context"
"os"
"github.com/tetratelabs/wazero/api"
)
const (
// No locks are held on the database.
// The database may be neither read nor written.
// Any internally cached data is considered suspect and subject to
// verification against the database file before being used.
// Other processes can read or write the database as their own locking
// states permit.
// This is the default state.
_NO_LOCK = 0
// The database may be read but not written.
// Any number of processes can hold SHARED locks at the same time,
// hence there can be many simultaneous readers.
// But no other thread or process is allowed to write to the database file
// while one or more SHARED locks are active.
_SHARED_LOCK = 1
// A RESERVED lock means that the process is planning on writing to the
// database file at some point in the future but that it is currently just
// reading from the file.
// Only a single RESERVED lock may be active at one time,
// though multiple SHARED locks can coexist with a single RESERVED lock.
// RESERVED differs from PENDING in that new SHARED locks can be acquired
// while there is a RESERVED lock.
_RESERVED_LOCK = 2
// A PENDING lock means that the process holding the lock wants to write to
// the database as soon as possible and is just waiting on all current
// SHARED locks to clear so that it can get an EXCLUSIVE lock.
// No new SHARED locks are permitted against the database if a PENDING lock
// is active, though existing SHARED locks are allowed to continue.
_PENDING_LOCK = 3
// An EXCLUSIVE lock is needed in order to write to the database file.
// Only one EXCLUSIVE lock is allowed on the file and no other locks of any
// kind are allowed to coexist with an EXCLUSIVE lock.
// In order to maximize concurrency, SQLite works to minimize the amount of
// time that EXCLUSIVE locks are held.
_EXCLUSIVE_LOCK = 4
_PENDING_BYTE = 0x40000000
_RESERVED_BYTE = (_PENDING_BYTE + 1)
_SHARED_FIRST = (_PENDING_BYTE + 2)
_SHARED_SIZE = 510
)
type (
vfsLockState uint32
xErrorCode = ExtendedErrorCode
)
type vfsLocker interface {
LockState() vfsLockState
LockShared() xErrorCode // UNLOCKED -> SHARED
LockReserved() xErrorCode // SHARED -> RESERVED
LockPending() xErrorCode // SHARED|RESERVED -> PENDING
LockExclusive() xErrorCode // PENDING -> EXCLUSIVE
DowngradeLock() xErrorCode // SHARED <- EXCLUSIVE|PENDING|RESERVED
Unlock() xErrorCode // UNLOCKED <- EXCLUSIVE|PENDING|RESERVED|SHARED
CheckReservedLock() (bool, xErrorCode)
}
type vfsFileLocker struct {
*os.File
state vfsLockState
}
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
if assert && (eLock == _NO_LOCK || eLock == _PENDING_LOCK) {
panic(assertErr + " [d4oxww]")
}
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
// If we already have an equal or more restrictive lock, do nothing.
if cLock >= eLock {
return _OK
}
if assert {
switch {
case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
// We never move from unlocked to anything higher than shared lock.
panic(assertErr + " [pfa77m]")
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
// A shared lock is always held when a reserved lock is requested.
panic(assertErr + " [5cfmsp]")
}
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := of.LockState()
// If some other connection has a lock that precludes the requested lock, return BUSY.
if cLock != fLock && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) {
return uint32(BUSY)
}
if eLock == _EXCLUSIVE_LOCK && of.shared > 1 {
// We are trying for an exclusive lock but another connection in this
// same process is still holding a shared lock.
return uint32(BUSY)
}
// If a SHARED lock is requested, and some other connection has a SHARED or RESERVED lock,
// then increment the reference count and return OK.
if eLock == _SHARED_LOCK && (fLock == _SHARED_LOCK || fLock == _RESERVED_LOCK) {
if assert && !(cLock == _NO_LOCK && of.shared > 0) {
panic(assertErr + " [k7coz6]")
}
ptr.SetLock(_SHARED_LOCK)
of.shared++
return _OK
}
// If control gets to this point, then actually go ahead and make
// operating system calls for the specified lock.
switch eLock {
case _SHARED_LOCK:
if assert && !(fLock == _NO_LOCK && of.shared == 0) {
panic(assertErr + " [jsyttq]")
}
if rc := of.LockShared(); rc != _OK {
return uint32(rc)
}
of.shared = 1
ptr.SetLock(_SHARED_LOCK)
return _OK
case _RESERVED_LOCK:
if rc := of.LockReserved(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_RESERVED_LOCK)
return _OK
case _EXCLUSIVE_LOCK:
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if cLock < _PENDING_LOCK {
if rc := of.LockPending(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_PENDING_LOCK)
}
if rc := of.LockExclusive(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_EXCLUSIVE_LOCK)
return _OK
default:
panic(assertErr + " [56ng2l]")
}
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
if assert && (eLock != _NO_LOCK && eLock != _SHARED_LOCK) {
panic(assertErr + " [7i4jw3]")
}
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
// If we don't have a more restrictive lock, do nothing.
if cLock <= eLock {
return _OK
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := of.LockState()
if assert && of.shared <= 0 {
panic(assertErr + " [2bhkwg]")
}
if cLock > _SHARED_LOCK {
if assert && cLock != fLock {
panic(assertErr + " [6pmjqf]")
}
if eLock == _SHARED_LOCK {
if rc := of.DowngradeLock(); rc != _OK {
// In theory, the downgrade to a SHARED cannot fail because another
// process is holding an incompatible lock. If it does, this
// indicates that the other process is not following the locking
// protocol. If this happens, return IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer.
return uint32(IOERR_RDLOCK)
}
ptr.SetLock(_SHARED_LOCK)
return _OK
}
}
if assert && eLock != _NO_LOCK {
panic(assertErr + " [gilo9p]")
}
// Decrement the shared lock counter. Release the file lock
// only when all connections have released the lock.
switch {
case of.shared > 1:
ptr.SetLock(_NO_LOCK)
of.shared--
return _OK
case of.shared == 1:
if rc := of.Unlock(); rc != _OK {
return uint32(IOERR_UNLOCK)
}
ptr.SetLock(_NO_LOCK)
of.shared = 0
return _OK
default:
panic(assertErr + " [50gw51]")
}
}
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
if assert && cLock > _SHARED_LOCK {
panic(assertErr + " [zarygt]")
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
locked, rc := of.CheckReservedLock()
if rc != _OK {
return uint32(IOERR_CHECKRESERVEDLOCK)
}
var res uint32
if locked {
res = 1
}
if pResOut == 0 {
panic(nilErr)
}
if ok := mod.Memory().WriteUint32Le(pResOut, res); !ok {
panic(rangeErr)
}
return _OK
}
package sqlite3
type vfsNoopLocker struct {
state vfsLockState
}
var _ vfsLocker = &vfsNoopLocker{}
func (l *vfsNoopLocker) LockState() vfsLockState {
return l.state
}
func (l *vfsNoopLocker) LockShared() xErrorCode {
if assert && !(l.state == _NO_LOCK) {
panic(assertErr + " [wz9dcw]")
}
l.state = _SHARED_LOCK
return _OK
}
func (l *vfsNoopLocker) LockReserved() xErrorCode {
if assert && !(l.state == _SHARED_LOCK) {
panic(assertErr + " [m9hcil]")
}
l.state = _RESERVED_LOCK
return _OK
}
func (l *vfsNoopLocker) LockPending() xErrorCode {
if assert && !(l.state == _SHARED_LOCK || l.state == _RESERVED_LOCK) {
panic(assertErr + " [wx8nk2]")
}
l.state = _PENDING_LOCK
return _OK
}
func (l *vfsNoopLocker) LockExclusive() xErrorCode {
if assert && !(l.state == _PENDING_LOCK) {
panic(assertErr + " [84nbax]")
}
l.state = _EXCLUSIVE_LOCK
return _OK
}
func (l *vfsNoopLocker) DowngradeLock() xErrorCode {
if assert && !(l.state > _SHARED_LOCK) {
panic(assertErr + " [je31i3]")
}
l.state = _SHARED_LOCK
return _OK
}
func (l *vfsNoopLocker) Unlock() xErrorCode {
if assert && !(l.state > _NO_LOCK) {
panic(assertErr + " [m6e9w5]")
}
l.state = _NO_LOCK
return _OK
}
func (l *vfsNoopLocker) CheckReservedLock() (bool, xErrorCode) {
if l.state >= _RESERVED_LOCK {
return true, _OK
}
return false, _OK
}
//go:build unix
package sqlite3
import (
"os"
"runtime"
"syscall"
)
func deleteOnClose(f *os.File) {
_ = os.Remove(f.Name())
}
func (l *vfsFileLocker) LockState() vfsLockState {
return l.state
}
func (l *vfsFileLocker) LockShared() xErrorCode {
// A PENDING lock is needed before acquiring a SHARED lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _PENDING_BYTE,
Len: 1,
}) {
return IOERR_LOCK
}
// Acquire the SHARED lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
}) {
return IOERR_LOCK
}
l.state = _SHARED_LOCK
// Relese the PENDING lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: _PENDING_BYTE,
Len: 1,
}) {
return IOERR_UNLOCK
}
return _OK
}
func (l *vfsFileLocker) LockReserved() xErrorCode {
// Acquire the RESERVED lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _RESERVED_BYTE,
Len: 1,
}) {
return IOERR_LOCK
}
l.state = _RESERVED_LOCK
return _OK
}
func (l *vfsFileLocker) LockPending() xErrorCode {
// Acquire the PENDING lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _PENDING_BYTE,
Len: 1,
}) {
return IOERR_LOCK
}
l.state = _PENDING_LOCK
return _OK
}
func (l *vfsFileLocker) LockExclusive() xErrorCode {
// Acquire the EXCLUSIVE lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
}) {
return IOERR_LOCK
}
l.state = _EXCLUSIVE_LOCK
return _OK
}
func (l *vfsFileLocker) DowngradeLock() xErrorCode {
// Downgrade to a SHARED lock.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
}) {
return IOERR_RDLOCK
}
l.state = _SHARED_LOCK
// Release the PENDING and RESERVED locks.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: _PENDING_BYTE,
Len: 2,
}) {
return IOERR_UNLOCK
}
return _OK
}
func (l *vfsFileLocker) Unlock() xErrorCode {
// Release all locks.
if !l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
}) {
return IOERR_UNLOCK
}
l.state = _NO_LOCK
return _OK
}
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
if l.state >= _RESERVED_LOCK {
return true, _OK
}
// Test all write locks.
lock := syscall.Flock_t{
Type: syscall.F_RDLCK,
}
if !l.fcntlGetLock(&lock) {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type == syscall.F_UNLCK, _OK
}
func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) bool {
F_GETLK := syscall.F_GETLK
if runtime.GOOS == "linux" {
F_GETLK = 36 // F_OFD_GETLK
}
return syscall.FcntlFlock(l.Fd(), F_GETLK, lock) == nil
}
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) bool {
F_SETLK := syscall.F_SETLK
if runtime.GOOS == "linux" {
F_SETLK = 37 // F_OFD_SETLK
}
return syscall.FcntlFlock(l.Fd(), F_SETLK, lock) == nil
}