Refactor VFS.

This commit is contained in:
Nuno Cruces
2023-05-18 16:00:34 +01:00
parent 67cc3d35d5
commit df953b31c2
11 changed files with 475 additions and 345 deletions

View File

@@ -3,6 +3,8 @@ package sqlite3
import (
"strconv"
"strings"
"github.com/ncruces/go-sqlite3/internal/util"
)
// Error wraps an SQLite Error Code.
@@ -83,72 +85,7 @@ func (e *Error) SQL() string {
// Error implements the error interface.
func (e ErrorCode) Error() string {
switch e {
case _OK:
return "sqlite3: not an error"
case _ROW:
return "sqlite3: another row available"
case _DONE:
return "sqlite3: no more rows available"
case ERROR:
return "sqlite3: SQL logic error"
case INTERNAL:
break
case PERM:
return "sqlite3: access permission denied"
case ABORT:
return "sqlite3: query aborted"
case BUSY:
return "sqlite3: database is locked"
case LOCKED:
return "sqlite3: database table is locked"
case NOMEM:
return "sqlite3: out of memory"
case READONLY:
return "sqlite3: attempt to write a readonly database"
case INTERRUPT:
return "sqlite3: interrupted"
case IOERR:
return "sqlite3: disk I/O error"
case CORRUPT:
return "sqlite3: database disk image is malformed"
case NOTFOUND:
return "sqlite3: unknown operation"
case FULL:
return "sqlite3: database or disk is full"
case CANTOPEN:
return "sqlite3: unable to open database file"
case PROTOCOL:
return "sqlite3: locking protocol"
case FORMAT:
break
case SCHEMA:
return "sqlite3: database schema has changed"
case TOOBIG:
return "sqlite3: string or blob too big"
case CONSTRAINT:
return "sqlite3: constraint failed"
case MISMATCH:
return "sqlite3: datatype mismatch"
case MISUSE:
return "sqlite3: bad parameter or other API misuse"
case NOLFS:
break
case AUTH:
return "sqlite3: authorization denied"
case EMPTY:
break
case RANGE:
return "sqlite3: column index out of range"
case NOTADB:
return "sqlite3: file is not a database"
case NOTICE:
return "sqlite3: notification message"
case WARNING:
return "sqlite3: warning message"
}
return "sqlite3: unknown error"
return util.ErrorCodeString(uint32(e))
}
// Temporary returns true for [BUSY] errors.
@@ -158,17 +95,7 @@ func (e ErrorCode) Temporary() bool {
// Error implements the error interface.
func (e ExtendedErrorCode) Error() string {
switch x := ErrorCode(e); {
case e == ABORT_ROLLBACK:
return "sqlite3: abort due to ROLLBACK"
case x < _ROW:
return x.Error()
case e == _ROW:
return "sqlite3: another row available"
case e == _DONE:
return "sqlite3: no more rows available"
}
return "sqlite3: unknown error"
return util.ErrorCodeString(uint32(e))
}
// Is tests whether this error matches a given [ErrorCode].

116
internal/util/const.go Normal file
View File

@@ -0,0 +1,116 @@
package util
// https://sqlite.com/matrix/rescode.html
const (
OK = 0 /* Successful result */
ERROR = 1 /* Generic error */
INTERNAL = 2 /* Internal logic error in SQLite */
PERM = 3 /* Access permission denied */
ABORT = 4 /* Callback routine requested an abort */
BUSY = 5 /* The database file is locked */
LOCKED = 6 /* A table in the database is locked */
NOMEM = 7 /* A malloc() failed */
READONLY = 8 /* Attempt to write a readonly database */
INTERRUPT = 9 /* Operation terminated by sqlite3_interrupt() */
IOERR = 10 /* Some kind of disk I/O error occurred */
CORRUPT = 11 /* The database disk image is malformed */
NOTFOUND = 12 /* Unknown opcode in sqlite3_file_control() */
FULL = 13 /* Insertion failed because database is full */
CANTOPEN = 14 /* Unable to open the database file */
PROTOCOL = 15 /* Database lock protocol error */
EMPTY = 16 /* Internal use only */
SCHEMA = 17 /* The database schema changed */
TOOBIG = 18 /* String or BLOB exceeds size limit */
CONSTRAINT = 19 /* Abort due to constraint violation */
MISMATCH = 20 /* Data type mismatch */
MISUSE = 21 /* Library used incorrectly */
NOLFS = 22 /* Uses OS features not supported on host */
AUTH = 23 /* Authorization denied */
FORMAT = 24 /* Not used */
RANGE = 25 /* 2nd parameter to sqlite3_bind out of range */
NOTADB = 26 /* File opened that is not a database file */
NOTICE = 27 /* Notifications from sqlite3_log() */
WARNING = 28 /* Warnings from sqlite3_log() */
ROW = 100 /* sqlite3_step() has another row ready */
DONE = 101 /* sqlite3_step() has finished executing */
ERROR_MISSING_COLLSEQ = ERROR | (1 << 8)
ERROR_RETRY = ERROR | (2 << 8)
ERROR_SNAPSHOT = ERROR | (3 << 8)
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)
LOCKED_SHAREDCACHE = LOCKED | (1 << 8)
LOCKED_VTAB = LOCKED | (2 << 8)
BUSY_RECOVERY = BUSY | (1 << 8)
BUSY_SNAPSHOT = BUSY | (2 << 8)
BUSY_TIMEOUT = BUSY | (3 << 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)
CORRUPT_VTAB = CORRUPT | (1 << 8)
CORRUPT_SEQUENCE = CORRUPT | (2 << 8)
CORRUPT_INDEX = CORRUPT | (3 << 8)
READONLY_RECOVERY = READONLY | (1 << 8)
READONLY_CANTLOCK = READONLY | (2 << 8)
READONLY_ROLLBACK = READONLY | (3 << 8)
READONLY_DBMOVED = READONLY | (4 << 8)
READONLY_CANTINIT = READONLY | (5 << 8)
READONLY_DIRECTORY = READONLY | (6 << 8)
ABORT_ROLLBACK = ABORT | (2 << 8)
CONSTRAINT_CHECK = CONSTRAINT | (1 << 8)
CONSTRAINT_COMMITHOOK = CONSTRAINT | (2 << 8)
CONSTRAINT_FOREIGNKEY = CONSTRAINT | (3 << 8)
CONSTRAINT_FUNCTION = CONSTRAINT | (4 << 8)
CONSTRAINT_NOTNULL = CONSTRAINT | (5 << 8)
CONSTRAINT_PRIMARYKEY = CONSTRAINT | (6 << 8)
CONSTRAINT_TRIGGER = CONSTRAINT | (7 << 8)
CONSTRAINT_UNIQUE = CONSTRAINT | (8 << 8)
CONSTRAINT_VTAB = CONSTRAINT | (9 << 8)
CONSTRAINT_ROWID = CONSTRAINT | (10 << 8)
CONSTRAINT_PINNED = CONSTRAINT | (11 << 8)
CONSTRAINT_DATATYPE = CONSTRAINT | (12 << 8)
NOTICE_RECOVER_WAL = NOTICE | (1 << 8)
NOTICE_RECOVER_ROLLBACK = NOTICE | (2 << 8)
NOTICE_RBU = NOTICE | (3 << 8)
WARNING_AUTOINDEX = WARNING | (1 << 8)
AUTH_USER = AUTH | (1 << 8)
OK_LOAD_PERMANENTLY = OK | (1 << 8)
OK_SYMLINK = OK | (2 << 8) /* internal use only */
)

View File

@@ -40,3 +40,75 @@ func Finalizer[T any](skip int) func(*T) {
}
return func(*T) { panic(ErrorString(msg)) }
}
func ErrorCodeString(rc uint32) string {
switch rc {
case ABORT_ROLLBACK:
return "sqlite3: abort due to ROLLBACK"
case ROW:
return "sqlite3: another row available"
case DONE:
return "sqlite3: no more rows available"
}
switch rc & 0xff {
case OK:
return "sqlite3: not an error"
case ERROR:
return "sqlite3: SQL logic error"
case INTERNAL:
break
case PERM:
return "sqlite3: access permission denied"
case ABORT:
return "sqlite3: query aborted"
case BUSY:
return "sqlite3: database is locked"
case LOCKED:
return "sqlite3: database table is locked"
case NOMEM:
return "sqlite3: out of memory"
case READONLY:
return "sqlite3: attempt to write a readonly database"
case INTERRUPT:
return "sqlite3: interrupted"
case IOERR:
return "sqlite3: disk I/O error"
case CORRUPT:
return "sqlite3: database disk image is malformed"
case NOTFOUND:
return "sqlite3: unknown operation"
case FULL:
return "sqlite3: database or disk is full"
case CANTOPEN:
return "sqlite3: unable to open database file"
case PROTOCOL:
return "sqlite3: locking protocol"
case FORMAT:
break
case SCHEMA:
return "sqlite3: database schema has changed"
case TOOBIG:
return "sqlite3: string or blob too big"
case CONSTRAINT:
return "sqlite3: constraint failed"
case MISMATCH:
return "sqlite3: datatype mismatch"
case MISUSE:
return "sqlite3: bad parameter or other API misuse"
case NOLFS:
break
case AUTH:
return "sqlite3: authorization denied"
case EMPTY:
break
case RANGE:
return "sqlite3: column index out of range"
case NOTADB:
return "sqlite3: file is not a database"
case NOTICE:
return "sqlite3: notification message"
case WARNING:
return "sqlite3: warning message"
}
return "sqlite3: unknown error"
}

View File

@@ -1,6 +1,9 @@
package vfs
import "github.com/ncruces/go-sqlite3/sqlite3vfs"
import (
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/sqlite3vfs"
)
const (
_MAX_STRING = 512 // Used for short strings: names, error messages…
@@ -11,54 +14,35 @@ const (
// https://www.sqlite.org/rescode.html
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 */
func (e _ErrorCode) Error() string {
return util.ErrorCodeString(uint32(e))
}
_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 */
const (
_OK _ErrorCode = util.OK
_PERM _ErrorCode = util.PERM
_BUSY _ErrorCode = util.BUSY
_IOERR _ErrorCode = util.IOERR
_NOTFOUND _ErrorCode = util.NOTFOUND
_CANTOPEN _ErrorCode = util.CANTOPEN
_IOERR_READ _ErrorCode = util.IOERR_READ
_IOERR_SHORT_READ _ErrorCode = util.IOERR_SHORT_READ
_IOERR_WRITE _ErrorCode = util.IOERR_WRITE
_IOERR_FSYNC _ErrorCode = util.IOERR_FSYNC
_IOERR_DIR_FSYNC _ErrorCode = util.IOERR_DIR_FSYNC
_IOERR_TRUNCATE _ErrorCode = util.IOERR_TRUNCATE
_IOERR_FSTAT _ErrorCode = util.IOERR_FSTAT
_IOERR_UNLOCK _ErrorCode = util.IOERR_UNLOCK
_IOERR_RDLOCK _ErrorCode = util.IOERR_RDLOCK
_IOERR_DELETE _ErrorCode = util.IOERR_DELETE
_IOERR_ACCESS _ErrorCode = util.IOERR_ACCESS
_IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK
_IOERR_LOCK _ErrorCode = util.IOERR_LOCK
_IOERR_CLOSE _ErrorCode = util.IOERR_CLOSE
_IOERR_SEEK _ErrorCode = util.IOERR_SEEK
_IOERR_DELETE_NOENT _ErrorCode = util.IOERR_DELETE_NOENT
_CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH
_OK_SYMLINK _ErrorCode = util.OK_SYMLINK
)
// https://www.sqlite.org/c3ref/c_open_autoproxy.html

View File

@@ -7,8 +7,6 @@ import (
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
@@ -46,7 +44,7 @@ func Export(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
type vfsKey struct{}
type vfsState struct {
files []vfsFile
files []sqlite3vfs.File
}
func Context(ctx context.Context) (context.Context, io.Closer) {
@@ -56,7 +54,7 @@ func Context(ctx context.Context) (context.Context, io.Closer) {
func (vfs *vfsState) Close() error {
for _, f := range vfs.files {
if f.File != nil {
if f != nil {
f.Close()
}
}
@@ -119,161 +117,57 @@ func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) _ErrorCode {
vfs := vfsAPIGet(mod, pVfs)
rel := util.ReadString(mod, zRelative, _MAX_PATHNAME)
path := util.ReadString(mod, zRelative, _MAX_PATHNAME)
var abs string
var symlink bool
if vfs != nil {
p, err := vfs.FullPathname(rel)
if err != nil {
return vfsAPIErrorCode(err, _CANTOPEN_FULLPATH)
}
abs = p
} else {
p, err := filepath.Abs(rel)
if err != nil {
return _CANTOPEN_FULLPATH
}
s, err := os.Lstat(p)
if err == nil {
symlink = s.Mode()&fs.ModeSymlink != 0
} else if !errors.Is(err, fs.ErrNotExist) {
return _CANTOPEN_FULLPATH
}
abs = p
}
path, err := vfs.FullPathname(path)
size := uint64(len(abs) + 1)
size := uint64(len(path) + 1)
if size > uint64(nFull) {
return _CANTOPEN_FULLPATH
}
mem := util.View(mod, zFull, size)
mem[len(abs)] = 0
copy(mem, abs)
mem[len(path)] = 0
copy(mem, path)
if symlink {
return _OK_SYMLINK
}
return _OK
return vfsAPIErrorCode(err, _CANTOPEN_FULLPATH)
}
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode {
vfs := vfsAPIGet(mod, pVfs)
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
if vfs != nil {
err := vfs.Delete(path, syncDir != 0)
return vfsAPIErrorCode(err, _IOERR_DELETE)
}
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
err := vfs.Delete(path, syncDir != 0)
return vfsAPIErrorCode(err, _IOERR_DELETE)
}
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags _AccessFlag, pResOut uint32) _ErrorCode {
vfs := vfsAPIGet(mod, pVfs)
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
ok, err := vfs.Access(path, flags)
var res uint32
var rc _ErrorCode
if vfs != nil {
ok, err := vfs.Access(path, flags)
if ok {
res = 1
} else {
res = 0
}
rc = vfsAPIErrorCode(err, _IOERR_ACCESS)
} else {
err := osAccess(path, flags)
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
}
}
if ok {
res = 1
}
util.WriteUint32(mod, pResOut, res)
return rc
return vfsAPIErrorCode(err, _IOERR_ACCESS)
}
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zName, pFile uint32, flags _OpenFlag, pOutFlags uint32) _ErrorCode {
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags _OpenFlag, pOutFlags uint32) _ErrorCode {
vfs := vfsAPIGet(mod, pVfs)
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 path string
if zPath != 0 {
path = util.ReadString(mod, zPath, _MAX_PATHNAME)
}
var err error
var name string
var f *os.File
if zName != 0 {
name = util.ReadString(mod, zName, _MAX_PATHNAME)
}
switch {
case vfs != nil:
_, flags, err = vfs.Open(name, flags)
case name == "":
f, err = os.CreateTemp("", "*.db")
default:
f, err = osOpenFile(name, oflags, 0666)
}
file, flags, err := vfs.Open(path, flags)
if err != nil {
return _CANTOPEN
return vfsAPIErrorCode(err, _CANTOPEN)
}
if flags&_OPEN_DELETEONCLOSE != 0 {
os.Remove(f.Name())
}
file := vfsFileOpen(ctx, mod, pFile, f)
file.psow = true
file.readOnly = flags&_OPEN_READONLY != 0
file.syncDir = runtime.GOOS != "windows" &&
flags&(_OPEN_CREATE) != 0 &&
flags&(_OPEN_MAIN_JOURNAL|_OPEN_SUPER_JOURNAL|_OPEN_WAL) != 0
vfsFileRegister(ctx, mod, pFile, file)
if pOutFlags != 0 {
util.WriteUint32(mod, pOutFlags, uint32(flags))
}
@@ -289,9 +183,9 @@ func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
}
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
buf := util.View(mod, zBuf, uint64(iAmt))
file := vfsFileGet(ctx, mod, pFile)
n, err := file.ReadAt(buf, iOfst)
if n == int(iAmt) {
return _OK
@@ -306,9 +200,9 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfs
}
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
buf := util.View(mod, zBuf, uint64(iAmt))
file := vfsFileGet(ctx, mod, pFile)
_, err := file.WriteAt(buf, iOfst)
if err != nil {
return _IOERR_WRITE
@@ -319,60 +213,70 @@ func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOf
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
err := file.Truncate(nByte)
if err != nil {
return _IOERR_TRUNCATE
}
return _OK
return vfsAPIErrorCode(err, _IOERR_TRUNCATE)
}
func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags _SyncFlag) _ErrorCode {
dataonly := (flags & _SYNC_DATAONLY) != 0
fullsync := (flags & 0x0f) == _SYNC_FULL
file := vfsFileGet(ctx, mod, pFile)
err := osSync(file.File, fullsync, dataonly)
if err != nil {
return _IOERR_FSYNC
}
if runtime.GOOS != "windows" && file.syncDir {
file.syncDir = 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
err := file.Sync(flags)
return vfsAPIErrorCode(err, _IOERR_FSYNC)
}
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return _IOERR_SEEK
size, err := file.FileSize()
util.WriteUint64(mod, pSize, uint64(size))
return vfsAPIErrorCode(err, _IOERR_SEEK)
}
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
err := file.Lock(eLock)
return vfsAPIErrorCode(err, _IOERR_LOCK)
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
err := file.Unlock(eLock)
return vfsAPIErrorCode(err, _IOERR_UNLOCK)
}
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
locked, err := file.CheckReservedLock()
var res uint32
if locked {
res = 1
}
util.WriteUint64(mod, pSize, uint64(off))
return _OK
util.WriteUint32(mod, pResOut, res)
return vfsAPIErrorCode(err, _IOERR_CHECKRESERVEDLOCK)
}
func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode {
switch op {
case _FCNTL_LOCKSTATE:
util.WriteUint32(mod, pArg, uint32(vfsFileGet(ctx, mod, pFile).lock))
file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile)
if !ok {
return _NOTFOUND
}
util.WriteUint32(mod, pArg, uint32(file.lock))
return _OK
case _FCNTL_LOCK_TIMEOUT:
file := vfsFileGet(ctx, mod, pFile)
file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile)
if !ok {
return _NOTFOUND
}
millis := file.lockTimeout.Milliseconds()
file.lockTimeout = time.Duration(util.ReadUint32(mod, pArg)) * time.Millisecond
util.WriteUint32(mod, pArg, uint32(millis))
return _OK
case _FCNTL_POWERSAFE_OVERWRITE:
file := vfsFileGet(ctx, mod, pFile)
file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile)
if !ok {
return _NOTFOUND
}
switch util.ReadUint32(mod, pArg) {
case 0:
file.psow = false
@@ -400,19 +304,23 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
}
func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 {
return _DEFAULT_SECTOR_SIZE
file := vfsFileGet(ctx, mod, pFile)
return uint32(file.SectorSize())
}
func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) _DeviceCharacteristic {
file := vfsFileGet(ctx, mod, pFile)
if file.psow {
file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile)
if ok && file.psow {
return _IOCAP_POWERSAFE_OVERWRITE
}
return 0
}
func vfsSizeHint(ctx context.Context, mod api.Module, pFile, pArg uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile)
if !ok {
return _NOTFOUND
}
size := util.ReadUint64(mod, pArg)
err := osAllocate(file.File, int64(size))
if err != nil {
@@ -422,7 +330,10 @@ func vfsSizeHint(ctx context.Context, mod api.Module, pFile, pArg uint32) _Error
}
func vfsFileMoved(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile)
if !ok {
return _NOTFOUND
}
fi, err := file.Stat()
if err != nil {
return _IOERR_FSTAT

View File

@@ -8,12 +8,15 @@ import (
"github.com/tetratelabs/wazero/api"
)
func vfsAPIGet(mod api.Module, pVfs uint32) sqlite3vfs.VFS {
if pVfs == 0 {
return nil
func vfsAPIGet(mod api.Module, pVfs uint32) (vfs sqlite3vfs.VFS) {
if pVfs != 0 {
name := util.ReadString(mod, util.ReadUint32(mod, pVfs+16), _MAX_STRING)
vfs = sqlite3vfs.Find(name)
}
name := util.ReadString(mod, util.ReadUint32(mod, pVfs+16), _MAX_STRING)
return sqlite3vfs.Find(name)
if vfs == nil {
vfs = vfsOS
}
return
}
func vfsAPIErrorCode(err error, def _ErrorCode) _ErrorCode {

View File

@@ -2,13 +2,118 @@ package vfs
import (
"context"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/go-sqlite3/sqlite3vfs"
"github.com/tetratelabs/wazero/api"
)
const vfsOS vfsOSAPI = false
type vfsOSAPI bool
func (vfsOSAPI) FullPathname(path string) (string, error) {
path, err := filepath.Abs(path)
if err != nil {
return "", err
}
fi, err := os.Lstat(path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return path, nil
}
return "", err
}
if fi.Mode()&fs.ModeSymlink != 0 {
err = _OK_SYMLINK
}
return path, err
}
func (vfsOSAPI) Delete(path string, syncDir bool) error {
err := os.Remove(path)
if errors.Is(err, fs.ErrNotExist) {
return _IOERR_DELETE_NOENT
}
if err != nil {
return err
}
if runtime.GOOS != "windows" && syncDir {
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 nil
}
func (vfsOSAPI) Access(name string, flags sqlite3vfs.AccessFlag) (bool, error) {
err := osAccess(name, flags)
if flags == _ACCESS_EXISTS {
if errors.Is(err, fs.ErrNotExist) {
return false, nil
}
} else {
if errors.Is(err, fs.ErrPermission) {
return false, nil
}
}
return err == nil, err
}
func (vfsOSAPI) Open(name string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File, sqlite3vfs.OpenFlag, error) {
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 f *os.File
if name == "" {
f, err = os.CreateTemp("", "*.db")
} else {
f, err = osOpenFile(name, oflags, 0666)
}
if err != nil {
return nil, flags, err
}
if flags&_OPEN_DELETEONCLOSE != 0 {
os.Remove(f.Name())
}
file := vfsFile{
File: f,
psow: true,
readOnly: flags&_OPEN_READONLY != 0,
syncDir: runtime.GOOS != "windows" &&
flags&(_OPEN_CREATE) != 0 &&
flags&(_OPEN_MAIN_JOURNAL|_OPEN_SUPER_JOURNAL|_OPEN_WAL) != 0,
}
return &file, flags, nil
}
type vfsFile struct {
*os.File
lockTimeout time.Duration
@@ -18,37 +123,66 @@ type vfsFile struct {
readOnly bool
}
func vfsFileNew(vfs *vfsState, file *os.File) uint32 {
func vfsFileNew(vfs *vfsState, file sqlite3vfs.File) uint32 {
// Find an empty slot.
for id, f := range vfs.files {
if f.File == nil {
vfs.files[id] = vfsFile{File: file}
if f == nil {
vfs.files[id] = file
return uint32(id)
}
}
// Add a new slot.
vfs.files = append(vfs.files, vfsFile{File: file})
vfs.files = append(vfs.files, file)
return uint32(len(vfs.files) - 1)
}
func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) *vfsFile {
vfs := ctx.Value(vfsKey{}).(*vfsState)
id := util.ReadUint32(mod, pFile+4)
return &vfs.files[id]
func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file sqlite3vfs.File) {
id := vfsFileNew(ctx.Value(vfsKey{}).(*vfsState), file)
util.WriteUint32(mod, pFile+4, id)
}
func vfsFileOpen(ctx context.Context, mod api.Module, pFile uint32, file *os.File) *vfsFile {
func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) sqlite3vfs.File {
vfs := ctx.Value(vfsKey{}).(*vfsState)
id := vfsFileNew(vfs, file)
util.WriteUint32(mod, pFile+4, id)
return &vfs.files[id]
id := util.ReadUint32(mod, pFile+4)
return vfs.files[id]
}
func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error {
vfs := ctx.Value(vfsKey{}).(*vfsState)
id := util.ReadUint32(mod, pFile+4)
file := vfs.files[id]
vfs.files[id] = vfsFile{}
vfs.files[id] = nil
return file.Close()
}
func (f *vfsFile) Sync(flags sqlite3vfs.SyncFlag) error {
dataonly := (flags & _SYNC_DATAONLY) != 0
fullsync := (flags & 0x0f) == _SYNC_FULL
err := osSync(f.File, fullsync, dataonly)
if err != nil {
return err
}
if runtime.GOOS != "windows" && f.syncDir {
f.syncDir = false
d, err := os.Open(filepath.Dir(f.File.Name()))
if err != nil {
return nil
}
defer d.Close()
err = osSync(d, false, false)
if err != nil {
return _IOERR_DIR_FSYNC
}
}
return nil
}
func (f *vfsFile) FileSize() (int64, error) {
return f.Seek(0, io.SeekEnd)
}
func (*vfsFile) SectorSize() int {
return _DEFAULT_SECTOR_SIZE
}

View File

@@ -1,12 +1,11 @@
package vfs
import (
"context"
"os"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/sqlite3vfs"
)
const (
@@ -16,14 +15,12 @@ const (
_SHARED_SIZE = 510
)
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
func (file *vfsFile) Lock(eLock sqlite3vfs.LockLevel) error {
// Argument check. SQLite never explicitly requests a pending lock.
if eLock != _LOCK_SHARED && eLock != _LOCK_RESERVED && eLock != _LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
file := vfsFileGet(ctx, mod, pFile)
switch {
case file.lock < _LOCK_NONE || file.lock > _LOCK_EXCLUSIVE:
// Connection state check.
@@ -38,7 +35,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel
// If we already have an equal or more restrictive lock, do nothing.
if file.lock >= eLock {
return _OK
return nil
}
// Do not allow any kind of write-lock on a read-only database.
@@ -56,7 +53,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel
return rc
}
file.lock = _LOCK_SHARED
return _OK
return nil
case _LOCK_RESERVED:
// Must be SHARED to get RESERVED.
@@ -67,7 +64,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel
return rc
}
file.lock = _LOCK_RESERVED
return _OK
return nil
case _LOCK_EXCLUSIVE:
// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
@@ -85,21 +82,19 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel
return rc
}
file.lock = _LOCK_EXCLUSIVE
return _OK
return nil
default:
panic(util.AssertErr())
}
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
func (file *vfsFile) Unlock(eLock sqlite3vfs.LockLevel) error {
// Argument check.
if eLock != _LOCK_NONE && eLock != _LOCK_SHARED {
panic(util.AssertErr())
}
file := vfsFileGet(ctx, mod, pFile)
// Connection state check.
if file.lock < _LOCK_NONE || file.lock > _LOCK_EXCLUSIVE {
panic(util.AssertErr())
@@ -107,7 +102,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLev
// If we don't have a more restrictive lock, do nothing.
if file.lock <= eLock {
return _OK
return nil
}
switch eLock {
@@ -116,7 +111,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLev
return rc
}
file.lock = _LOCK_SHARED
return _OK
return nil
case _LOCK_NONE:
rc := osReleaseLock(file.File, file.lock)
@@ -128,28 +123,16 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLev
}
}
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
func (file *vfsFile) CheckReservedLock() (bool, error) {
// Connection state check.
if file.lock < _LOCK_NONE || file.lock > _LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
var locked bool
var rc _ErrorCode
if file.lock >= _LOCK_RESERVED {
locked = true
} else {
locked, rc = osCheckReservedLock(file.File)
return true, nil
}
var res uint32
if locked {
res = 1
}
util.WriteUint32(mod, pResOut, res)
return rc
return osCheckReservedLock(file.File)
}
func osGetReservedLock(file *os.File, timeout time.Duration) _ErrorCode {

View File

@@ -43,8 +43,8 @@ func Test_vfsLock(t *testing.T) {
ctx, vfs := Context(context.TODO())
defer vfs.Close()
vfsFileOpen(ctx, mod, pFile1, file1)
vfsFileOpen(ctx, mod, pFile2, file2)
vfsFileRegister(ctx, mod, pFile1, &vfsFile{File: file1})
vfsFileRegister(ctx, mod, pFile2, &vfsFile{File: file2})
rc := vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
if rc != _OK {

View File

@@ -19,7 +19,7 @@ type File interface {
Lock(lock LockLevel) error
Unlock(lock LockLevel) error
CheckReservedLock() (bool, error)
SectorSize() int64
SectorSize() int
}
var (

View File

@@ -18,8 +18,8 @@ func (t testVFS) Open(name string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File,
return nil, flags, nil
}
func (t testVFS) Delete(name string, dirSync bool) error {
t.Log("Delete", name, dirSync)
func (t testVFS) Delete(name string, syncDir bool) error {
t.Log("Delete", name, syncDir)
return nil
}
@@ -38,11 +38,11 @@ func TestRegister(t *testing.T) {
sqlite3vfs.Register("foo", vfs)
defer sqlite3vfs.Unregister("foo")
defer func() { _ = recover() }()
conn, err := sqlite3.Open("file:file.db?vfs=foo")
if err != nil {
t.Fatal(err)
}
defer conn.Close()
t.Error("want skip")
}