diff --git a/.github/coverage.html b/.github/coverage.html
deleted file mode 100644
index efe446a..0000000
--- a/.github/coverage.html
+++ /dev/null
@@ -1,1667 +0,0 @@
-
-
-
-
-
-
-
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 := memory{module}.readUint32(uint32(global.Get()))
-
- return &Conn{
- module: module,
- memory: memory{module},
- 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 (
- "context"
- "strconv"
-
- "github.com/tetratelabs/wazero"
- "github.com/tetratelabs/wazero/api"
-)
-
-type Conn struct {
- ctx context.Context
- handle uint32
- module api.Module
- memory 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.readUint32(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.readUint32(stmtPtr)
- i := c.memory.readUint32(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(b []byte) uint32 {
- if b == nil {
- return 0
- }
-
- siz := uint32(len(b))
- ptr := c.new(siz)
- buf, ok := c.memory.read(ptr, siz)
- if !ok {
- c.api.free.Call(c.ctx, uint64(ptr))
- panic(rangeErr)
- }
-
- copy(buf, b)
- return ptr
-}
-
-func (c *Conn) newString(s string) uint32 {
- siz := uint32(len(s) + 1)
- ptr := c.new(siz)
- buf, ok := c.memory.read(ptr, siz)
- if !ok {
- c.api.free.Call(c.ctx, uint64(ptr))
- panic(rangeErr)
- }
-
- buf[len(s)] = 0
- copy(buf, s)
- return ptr
-}
-
-func (c *Conn) getString(ptr, maxlen uint32) string {
- return c.memory.readString(ptr, maxlen)
-}
-
-
-
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 (
- "bytes"
- "math"
-
- "github.com/tetratelabs/wazero/api"
-)
-
-type memory struct {
- mod api.Module
-}
-
-func (m memory) size() uint32 {
- return m.mod.Memory().Size()
-}
-
-func (m memory) read(offset, byteCount uint32) ([]byte, bool) {
- if offset == 0 {
- panic(nilErr)
- }
- return m.mod.Memory().Read(offset, byteCount)
-}
-
-func (m memory) mustRead(offset, byteCount uint32) []byte {
- buf, ok := m.read(offset, byteCount)
- if !ok {
- panic(rangeErr)
- }
- return buf
-}
-
-func (m memory) readUint32(offset uint32) uint32 {
- if offset == 0 {
- panic(nilErr)
- }
- v, ok := m.mod.Memory().ReadUint32Le(offset)
- if !ok {
- panic(rangeErr)
- }
- return v
-}
-
-func (m memory) writeUint32(offset, v uint32) {
- if offset == 0 {
- panic(nilErr)
- }
- ok := m.mod.Memory().WriteUint32Le(offset, v)
- if !ok {
- panic(rangeErr)
- }
-}
-
-func (m memory) readUint64(offset uint32) uint64 {
- if offset == 0 {
- panic(nilErr)
- }
- v, ok := m.mod.Memory().ReadUint64Le(offset)
- if !ok {
- panic(rangeErr)
- }
- return v
-}
-
-func (m memory) writeUint64(offset uint32, v uint64) {
- if offset == 0 {
- panic(nilErr)
- }
- ok := m.mod.Memory().WriteUint64Le(offset, v)
- if !ok {
- panic(rangeErr)
- }
-}
-
-func (m memory) readFloat64(offset uint32) float64 {
- return math.Float64frombits(m.readUint64(offset))
-}
-
-func (m memory) writeFloat64(offset uint32, v float64) {
- m.writeUint64(offset, math.Float64bits(v))
-}
-
-func (m memory) readString(ptr, maxlen uint32) string {
- switch maxlen {
- case 0:
- return ""
- case math.MaxUint32:
- //
- default:
- maxlen = maxlen + 1
- }
- buf, ok := m.read(ptr, maxlen)
- if !ok {
- buf = m.mustRead(ptr, m.size()-ptr)
- }
- if i := bytes.IndexByte(buf, 0); i < 0 {
- panic(noNulErr)
- } else {
- return string(buf[:i])
- }
-}
-
-
-
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 := s.c.memory.mustRead(ptr, uint32(r[0]))
- 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 := s.c.memory.mustRead(ptr, uint32(r[0]))
- return append(buf[0:0], mem...)
-}
-
-
-
package sqlite3
-
-import (
- "context"
- "errors"
- "io"
- "io/fs"
- "math/rand"
- "os"
- "path/filepath"
- "runtime"
- "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
- }
-
- // https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
- mem := memory{mod}
- mem.writeUint32(pTm+0*ptrlen, uint32(tm.Second()))
- mem.writeUint32(pTm+1*ptrlen, uint32(tm.Minute()))
- mem.writeUint32(pTm+2*ptrlen, uint32(tm.Hour()))
- mem.writeUint32(pTm+3*ptrlen, uint32(tm.Day()))
- mem.writeUint32(pTm+4*ptrlen, uint32(tm.Month()-time.January))
- mem.writeUint32(pTm+5*ptrlen, uint32(tm.Year()-1900))
- mem.writeUint32(pTm+6*ptrlen, uint32(tm.Weekday()-time.Sunday))
- mem.writeUint32(pTm+7*ptrlen, uint32(tm.YearDay()-1))
- mem.writeUint32(pTm+8*ptrlen, uint32(isdst))
- return _OK
-}
-
-func vfsRandomness(ctx context.Context, mod api.Module, pVfs, nByte, zByte uint32) uint32 {
- mem := memory{mod}.mustRead(zByte, nByte)
- 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())
- memory{mod}.writeFloat64(prNow, day)
- 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
- memory{mod}.writeUint64(piNow, uint64(msec))
- return _OK
-}
-
-func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) uint32 {
- rel := memory{mod}.readString(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)
- }
- mem := memory{mod}.mustRead(zFull, siz)
-
- mem[len(abs)] = 0
- copy(mem, abs)
- return _OK
-}
-
-func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) uint32 {
- path := memory{mod}.readString(zPath, _MAX_PATHNAME)
- err := os.Remove(path)
- if errors.Is(err, fs.ErrNotExist) {
- return _OK
- }
- if err != nil {
- return uint32(IOERR_DELETE)
- }
- if runtime.GOOS != "windows" && 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 := memory{mod}.readString(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)
- }
-
- memory{mod}.writeUint32(pResOut, res)
- 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 := memory{mod}.readString(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 {
- memory{mod}.writeUint32(pOutFlags, uint32(flags))
- }
- 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 {
- buf := memory{mod}.mustRead(zBuf, iAmt)
-
- 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 {
- buf := memory{mod}.mustRead(zBuf, iAmt)
-
- 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)
- }
-
- memory{mod}.writeUint64(pSize, uint64(off))
- 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 {
- return memory{p}.readUint32(p.ptr + ptrlen)
-}
-
-func (p vfsFilePtr) Lock() vfsLockState {
- return vfsLockState(memory{p}.readUint32(p.ptr + 2*ptrlen))
-}
-
-func (p vfsFilePtr) SetID(id uint32) vfsFilePtr {
- memory{p}.writeUint32(p.ptr+ptrlen, id)
- return p
-}
-
-func (p vfsFilePtr) SetLock(lock vfsLockState) vfsFilePtr {
- memory{p}.writeUint32(p.ptr+2*ptrlen, uint32(lock))
- 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 {
- return uint32(rc)
- }
- 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(rc)
- }
- 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
- }
- memory{mod}.writeUint32(pResOut, res)
- return _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 {
- if assert && !(l.state == _NO_LOCK) {
- panic(assertErr + " [wz9dcw]")
- }
-
- // A PENDING lock is needed before acquiring a SHARED lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_RDLCK,
- Start: _PENDING_BYTE,
- Len: 1,
- }); err != nil {
- return l.errorCode(err, IOERR_LOCK)
- }
-
- // Acquire the SHARED lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_RDLCK,
- Start: _SHARED_FIRST,
- Len: _SHARED_SIZE,
- }); err != nil {
- return l.errorCode(err, IOERR_LOCK)
- }
- l.state = _SHARED_LOCK
-
- // Relese the PENDING lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_UNLCK,
- Start: _PENDING_BYTE,
- Len: 1,
- }); err != nil {
- return IOERR_UNLOCK
- }
-
- return _OK
-}
-
-func (l *vfsFileLocker) LockReserved() xErrorCode {
- if assert && !(l.state == _SHARED_LOCK) {
- panic(assertErr + " [m9hcil]")
- }
-
- // Acquire the RESERVED lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_WRLCK,
- Start: _RESERVED_BYTE,
- Len: 1,
- }); err != nil {
- return l.errorCode(err, IOERR_LOCK)
- }
- l.state = _RESERVED_LOCK
- return _OK
-}
-
-func (l *vfsFileLocker) LockPending() xErrorCode {
- if assert && !(l.state == _SHARED_LOCK || l.state == _RESERVED_LOCK) {
- panic(assertErr + " [wx8nk2]")
- }
-
- // Acquire the PENDING lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_WRLCK,
- Start: _PENDING_BYTE,
- Len: 1,
- }); err != nil {
- return l.errorCode(err, IOERR_LOCK)
- }
- l.state = _PENDING_LOCK
- return _OK
-}
-
-func (l *vfsFileLocker) LockExclusive() xErrorCode {
- if assert && !(l.state == _PENDING_LOCK) {
- panic(assertErr + " [84nbax]")
- }
-
- // Acquire the EXCLUSIVE lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_WRLCK,
- Start: _SHARED_FIRST,
- Len: _SHARED_SIZE,
- }); err != nil {
- return l.errorCode(err, IOERR_LOCK)
- }
- l.state = _EXCLUSIVE_LOCK
- return _OK
-}
-
-func (l *vfsFileLocker) DowngradeLock() xErrorCode {
- if assert && !(l.state > _SHARED_LOCK) {
- panic(assertErr + " [je31i3]")
- }
-
- // Downgrade to a SHARED lock.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_RDLCK,
- Start: _SHARED_FIRST,
- Len: _SHARED_SIZE,
- }); err != nil {
- // 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 IOERR_RDLOCK
- }
- l.state = _SHARED_LOCK
-
- // Release the PENDING and RESERVED locks.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_UNLCK,
- Start: _PENDING_BYTE,
- Len: 2,
- }); err != nil {
- return IOERR_UNLOCK
- }
- return _OK
-}
-
-func (l *vfsFileLocker) Unlock() xErrorCode {
- // Release all locks.
- if err := l.fcntlSetLock(&syscall.Flock_t{
- Type: syscall.F_UNLCK,
- }); err != nil {
- 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) != nil {
- return false, IOERR_CHECKRESERVEDLOCK
- }
- return lock.Type == syscall.F_UNLCK, _OK
-}
-
-func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error {
- F_GETLK := syscall.F_GETLK
- if runtime.GOOS == "linux" {
- F_GETLK = 36 // F_OFD_GETLK
- }
- return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)
-}
-
-func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
- F_SETLK := syscall.F_SETLK
- if runtime.GOOS == "linux" {
- F_SETLK = 37 // F_OFD_SETLK
- }
- return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)
-}
-
-func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
- if errno, ok := err.(syscall.Errno); ok {
- switch errno {
- case syscall.EACCES:
- case syscall.EAGAIN:
- case syscall.EBUSY:
- case syscall.EINTR:
- case syscall.ENOLCK:
- case syscall.EDEADLK:
- case syscall.ETIMEDOUT:
- return xErrorCode(BUSY)
- case syscall.EPERM:
- return xErrorCode(PERM)
- }
- }
- return def
-}
-
-
-
-
-
-
diff --git a/.github/coverage.sh b/.github/coverage.sh
deleted file mode 100755
index 97d3ac7..0000000
--- a/.github/coverage.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
-
-go test ./... -coverprofile "$SCRIPT_DIR/coverage.out"
-go tool cover -html="$SCRIPT_DIR/coverage.out" -o "$SCRIPT_DIR/coverage.html"
-COVERAGE=$(go tool cover -func="$SCRIPT_DIR/coverage.out" | tail -1 | grep -Eo '\d+\.\d')
-
-echo "coverage: $COVERAGE% of statements"
-
-COLOR=orange
-if awk "BEGIN {exit !($COVERAGE <= 50)}"; then
- COLOR=red
-elif awk "BEGIN {exit !($COVERAGE > 80)}"; then
- COLOR=green
-fi
-curl -s "https://img.shields.io/badge/coverage-$COVERAGE%25-$COLOR" > "$SCRIPT_DIR/coverage.svg"
-
-git add "$SCRIPT_DIR/coverage.html" "$SCRIPT_DIR/coverage.svg"
\ No newline at end of file
diff --git a/.github/coverage.svg b/.github/coverage.svg
deleted file mode 100644
index ea2a384..0000000
--- a/.github/coverage.svg
+++ /dev/null
@@ -1 +0,0 @@
-