Refactor.

This commit is contained in:
Nuno Cruces
2023-03-29 15:01:25 +01:00
parent 80039385d3
commit 9c07e57252
39 changed files with 1338 additions and 1267 deletions

14
blob.go
View File

@@ -1,6 +1,10 @@
package sqlite3 package sqlite3
import "io" import (
"io"
"github.com/ncruces/go-sqlite3/internal/util"
)
// ZeroBlob represents a zero-filled, length n BLOB // ZeroBlob represents a zero-filled, length n BLOB
// that can be used as an argument to // that can be used as an argument to
@@ -46,7 +50,7 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
} }
blob := Blob{c: c} blob := Blob{c: c}
blob.handle = c.mem.readUint32(blobPtr) blob.handle = util.ReadUint32(c.mod, blobPtr)
blob.bytes = int64(c.call(c.api.blobBytes, uint64(blob.handle))[0]) blob.bytes = int64(c.call(c.api.blobBytes, uint64(blob.handle))[0])
return &blob, nil return &blob, nil
} }
@@ -98,7 +102,7 @@ func (b *Blob) Read(p []byte) (n int, err error) {
return 0, err return 0, err
} }
mem := b.c.mem.view(ptr, uint64(want)) mem := util.View(b.c.mod, ptr, uint64(want))
copy(p, mem) copy(p, mem)
b.offset += want b.offset += want
if b.offset >= b.bytes { if b.offset >= b.bytes {
@@ -133,7 +137,7 @@ func (b *Blob) Write(p []byte) (n int, err error) {
func (b *Blob) Seek(offset int64, whence int) (int64, error) { func (b *Blob) Seek(offset int64, whence int) (int64, error) {
switch whence { switch whence {
default: default:
return 0, whenceErr return 0, util.WhenceErr
case io.SeekStart: case io.SeekStart:
break break
case io.SeekCurrent: case io.SeekCurrent:
@@ -142,7 +146,7 @@ func (b *Blob) Seek(offset int64, whence int) (int64, error) {
offset += b.bytes offset += b.bytes
} }
if offset < 0 { if offset < 0 {
return 0, offsetErr return 0, util.OffsetErr
} }
b.offset = offset b.offset = offset
return offset, nil return offset, nil

14
conn.go
View File

@@ -10,6 +10,8 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"unsafe" "unsafe"
"github.com/ncruces/go-sqlite3/internal/util"
) )
// Conn is a database connection handle. // Conn is a database connection handle.
@@ -55,7 +57,7 @@ func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
if conn == nil { if conn == nil {
mod.close() mod.close()
} else { } else {
runtime.SetFinalizer(conn, finalizer[Conn](3)) runtime.SetFinalizer(conn, util.Finalizer[Conn](3))
} }
}() }()
@@ -76,7 +78,7 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
flags |= OPEN_EXRESCODE flags |= OPEN_EXRESCODE
r := c.call(c.api.open, uint64(namePtr), uint64(connPtr), uint64(flags), 0) r := c.call(c.api.open, uint64(namePtr), uint64(connPtr), uint64(flags), 0)
handle := c.mem.readUint32(connPtr) handle := util.ReadUint32(c.mod, connPtr)
if err := c.module.error(r[0], handle); err != nil { if err := c.module.error(r[0], handle); err != nil {
c.closeDB(handle) c.closeDB(handle)
return 0, err return 0, err
@@ -182,8 +184,8 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
uint64(stmtPtr), uint64(tailPtr)) uint64(stmtPtr), uint64(tailPtr))
stmt = &Stmt{c: c} stmt = &Stmt{c: c}
stmt.handle = c.mem.readUint32(stmtPtr) stmt.handle = util.ReadUint32(c.mod, stmtPtr)
i := c.mem.readUint32(tailPtr) i := util.ReadUint32(c.mod, tailPtr)
tail = sql[i-sqlPtr:] tail = sql[i-sqlPtr:]
if err := c.error(r[0], sql); err != nil { if err := c.error(r[0], sql); err != nil {
@@ -275,7 +277,7 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
break break
case <-ctx.Done(): // Done was closed. case <-ctx.Done(): // Done was closed.
buf := c.mem.view(c.handle+c.api.interrupt, 4) buf := util.View(c.mod, c.handle+c.api.interrupt, 4)
(*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1) (*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1)
// Wait for the next call to SetInterrupt. // Wait for the next call to SetInterrupt.
<-waiter <-waiter
@@ -291,7 +293,7 @@ func (c *Conn) checkInterrupt() bool {
if c.interrupt == nil || c.interrupt.Err() == nil { if c.interrupt == nil || c.interrupt.Err() == nil {
return false return false
} }
buf := c.mem.view(c.handle+c.api.interrupt, 4) buf := util.View(c.mod, c.handle+c.api.interrupt, 4)
(*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1) (*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1)
return true return true
} }

View File

@@ -7,8 +7,6 @@ const (
_ROW = 100 /* sqlite3_step() has another row ready */ _ROW = 100 /* sqlite3_step() has another row ready */
_DONE = 101 /* sqlite3_step() has finished executing */ _DONE = 101 /* sqlite3_step() has finished executing */
_OK_SYMLINK = (_OK | (2 << 8)) /* internal use only */
_UTF8 = 1 _UTF8 = 1
_MAX_STRING = 512 // Used for short strings: names, error messages… _MAX_STRING = 512 // Used for short strings: names, error messages…
@@ -211,65 +209,3 @@ func (t Datatype) String() string {
} }
return strconv.FormatUint(uint64(t), 10) return strconv.FormatUint(uint64(t), 10)
} }
type _AccessFlag uint32
const (
_ACCESS_EXISTS _AccessFlag = 0
_ACCESS_READWRITE _AccessFlag = 1 /* Used by PRAGMA temp_store_directory */
_ACCESS_READ _AccessFlag = 2 /* Unused */
)
type _SyncFlag uint32
const (
_SYNC_NORMAL _SyncFlag = 0x00002
_SYNC_FULL _SyncFlag = 0x00003
_SYNC_DATAONLY _SyncFlag = 0x00010
)
type _FcntlOpcode uint32
const (
_FCNTL_LOCKSTATE = 1
_FCNTL_GET_LOCKPROXYFILE = 2
_FCNTL_SET_LOCKPROXYFILE = 3
_FCNTL_LAST_ERRNO = 4
_FCNTL_SIZE_HINT = 5
_FCNTL_CHUNK_SIZE = 6
_FCNTL_FILE_POINTER = 7
_FCNTL_SYNC_OMITTED = 8
_FCNTL_WIN32_AV_RETRY = 9
_FCNTL_PERSIST_WAL = 10
_FCNTL_OVERWRITE = 11
_FCNTL_VFSNAME = 12
_FCNTL_POWERSAFE_OVERWRITE = 13
_FCNTL_PRAGMA = 14
_FCNTL_BUSYHANDLER = 15
_FCNTL_TEMPFILENAME = 16
_FCNTL_MMAP_SIZE = 18
_FCNTL_TRACE = 19
_FCNTL_HAS_MOVED = 20
_FCNTL_SYNC = 21
_FCNTL_COMMIT_PHASETWO = 22
_FCNTL_WIN32_SET_HANDLE = 23
_FCNTL_WAL_BLOCK = 24
_FCNTL_ZIPVFS = 25
_FCNTL_RBU = 26
_FCNTL_VFS_POINTER = 27
_FCNTL_JOURNAL_POINTER = 28
_FCNTL_WIN32_GET_HANDLE = 29
_FCNTL_PDB = 30
_FCNTL_BEGIN_ATOMIC_WRITE = 31
_FCNTL_COMMIT_ATOMIC_WRITE = 32
_FCNTL_ROLLBACK_ATOMIC_WRITE = 33
_FCNTL_LOCK_TIMEOUT = 34
_FCNTL_DATA_VERSION = 35
_FCNTL_SIZE_LIMIT = 36
_FCNTL_CKPT_DONE = 37
_FCNTL_RESERVE_BYTES = 38
_FCNTL_CKPT_START = 39
_FCNTL_EXTERNAL_READER = 40
_FCNTL_CKSM_FILE = 41
_FCNTL_RESET_CACHE = 42
)

View File

@@ -35,6 +35,7 @@ import (
"time" "time"
"github.com/ncruces/go-sqlite3" "github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
) )
func init() { func init() {
@@ -134,7 +135,7 @@ func (c conn) BeginTx(_ context.Context, opts driver.TxOptions) (driver.Tx, erro
switch opts.Isolation { switch opts.Isolation {
default: default:
return nil, isolationErr return nil, util.IsolationErr
case case
driver.IsolationLevel(sql.LevelDefault), driver.IsolationLevel(sql.LevelDefault),
driver.IsolationLevel(sql.LevelSerializable): driver.IsolationLevel(sql.LevelSerializable):
@@ -183,7 +184,7 @@ func (c conn) Prepare(query string) (driver.Stmt, error) {
if st != nil { if st != nil {
s.Close() s.Close()
st.Close() st.Close()
return nil, tailErr return nil, util.TailErr
} }
} }
return stmt{s, c.conn}, nil return stmt{s, c.conn}, nil
@@ -316,7 +317,7 @@ func (s stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (drive
case nil: case nil:
err = s.stmt.BindNull(id) err = s.stmt.BindNull(id)
default: default:
panic(assertErr) panic(util.AssertErr())
} }
} }
if err != nil { if err != nil {
@@ -394,7 +395,7 @@ func (r rows) Next(dest []driver.Value) error {
dest[i] = nil dest[i] = nil
} }
default: default:
panic(assertErr) panic(util.AssertErr())
} }
} }

View File

@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/ncruces/go-sqlite3" "github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
) )
func Test_Open_dir(t *testing.T) { func Test_Open_dir(t *testing.T) {
@@ -142,7 +143,7 @@ func Test_BeginTx(t *testing.T) {
defer db.Close() defer db.Close()
_, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted}) _, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err.Error() != string(isolationErr) { if err.Error() != string(util.IsolationErr) {
t.Error("want isolationErr") t.Error("want isolationErr")
} }
@@ -230,7 +231,7 @@ func Test_Prepare(t *testing.T) {
} }
_, err = db.Prepare(`SELECT 1; SELECT 2`) _, err = db.Prepare(`SELECT 1; SELECT 2`)
if err.Error() != string(tailErr) { if err.Error() != string(util.TailErr) {
t.Error("want tailErr") t.Error("want tailErr")
} }
} }

View File

@@ -1,11 +0,0 @@
package driver
type errorString string
func (e errorString) Error() string { return string(e) }
const (
assertErr = errorString("sqlite3: assertion failed")
tailErr = errorString("sqlite3: multiple statements")
isolationErr = errorString("sqlite3: unsupported isolation level")
)

View File

@@ -1,8 +1,6 @@
package sqlite3 package sqlite3
import ( import (
"fmt"
"runtime"
"strconv" "strconv"
"strings" "strings"
) )
@@ -188,36 +186,3 @@ func (e ExtendedErrorCode) Temporary() bool {
func (e ExtendedErrorCode) Timeout() bool { func (e ExtendedErrorCode) Timeout() bool {
return e == BUSY_TIMEOUT return e == BUSY_TIMEOUT
} }
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: ")
binaryErr = errorString("sqlite3: no SQLite binary embed/set/loaded")
timeErr = errorString("sqlite3: invalid time value")
whenceErr = errorString("sqlite3: invalid whence")
offsetErr = errorString("sqlite3: invalid offset")
)
func assertErr() errorString {
msg := "sqlite3: assertion failed"
if _, file, line, ok := runtime.Caller(1); ok {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return errorString(msg)
}
func finalizer[T any](skip int) func(*T) {
msg := fmt.Sprintf("sqlite3: %T not closed", new(T))
if _, file, line, ok := runtime.Caller(skip + 1); ok && skip >= 0 {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return func(*T) { panic(errorString(msg)) }
}

View File

@@ -4,11 +4,13 @@ import (
"errors" "errors"
"strings" "strings"
"testing" "testing"
"github.com/ncruces/go-sqlite3/internal/util"
) )
func Test_assertErr(t *testing.T) { func Test_assertErr(t *testing.T) {
err := assertErr() err := util.AssertErr()
if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:10)") { if s := err.Error(); !strings.HasPrefix(s, "sqlite3: assertion failed") || !strings.HasSuffix(s, "error_test.go:12)") {
t.Errorf("got %q", s) t.Errorf("got %q", s)
} }
} }
@@ -120,7 +122,7 @@ func Test_ErrorCode_Error(t *testing.T) {
for i := 0; i == int(ErrorCode(i)); i++ { for i := 0; i == int(ErrorCode(i)); i++ {
want := "sqlite3: " want := "sqlite3: "
r := db.call(db.api.errstr, uint64(i)) r := db.call(db.api.errstr, uint64(i))
want += db.mem.readString(uint32(r[0]), _MAX_STRING) want += util.ReadString(db.mod, uint32(r[0]), _MAX_STRING)
got := ErrorCode(i).Error() got := ErrorCode(i).Error()
if got != want { if got != want {
@@ -142,7 +144,7 @@ func Test_ExtendedErrorCode_Error(t *testing.T) {
for i := 0; i == int(ExtendedErrorCode(i)); i++ { for i := 0; i == int(ExtendedErrorCode(i)); i++ {
want := "sqlite3: " want := "sqlite3: "
r := db.call(db.api.errstr, uint64(i)) r := db.call(db.api.errstr, uint64(i))
want += db.mem.readString(uint32(r[0]), _MAX_STRING) want += util.ReadString(db.mod, uint32(r[0]), _MAX_STRING)
got := ExtendedErrorCode(i).Error() got := ExtendedErrorCode(i).Error()
if got != want { if got != want {

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.19
require ( require (
github.com/ncruces/julianday v0.1.5 github.com/ncruces/julianday v0.1.5
github.com/tetratelabs/wazero v1.0.0 github.com/tetratelabs/wazero v1.0.1
golang.org/x/sync v0.1.0 golang.org/x/sync v0.1.0
golang.org/x/sys v0.6.0 golang.org/x/sys v0.6.0
) )

4
go.sum
View File

@@ -1,7 +1,7 @@
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk= github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.0.0 h1:sCE9+mjFex95Ki6hdqwvhyF25x5WslADjDKIFU5BXzI= github.com/tetratelabs/wazero v1.0.1 h1:xyWBoGyMjYekG3mEQ/W7xm9E05S89kJ/at696d/9yuc=
github.com/tetratelabs/wazero v1.0.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= github.com/tetratelabs/wazero v1.0.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=

42
internal/util/error.go Normal file
View File

@@ -0,0 +1,42 @@
package util
import (
"fmt"
"runtime"
"strconv"
)
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: ")
BinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
TimeErr = ErrorString("sqlite3: invalid time value")
WhenceErr = ErrorString("sqlite3: invalid whence")
OffsetErr = ErrorString("sqlite3: invalid offset")
TailErr = ErrorString("sqlite3: multiple statements")
IsolationErr = ErrorString("sqlite3: unsupported isolation level")
)
func AssertErr() ErrorString {
msg := "sqlite3: assertion failed"
if _, file, line, ok := runtime.Caller(1); ok {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return ErrorString(msg)
}
func Finalizer[T any](skip int) func(*T) {
msg := fmt.Sprintf("sqlite3: %T not closed", new(T))
if _, file, line, ok := runtime.Caller(skip + 1); ok && skip >= 0 {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return func(*T) { panic(ErrorString(msg)) }
}

143
internal/util/mem.go Normal file
View File

@@ -0,0 +1,143 @@
package util
import (
"bytes"
"math"
"github.com/tetratelabs/wazero/api"
)
func View(mod api.Module, ptr uint32, size uint64) []byte {
if ptr == 0 {
panic(NilErr)
}
if size > math.MaxUint32 {
panic(RangeErr)
}
buf, ok := mod.Memory().Read(ptr, uint32(size))
if !ok {
panic(RangeErr)
}
return buf
}
func ReadUint8(mod api.Module, ptr uint32) uint8 {
if ptr == 0 {
panic(NilErr)
}
v, ok := mod.Memory().ReadByte(ptr)
if !ok {
panic(RangeErr)
}
return v
}
func WriteUint8(mod api.Module, ptr uint32, v uint8) {
if ptr == 0 {
panic(NilErr)
}
ok := mod.Memory().WriteByte(ptr, v)
if !ok {
panic(RangeErr)
}
}
func ReadUint32(mod api.Module, ptr uint32) uint32 {
if ptr == 0 {
panic(NilErr)
}
v, ok := mod.Memory().ReadUint32Le(ptr)
if !ok {
panic(RangeErr)
}
return v
}
func WriteUint32(mod api.Module, ptr uint32, v uint32) {
if ptr == 0 {
panic(NilErr)
}
ok := mod.Memory().WriteUint32Le(ptr, v)
if !ok {
panic(RangeErr)
}
}
func ReadUint64(mod api.Module, ptr uint32) uint64 {
if ptr == 0 {
panic(NilErr)
}
v, ok := mod.Memory().ReadUint64Le(ptr)
if !ok {
panic(RangeErr)
}
return v
}
func WriteUint64(mod api.Module, ptr uint32, v uint64) {
if ptr == 0 {
panic(NilErr)
}
ok := mod.Memory().WriteUint64Le(ptr, v)
if !ok {
panic(RangeErr)
}
}
func ReadBool8(mod api.Module, ptr uint32) bool {
return ReadUint8(mod, ptr) != 0
}
func WriteBool8(mod api.Module, ptr uint32, v bool) {
var b uint8
if v {
b = 1
}
WriteUint8(mod, ptr, b)
}
func ReadFloat64(mod api.Module, ptr uint32) float64 {
return math.Float64frombits(ReadUint64(mod, ptr))
}
func WriteFloat64(mod api.Module, ptr uint32, v float64) {
WriteUint64(mod, ptr, math.Float64bits(v))
}
func ReadString(mod api.Module, ptr, maxlen uint32) string {
if ptr == 0 {
panic(NilErr)
}
switch maxlen {
case 0:
return ""
case math.MaxUint32:
// avoid overflow
default:
maxlen = maxlen + 1
}
mem := mod.Memory()
buf, ok := mem.Read(ptr, maxlen)
if !ok {
buf, ok = mem.Read(ptr, mem.Size()-ptr)
if !ok {
panic(RangeErr)
}
}
if i := bytes.IndexByte(buf, 0); i < 0 {
panic(NoNulErr)
} else {
return string(buf[:i])
}
}
func WriteBytes(mod api.Module, ptr uint32, b []byte) {
buf := View(mod, ptr, uint64(len(b)))
copy(buf, b)
}
func WriteString(mod api.Module, ptr uint32, s string) {
buf := View(mod, ptr, uint64(len(s)+1))
buf[len(s)] = 0
copy(buf, s)
}

90
internal/util/mem_test.go Normal file
View File

@@ -0,0 +1,90 @@
package util
import (
"math"
"testing"
)
func TestView_nil(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
View(mock, 0, 8)
t.Error("want panic")
}
func TestView_range(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
View(mock, 126, 8)
t.Error("want panic")
}
func TestView_overflow(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
View(mock, 1, math.MaxInt64)
t.Error("want panic")
}
func TestReadUint32_nil(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
ReadUint32(mock, 0)
t.Error("want panic")
}
func TestReadUint32_range(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
ReadUint32(mock, 126)
t.Error("want panic")
}
func TestReadUint64_nil(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
ReadUint64(mock, 0)
t.Error("want panic")
}
func TestReadUint64_range(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
ReadUint64(mock, 126)
t.Error("want panic")
}
func TestWriteUint32_nil(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
WriteUint32(mock, 0, 1)
t.Error("want panic")
}
func TestWriteUint32_range(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
WriteUint32(mock, 126, 1)
t.Error("want panic")
}
func TestWriteUint64_nil(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
WriteUint64(mock, 0, 1)
t.Error("want panic")
}
func TestWriteUint64_range(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
WriteUint64(mock, 126, 1)
t.Error("want panic")
}
func TestReadString_range(t *testing.T) {
defer func() { _ = recover() }()
mock := NewMockModule(128)
ReadString(mock, 130, math.MaxUint32)
t.Error("want panic")
}

View File

@@ -1,4 +1,4 @@
package sqlite3 package util
import ( import (
"context" "context"
@@ -8,13 +8,9 @@ import (
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
) )
func init() { func NewMockModule(size uint32) api.Module {
Path = "./embed/sqlite3.wasm"
}
func newMemory(size uint32) memory {
mem := make(mockMemory, size) mem := make(mockMemory, size)
return memory{mockModule{&mem}} return mockModule{&mem}
} }
type mockModule struct { type mockModule struct {

148
internal/vfs/const.go Normal file
View File

@@ -0,0 +1,148 @@
package vfs
const (
_MAX_PATHNAME = 512
ptrlen = 4
)
type _ErrorCode uint32
const (
_OK _ErrorCode = 0 /* Successful result */
_PERM _ErrorCode = 3 /* Access permission denied */
_BUSY _ErrorCode = 5 /* The database file is locked */
_IOERR _ErrorCode = 10 /* Some kind of disk I/O error occurred */
_NOTFOUND _ErrorCode = 12 /* Unknown opcode in sqlite3_file_control() */
_CANTOPEN _ErrorCode = 14 /* Unable to open the database file */
_IOERR_READ = _IOERR | (1 << 8)
_IOERR_SHORT_READ = _IOERR | (2 << 8)
_IOERR_WRITE = _IOERR | (3 << 8)
_IOERR_FSYNC = _IOERR | (4 << 8)
_IOERR_DIR_FSYNC = _IOERR | (5 << 8)
_IOERR_TRUNCATE = _IOERR | (6 << 8)
_IOERR_FSTAT = _IOERR | (7 << 8)
_IOERR_UNLOCK = _IOERR | (8 << 8)
_IOERR_RDLOCK = _IOERR | (9 << 8)
_IOERR_DELETE = _IOERR | (10 << 8)
_IOERR_BLOCKED = _IOERR | (11 << 8)
_IOERR_NOMEM = _IOERR | (12 << 8)
_IOERR_ACCESS = _IOERR | (13 << 8)
_IOERR_CHECKRESERVEDLOCK = _IOERR | (14 << 8)
_IOERR_LOCK = _IOERR | (15 << 8)
_IOERR_CLOSE = _IOERR | (16 << 8)
_IOERR_DIR_CLOSE = _IOERR | (17 << 8)
_IOERR_SHMOPEN = _IOERR | (18 << 8)
_IOERR_SHMSIZE = _IOERR | (19 << 8)
_IOERR_SHMLOCK = _IOERR | (20 << 8)
_IOERR_SHMMAP = _IOERR | (21 << 8)
_IOERR_SEEK = _IOERR | (22 << 8)
_IOERR_DELETE_NOENT = _IOERR | (23 << 8)
_IOERR_MMAP = _IOERR | (24 << 8)
_IOERR_GETTEMPPATH = _IOERR | (25 << 8)
_IOERR_CONVPATH = _IOERR | (26 << 8)
_IOERR_VNODE = _IOERR | (27 << 8)
_IOERR_AUTH = _IOERR | (28 << 8)
_IOERR_BEGIN_ATOMIC = _IOERR | (29 << 8)
_IOERR_COMMIT_ATOMIC = _IOERR | (30 << 8)
_IOERR_ROLLBACK_ATOMIC = _IOERR | (31 << 8)
_IOERR_DATA = _IOERR | (32 << 8)
_IOERR_CORRUPTFS = _IOERR | (33 << 8)
_CANTOPEN_NOTEMPDIR = _CANTOPEN | (1 << 8)
_CANTOPEN_ISDIR = _CANTOPEN | (2 << 8)
_CANTOPEN_FULLPATH = _CANTOPEN | (3 << 8)
_CANTOPEN_CONVPATH = _CANTOPEN | (4 << 8)
_CANTOPEN_DIRTYWAL = _CANTOPEN | (5 << 8) /* Not Used */
_CANTOPEN_SYMLINK = _CANTOPEN | (6 << 8)
_OK_SYMLINK = (_OK | (2 << 8)) /* internal use only */
)
type _OpenFlag uint32
const (
_OPEN_READONLY _OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */
_OPEN_READWRITE _OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */
_OPEN_CREATE _OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */
_OPEN_DELETEONCLOSE _OpenFlag = 0x00000008 /* VFS only */
_OPEN_EXCLUSIVE _OpenFlag = 0x00000010 /* VFS only */
_OPEN_AUTOPROXY _OpenFlag = 0x00000020 /* VFS only */
_OPEN_URI _OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */
_OPEN_MEMORY _OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */
_OPEN_MAIN_DB _OpenFlag = 0x00000100 /* VFS only */
_OPEN_TEMP_DB _OpenFlag = 0x00000200 /* VFS only */
_OPEN_TRANSIENT_DB _OpenFlag = 0x00000400 /* VFS only */
_OPEN_MAIN_JOURNAL _OpenFlag = 0x00000800 /* VFS only */
_OPEN_TEMP_JOURNAL _OpenFlag = 0x00001000 /* VFS only */
_OPEN_SUBJOURNAL _OpenFlag = 0x00002000 /* VFS only */
_OPEN_SUPER_JOURNAL _OpenFlag = 0x00004000 /* VFS only */
_OPEN_NOMUTEX _OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */
_OPEN_FULLMUTEX _OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */
_OPEN_SHAREDCACHE _OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */
_OPEN_PRIVATECACHE _OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */
_OPEN_WAL _OpenFlag = 0x00080000 /* VFS only */
_OPEN_NOFOLLOW _OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */
_OPEN_EXRESCODE _OpenFlag = 0x02000000 /* Extended result codes */
)
type _AccessFlag uint32
const (
_ACCESS_EXISTS _AccessFlag = 0
_ACCESS_READWRITE _AccessFlag = 1 /* Used by PRAGMA temp_store_directory */
_ACCESS_READ _AccessFlag = 2 /* Unused */
)
type _SyncFlag uint32
const (
_SYNC_NORMAL _SyncFlag = 0x00002
_SYNC_FULL _SyncFlag = 0x00003
_SYNC_DATAONLY _SyncFlag = 0x00010
)
type _FcntlOpcode uint32
const (
_FCNTL_LOCKSTATE = 1
_FCNTL_GET_LOCKPROXYFILE = 2
_FCNTL_SET_LOCKPROXYFILE = 3
_FCNTL_LAST_ERRNO = 4
_FCNTL_SIZE_HINT = 5
_FCNTL_CHUNK_SIZE = 6
_FCNTL_FILE_POINTER = 7
_FCNTL_SYNC_OMITTED = 8
_FCNTL_WIN32_AV_RETRY = 9
_FCNTL_PERSIST_WAL = 10
_FCNTL_OVERWRITE = 11
_FCNTL_VFSNAME = 12
_FCNTL_POWERSAFE_OVERWRITE = 13
_FCNTL_PRAGMA = 14
_FCNTL_BUSYHANDLER = 15
_FCNTL_TEMPFILENAME = 16
_FCNTL_MMAP_SIZE = 18
_FCNTL_TRACE = 19
_FCNTL_HAS_MOVED = 20
_FCNTL_SYNC = 21
_FCNTL_COMMIT_PHASETWO = 22
_FCNTL_WIN32_SET_HANDLE = 23
_FCNTL_WAL_BLOCK = 24
_FCNTL_ZIPVFS = 25
_FCNTL_RBU = 26
_FCNTL_VFS_POINTER = 27
_FCNTL_JOURNAL_POINTER = 28
_FCNTL_WIN32_GET_HANDLE = 29
_FCNTL_PDB = 30
_FCNTL_BEGIN_ATOMIC_WRITE = 31
_FCNTL_COMMIT_ATOMIC_WRITE = 32
_FCNTL_ROLLBACK_ATOMIC_WRITE = 33
_FCNTL_LOCK_TIMEOUT = 34
_FCNTL_DATA_VERSION = 35
_FCNTL_SIZE_LIMIT = 36
_FCNTL_CKPT_DONE = 37
_FCNTL_RESERVE_BYTES = 38
_FCNTL_CKPT_START = 39
_FCNTL_EXTERNAL_READER = 40
_FCNTL_CKSM_FILE = 41
_FCNTL_RESET_CACHE = 42
)

78
internal/vfs/func.go Normal file
View File

@@ -0,0 +1,78 @@
package vfs
import (
"context"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
func registerFunc1[T0, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0])))
}),
[]api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func registerFunc2[T0, T1, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func registerFunc3[T0, T1, T2, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func registerFunc4[T0, T1, T2, T3, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func registerFunc5[T0, T1, T2, T3, T4, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3, _ T4) TR) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func registerFuncRW(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _, _, _ uint32, _ int64) _ErrorCode) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), uint32(stack[1]), uint32(stack[2]), int64(stack[3])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func registerFuncT(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ uint32, _ int64) _ErrorCode) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), int64(stack[1])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}

359
internal/vfs/vfs.go Normal file
View File

@@ -0,0 +1,359 @@
package vfs
import (
"context"
"crypto/rand"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/julianday"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
func Instantiate(ctx context.Context, r wazero.Runtime) {
env := NewEnvModuleBuilder(r)
_, err := env.Instantiate(ctx)
if err != nil {
panic(err)
}
}
func NewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
env := r.NewHostModuleBuilder("env")
registerFuncT(env, "os_localtime", vfsLocaltime)
registerFunc3(env, "os_randomness", vfsRandomness)
registerFunc2(env, "os_sleep", vfsSleep)
registerFunc2(env, "os_current_time", vfsCurrentTime)
registerFunc2(env, "os_current_time_64", vfsCurrentTime64)
registerFunc4(env, "os_full_pathname", vfsFullPathname)
registerFunc3(env, "os_delete", vfsDelete)
registerFunc4(env, "os_access", vfsAccess)
registerFunc5(env, "os_open", vfsOpen)
registerFunc1(env, "os_close", vfsClose)
registerFuncRW(env, "os_read", vfsRead)
registerFuncRW(env, "os_write", vfsWrite)
registerFuncT(env, "os_truncate", vfsTruncate)
registerFunc2(env, "os_sync", vfsSync)
registerFunc2(env, "os_file_size", vfsFileSize)
registerFunc2(env, "os_lock", vfsLock)
registerFunc2(env, "os_unlock", vfsUnlock)
registerFunc2(env, "os_check_reserved_lock", vfsCheckReservedLock)
registerFunc3(env, "os_file_control", vfsFileControl)
return env
}
type vfsKey struct{}
type vfsState struct {
files []*os.File
}
func Context(ctx context.Context) (context.Context, io.Closer) {
vfs := &vfsState{}
return context.WithValue(ctx, vfsKey{}, vfs), vfs
}
func (vfs *vfsState) Close() error {
for _, f := range vfs.files {
if f != nil {
f.Close()
}
}
vfs.files = nil
return nil
}
func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _ErrorCode {
tm := time.Unix(t, 0)
var isdst int
if tm.IsDST() {
isdst = 1
}
// https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
util.WriteUint32(mod, pTm+0*ptrlen, uint32(tm.Second()))
util.WriteUint32(mod, pTm+1*ptrlen, uint32(tm.Minute()))
util.WriteUint32(mod, pTm+2*ptrlen, uint32(tm.Hour()))
util.WriteUint32(mod, pTm+3*ptrlen, uint32(tm.Day()))
util.WriteUint32(mod, pTm+4*ptrlen, uint32(tm.Month()-time.January))
util.WriteUint32(mod, pTm+5*ptrlen, uint32(tm.Year()-1900))
util.WriteUint32(mod, pTm+6*ptrlen, uint32(tm.Weekday()-time.Sunday))
util.WriteUint32(mod, pTm+7*ptrlen, uint32(tm.YearDay()-1))
util.WriteUint32(mod, pTm+8*ptrlen, uint32(isdst))
return _OK
}
func vfsRandomness(ctx context.Context, mod api.Module, pVfs, nByte, zByte uint32) uint32 {
mem := util.View(mod, zByte, uint64(nByte))
n, _ := rand.Reader.Read(mem)
return uint32(n)
}
func vfsSleep(ctx context.Context, mod api.Module, pVfs, nMicro uint32) _ErrorCode {
time.Sleep(time.Duration(nMicro) * time.Microsecond)
return _OK
}
func vfsCurrentTime(ctx context.Context, mod api.Module, pVfs, prNow uint32) _ErrorCode {
day := julianday.Float(time.Now())
util.WriteFloat64(mod, prNow, day)
return _OK
}
func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ErrorCode {
day, nsec := julianday.Date(time.Now())
msec := day*86_400_000 + nsec/1_000_000
util.WriteUint64(mod, piNow, uint64(msec))
return _OK
}
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) _ErrorCode {
rel := util.ReadString(mod, zRelative, _MAX_PATHNAME)
abs, err := filepath.Abs(rel)
if err != nil {
return _CANTOPEN_FULLPATH
}
size := uint64(len(abs) + 1)
if size > uint64(nFull) {
return _CANTOPEN_FULLPATH
}
mem := util.View(mod, zFull, size)
mem[len(abs)] = 0
copy(mem, abs)
if fi, err := os.Lstat(abs); err == nil {
if fi.Mode()&fs.ModeSymlink != 0 {
return _OK_SYMLINK
}
return _OK
} else if errors.Is(err, fs.ErrNotExist) {
return _OK
}
return _CANTOPEN_FULLPATH
}
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode {
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
err := os.Remove(path)
if errors.Is(err, fs.ErrNotExist) {
return _IOERR_DELETE_NOENT
}
if err != nil {
return _IOERR_DELETE
}
if runtime.GOOS != "windows" && syncDir != 0 {
f, err := os.Open(filepath.Dir(path))
if err != nil {
return _OK
}
defer f.Close()
err = osSync(f, false, false)
if err != nil {
return _IOERR_DIR_FSYNC
}
}
return _OK
}
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags _AccessFlag, pResOut uint32) _ErrorCode {
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
err := osAccess(path, flags)
var res uint32
var rc _ErrorCode
if flags == _ACCESS_EXISTS {
switch {
case err == nil:
res = 1
case errors.Is(err, fs.ErrNotExist):
res = 0
default:
rc = _IOERR_ACCESS
}
} else {
switch {
case err == nil:
res = 1
case errors.Is(err, fs.ErrPermission):
res = 0
default:
rc = _IOERR_ACCESS
}
}
util.WriteUint32(mod, pResOut, res)
return rc
}
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zName, pFile uint32, flags _OpenFlag, pOutFlags uint32) _ErrorCode {
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 := util.ReadString(mod, zName, _MAX_PATHNAME)
file, err = osOpenFile(name, oflags, 0666)
}
if err != nil {
return _CANTOPEN
}
if flags&_OPEN_DELETEONCLOSE != 0 {
os.Remove(file.Name())
}
openFile(ctx, mod, pFile, file)
if flags&_OPEN_READONLY != 0 {
setFileReadOnly(ctx, mod, pFile, true)
}
if runtime.GOOS != "windows" &&
flags&(_OPEN_CREATE) != 0 &&
flags&(_OPEN_MAIN_JOURNAL|_OPEN_SUPER_JOURNAL|_OPEN_WAL) != 0 {
setFileSyncDir(ctx, mod, pFile, true)
}
if pOutFlags != 0 {
util.WriteUint32(mod, pOutFlags, uint32(flags))
}
return _OK
}
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
err := closeFile(ctx, mod, pFile)
if err != nil {
return _IOERR_CLOSE
}
return _OK
}
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
buf := util.View(mod, zBuf, uint64(iAmt))
file := getOSFile(ctx, mod, pFile)
n, err := file.ReadAt(buf, iOfst)
if n == int(iAmt) {
return _OK
}
if n == 0 && err != io.EOF {
return _IOERR_READ
}
for i := range buf[n:] {
buf[n+i] = 0
}
return _IOERR_SHORT_READ
}
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
buf := util.View(mod, zBuf, uint64(iAmt))
file := getOSFile(ctx, mod, pFile)
_, err := file.WriteAt(buf, iOfst)
if err != nil {
return _IOERR_WRITE
}
return _OK
}
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode {
file := getOSFile(ctx, mod, pFile)
err := file.Truncate(nByte)
if err != nil {
return _IOERR_TRUNCATE
}
return _OK
}
func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags _SyncFlag) _ErrorCode {
dataonly := (flags & _SYNC_DATAONLY) != 0
fullsync := (flags & 0x0f) == _SYNC_FULL
file := getOSFile(ctx, mod, pFile)
err := osSync(file, fullsync, dataonly)
if err != nil {
return _IOERR_FSYNC
}
if runtime.GOOS != "windows" && getFileSyncDir(ctx, mod, pFile) {
setFileSyncDir(ctx, mod, pFile, false)
f, err := os.Open(filepath.Dir(file.Name()))
if err != nil {
return _OK
}
defer f.Close()
err = osSync(f, false, false)
if err != nil {
return _IOERR_DIR_FSYNC
}
}
return _OK
}
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode {
file := getOSFile(ctx, mod, pFile)
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return _IOERR_SEEK
}
util.WriteUint64(mod, pSize, uint64(off))
return _OK
}
func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode {
switch op {
case _FCNTL_SIZE_HINT:
return vfsSizeHint(ctx, mod, pFile, pArg)
case _FCNTL_HAS_MOVED:
return vfsFileMoved(ctx, mod, pFile, pArg)
}
return _NOTFOUND
}
func vfsSizeHint(ctx context.Context, mod api.Module, pFile, pArg uint32) _ErrorCode {
file := getOSFile(ctx, mod, pFile)
size := util.ReadUint64(mod, pArg)
err := osAllocate(file, int64(size))
if err != nil {
return _IOERR_TRUNCATE
}
return _OK
}
func vfsFileMoved(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
file := getOSFile(ctx, mod, pFile)
fi, err := file.Stat()
if err != nil {
return _IOERR_FSTAT
}
pi, err := os.Stat(file.Name())
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return _IOERR_FSTAT
}
var res uint32
if !os.SameFile(fi, pi) {
res = 1
}
util.WriteUint32(mod, pResOut, res)
return _OK
}

82
internal/vfs/vfs_file.go Normal file
View File

@@ -0,0 +1,82 @@
package vfs
import (
"context"
"os"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/tetratelabs/wazero/api"
)
const (
// These need to match the offsets asserted in os.c
vfsFileIDOffset = 4
vfsFileLockOffset = 8
vfsFileSyncDirOffset = 10
vfsFileReadOnlyOffset = 11
vfsFileLockTimeoutOffset = 12
)
func newFileID(ctx context.Context, file *os.File) uint32 {
vfs := ctx.Value(vfsKey{}).(*vfsState)
// Find an empty slot.
for id, ptr := range vfs.files {
if ptr == nil {
vfs.files[id] = file
return uint32(id)
}
}
// Add a new slot.
vfs.files = append(vfs.files, file)
return uint32(len(vfs.files) - 1)
}
func openFile(ctx context.Context, mod api.Module, pFile uint32, file *os.File) {
id := newFileID(ctx, file)
util.WriteUint32(mod, pFile+vfsFileIDOffset, id)
}
func closeFile(ctx context.Context, mod api.Module, pFile uint32) error {
id := util.ReadUint32(mod, pFile+vfsFileIDOffset)
vfs := ctx.Value(vfsKey{}).(*vfsState)
file := vfs.files[id]
vfs.files[id] = nil
return file.Close()
}
func getOSFile(ctx context.Context, mod api.Module, pFile uint32) *os.File {
id := util.ReadUint32(mod, pFile+vfsFileIDOffset)
vfs := ctx.Value(vfsKey{}).(*vfsState)
return vfs.files[id]
}
func getFileLock(ctx context.Context, mod api.Module, pFile uint32) vfsLockState {
return vfsLockState(util.ReadUint8(mod, pFile+vfsFileLockOffset))
}
func setFileLock(ctx context.Context, mod api.Module, pFile uint32, lock vfsLockState) {
util.WriteUint8(mod, pFile+vfsFileLockOffset, uint8(lock))
}
func getFileLockTimeout(ctx context.Context, mod api.Module, pFile uint32) time.Duration {
return time.Duration(util.ReadUint32(mod, pFile+vfsFileLockTimeoutOffset)) * time.Millisecond
}
func getFileSyncDir(ctx context.Context, mod api.Module, pFile uint32) bool {
return util.ReadBool8(mod, pFile+vfsFileSyncDirOffset)
}
func setFileSyncDir(ctx context.Context, mod api.Module, pFile uint32, val bool) {
util.WriteBool8(mod, pFile+vfsFileSyncDirOffset, val)
}
func getFileReadOnly(ctx context.Context, mod api.Module, pFile uint32) bool {
return util.ReadBool8(mod, pFile+vfsFileReadOnlyOffset)
}
func setFileReadOnly(ctx context.Context, mod api.Module, pFile uint32, val bool) {
util.WriteBool8(mod, pFile+vfsFileReadOnlyOffset, val)
}

View File

@@ -1,10 +1,11 @@
package sqlite3 package vfs
import ( import (
"context" "context"
"os" "os"
"time" "time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
) )
@@ -56,27 +57,27 @@ const (
type vfsLockState uint32 type vfsLockState uint32
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 { func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) _ErrorCode {
// Argument check. SQLite never explicitly requests a pending lock. // Argument check. SQLite never explicitly requests a pending lock.
if eLock != _SHARED_LOCK && eLock != _RESERVED_LOCK && eLock != _EXCLUSIVE_LOCK { if eLock != _SHARED_LOCK && eLock != _RESERVED_LOCK && eLock != _EXCLUSIVE_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
file := vfsFile.GetOS(ctx, mod, pFile) file := getOSFile(ctx, mod, pFile)
cLock := vfsFile.GetLock(ctx, mod, pFile) cLock := getFileLock(ctx, mod, pFile)
timeout := vfsFile.GetLockTimeout(ctx, mod, pFile) timeout := getFileLockTimeout(ctx, mod, pFile)
readOnly := vfsFile.GetReadOnly(ctx, mod, pFile) readOnly := getFileReadOnly(ctx, mod, pFile)
switch { switch {
case cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK: case cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK:
// Connection state check. // Connection state check.
panic(assertErr()) panic(util.AssertErr())
case cLock == _NO_LOCK && eLock > _SHARED_LOCK: case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
// We never move from unlocked to anything higher than a shared lock. // We never move from unlocked to anything higher than a shared lock.
panic(assertErr()) panic(util.AssertErr())
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK: case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
// A shared lock is always held when a reserved lock is requested. // A shared lock is always held when a reserved lock is requested.
panic(assertErr()) panic(util.AssertErr())
} }
// If we already have an equal or more restrictive lock, do nothing. // If we already have an equal or more restrictive lock, do nothing.
@@ -86,67 +87,67 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
// Do not allow any kind of write-lock on a read-only database. // Do not allow any kind of write-lock on a read-only database.
if readOnly && eLock > _RESERVED_LOCK { if readOnly && eLock > _RESERVED_LOCK {
return uint32(IOERR_LOCK) return _IOERR_LOCK
} }
switch eLock { switch eLock {
case _SHARED_LOCK: case _SHARED_LOCK:
// Must be unlocked to get SHARED. // Must be unlocked to get SHARED.
if cLock != _NO_LOCK { if cLock != _NO_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
if rc := vfsOS.GetSharedLock(file, timeout); rc != _OK { if rc := osGetSharedLock(file, timeout); rc != _OK {
return uint32(rc) return rc
} }
vfsFile.SetLock(ctx, mod, pFile, _SHARED_LOCK) setFileLock(ctx, mod, pFile, _SHARED_LOCK)
return _OK return _OK
case _RESERVED_LOCK: case _RESERVED_LOCK:
// Must be SHARED to get RESERVED. // Must be SHARED to get RESERVED.
if cLock != _SHARED_LOCK { if cLock != _SHARED_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
if rc := vfsOS.GetReservedLock(file, timeout); rc != _OK { if rc := osGetReservedLock(file, timeout); rc != _OK {
return uint32(rc) return rc
} }
vfsFile.SetLock(ctx, mod, pFile, _RESERVED_LOCK) setFileLock(ctx, mod, pFile, _RESERVED_LOCK)
return _OK return _OK
case _EXCLUSIVE_LOCK: case _EXCLUSIVE_LOCK:
// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE. // Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
if cLock <= _NO_LOCK || cLock >= _EXCLUSIVE_LOCK { if cLock <= _NO_LOCK || cLock >= _EXCLUSIVE_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
// A PENDING lock is needed before acquiring an EXCLUSIVE lock. // A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if cLock < _PENDING_LOCK { if cLock < _PENDING_LOCK {
if rc := vfsOS.GetPendingLock(file); rc != _OK { if rc := osGetPendingLock(file); rc != _OK {
return uint32(rc) return rc
} }
vfsFile.SetLock(ctx, mod, pFile, _PENDING_LOCK) setFileLock(ctx, mod, pFile, _PENDING_LOCK)
} }
if rc := vfsOS.GetExclusiveLock(file, timeout); rc != _OK { if rc := osGetExclusiveLock(file, timeout); rc != _OK {
return uint32(rc) return rc
} }
vfsFile.SetLock(ctx, mod, pFile, _EXCLUSIVE_LOCK) setFileLock(ctx, mod, pFile, _EXCLUSIVE_LOCK)
return _OK return _OK
default: default:
panic(assertErr()) panic(util.AssertErr())
} }
} }
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 { func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) _ErrorCode {
// Argument check. // Argument check.
if eLock != _NO_LOCK && eLock != _SHARED_LOCK { if eLock != _NO_LOCK && eLock != _SHARED_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
file := vfsFile.GetOS(ctx, mod, pFile) file := getOSFile(ctx, mod, pFile)
cLock := vfsFile.GetLock(ctx, mod, pFile) cLock := getFileLock(ctx, mod, pFile)
// Connection state check. // Connection state check.
if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK { if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
// If we don't have a more restrictive lock, do nothing. // If we don't have a more restrictive lock, do nothing.
@@ -156,58 +157,58 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
switch eLock { switch eLock {
case _SHARED_LOCK: case _SHARED_LOCK:
if rc := vfsOS.DowngradeLock(file, cLock); rc != _OK { if rc := osDowngradeLock(file, cLock); rc != _OK {
return uint32(rc) return rc
} }
vfsFile.SetLock(ctx, mod, pFile, _SHARED_LOCK) setFileLock(ctx, mod, pFile, _SHARED_LOCK)
return _OK return _OK
case _NO_LOCK: case _NO_LOCK:
rc := vfsOS.ReleaseLock(file, cLock) rc := osReleaseLock(file, cLock)
vfsFile.SetLock(ctx, mod, pFile, _NO_LOCK) setFileLock(ctx, mod, pFile, _NO_LOCK)
return uint32(rc) return rc
default: default:
panic(assertErr()) panic(util.AssertErr())
} }
} }
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 { func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
file := vfsFile.GetOS(ctx, mod, pFile) file := getOSFile(ctx, mod, pFile)
cLock := vfsFile.GetLock(ctx, mod, pFile) cLock := getFileLock(ctx, mod, pFile)
// Connection state check. // Connection state check.
if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK { if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK {
panic(assertErr()) panic(util.AssertErr())
} }
var locked bool var locked bool
var rc xErrorCode var rc _ErrorCode
if cLock >= _RESERVED_LOCK { if cLock >= _RESERVED_LOCK {
locked = true locked = true
} else { } else {
locked, rc = vfsOS.CheckReservedLock(file) locked, rc = osCheckReservedLock(file)
} }
var res uint32 var res uint32
if locked { if locked {
res = 1 res = 1
} }
memory{mod}.writeUint32(pResOut, res) util.WriteUint32(mod, pResOut, res)
return uint32(rc) return rc
} }
func (vfsOSMethods) GetReservedLock(file *os.File, timeout time.Duration) xErrorCode { func osGetReservedLock(file *os.File, timeout time.Duration) _ErrorCode {
// Acquire the RESERVED lock. // Acquire the RESERVED lock.
return vfsOS.writeLock(file, _RESERVED_BYTE, 1, timeout) return osWriteLock(file, _RESERVED_BYTE, 1, timeout)
} }
func (vfsOSMethods) GetPendingLock(file *os.File) xErrorCode { func osGetPendingLock(file *os.File) _ErrorCode {
// Acquire the PENDING lock. // Acquire the PENDING lock.
return vfsOS.writeLock(file, _PENDING_BYTE, 1, 0) return osWriteLock(file, _PENDING_BYTE, 1, 0)
} }
func (vfsOSMethods) CheckReservedLock(file *os.File) (bool, xErrorCode) { func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock. // Test the RESERVED lock.
return vfsOS.checkLock(file, _RESERVED_BYTE, 1) return osCheckLock(file, _RESERVED_BYTE, 1)
} }

View File

@@ -1,4 +1,4 @@
package sqlite3 package vfs
import ( import (
"context" "context"
@@ -6,6 +6,8 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"testing" "testing"
"github.com/ncruces/go-sqlite3/internal/util"
) )
func Test_vfsLock(t *testing.T) { func Test_vfsLock(t *testing.T) {
@@ -37,133 +39,133 @@ func Test_vfsLock(t *testing.T) {
pFile2 = 16 pFile2 = 16
pOutput = 32 pOutput = 32
) )
mem := newMemory(128) mod := util.NewMockModule(128)
ctx, vfs := vfsContext(context.TODO()) ctx, vfs := Context(context.TODO())
defer vfs.Close() defer vfs.Close()
vfsFile.Open(ctx, mem.mod, pFile1, file1) openFile(ctx, mod, pFile1, file1)
vfsFile.Open(ctx, mem.mod, pFile2, file2) openFile(ctx, mod, pFile2, file2)
rc := vfsCheckReservedLock(ctx, mem.mod, pFile1, pOutput) rc := vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got != 0 { if got := util.ReadUint32(mod, pOutput); got != 0 {
t.Error("file was locked") t.Error("file was locked")
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got != 0 { if got := util.ReadUint32(mod, pOutput); got != 0 {
t.Error("file was locked") t.Error("file was locked")
} }
rc = vfsLock(ctx, mem.mod, pFile2, _SHARED_LOCK) rc = vfsLock(ctx, mod, pFile2, _SHARED_LOCK)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile1, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got != 0 { if got := util.ReadUint32(mod, pOutput); got != 0 {
t.Error("file was locked") t.Error("file was locked")
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got != 0 { if got := util.ReadUint32(mod, pOutput); got != 0 {
t.Error("file was locked") t.Error("file was locked")
} }
rc = vfsLock(ctx, mem.mod, pFile2, _RESERVED_LOCK) rc = vfsLock(ctx, mod, pFile2, _RESERVED_LOCK)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
rc = vfsLock(ctx, mem.mod, pFile2, _SHARED_LOCK) rc = vfsLock(ctx, mod, pFile2, _SHARED_LOCK)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile1, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got == 0 { if got := util.ReadUint32(mod, pOutput); got == 0 {
t.Error("file wasn't locked") t.Error("file wasn't locked")
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got == 0 { if got := util.ReadUint32(mod, pOutput); got == 0 {
t.Error("file wasn't locked") t.Error("file wasn't locked")
} }
rc = vfsLock(ctx, mem.mod, pFile2, _EXCLUSIVE_LOCK) rc = vfsLock(ctx, mod, pFile2, _EXCLUSIVE_LOCK)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile1, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got == 0 { if got := util.ReadUint32(mod, pOutput); got == 0 {
t.Error("file wasn't locked") t.Error("file wasn't locked")
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got == 0 { if got := util.ReadUint32(mod, pOutput); got == 0 {
t.Error("file wasn't locked") t.Error("file wasn't locked")
} }
rc = vfsLock(ctx, mem.mod, pFile1, _SHARED_LOCK) rc = vfsLock(ctx, mod, pFile1, _SHARED_LOCK)
if rc == _OK { if rc == _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile1, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got == 0 { if got := util.ReadUint32(mod, pOutput); got == 0 {
t.Error("file wasn't locked") t.Error("file wasn't locked")
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got == 0 { if got := util.ReadUint32(mod, pOutput); got == 0 {
t.Error("file wasn't locked") t.Error("file wasn't locked")
} }
rc = vfsUnlock(ctx, mem.mod, pFile2, _SHARED_LOCK) rc = vfsUnlock(ctx, mod, pFile2, _SHARED_LOCK)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile1, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got != 0 { if got := util.ReadUint32(mod, pOutput); got != 0 {
t.Error("file was locked") t.Error("file was locked")
} }
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput) rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(pOutput); got != 0 { if got := util.ReadUint32(mod, pOutput); got != 0 {
t.Error("file was locked") t.Error("file was locked")
} }
rc = vfsLock(ctx, mem.mod, pFile1, _SHARED_LOCK) rc = vfsLock(ctx, mod, pFile1, _SHARED_LOCK)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }

View File

@@ -0,0 +1,56 @@
//go:build freebsd || openbsd || netbsd || dragonfly || (darwin && sqlite3_bsd)
package vfs
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func osUnlock(file *os.File, start, len int64) _ErrorCode {
if start == 0 && len == 0 {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return _IOERR_UNLOCK
}
}
return _OK
}
func osLock(file *os.File, how int, timeout time.Duration, def _ErrorCode) _ErrorCode {
var err error
for {
err = unix.Flock(int(file.Fd()), how)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, timeout, _IOERR_LOCK)
}
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return false, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

View File

@@ -1,6 +1,6 @@
//go:build !sqlite3_bsd //go:build !sqlite3_bsd
package sqlite3 package vfs
import ( import (
"io" "io"
@@ -23,14 +23,14 @@ type flocktimeout_t struct {
timeout unix.Timespec timeout unix.Timespec
} }
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { func osSync(file *os.File, fullsync, dataonly bool) error {
if fullsync { if fullsync {
return file.Sync() return file.Sync()
} }
return unix.Fsync(int(file.Fd())) return unix.Fsync(int(file.Fd()))
} }
func (vfsOSMethods) Allocate(file *os.File, size int64) error { func osAllocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd) off, err := file.Seek(0, io.SeekEnd)
if err != nil { if err != nil {
return err return err
@@ -57,19 +57,19 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error {
return file.Truncate(size) return file.Truncate(size)
} }
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode { func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{ err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK, Type: unix.F_UNLCK,
Start: start, Start: start,
Len: len, Len: len,
}) })
if err != nil { if err != nil {
return IOERR_UNLOCK return _IOERR_UNLOCK
} }
return _OK return _OK
} }
func (vfsOSMethods) lock(file *os.File, typ int16, start, len int64, timeout time.Duration, def xErrorCode) xErrorCode { func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := flocktimeout_t{fl: unix.Flock_t{ lock := flocktimeout_t{fl: unix.Flock_t{
Type: typ, Type: typ,
Start: start, Start: start,
@@ -82,25 +82,25 @@ func (vfsOSMethods) lock(file *os.File, typ int16, start, len int64, timeout tim
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond)) lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl) err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
} }
return vfsOS.lockErrorCode(err, def) return osLockErrorCode(err, def)
} }
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return vfsOS.lock(file, unix.F_RDLCK, start, len, timeout, IOERR_RDLOCK) return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
} }
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return vfsOS.lock(file, unix.F_WRLCK, start, len, timeout, IOERR_LOCK) return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
} }
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) { func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
lock := unix.Flock_t{ lock := unix.Flock_t{
Type: unix.F_RDLCK, Type: unix.F_RDLCK,
Start: start, Start: start,
Len: len, Len: len,
} }
if unix.FcntlFlock(file.Fd(), _F_OFD_GETLK, &lock) != nil { if unix.FcntlFlock(file.Fd(), _F_OFD_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK return false, _IOERR_CHECKRESERVEDLOCK
} }
return lock.Type != unix.F_UNLCK, _OK return lock.Type != unix.F_UNLCK, _OK
} }

View File

@@ -1,4 +1,4 @@
package sqlite3 package vfs
import ( import (
"os" "os"
@@ -6,7 +6,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { func osSync(file *os.File, fullsync, dataonly bool) error {
if dataonly { if dataonly {
_, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0) _, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0)
if err != 0 { if err != 0 {
@@ -17,7 +17,7 @@ func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
return file.Sync() return file.Sync()
} }
func (vfsOSMethods) Allocate(file *os.File, size int64) error { func osAllocate(file *os.File, size int64) error {
if size == 0 { if size == 0 {
return nil return nil
} }

View File

@@ -1,6 +1,6 @@
//go:build linux || illumos //go:build linux || illumos
package sqlite3 package vfs
import ( import (
"os" "os"
@@ -9,19 +9,19 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode { func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK, Type: unix.F_UNLCK,
Start: start, Start: start,
Len: len, Len: len,
}) })
if err != nil { if err != nil {
return IOERR_UNLOCK return _IOERR_UNLOCK
} }
return _OK return _OK
} }
func (vfsOSMethods) lock(file *os.File, typ int16, start, len int64, timeout time.Duration, def xErrorCode) xErrorCode { func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := unix.Flock_t{ lock := unix.Flock_t{
Type: typ, Type: typ,
Start: start, Start: start,
@@ -39,25 +39,25 @@ func (vfsOSMethods) lock(file *os.File, typ int16, start, len int64, timeout tim
timeout -= time.Millisecond timeout -= time.Millisecond
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
return vfsOS.lockErrorCode(err, def) return osLockErrorCode(err, def)
} }
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return vfsOS.lock(file, unix.F_RDLCK, start, len, timeout, IOERR_RDLOCK) return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
} }
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return vfsOS.lock(file, unix.F_WRLCK, start, len, timeout, IOERR_LOCK) return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
} }
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) { func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
lock := unix.Flock_t{ lock := unix.Flock_t{
Type: unix.F_RDLCK, Type: unix.F_RDLCK,
Start: start, Start: start,
Len: len, Len: len,
} }
if unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, &lock) != nil { if unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK return false, _IOERR_CHECKRESERVEDLOCK
} }
return lock.Type != unix.F_UNLCK, _OK return lock.Type != unix.F_UNLCK, _OK
} }

View File

@@ -1,17 +1,17 @@
//go:build !linux && (!darwin || sqlite3_bsd) //go:build !linux && (!darwin || sqlite3_bsd)
package sqlite3 package vfs
import ( import (
"io" "io"
"os" "os"
) )
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { func osSync(file *os.File, fullsync, dataonly bool) error {
return file.Sync() return file.Sync()
} }
func (vfsOSMethods) Allocate(file *os.File, size int64) error { func osAllocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd) off, err := file.Seek(0, io.SeekEnd)
if err != nil { if err != nil {
return err return err

View File

@@ -1,6 +1,6 @@
//go:build unix //go:build unix
package sqlite3 package vfs
import ( import (
"io/fs" "io/fs"
@@ -10,11 +10,11 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func (vfsOSMethods) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
return os.OpenFile(name, flag, perm) return os.OpenFile(name, flag, perm)
} }
func (vfsOSMethods) Access(path string, flags _AccessFlag) error { func osAccess(path string, flags _AccessFlag) error {
var access uint32 // unix.F_OK var access uint32 // unix.F_OK
switch flags { switch flags {
case _ACCESS_READWRITE: case _ACCESS_READWRITE:
@@ -25,46 +25,46 @@ func (vfsOSMethods) Access(path string, flags _AccessFlag) error {
return unix.Access(path, access) return unix.Access(path, access)
} }
func (vfsOSMethods) GetSharedLock(file *os.File, timeout time.Duration) xErrorCode { func osGetSharedLock(file *os.File, timeout time.Duration) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock. // Test the PENDING lock before acquiring a new SHARED lock.
if pending, _ := vfsOS.checkLock(file, _PENDING_BYTE, 1); pending { if pending, _ := osCheckLock(file, _PENDING_BYTE, 1); pending {
return xErrorCode(BUSY) return _ErrorCode(_BUSY)
} }
// Acquire the SHARED lock. // Acquire the SHARED lock.
return vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
} }
func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode { func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
if timeout == 0 { if timeout == 0 {
timeout = time.Millisecond timeout = time.Millisecond
} }
// Acquire the EXCLUSIVE lock. // Acquire the EXCLUSIVE lock.
return vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
} }
func (vfsOSMethods) DowngradeLock(file *os.File, state vfsLockState) xErrorCode { func osDowngradeLock(file *os.File, state vfsLockState) _ErrorCode {
if state >= _EXCLUSIVE_LOCK { if state >= _EXCLUSIVE_LOCK {
// Downgrade to a SHARED lock. // Downgrade to a SHARED lock.
if rc := vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// In theory, the downgrade to a SHARED cannot fail because another // In theory, the downgrade to a SHARED cannot fail because another
// process is holding an incompatible lock. If it does, this // process is holding an incompatible lock. If it does, this
// indicates that the other process is not following the locking // indicates that the other process is not following the locking
// protocol. If this happens, return IOERR_RDLOCK. Returning // protocol. If this happens, return _IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer. // BUSY would confuse the upper layer.
return IOERR_RDLOCK return _IOERR_RDLOCK
} }
} }
// Release the PENDING and RESERVED locks. // Release the PENDING and RESERVED locks.
return vfsOS.unlock(file, _PENDING_BYTE, 2) return osUnlock(file, _PENDING_BYTE, 2)
} }
func (vfsOSMethods) ReleaseLock(file *os.File, _ vfsLockState) xErrorCode { func osReleaseLock(file *os.File, _ vfsLockState) _ErrorCode {
// Release all locks. // Release all locks.
return vfsOS.unlock(file, 0, 0) return osUnlock(file, 0, 0)
} }
func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode { func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil { if err == nil {
return _OK return _OK
} }
@@ -78,9 +78,9 @@ func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode {
unix.ENOLCK, unix.ENOLCK,
unix.EDEADLK, unix.EDEADLK,
unix.ETIMEDOUT: unix.ETIMEDOUT:
return xErrorCode(BUSY) return _ErrorCode(_BUSY)
case unix.EPERM: case unix.EPERM:
return xErrorCode(PERM) return _ErrorCode(_PERM)
} }
} }
return def return def

View File

@@ -1,4 +1,4 @@
package sqlite3 package vfs
import ( import (
"io/fs" "io/fs"
@@ -9,12 +9,12 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
// OpenFile is a simplified copy of [os.openFileNolog] // osOpenFile is a simplified copy of [os.openFileNolog]
// that uses syscall.FILE_SHARE_DELETE. // that uses syscall.FILE_SHARE_DELETE.
// https://go.dev/src/os/file_windows.go // https://go.dev/src/os/file_windows.go
// //
// See: https://go.dev/issue/32088 // See: https://go.dev/issue/32088
func (vfsOSMethods) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
if name == "" { if name == "" {
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT} return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
} }
@@ -25,7 +25,7 @@ func (vfsOSMethods) OpenFile(name string, flag int, perm fs.FileMode) (*os.File,
return os.NewFile(uintptr(r), name), nil return os.NewFile(uintptr(r), name), nil
} }
func (vfsOSMethods) Access(path string, flags _AccessFlag) error { func osAccess(path string, flags _AccessFlag) error {
fi, err := os.Stat(path) fi, err := os.Stat(path)
if err != nil { if err != nil {
return err return err
@@ -47,88 +47,88 @@ func (vfsOSMethods) Access(path string, flags _AccessFlag) error {
return nil return nil
} }
func (vfsOSMethods) GetSharedLock(file *os.File, timeout time.Duration) xErrorCode { func osGetSharedLock(file *os.File, timeout time.Duration) _ErrorCode {
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock. // Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
rc := vfsOS.readLock(file, _PENDING_BYTE, 1, timeout) rc := osReadLock(file, _PENDING_BYTE, 1, timeout)
if rc == _OK { if rc == _OK {
// Acquire the SHARED lock. // Acquire the SHARED lock.
rc = vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
// Release the PENDING lock. // Release the PENDING lock.
vfsOS.unlock(file, _PENDING_BYTE, 1) osUnlock(file, _PENDING_BYTE, 1)
} }
return rc return rc
} }
func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode { func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
if timeout == 0 { if timeout == 0 {
timeout = time.Millisecond timeout = time.Millisecond
} }
// Release the SHARED lock. // Release the SHARED lock.
vfsOS.unlock(file, _SHARED_FIRST, _SHARED_SIZE) osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
// Acquire the EXCLUSIVE lock. // Acquire the EXCLUSIVE lock.
rc := vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
if rc != _OK { if rc != _OK {
// Reacquire the SHARED lock. // Reacquire the SHARED lock.
vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
} }
return rc return rc
} }
func (vfsOSMethods) DowngradeLock(file *os.File, state vfsLockState) xErrorCode { func osDowngradeLock(file *os.File, state vfsLockState) _ErrorCode {
if state >= _EXCLUSIVE_LOCK { if state >= _EXCLUSIVE_LOCK {
// Release the SHARED lock. // Release the SHARED lock.
vfsOS.unlock(file, _SHARED_FIRST, _SHARED_SIZE) osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
// Reacquire the SHARED lock. // Reacquire the SHARED lock.
if rc := vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// This should never happen. // This should never happen.
// We should always be able to reacquire the read lock. // We should always be able to reacquire the read lock.
return IOERR_RDLOCK return _IOERR_RDLOCK
} }
} }
// Release the PENDING and RESERVED locks. // Release the PENDING and RESERVED locks.
if state >= _RESERVED_LOCK { if state >= _RESERVED_LOCK {
vfsOS.unlock(file, _RESERVED_BYTE, 1) osUnlock(file, _RESERVED_BYTE, 1)
} }
if state >= _PENDING_LOCK { if state >= _PENDING_LOCK {
vfsOS.unlock(file, _PENDING_BYTE, 1) osUnlock(file, _PENDING_BYTE, 1)
} }
return _OK return _OK
} }
func (vfsOSMethods) ReleaseLock(file *os.File, state vfsLockState) xErrorCode { func osReleaseLock(file *os.File, state vfsLockState) _ErrorCode {
// Release all locks. // Release all locks.
if state >= _RESERVED_LOCK { if state >= _RESERVED_LOCK {
vfsOS.unlock(file, _RESERVED_BYTE, 1) osUnlock(file, _RESERVED_BYTE, 1)
} }
if state >= _SHARED_LOCK { if state >= _SHARED_LOCK {
vfsOS.unlock(file, _SHARED_FIRST, _SHARED_SIZE) osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
} }
if state >= _PENDING_LOCK { if state >= _PENDING_LOCK {
vfsOS.unlock(file, _PENDING_BYTE, 1) osUnlock(file, _PENDING_BYTE, 1)
} }
return _OK return _OK
} }
func (vfsOSMethods) unlock(file *os.File, start, len uint32) xErrorCode { func osUnlock(file *os.File, start, len uint32) _ErrorCode {
err := windows.UnlockFileEx(windows.Handle(file.Fd()), err := windows.UnlockFileEx(windows.Handle(file.Fd()),
0, len, 0, &windows.Overlapped{Offset: start}) 0, len, 0, &windows.Overlapped{Offset: start})
if err == windows.ERROR_NOT_LOCKED { if err == windows.ERROR_NOT_LOCKED {
return _OK return _OK
} }
if err != nil { if err != nil {
return IOERR_UNLOCK return _IOERR_UNLOCK
} }
return _OK return _OK
} }
func (vfsOSMethods) lock(file *os.File, flags, start, len uint32, timeout time.Duration, def xErrorCode) xErrorCode { func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
var err error var err error
for { for {
err = windows.LockFileEx(windows.Handle(file.Fd()), flags, err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
@@ -142,35 +142,35 @@ func (vfsOSMethods) lock(file *os.File, flags, start, len uint32, timeout time.D
timeout -= time.Millisecond timeout -= time.Millisecond
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
return vfsOS.lockErrorCode(err, def) return osLockErrorCode(err, def)
} }
func (vfsOSMethods) readLock(file *os.File, start, len uint32, timeout time.Duration) xErrorCode { func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return vfsOS.lock(file, return osLock(file,
windows.LOCKFILE_FAIL_IMMEDIATELY, windows.LOCKFILE_FAIL_IMMEDIATELY,
start, len, timeout, IOERR_RDLOCK) start, len, timeout, _IOERR_RDLOCK)
} }
func (vfsOSMethods) writeLock(file *os.File, start, len uint32, timeout time.Duration) xErrorCode { func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return vfsOS.lock(file, return osLock(file,
windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK, windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK,
start, len, timeout, IOERR_LOCK) start, len, timeout, _IOERR_LOCK)
} }
func (vfsOSMethods) checkLock(file *os.File, start, len uint32) (bool, xErrorCode) { func osCheckLock(file *os.File, start, len uint32) (bool, _ErrorCode) {
rc := vfsOS.lock(file, rc := osLock(file,
windows.LOCKFILE_FAIL_IMMEDIATELY, windows.LOCKFILE_FAIL_IMMEDIATELY,
start, len, 0, IOERR_CHECKRESERVEDLOCK) start, len, 0, _IOERR_CHECKRESERVEDLOCK)
if rc == xErrorCode(BUSY) { if rc == _BUSY {
return true, _OK return true, _OK
} }
if rc == _OK { if rc == _OK {
vfsOS.unlock(file, start, len) osUnlock(file, start, len)
} }
return false, rc return false, rc
} }
func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode { func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil { if err == nil {
return _OK return _OK
} }
@@ -181,7 +181,7 @@ func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode {
windows.ERROR_LOCK_VIOLATION, windows.ERROR_LOCK_VIOLATION,
windows.ERROR_IO_PENDING, windows.ERROR_IO_PENDING,
windows.ERROR_OPERATION_ABORTED: windows.ERROR_OPERATION_ABORTED:
return xErrorCode(BUSY) return _BUSY
} }
} }
return def return def

View File

@@ -1,4 +1,4 @@
package sqlite3 package vfs
import ( import (
"bytes" "bytes"
@@ -11,66 +11,67 @@ import (
"testing" "testing"
"time" "time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/julianday" "github.com/ncruces/julianday"
) )
func Test_vfsLocaltime(t *testing.T) { func Test_vfsLocaltime(t *testing.T) {
mem := newMemory(128) mod := util.NewMockModule(128)
ctx := context.TODO() ctx := context.TODO()
tm := time.Now() tm := time.Now()
rc := vfsLocaltime(ctx, mem.mod, 4, tm.Unix()) rc := vfsLocaltime(ctx, mod, 4, tm.Unix())
if rc != 0 { if rc != 0 {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if s := mem.readUint32(4 + 0*4); int(s) != tm.Second() { if s := util.ReadUint32(mod, 4+0*4); int(s) != tm.Second() {
t.Error("wrong second") t.Error("wrong second")
} }
if m := mem.readUint32(4 + 1*4); int(m) != tm.Minute() { if m := util.ReadUint32(mod, 4+1*4); int(m) != tm.Minute() {
t.Error("wrong minute") t.Error("wrong minute")
} }
if h := mem.readUint32(4 + 2*4); int(h) != tm.Hour() { if h := util.ReadUint32(mod, 4+2*4); int(h) != tm.Hour() {
t.Error("wrong hour") t.Error("wrong hour")
} }
if d := mem.readUint32(4 + 3*4); int(d) != tm.Day() { if d := util.ReadUint32(mod, 4+3*4); int(d) != tm.Day() {
t.Error("wrong day") t.Error("wrong day")
} }
if m := mem.readUint32(4 + 4*4); time.Month(1+m) != tm.Month() { if m := util.ReadUint32(mod, 4+4*4); time.Month(1+m) != tm.Month() {
t.Error("wrong month") t.Error("wrong month")
} }
if y := mem.readUint32(4 + 5*4); 1900+int(y) != tm.Year() { if y := util.ReadUint32(mod, 4+5*4); 1900+int(y) != tm.Year() {
t.Error("wrong year") t.Error("wrong year")
} }
if w := mem.readUint32(4 + 6*4); time.Weekday(w) != tm.Weekday() { if w := util.ReadUint32(mod, 4+6*4); time.Weekday(w) != tm.Weekday() {
t.Error("wrong weekday") t.Error("wrong weekday")
} }
if d := mem.readUint32(4 + 7*4); int(d) != tm.YearDay()-1 { if d := util.ReadUint32(mod, 4+7*4); int(d) != tm.YearDay()-1 {
t.Error("wrong yearday") t.Error("wrong yearday")
} }
} }
func Test_vfsRandomness(t *testing.T) { func Test_vfsRandomness(t *testing.T) {
mem := newMemory(128) mod := util.NewMockModule(128)
ctx := context.TODO() ctx := context.TODO()
rc := vfsRandomness(ctx, mem.mod, 0, 16, 4) rc := vfsRandomness(ctx, mod, 0, 16, 4)
if rc != 16 { if rc != 16 {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
var zero [16]byte var zero [16]byte
if got := mem.view(4, 16); bytes.Equal(got, zero[:]) { if got := util.View(mod, 4, 16); bytes.Equal(got, zero[:]) {
t.Fatal("all zero") t.Fatal("all zero")
} }
} }
func Test_vfsSleep(t *testing.T) { func Test_vfsSleep(t *testing.T) {
mem := newMemory(128) mod := util.NewMockModule(128)
ctx := context.TODO() ctx := context.TODO()
now := time.Now() now := time.Now()
rc := vfsSleep(ctx, mem.mod, 0, 123456) rc := vfsSleep(ctx, mod, 0, 123456)
if rc != 0 { if rc != 0 {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
@@ -82,56 +83,56 @@ func Test_vfsSleep(t *testing.T) {
} }
func Test_vfsCurrentTime(t *testing.T) { func Test_vfsCurrentTime(t *testing.T) {
mem := newMemory(128) mod := util.NewMockModule(128)
ctx := context.TODO() ctx := context.TODO()
now := time.Now() now := time.Now()
rc := vfsCurrentTime(ctx, mem.mod, 0, 4) rc := vfsCurrentTime(ctx, mod, 0, 4)
if rc != 0 { if rc != 0 {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
want := julianday.Float(now) want := julianday.Float(now)
if got := mem.readFloat64(4); float32(got) != float32(want) { if got := util.ReadFloat64(mod, 4); float32(got) != float32(want) {
t.Errorf("got %v, want %v", got, want) t.Errorf("got %v, want %v", got, want)
} }
} }
func Test_vfsCurrentTime64(t *testing.T) { func Test_vfsCurrentTime64(t *testing.T) {
mem := newMemory(128) mod := util.NewMockModule(128)
ctx := context.TODO() ctx := context.TODO()
now := time.Now() now := time.Now()
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
rc := vfsCurrentTime64(ctx, mem.mod, 0, 4) rc := vfsCurrentTime64(ctx, mod, 0, 4)
if rc != 0 { if rc != 0 {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
day, nsec := julianday.Date(now) day, nsec := julianday.Date(now)
want := day*86_400_000 + nsec/1_000_000 want := day*86_400_000 + nsec/1_000_000
if got := mem.readUint64(4); float32(got) != float32(want) { if got := util.ReadUint64(mod, 4); float32(got) != float32(want) {
t.Errorf("got %v, want %v", got, want) t.Errorf("got %v, want %v", got, want)
} }
} }
func Test_vfsFullPathname(t *testing.T) { func Test_vfsFullPathname(t *testing.T) {
mem := newMemory(128 + _MAX_PATHNAME) mod := util.NewMockModule(128 + _MAX_PATHNAME)
mem.writeString(4, ".") util.WriteString(mod, 4, ".")
ctx := context.TODO() ctx := context.TODO()
rc := vfsFullPathname(ctx, mem.mod, 0, 4, 0, 8) rc := vfsFullPathname(ctx, mod, 0, 4, 0, 8)
if rc != uint32(CANTOPEN_FULLPATH) { if rc != _CANTOPEN_FULLPATH {
t.Errorf("returned %d, want %d", rc, CANTOPEN_FULLPATH) t.Errorf("returned %d, want %d", rc, _CANTOPEN_FULLPATH)
} }
rc = vfsFullPathname(ctx, mem.mod, 0, 4, _MAX_PATHNAME, 8) rc = vfsFullPathname(ctx, mod, 0, 4, _MAX_PATHNAME, 8)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
want, _ := filepath.Abs(".") want, _ := filepath.Abs(".")
if got := mem.readString(8, _MAX_PATHNAME); got != want { if got := util.ReadString(mod, 8, _MAX_PATHNAME); got != want {
t.Errorf("got %v, want %v", got, want) t.Errorf("got %v, want %v", got, want)
} }
} }
@@ -145,11 +146,11 @@ func Test_vfsDelete(t *testing.T) {
} }
file.Close() file.Close()
mem := newMemory(128 + _MAX_PATHNAME) mod := util.NewMockModule(128 + _MAX_PATHNAME)
mem.writeString(4, name) util.WriteString(mod, 4, name)
ctx := context.TODO() ctx := context.TODO()
rc := vfsDelete(ctx, mem.mod, 0, 4, 1) rc := vfsDelete(ctx, mod, 0, 4, 1)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
@@ -158,8 +159,8 @@ func Test_vfsDelete(t *testing.T) {
t.Fatal("did not delete the file") t.Fatal("did not delete the file")
} }
rc = vfsDelete(ctx, mem.mod, 0, 4, 1) rc = vfsDelete(ctx, mod, 0, 4, 1)
if rc != uint32(IOERR_DELETE_NOENT) { if rc != _IOERR_DELETE_NOENT {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
} }
@@ -176,99 +177,99 @@ func Test_vfsAccess(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
mem := newMemory(128 + _MAX_PATHNAME) mod := util.NewMockModule(128 + _MAX_PATHNAME)
mem.writeString(8, dir) util.WriteString(mod, 8, dir)
ctx := context.TODO() ctx := context.TODO()
rc := vfsAccess(ctx, mem.mod, 0, 8, _ACCESS_EXISTS, 4) rc := vfsAccess(ctx, mod, 0, 8, _ACCESS_EXISTS, 4)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(4); got != 1 { if got := util.ReadUint32(mod, 4); got != 1 {
t.Error("directory did not exist") t.Error("directory did not exist")
} }
rc = vfsAccess(ctx, mem.mod, 0, 8, _ACCESS_READWRITE, 4) rc = vfsAccess(ctx, mod, 0, 8, _ACCESS_READWRITE, 4)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(4); got != 1 { if got := util.ReadUint32(mod, 4); got != 1 {
t.Error("can't access directory") t.Error("can't access directory")
} }
mem.writeString(8, file) util.WriteString(mod, 8, file)
rc = vfsAccess(ctx, mem.mod, 0, 8, _ACCESS_READWRITE, 4) rc = vfsAccess(ctx, mod, 0, 8, _ACCESS_READWRITE, 4)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(4); got != 0 { if got := util.ReadUint32(mod, 4); got != 0 {
t.Error("can access file") t.Error("can access file")
} }
} }
func Test_vfsFile(t *testing.T) { func Test_vfsFile(t *testing.T) {
mem := newMemory(128) mod := util.NewMockModule(128)
ctx, vfs := vfsContext(context.TODO()) ctx, vfs := Context(context.TODO())
defer vfs.Close() defer vfs.Close()
// Open a temporary file. // Open a temporary file.
rc := vfsOpen(ctx, mem.mod, 0, 0, 4, OPEN_CREATE|OPEN_EXCLUSIVE|OPEN_READWRITE|OPEN_DELETEONCLOSE, 0) rc := vfsOpen(ctx, mod, 0, 0, 4, _OPEN_CREATE|_OPEN_EXCLUSIVE|_OPEN_READWRITE|_OPEN_DELETEONCLOSE, 0)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
// Write stuff. // Write stuff.
text := "Hello world!" text := "Hello world!"
mem.writeString(16, text) util.WriteString(mod, 16, text)
rc = vfsWrite(ctx, mem.mod, 4, 16, uint32(len(text)), 0) rc = vfsWrite(ctx, mod, 4, 16, uint32(len(text)), 0)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
// Check file size. // Check file size.
rc = vfsFileSize(ctx, mem.mod, 4, 16) rc = vfsFileSize(ctx, mod, 4, 16)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(16); got != uint32(len(text)) { if got := util.ReadUint32(mod, 16); got != uint32(len(text)) {
t.Errorf("got %d", got) t.Errorf("got %d", got)
} }
// Partial read at offset. // Partial read at offset.
rc = vfsRead(ctx, mem.mod, 4, 16, uint32(len(text)), 4) rc = vfsRead(ctx, mod, 4, 16, uint32(len(text)), 4)
if rc != uint32(IOERR_SHORT_READ) { if rc != _IOERR_SHORT_READ {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readString(16, 64); got != text[4:] { if got := util.ReadString(mod, 16, 64); got != text[4:] {
t.Errorf("got %q", got) t.Errorf("got %q", got)
} }
// Truncate the file. // Truncate the file.
rc = vfsTruncate(ctx, mem.mod, 4, 4) rc = vfsTruncate(ctx, mod, 4, 4)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
// Check file size. // Check file size.
rc = vfsFileSize(ctx, mem.mod, 4, 16) rc = vfsFileSize(ctx, mod, 4, 16)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readUint32(16); got != 4 { if got := util.ReadUint32(mod, 16); got != 4 {
t.Errorf("got %d", got) t.Errorf("got %d", got)
} }
// Read at offset. // Read at offset.
rc = vfsRead(ctx, mem.mod, 4, 32, 4, 0) rc = vfsRead(ctx, mod, 4, 32, 4, 0)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }
if got := mem.readString(32, 64); got != text[:4] { if got := util.ReadString(mod, 32, 64); got != text[:4] {
t.Errorf("got %q", got) t.Errorf("got %q", got)
} }
// Close the file. // Close the file.
rc = vfsClose(ctx, mem.mod, 4) rc = vfsClose(ctx, mod, 4)
if rc != _OK { if rc != _OK {
t.Fatal("returned", rc) t.Fatal("returned", rc)
} }

151
mem.go
View File

@@ -1,151 +0,0 @@
package sqlite3
import (
"bytes"
"math"
"github.com/tetratelabs/wazero/api"
)
type memory struct {
mod api.Module
}
func (m memory) view(ptr uint32, size uint64) []byte {
if ptr == 0 {
panic(nilErr)
}
if size > math.MaxUint32 {
panic(rangeErr)
}
buf, ok := m.mod.Memory().Read(ptr, uint32(size))
if !ok {
panic(rangeErr)
}
return buf
}
func (m memory) readUint8(ptr uint32) uint8 {
if ptr == 0 {
panic(nilErr)
}
v, ok := m.mod.Memory().ReadByte(ptr)
if !ok {
panic(rangeErr)
}
return v
}
func (m memory) writeUint8(ptr uint32, v uint8) {
if ptr == 0 {
panic(nilErr)
}
ok := m.mod.Memory().WriteByte(ptr, v)
if !ok {
panic(rangeErr)
}
}
func (m memory) readUint32(ptr uint32) uint32 {
if ptr == 0 {
panic(nilErr)
}
v, ok := m.mod.Memory().ReadUint32Le(ptr)
if !ok {
panic(rangeErr)
}
return v
}
func (m memory) writeUint32(ptr uint32, v uint32) {
if ptr == 0 {
panic(nilErr)
}
ok := m.mod.Memory().WriteUint32Le(ptr, v)
if !ok {
panic(rangeErr)
}
}
func (m memory) readUint64(ptr uint32) uint64 {
if ptr == 0 {
panic(nilErr)
}
v, ok := m.mod.Memory().ReadUint64Le(ptr)
if !ok {
panic(rangeErr)
}
return v
}
func (m memory) writeUint64(ptr uint32, v uint64) {
if ptr == 0 {
panic(nilErr)
}
ok := m.mod.Memory().WriteUint64Le(ptr, v)
if !ok {
panic(rangeErr)
}
}
func (m memory) readBool8(ptr uint32) bool {
v := m.readUint8(ptr)
if v != 0 {
return true
}
return false
}
func (m memory) writeBool8(ptr uint32, v bool) {
var b uint8
if v {
b = 1
}
m.writeUint8(ptr, b)
}
func (m memory) readFloat64(ptr uint32) float64 {
return math.Float64frombits(m.readUint64(ptr))
}
func (m memory) writeFloat64(ptr uint32, v float64) {
m.writeUint64(ptr, math.Float64bits(v))
}
func (m memory) readString(ptr, maxlen uint32) string {
if ptr == 0 {
panic(nilErr)
}
switch maxlen {
case 0:
return ""
case math.MaxUint32:
// avoid overflow
default:
maxlen = maxlen + 1
}
mem := m.mod.Memory()
buf, ok := mem.Read(ptr, maxlen)
if !ok {
buf, ok = mem.Read(ptr, mem.Size()-ptr)
if !ok {
panic(rangeErr)
}
}
if i := bytes.IndexByte(buf, 0); i < 0 {
panic(noNulErr)
} else {
return string(buf[:i])
}
}
func (m memory) writeBytes(ptr uint32, b []byte) {
buf := m.view(ptr, uint64(len(b)))
copy(buf, b)
}
func (m memory) writeString(ptr uint32, s string) {
buf := m.view(ptr, uint64(len(s)+1))
buf[len(s)] = 0
copy(buf, s)
}

View File

@@ -1,90 +0,0 @@
package sqlite3
import (
"math"
"testing"
)
func Test_memory_view_nil(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.view(0, 8)
t.Error("want panic")
}
func Test_memory_view_range(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.view(126, 8)
t.Error("want panic")
}
func Test_memory_view_overflow(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.view(1, math.MaxInt64)
t.Error("want panic")
}
func Test_memory_readUint32_nil(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.readUint32(0)
t.Error("want panic")
}
func Test_memory_readUint32_range(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.readUint32(126)
t.Error("want panic")
}
func Test_memory_readUint64_nil(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.readUint64(0)
t.Error("want panic")
}
func Test_memory_readUint64_range(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.readUint64(126)
t.Error("want panic")
}
func Test_memory_writeUint32_nil(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.writeUint32(0, 1)
t.Error("want panic")
}
func Test_memory_writeUint32_range(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.writeUint32(126, 1)
t.Error("want panic")
}
func Test_memory_writeUint64_nil(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.writeUint64(0, 1)
t.Error("want panic")
}
func Test_memory_writeUint64_range(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.writeUint64(126, 1)
t.Error("want panic")
}
func Test_memory_readString_range(t *testing.T) {
defer func() { _ = recover() }()
mem := newMemory(128)
mem.readString(130, math.MaxUint32)
t.Error("want panic")
}

View File

@@ -8,6 +8,8 @@ import (
"os" "os"
"sync" "sync"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/internal/vfs"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
) )
@@ -50,7 +52,7 @@ func instantiateModule() (*module, error) {
func compileModule() { func compileModule() {
ctx := context.Background() ctx := context.Background()
sqlite3.runtime = wazero.NewRuntime(ctx) sqlite3.runtime = wazero.NewRuntime(ctx)
vfsInstantiate(ctx, sqlite3.runtime) vfs.Instantiate(ctx, sqlite3.runtime)
bin := Binary bin := Binary
if bin == nil && Path != "" { if bin == nil && Path != "" {
@@ -60,7 +62,7 @@ func compileModule() {
} }
} }
if bin == nil { if bin == nil {
sqlite3.err = binaryErr sqlite3.err = util.BinaryErr
return return
} }
@@ -69,20 +71,20 @@ func compileModule() {
type module struct { type module struct {
ctx context.Context ctx context.Context
mem memory mod api.Module
api sqliteAPI api sqliteAPI
vfs io.Closer vfs io.Closer
} }
func newModule(mod api.Module) (m *module, err error) { func newModule(mod api.Module) (m *module, err error) {
m = &module{} m = &module{}
m.mem = memory{mod} m.mod = mod
m.ctx, m.vfs = vfsContext(context.Background()) m.ctx, m.vfs = vfs.Context(context.Background())
getFun := func(name string) api.Function { getFun := func(name string) api.Function {
f := mod.ExportedFunction(name) f := mod.ExportedFunction(name)
if f == nil { if f == nil {
err = noFuncErr + errorString(name) err = util.NoFuncErr + util.ErrorString(name)
return nil return nil
} }
return f return f
@@ -91,10 +93,10 @@ func newModule(mod api.Module) (m *module, err error) {
getVal := func(name string) uint32 { getVal := func(name string) uint32 {
global := mod.ExportedGlobal(name) global := mod.ExportedGlobal(name)
if global == nil { if global == nil {
err = noGlobalErr + errorString(name) err = util.NoGlobalErr + util.ErrorString(name)
return 0 return 0
} }
return m.mem.readUint32(uint32(global.Get())) return util.ReadUint32(mod, uint32(global.Get()))
} }
m.api = sqliteAPI{ m.api = sqliteAPI{
@@ -154,7 +156,7 @@ func newModule(mod api.Module) (m *module, err error) {
} }
func (m *module) close() error { func (m *module) close() error {
err := m.mem.mod.Close(m.ctx) err := m.mod.Close(m.ctx)
m.vfs.Close() m.vfs.Close()
return err return err
} }
@@ -167,19 +169,19 @@ func (m *module) error(rc uint64, handle uint32, sql ...string) error {
err := Error{code: rc} err := Error{code: rc}
if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM { if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM {
panic(oomErr) panic(util.OOMErr)
} }
var r []uint64 var r []uint64
r = m.call(m.api.errstr, rc) r = m.call(m.api.errstr, rc)
if r != nil { if r != nil {
err.str = m.mem.readString(uint32(r[0]), _MAX_STRING) err.str = util.ReadString(m.mod, uint32(r[0]), _MAX_STRING)
} }
r = m.call(m.api.errmsg, uint64(handle)) r = m.call(m.api.errmsg, uint64(handle))
if r != nil { if r != nil {
err.msg = m.mem.readString(uint32(r[0]), _MAX_STRING) err.msg = util.ReadString(m.mod, uint32(r[0]), _MAX_STRING)
} }
if sql != nil { if sql != nil {
@@ -215,12 +217,12 @@ func (m *module) free(ptr uint32) {
func (m *module) new(size uint64) uint32 { func (m *module) new(size uint64) uint32 {
if size > _MAX_ALLOCATION_SIZE { if size > _MAX_ALLOCATION_SIZE {
panic(oomErr) panic(util.OOMErr)
} }
r := m.call(m.api.malloc, size) r := m.call(m.api.malloc, size)
ptr := uint32(r[0]) ptr := uint32(r[0])
if ptr == 0 && size != 0 { if ptr == 0 && size != 0 {
panic(oomErr) panic(util.OOMErr)
} }
return ptr return ptr
} }
@@ -230,13 +232,13 @@ func (m *module) newBytes(b []byte) uint32 {
return 0 return 0
} }
ptr := m.new(uint64(len(b))) ptr := m.new(uint64(len(b)))
m.mem.writeBytes(ptr, b) util.WriteBytes(m.mod, ptr, b)
return ptr return ptr
} }
func (m *module) newString(s string) uint32 { func (m *module) newString(s string) uint32 {
ptr := m.new(uint64(len(s) + 1)) ptr := m.new(uint64(len(s) + 1))
m.mem.writeString(ptr, s) util.WriteString(m.mod, ptr, s)
return ptr return ptr
} }
@@ -286,7 +288,7 @@ func (a *arena) new(size uint64) uint32 {
func (a *arena) string(s string) uint32 { func (a *arena) string(s string) uint32 {
ptr := a.new(uint64(len(s) + 1)) ptr := a.new(uint64(len(s) + 1))
a.m.mem.writeString(ptr, s) util.WriteString(a.m.mod, ptr, s)
return ptr return ptr
} }

View File

@@ -4,8 +4,14 @@ import (
"bytes" "bytes"
"math" "math"
"testing" "testing"
"github.com/ncruces/go-sqlite3/internal/util"
) )
func init() {
Path = "./embed/sqlite3.wasm"
}
func TestConn_error_OOM(t *testing.T) { func TestConn_error_OOM(t *testing.T) {
t.Parallel() t.Parallel()
@@ -71,7 +77,7 @@ func TestConn_newArena(t *testing.T) {
if ptr == 0 { if ptr == 0 {
t.Fatalf("got nullptr") t.Fatalf("got nullptr")
} }
if got := m.mem.readString(ptr, math.MaxUint32); got != title { if got := util.ReadString(m.mod, ptr, math.MaxUint32); got != title {
t.Errorf("got %q, want %q", got, title) t.Errorf("got %q, want %q", got, title)
} }
@@ -80,7 +86,7 @@ func TestConn_newArena(t *testing.T) {
if ptr == 0 { if ptr == 0 {
t.Fatalf("got nullptr") t.Fatalf("got nullptr")
} }
if got := m.mem.readString(ptr, math.MaxUint32); got != body { if got := util.ReadString(m.mod, ptr, math.MaxUint32); got != body {
t.Errorf("got %q, want %q", got, body) t.Errorf("got %q, want %q", got, body)
} }
arena.free() arena.free()
@@ -107,7 +113,7 @@ func TestConn_newBytes(t *testing.T) {
} }
want := buf want := buf
if got := m.mem.view(ptr, uint64(len(want))); !bytes.Equal(got, want) { if got := util.View(m.mod, ptr, uint64(len(want))); !bytes.Equal(got, want) {
t.Errorf("got %q, want %q", got, want) t.Errorf("got %q, want %q", got, want)
} }
} }
@@ -133,7 +139,7 @@ func TestConn_newString(t *testing.T) {
} }
want := str + "\000" want := str + "\000"
if got := m.mem.view(ptr, uint64(len(want))); string(got) != want { if got := util.View(m.mod, ptr, uint64(len(want))); string(got) != want {
t.Errorf("got %q, want %q", got, want) t.Errorf("got %q, want %q", got, want)
} }
} }
@@ -159,22 +165,22 @@ func TestConn_getString(t *testing.T) {
} }
want := "sqlite3" want := "sqlite3"
if got := m.mem.readString(ptr, math.MaxUint32); got != want { if got := util.ReadString(m.mod, ptr, math.MaxUint32); got != want {
t.Errorf("got %q, want %q", got, want) t.Errorf("got %q, want %q", got, want)
} }
if got := m.mem.readString(ptr, 0); got != "" { if got := util.ReadString(m.mod, ptr, 0); got != "" {
t.Errorf("got %q, want empty", got) t.Errorf("got %q, want empty", got)
} }
func() { func() {
defer func() { _ = recover() }() defer func() { _ = recover() }()
m.mem.readString(ptr, uint32(len(want)/2)) util.ReadString(m.mod, ptr, uint32(len(want)/2))
t.Error("want panic") t.Error("want panic")
}() }()
func() { func() {
defer func() { _ = recover() }() defer func() { _ = recover() }()
m.mem.readString(0, math.MaxUint32) util.ReadString(m.mod, 0, math.MaxUint32)
t.Error("want panic") t.Error("want panic")
}() }()
} }

16
stmt.go
View File

@@ -3,6 +3,8 @@ package sqlite3
import ( import (
"math" "math"
"time" "time"
"github.com/ncruces/go-sqlite3/internal/util"
) )
// Stmt is a prepared statement object. // Stmt is a prepared statement object.
@@ -119,7 +121,7 @@ func (s *Stmt) BindName(param int) string {
if ptr == 0 { if ptr == 0 {
return "" return ""
} }
return s.c.mem.readString(ptr, _MAX_STRING) return util.ReadString(s.c.mod, ptr, _MAX_STRING)
} }
// BindBool binds a bool to the prepared statement. // BindBool binds a bool to the prepared statement.
@@ -223,7 +225,7 @@ func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error {
case float64: case float64:
s.BindFloat(param, v) s.BindFloat(param, v)
default: default:
panic(assertErr()) panic(util.AssertErr())
} }
return nil return nil
} }
@@ -247,9 +249,9 @@ func (s *Stmt) ColumnName(col int) string {
ptr := uint32(r[0]) ptr := uint32(r[0])
if ptr == 0 { if ptr == 0 {
panic(oomErr) panic(util.OOMErr)
} }
return s.c.mem.readString(ptr, _MAX_STRING) return util.ReadString(s.c.mod, ptr, _MAX_STRING)
} }
// ColumnType returns the initial [Datatype] of the result column. // ColumnType returns the initial [Datatype] of the result column.
@@ -320,7 +322,7 @@ func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time {
case NULL: case NULL:
return time.Time{} return time.Time{}
default: default:
panic(assertErr()) panic(util.AssertErr())
} }
t, err := format.Decode(v) t, err := format.Decode(v)
if err != nil { if err != nil {
@@ -366,7 +368,7 @@ func (s *Stmt) ColumnRawText(col int) []byte {
r = s.c.call(s.c.api.columnBytes, r = s.c.call(s.c.api.columnBytes,
uint64(s.handle), uint64(col)) uint64(s.handle), uint64(col))
return s.c.mem.view(ptr, r[0]) return util.View(s.c.mod, ptr, r[0])
} }
// ColumnRawBlob returns the value of the result column as a []byte. // ColumnRawBlob returns the value of the result column as a []byte.
@@ -389,7 +391,7 @@ func (s *Stmt) ColumnRawBlob(col int) []byte {
r = s.c.call(s.c.api.columnBytes, r = s.c.call(s.c.api.columnBytes,
uint64(s.handle), uint64(col)) uint64(s.handle), uint64(col))
return s.c.mem.view(ptr, r[0]) return util.View(s.c.mod, ptr, r[0])
} }
// Return true if stmt is an empty SQL statement. // Return true if stmt is an empty SQL statement.

View File

@@ -23,6 +23,7 @@ import (
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
_ "github.com/ncruces/go-sqlite3" _ "github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/vfs"
) )
//go:embed testdata/mptest.wasm //go:embed testdata/mptest.wasm
@@ -31,12 +32,6 @@ var binary []byte
//go:embed testdata/*.*test //go:embed testdata/*.*test
var scripts embed.FS var scripts embed.FS
//go:linkname vfsNewEnvModuleBuilder github.com/ncruces/go-sqlite3.vfsNewEnvModuleBuilder
func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder
//go:linkname vfsContext github.com/ncruces/go-sqlite3.vfsContext
func vfsContext(ctx context.Context) (context.Context, io.Closer)
var ( var (
rt wazero.Runtime rt wazero.Runtime
module wazero.CompiledModule module wazero.CompiledModule
@@ -48,7 +43,7 @@ func init() {
rt = wazero.NewRuntime(ctx) rt = wazero.NewRuntime(ctx)
wasi_snapshot_preview1.MustInstantiate(ctx, rt) wasi_snapshot_preview1.MustInstantiate(ctx, rt)
env := vfsNewEnvModuleBuilder(rt) env := vfs.NewEnvModuleBuilder(rt)
env.NewFunctionBuilder().WithFunc(system).Export("system") env.NewFunctionBuilder().WithFunc(system).Export("system")
_, err := env.Instantiate(ctx) _, err := env.Instantiate(ctx)
if err != nil { if err != nil {
@@ -88,7 +83,7 @@ func system(ctx context.Context, mod api.Module, ptr uint32) uint32 {
cfg := config(ctx).WithArgs(args...) cfg := config(ctx).WithArgs(args...)
go func() { go func() {
ctx, vfs := vfsContext(ctx) ctx, vfs := vfs.Context(ctx)
rt.InstantiateModule(ctx, module, cfg) rt.InstantiateModule(ctx, module, cfg)
vfs.Close() vfs.Close()
}() }()
@@ -96,7 +91,7 @@ func system(ctx context.Context, mod api.Module, ptr uint32) uint32 {
} }
func Test_config01(t *testing.T) { func Test_config01(t *testing.T) {
ctx, vfs := vfsContext(newContext(t)) ctx, vfs := vfs.Context(newContext(t))
name := filepath.Join(t.TempDir(), "test.db") name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "config01.test") cfg := config(ctx).WithArgs("mptest", name, "config01.test")
mod, err := rt.InstantiateModule(ctx, module, cfg) mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -115,7 +110,7 @@ func Test_config02(t *testing.T) {
t.Skip("skipping in CI") t.Skip("skipping in CI")
} }
ctx, vfs := vfsContext(newContext(t)) ctx, vfs := vfs.Context(newContext(t))
name := filepath.Join(t.TempDir(), "test.db") name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "config02.test") cfg := config(ctx).WithArgs("mptest", name, "config02.test")
mod, err := rt.InstantiateModule(ctx, module, cfg) mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -131,7 +126,7 @@ func Test_crash01(t *testing.T) {
t.Skip("skipping in short mode") t.Skip("skipping in short mode")
} }
ctx, vfs := vfsContext(newContext(t)) ctx, vfs := vfs.Context(newContext(t))
name := filepath.Join(t.TempDir(), "test.db") name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "crash01.test") cfg := config(ctx).WithArgs("mptest", name, "crash01.test")
mod, err := rt.InstantiateModule(ctx, module, cfg) mod, err := rt.InstantiateModule(ctx, module, cfg)
@@ -147,7 +142,7 @@ func Test_multiwrite01(t *testing.T) {
t.Skip("skipping in short mode") t.Skip("skipping in short mode")
} }
ctx, vfs := vfsContext(newContext(t)) ctx, vfs := vfs.Context(newContext(t))
name := filepath.Join(t.TempDir(), "test.db") name := filepath.Join(t.TempDir(), "test.db")
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test") cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test")
mod, err := rt.InstantiateModule(ctx, module, cfg) mod, err := rt.InstantiateModule(ctx, module, cfg)

View File

@@ -19,17 +19,12 @@ import (
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
_ "github.com/ncruces/go-sqlite3" _ "github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/vfs"
) )
//go:embed testdata/speedtest1.wasm //go:embed testdata/speedtest1.wasm
var binary []byte var binary []byte
//go:linkname vfsNewEnvModuleBuilder github.com/ncruces/go-sqlite3.vfsNewEnvModuleBuilder
func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder
//go:linkname vfsContext github.com/ncruces/go-sqlite3.vfsContext
func vfsContext(ctx context.Context) (context.Context, io.Closer)
var ( var (
rt wazero.Runtime rt wazero.Runtime
module wazero.CompiledModule module wazero.CompiledModule
@@ -42,7 +37,7 @@ func init() {
rt = wazero.NewRuntime(ctx) rt = wazero.NewRuntime(ctx)
wasi_snapshot_preview1.MustInstantiate(ctx, rt) wasi_snapshot_preview1.MustInstantiate(ctx, rt)
env := vfsNewEnvModuleBuilder(rt) env := vfs.NewEnvModuleBuilder(rt)
_, err := env.Instantiate(ctx) _, err := env.Instantiate(ctx)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -74,7 +69,7 @@ func TestMain(m *testing.M) {
func Benchmark_speedtest1(b *testing.B) { func Benchmark_speedtest1(b *testing.B) {
output.Reset() output.Reset()
ctx, vfs := vfsContext(context.Background()) ctx, vfs := vfs.Context(context.Background())
name := filepath.Join(b.TempDir(), "test.db") name := filepath.Join(b.TempDir(), "test.db")
args := append(options, "--size", strconv.Itoa(b.N), name) args := append(options, "--size", strconv.Itoa(b.N), name)
cfg := wazero.NewModuleConfig(). cfg := wazero.NewModuleConfig().

21
time.go
View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/julianday" "github.com/ncruces/julianday"
) )
@@ -148,7 +149,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
case int64: case int64:
return julianday.Time(v, 0), nil return julianday.Time(v, 0), nil
default: default:
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
case TimeFormatUnix, TimeFormatUnixFrac: case TimeFormatUnix, TimeFormatUnixFrac:
@@ -167,7 +168,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
case int64: case int64:
return time.Unix(v, 0), nil return time.Unix(v, 0), nil
default: default:
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
case TimeFormatUnixMilli: case TimeFormatUnixMilli:
@@ -184,7 +185,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
case int64: case int64:
return time.UnixMilli(int64(v)), nil return time.UnixMilli(int64(v)), nil
default: default:
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
case TimeFormatUnixMicro: case TimeFormatUnixMicro:
@@ -201,14 +202,14 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
case int64: case int64:
return time.UnixMicro(int64(v)), nil return time.UnixMicro(int64(v)), nil
default: default:
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
case TimeFormatUnixNano: case TimeFormatUnixNano:
if s, ok := v.(string); ok { if s, ok := v.(string); ok {
i, err := strconv.ParseInt(s, 10, 64) i, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
v = i v = i
} }
@@ -218,7 +219,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
case int64: case int64:
return time.Unix(0, int64(v)), nil return time.Unix(0, int64(v)), nil
default: default:
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
// Special formats // Special formats
@@ -288,7 +289,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
} }
return TimeFormatUnixNano.Decode(v) return TimeFormatUnixNano.Decode(v)
default: default:
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
case case
@@ -300,7 +301,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
TimeFormat7, TimeFormat7TZ: TimeFormat7, TimeFormat7TZ:
s, ok := v.(string) s, ok := v.(string)
if !ok { if !ok {
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
return f.parseRelaxed(s) return f.parseRelaxed(s)
@@ -310,7 +311,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
TimeFormat10, TimeFormat10TZ: TimeFormat10, TimeFormat10TZ:
s, ok := v.(string) s, ok := v.(string)
if !ok { if !ok {
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
t, err := f.parseRelaxed(s) t, err := f.parseRelaxed(s)
return t.AddDate(2000, 0, 0), err return t.AddDate(2000, 0, 0), err
@@ -318,7 +319,7 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
default: default:
s, ok := v.(string) s, ok := v.(string)
if !ok { if !ok {
return time.Time{}, timeErr return time.Time{}, util.TimeErr
} }
if f == "" { if f == "" {
f = time.RFC3339Nano f = time.RFC3339Nano

440
vfs.go
View File

@@ -1,440 +0,0 @@
package sqlite3
import (
"context"
"crypto/rand"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"time"
"github.com/ncruces/julianday"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
func vfsInstantiate(ctx context.Context, r wazero.Runtime) {
env := vfsNewEnvModuleBuilder(r)
_, err := env.Instantiate(ctx)
if err != nil {
panic(err)
}
}
func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
env := r.NewHostModuleBuilder("env")
vfsRegisterFuncT(env, "os_localtime", vfsLocaltime)
vfsRegisterFunc3(env, "os_randomness", vfsRandomness)
vfsRegisterFunc2(env, "os_sleep", vfsSleep)
vfsRegisterFunc2(env, "os_current_time", vfsCurrentTime)
vfsRegisterFunc2(env, "os_current_time_64", vfsCurrentTime64)
vfsRegisterFunc4(env, "os_full_pathname", vfsFullPathname)
vfsRegisterFunc3(env, "os_delete", vfsDelete)
vfsRegisterFunc4(env, "os_access", vfsAccess)
vfsRegisterFunc5(env, "os_open", vfsOpen)
vfsRegisterFunc1(env, "os_close", vfsClose)
vfsRegisterFuncRW(env, "os_read", vfsRead)
vfsRegisterFuncRW(env, "os_write", vfsWrite)
vfsRegisterFuncT(env, "os_truncate", vfsTruncate)
vfsRegisterFunc2(env, "os_sync", vfsSync)
vfsRegisterFunc2(env, "os_file_size", vfsFileSize)
vfsRegisterFunc2(env, "os_lock", vfsLock)
vfsRegisterFunc2(env, "os_unlock", vfsUnlock)
vfsRegisterFunc2(env, "os_check_reserved_lock", vfsCheckReservedLock)
vfsRegisterFunc3(env, "os_file_control", vfsFileControl)
return env
}
// Poor man's namespaces.
const (
vfsOS vfsOSMethods = false
vfsFile vfsFileMethods = false
)
type (
vfsOSMethods bool
vfsFileMethods bool
)
type vfsKey struct{}
type vfsState struct {
files []*os.File
}
func vfsContext(ctx context.Context) (context.Context, io.Closer) {
vfs := &vfsState{}
return context.WithValue(ctx, vfsKey{}, vfs), vfs
}
func (vfs *vfsState) Close() error {
for _, f := range vfs.files {
if f != nil {
f.Close()
}
}
vfs.files = nil
return nil
}
func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) uint32 {
tm := time.Unix(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}.view(zByte, uint64(nByte))
n, _ := rand.Reader.Read(mem)
return uint32(n)
}
func vfsSleep(ctx context.Context, mod api.Module, 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(CANTOPEN_FULLPATH)
}
size := uint64(len(abs) + 1)
if size > uint64(nFull) {
return uint32(CANTOPEN_FULLPATH)
}
mem := memory{mod}.view(zFull, size)
mem[len(abs)] = 0
copy(mem, abs)
if fi, err := os.Lstat(abs); err == nil {
if fi.Mode()&fs.ModeSymlink != 0 {
return _OK_SYMLINK
}
return _OK
} else if errors.Is(err, fs.ErrNotExist) {
return _OK
}
return uint32(CANTOPEN_FULLPATH)
}
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 uint32(IOERR_DELETE_NOENT)
}
if err != nil {
return uint32(IOERR_DELETE)
}
if runtime.GOOS != "windows" && syncDir != 0 {
f, err := os.Open(filepath.Dir(path))
if err != nil {
return _OK
}
defer f.Close()
err = vfsOS.Sync(f, false, false)
if err != nil {
return uint32(IOERR_DIR_FSYNC)
}
}
return _OK
}
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags _AccessFlag, pResOut uint32) uint32 {
path := memory{mod}.readString(zPath, _MAX_PATHNAME)
err := vfsOS.Access(path, flags)
var res uint32
var rc xErrorCode
if flags == _ACCESS_EXISTS {
switch {
case err == nil:
res = 1
case errors.Is(err, fs.ErrNotExist):
res = 0
default:
rc = IOERR_ACCESS
}
} else {
switch {
case err == nil:
res = 1
case errors.Is(err, fs.ErrPermission):
res = 0
default:
rc = IOERR_ACCESS
}
}
memory{mod}.writeUint32(pResOut, res)
return uint32(rc)
}
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 = vfsOS.OpenFile(name, oflags, 0666)
}
if err != nil {
return uint32(CANTOPEN)
}
if flags&OPEN_DELETEONCLOSE != 0 {
os.Remove(file.Name())
}
vfsFile.Open(ctx, mod, pFile, file)
if flags&OPEN_READONLY != 0 {
vfsFile.SetReadOnly(ctx, mod, pFile, true)
}
if runtime.GOOS != "windows" &&
flags&(OPEN_CREATE) != 0 &&
flags&(OPEN_MAIN_JOURNAL|OPEN_SUPER_JOURNAL|OPEN_WAL) != 0 {
vfsFile.SetSyncDir(ctx, mod, pFile, true)
}
if pOutFlags != 0 {
memory{mod}.writeUint32(pOutFlags, uint32(flags))
}
return _OK
}
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) uint32 {
err := vfsFile.Close(ctx, mod, pFile)
if err != nil {
return uint32(IOERR_CLOSE)
}
return _OK
}
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) uint32 {
buf := memory{mod}.view(zBuf, uint64(iAmt))
file := vfsFile.GetOS(ctx, mod, pFile)
n, err := file.ReadAt(buf, iOfst)
if n == int(iAmt) {
return _OK
}
if n == 0 && err != io.EOF {
return uint32(IOERR_READ)
}
for i := range buf[n:] {
buf[n+i] = 0
}
return uint32(IOERR_SHORT_READ)
}
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) uint32 {
buf := memory{mod}.view(zBuf, uint64(iAmt))
file := vfsFile.GetOS(ctx, mod, pFile)
_, err := file.WriteAt(buf, iOfst)
if err != nil {
return uint32(IOERR_WRITE)
}
return _OK
}
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) uint32 {
file := vfsFile.GetOS(ctx, mod, pFile)
err := file.Truncate(nByte)
if err != nil {
return uint32(IOERR_TRUNCATE)
}
return _OK
}
func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags _SyncFlag) uint32 {
dataonly := (flags & _SYNC_DATAONLY) != 0
fullsync := (flags & 0x0f) == _SYNC_FULL
file := vfsFile.GetOS(ctx, mod, pFile)
err := vfsOS.Sync(file, fullsync, dataonly)
if err != nil {
return uint32(IOERR_FSYNC)
}
if runtime.GOOS != "windows" && vfsFile.GetSyncDir(ctx, mod, pFile) {
vfsFile.SetSyncDir(ctx, mod, pFile, false)
f, err := os.Open(filepath.Dir(file.Name()))
if err != nil {
return _OK
}
defer f.Close()
err = vfsOS.Sync(f, false, false)
if err != nil {
return uint32(IOERR_DIR_FSYNC)
}
}
return _OK
}
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) uint32 {
file := vfsFile.GetOS(ctx, mod, pFile)
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return uint32(IOERR_SEEK)
}
memory{mod}.writeUint64(pSize, uint64(off))
return _OK
}
func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) uint32 {
switch op {
case _FCNTL_SIZE_HINT:
return vfsSizeHint(ctx, mod, pFile, pArg)
case _FCNTL_HAS_MOVED:
return vfsFileMoved(ctx, mod, pFile, pArg)
}
return uint32(NOTFOUND)
}
func vfsSizeHint(ctx context.Context, mod api.Module, pFile, pArg uint32) uint32 {
file := vfsFile.GetOS(ctx, mod, pFile)
size := memory{mod}.readUint64(pArg)
err := vfsOS.Allocate(file, int64(size))
if err != nil {
return uint32(IOERR_TRUNCATE)
}
return _OK
}
func vfsFileMoved(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
file := vfsFile.GetOS(ctx, mod, pFile)
fi, err := file.Stat()
if err != nil {
return uint32(IOERR_FSTAT)
}
pi, err := os.Stat(file.Name())
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return uint32(IOERR_FSTAT)
}
var res uint32
if !os.SameFile(fi, pi) {
res = 1
}
memory{mod}.writeUint32(pResOut, res)
return _OK
}
func vfsRegisterFunc1(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ uint32) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, uint32(stack[0])))
}),
[]api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFunc2[T0, T1 ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFunc3[T0, T1, T2 ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFunc4[T0, T1, T2, T3 ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFunc5[T0, T1, T2, T3, T4 ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3, _ T4) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFuncRW(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _, _, _ uint32, _ int64) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), uint32(stack[1]), uint32(stack[2]), int64(stack[3])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFuncT(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ uint32, _ int64) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), int64(stack[1])))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}

View File

@@ -1,91 +0,0 @@
package sqlite3
import (
"context"
"os"
"time"
"github.com/tetratelabs/wazero/api"
)
const (
// These need to match the offsets asserted in os.c
vfsFileIDOffset = 4
vfsFileLockOffset = 8
vfsFileSyncDirOffset = 10
vfsFileReadOnlyOffset = 11
vfsFileLockTimeoutOffset = 12
)
func (vfsFileMethods) NewID(ctx context.Context, file *os.File) uint32 {
vfs := ctx.Value(vfsKey{}).(*vfsState)
// Find an empty slot.
for id, ptr := range vfs.files {
if ptr == nil {
vfs.files[id] = file
return uint32(id)
}
}
// Add a new slot.
vfs.files = append(vfs.files, file)
return uint32(len(vfs.files) - 1)
}
func (vfsFileMethods) Open(ctx context.Context, mod api.Module, pFile uint32, file *os.File) {
mem := memory{mod}
id := vfsFile.NewID(ctx, file)
mem.writeUint32(pFile+vfsFileIDOffset, id)
}
func (vfsFileMethods) Close(ctx context.Context, mod api.Module, pFile uint32) error {
mem := memory{mod}
id := mem.readUint32(pFile + vfsFileIDOffset)
vfs := ctx.Value(vfsKey{}).(*vfsState)
file := vfs.files[id]
vfs.files[id] = nil
return file.Close()
}
func (vfsFileMethods) GetOS(ctx context.Context, mod api.Module, pFile uint32) *os.File {
mem := memory{mod}
id := mem.readUint32(pFile + vfsFileIDOffset)
vfs := ctx.Value(vfsKey{}).(*vfsState)
return vfs.files[id]
}
func (vfsFileMethods) GetLock(ctx context.Context, mod api.Module, pFile uint32) vfsLockState {
mem := memory{mod}
return vfsLockState(mem.readUint8(pFile + vfsFileLockOffset))
}
func (vfsFileMethods) SetLock(ctx context.Context, mod api.Module, pFile uint32, lock vfsLockState) {
mem := memory{mod}
mem.writeUint8(pFile+vfsFileLockOffset, uint8(lock))
}
func (vfsFileMethods) GetLockTimeout(ctx context.Context, mod api.Module, pFile uint32) time.Duration {
mem := memory{mod}
return time.Duration(mem.readUint32(pFile+vfsFileLockTimeoutOffset)) * time.Millisecond
}
func (vfsFileMethods) GetSyncDir(ctx context.Context, mod api.Module, pFile uint32) bool {
mem := memory{mod}
return mem.readBool8(pFile + vfsFileSyncDirOffset)
}
func (vfsFileMethods) SetSyncDir(ctx context.Context, mod api.Module, pFile uint32, val bool) {
mem := memory{mod}
mem.writeBool8(pFile+vfsFileSyncDirOffset, val)
}
func (vfsFileMethods) GetReadOnly(ctx context.Context, mod api.Module, pFile uint32) bool {
mem := memory{mod}
return mem.readBool8(pFile + vfsFileReadOnlyOffset)
}
func (vfsFileMethods) SetReadOnly(ctx context.Context, mod api.Module, pFile uint32, val bool) {
mem := memory{mod}
mem.writeBool8(pFile+vfsFileReadOnlyOffset, val)
}

View File

@@ -1,56 +0,0 @@
//go:build freebsd || openbsd || netbsd || dragonfly || (darwin && sqlite3_bsd)
package sqlite3
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
if start == 0 && len == 0 {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return IOERR_UNLOCK
}
}
return _OK
}
func (vfsOSMethods) lock(file *os.File, how int, timeout time.Duration, def xErrorCode) xErrorCode {
var err error
for {
err = unix.Flock(int(file.Fd()), how)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, def)
}
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.LOCK_SH|unix.LOCK_NB, timeout, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.LOCK_EX|unix.LOCK_NB, timeout, IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}