From e1cce83f71c5fb16273b6845aec07f0f2ea0dd5a Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Fri, 19 May 2023 02:00:16 +0100 Subject: [PATCH] More VFS API. --- const.go | 2 +- internal/vfs/const.go | 42 +++++++------- internal/vfs/vfs.go | 116 ++++++++++++++++----------------------- internal/vfs/vfs_api.go | 11 ++-- internal/vfs/vfs_file.go | 47 +++++++++++++--- sqlite3vfs/const.go | 23 ++++++++ sqlite3vfs/vfs.go | 22 ++++++++ 7 files changed, 158 insertions(+), 105 deletions(-) diff --git a/const.go b/const.go index 6e51fe7..a1d6145 100644 --- a/const.go +++ b/const.go @@ -137,7 +137,7 @@ const ( AUTH_USER ExtendedErrorCode = xErrorCode(AUTH) | (1 << 8) ) -// OpenFlag is a flag for a file open operation. +// OpenFlag is a flag for the [OpenFlags] function. // // https://www.sqlite.org/c3ref/c_open_autoproxy.html type OpenFlag uint32 diff --git a/internal/vfs/const.go b/internal/vfs/const.go index 4dcc591..eb5d751 100644 --- a/internal/vfs/const.go +++ b/internal/vfs/const.go @@ -101,6 +101,27 @@ const ( _LOCK_EXCLUSIVE = sqlite3vfs.LOCK_EXCLUSIVE ) +// https://www.sqlite.org/c3ref/c_iocap_atomic.html +type _DeviceCharacteristic = sqlite3vfs.DeviceCharacteristic + +const ( + _IOCAP_ATOMIC = sqlite3vfs.IOCAP_ATOMIC + _IOCAP_ATOMIC512 = sqlite3vfs.IOCAP_ATOMIC512 + _IOCAP_ATOMIC1K = sqlite3vfs.IOCAP_ATOMIC1K + _IOCAP_ATOMIC2K = sqlite3vfs.IOCAP_ATOMIC2K + _IOCAP_ATOMIC4K = sqlite3vfs.IOCAP_ATOMIC4K + _IOCAP_ATOMIC8K = sqlite3vfs.IOCAP_ATOMIC8K + _IOCAP_ATOMIC16K = sqlite3vfs.IOCAP_ATOMIC16K + _IOCAP_ATOMIC32K = sqlite3vfs.IOCAP_ATOMIC32K + _IOCAP_ATOMIC64K = sqlite3vfs.IOCAP_ATOMIC64K + _IOCAP_SAFE_APPEND = sqlite3vfs.IOCAP_SAFE_APPEND + _IOCAP_SEQUENTIAL = sqlite3vfs.IOCAP_SEQUENTIAL + _IOCAP_UNDELETABLE_WHEN_OPEN = sqlite3vfs.IOCAP_UNDELETABLE_WHEN_OPEN + _IOCAP_POWERSAFE_OVERWRITE = sqlite3vfs.IOCAP_POWERSAFE_OVERWRITE + _IOCAP_IMMUTABLE = sqlite3vfs.IOCAP_IMMUTABLE + _IOCAP_BATCH_ATOMIC = sqlite3vfs.IOCAP_BATCH_ATOMIC +) + // https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html type _FcntlOpcode uint32 @@ -147,24 +168,3 @@ const ( _FCNTL_CKSM_FILE _FcntlOpcode = 41 _FCNTL_RESET_CACHE _FcntlOpcode = 42 ) - -// https://www.sqlite.org/c3ref/c_iocap_atomic.html -type _DeviceCharacteristic uint32 - -const ( - _IOCAP_ATOMIC _DeviceCharacteristic = 0x00000001 - _IOCAP_ATOMIC512 _DeviceCharacteristic = 0x00000002 - _IOCAP_ATOMIC1K _DeviceCharacteristic = 0x00000004 - _IOCAP_ATOMIC2K _DeviceCharacteristic = 0x00000008 - _IOCAP_ATOMIC4K _DeviceCharacteristic = 0x00000010 - _IOCAP_ATOMIC8K _DeviceCharacteristic = 0x00000020 - _IOCAP_ATOMIC16K _DeviceCharacteristic = 0x00000040 - _IOCAP_ATOMIC32K _DeviceCharacteristic = 0x00000080 - _IOCAP_ATOMIC64K _DeviceCharacteristic = 0x00000100 - _IOCAP_SAFE_APPEND _DeviceCharacteristic = 0x00000200 - _IOCAP_SEQUENTIAL _DeviceCharacteristic = 0x00000400 - _IOCAP_UNDELETABLE_WHEN_OPEN _DeviceCharacteristic = 0x00000800 - _IOCAP_POWERSAFE_OVERWRITE _DeviceCharacteristic = 0x00001000 - _IOCAP_IMMUTABLE _DeviceCharacteristic = 0x00002000 - _IOCAP_BATCH_ATOMIC _DeviceCharacteristic = 0x00004000 -) diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index 9e8257f..e05e765 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -3,10 +3,7 @@ package vfs import ( "context" "crypto/rand" - "errors" "io" - "io/fs" - "os" "time" "github.com/ncruces/go-sqlite3/internal/util" @@ -255,45 +252,61 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui } func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode { + file := vfsFileGet(ctx, mod, pFile) + switch op { case _FCNTL_LOCKSTATE: - file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile) - if !ok { - return _NOTFOUND + if file, ok := file.(sqlite3vfs.FileLockState); ok { + util.WriteUint32(mod, pArg, uint32(file.LockState())) + return _OK } - util.WriteUint32(mod, pArg, uint32(file.lock)) - return _OK + case _FCNTL_LOCK_TIMEOUT: - file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile) - if !ok { - return _NOTFOUND + if file, ok := file.(*vfsFile); ok { + millis := file.lockTimeout.Milliseconds() + file.lockTimeout = time.Duration(util.ReadUint32(mod, pArg)) * time.Millisecond + util.WriteUint32(mod, pArg, uint32(millis)) + return _OK } - 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, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile) - if !ok { - return _NOTFOUND - } - switch util.ReadUint32(mod, pArg) { - case 0: - file.psow = false - case 1: - file.psow = true - default: - if file.psow { - util.WriteUint32(mod, pArg, 1) - } else { - util.WriteUint32(mod, pArg, 0) + if file, ok := file.(sqlite3vfs.FilePowersafeOverwrite); ok { + switch util.ReadUint32(mod, pArg) { + case 0: + file.SetPowersafeOverwrite(false) + case 1: + file.SetPowersafeOverwrite(true) + default: + if file.PowersafeOverwrite() { + util.WriteUint32(mod, pArg, 1) + } else { + util.WriteUint32(mod, pArg, 0) + } } + return _OK } + case _FCNTL_SIZE_HINT: - return vfsSizeHint(ctx, mod, pFile, pArg) + if file, ok := file.(sqlite3vfs.FileSizeHint); ok { + size := util.ReadUint64(mod, pArg) + err := file.SizeHint(int64(size)) + return vfsAPIErrorCode(err, _IOERR_TRUNCATE) + } + case _FCNTL_HAS_MOVED: - return vfsFileMoved(ctx, mod, pFile, pArg) + if file, ok := file.(sqlite3vfs.FileHasMoved); ok { + moved, err := file.HasMoved() + + var res uint32 + if moved { + res = 1 + } + + util.WriteUint32(mod, pArg, res) + return vfsAPIErrorCode(err, _IOERR_FSTAT) + } } + // Consider also implementing these opcodes (in use by SQLite): // _FCNTL_BUSYHANDLER // _FCNTL_COMMIT_PHASETWO @@ -309,43 +322,6 @@ func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 { } func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) _DeviceCharacteristic { - 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, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile) - if !ok { - return _NOTFOUND - } - size := util.ReadUint64(mod, pArg) - err := osAllocate(file.File, int64(size)) - if err != nil { - return _IOERR_TRUNCATE - } - return _OK -} - -func vfsFileMoved(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode { - file, ok := vfsFileGet(ctx, mod, pFile).(*vfsFile) - if !ok { - return _NOTFOUND - } - 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 + file := vfsFileGet(ctx, mod, pFile) + return file.DeviceCharacteristics() } diff --git a/internal/vfs/vfs_api.go b/internal/vfs/vfs_api.go index 06c0be1..9248a05 100644 --- a/internal/vfs/vfs_api.go +++ b/internal/vfs/vfs_api.go @@ -8,15 +8,14 @@ import ( "github.com/tetratelabs/wazero/api" ) -func vfsAPIGet(mod api.Module, pVfs uint32) (vfs sqlite3vfs.VFS) { +func vfsAPIGet(mod api.Module, pVfs uint32) sqlite3vfs.VFS { if pVfs != 0 { name := util.ReadString(mod, util.ReadUint32(mod, pVfs+16), _MAX_STRING) - vfs = sqlite3vfs.Find(name) + if vfs := sqlite3vfs.Find(name); vfs != nil { + return vfs + } } - if vfs == nil { - vfs = vfsOS - } - return + return vfsOS{} } func vfsAPIErrorCode(err error, def _ErrorCode) _ErrorCode { diff --git a/internal/vfs/vfs_file.go b/internal/vfs/vfs_file.go index 07d1aba..b18c7af 100644 --- a/internal/vfs/vfs_file.go +++ b/internal/vfs/vfs_file.go @@ -15,11 +15,9 @@ import ( "github.com/tetratelabs/wazero/api" ) -const vfsOS vfsOSAPI = false +type vfsOS struct{} -type vfsOSAPI bool - -func (vfsOSAPI) FullPathname(path string) (string, error) { +func (vfsOS) FullPathname(path string) (string, error) { path, err := filepath.Abs(path) if err != nil { return "", err @@ -37,7 +35,7 @@ func (vfsOSAPI) FullPathname(path string) (string, error) { return path, err } -func (vfsOSAPI) Delete(path string, syncDir bool) error { +func (vfsOS) Delete(path string, syncDir bool) error { err := os.Remove(path) if errors.Is(err, fs.ErrNotExist) { return _IOERR_DELETE_NOENT @@ -59,7 +57,7 @@ func (vfsOSAPI) Delete(path string, syncDir bool) error { return nil } -func (vfsOSAPI) Access(name string, flags sqlite3vfs.AccessFlag) (bool, error) { +func (vfsOS) Access(name string, flags sqlite3vfs.AccessFlag) (bool, error) { err := osAccess(name, flags) if flags == _ACCESS_EXISTS { if errors.Is(err, fs.ErrNotExist) { @@ -73,7 +71,7 @@ func (vfsOSAPI) Access(name string, flags sqlite3vfs.AccessFlag) (bool, error) { return err == nil, err } -func (vfsOSAPI) Open(name string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File, sqlite3vfs.OpenFlag, error) { +func (vfsOS) Open(name string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File, sqlite3vfs.OpenFlag, error) { var oflags int if flags&_OPEN_EXCLUSIVE != 0 { oflags |= os.O_EXCL @@ -123,6 +121,14 @@ type vfsFile struct { readOnly bool } +var ( + // Ensure these interfaces are implemented: + _ sqlite3vfs.FileLockState = &vfsFile{} + _ sqlite3vfs.FileHasMoved = &vfsFile{} + _ sqlite3vfs.FileSizeHint = &vfsFile{} + _ sqlite3vfs.FilePowersafeOverwrite = &vfsFile{} +) + func vfsFileNew(vfs *vfsState, file sqlite3vfs.File) uint32 { // Find an empty slot. for id, f := range vfs.files { @@ -186,3 +192,30 @@ func (f *vfsFile) FileSize() (int64, error) { func (*vfsFile) SectorSize() int { return _DEFAULT_SECTOR_SIZE } + +func (f *vfsFile) DeviceCharacteristics() sqlite3vfs.DeviceCharacteristic { + if f.psow { + return _IOCAP_POWERSAFE_OVERWRITE + } + return 0 +} + +func (f *vfsFile) SizeHint(size int64) error { + return osAllocate(f.File, size) +} + +func (f *vfsFile) HasMoved() (bool, error) { + fi, err := f.Stat() + if err != nil { + return false, err + } + pi, err := os.Stat(f.Name()) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return false, err + } + return !os.SameFile(fi, pi), nil +} + +func (f *vfsFile) LockState() sqlite3vfs.LockLevel { return f.lock } +func (f *vfsFile) PowersafeOverwrite() bool { return f.psow } +func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow } diff --git a/sqlite3vfs/const.go b/sqlite3vfs/const.go index e92f10c..0ff4863 100644 --- a/sqlite3vfs/const.go +++ b/sqlite3vfs/const.go @@ -96,3 +96,26 @@ const ( // time that EXCLUSIVE locks are held. LOCK_EXCLUSIVE LockLevel = 4 /* xLock() only */ ) + +// DeviceCharacteristic is a flag retuned by the [File.DeviceCharacteristic] method. +// +// https://www.sqlite.org/c3ref/c_iocap_atomic.html +type DeviceCharacteristic uint32 + +const ( + IOCAP_ATOMIC DeviceCharacteristic = 0x00000001 + IOCAP_ATOMIC512 DeviceCharacteristic = 0x00000002 + IOCAP_ATOMIC1K DeviceCharacteristic = 0x00000004 + IOCAP_ATOMIC2K DeviceCharacteristic = 0x00000008 + IOCAP_ATOMIC4K DeviceCharacteristic = 0x00000010 + IOCAP_ATOMIC8K DeviceCharacteristic = 0x00000020 + IOCAP_ATOMIC16K DeviceCharacteristic = 0x00000040 + IOCAP_ATOMIC32K DeviceCharacteristic = 0x00000080 + IOCAP_ATOMIC64K DeviceCharacteristic = 0x00000100 + IOCAP_SAFE_APPEND DeviceCharacteristic = 0x00000200 + IOCAP_SEQUENTIAL DeviceCharacteristic = 0x00000400 + IOCAP_UNDELETABLE_WHEN_OPEN DeviceCharacteristic = 0x00000800 + IOCAP_POWERSAFE_OVERWRITE DeviceCharacteristic = 0x00001000 + IOCAP_IMMUTABLE DeviceCharacteristic = 0x00002000 + IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000 +) diff --git a/sqlite3vfs/vfs.go b/sqlite3vfs/vfs.go index 80c1eb2..cb28b6c 100644 --- a/sqlite3vfs/vfs.go +++ b/sqlite3vfs/vfs.go @@ -20,6 +20,28 @@ type File interface { Unlock(lock LockLevel) error CheckReservedLock() (bool, error) SectorSize() int + DeviceCharacteristics() DeviceCharacteristic +} + +type FileLockState interface { + File + LockState() LockLevel +} + +type FileSizeHint interface { + File + SizeHint(size int64) error +} + +type FileHasMoved interface { + File + HasMoved() (bool, error) +} + +type FilePowersafeOverwrite interface { + File + PowersafeOverwrite() bool + SetPowersafeOverwrite(bool) } var (