diff --git a/README.md b/README.md index e947214..ccc93b7 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,4 @@ Roadmap: - [x] port [`test_demovfs.c`](https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c) to Go - branch [`wasi`](https://github.com/ncruces/go-sqlite3/tree/wasi) uses `test_demovfs.c` directly - [x] come up with a simple, nice API, enough for simple queries -- [x] file locking, compatible with SQLite on Unix -- [ ] file locking, compatible with SQLite on Windows \ No newline at end of file +- [x] file locking, compatible with SQLite on Windows/Unix \ No newline at end of file diff --git a/go.mod b/go.mod index cf63a4c..5b76b5c 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/ncruces/julianday v0.1.4 github.com/tetratelabs/wazero v1.0.0-pre.8 golang.org/x/sync v0.1.0 + golang.org/x/sys v0.5.0 ) diff --git a/go.sum b/go.sum index 04a855e..36eab7a 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/tetratelabs/wazero v1.0.0-pre.8 h1:Ir82PWj79WCppH+9ny73eGY2qv+oCnE3Vw github.com/tetratelabs/wazero v1.0.0-pre.8/go.mod h1:u8wrFmpdrykiFK0DFPiFm5a4+0RzsdmXYVtijBKqUVo= 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/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/vfs_lock_test.go b/vfs_lock_test.go index da3d411..e2b296e 100644 --- a/vfs_lock_test.go +++ b/vfs_lock_test.go @@ -9,7 +9,7 @@ import ( func Test_vfsLock(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "solaris": + case "linux", "darwin", "solaris", "windows": // default: t.Skip() diff --git a/vfs_windows.go b/vfs_windows.go index 3e0a277..a48910d 100644 --- a/vfs_windows.go +++ b/vfs_windows.go @@ -1,33 +1,117 @@ package sqlite3 -import "os" +import ( + "os" + "syscall" + + "golang.org/x/sys/windows" +) func deleteOnClose(f *os.File) {} func (l *vfsFileLocker) GetShared() xErrorCode { - return _OK + // A PENDING lock is needed before acquiring a SHARED lock. + if rc := l.readLock(_PENDING_BYTE, 1); rc != _OK { + return rc + } + + // Acquire the SHARED lock. + rc := l.readLock(_SHARED_FIRST, _SHARED_SIZE) + + // Drop the temporary PENDING lock. + if rc2 := l.unlock(_PENDING_BYTE, 1); rc == _OK { + return rc2 + } + return rc } func (l *vfsFileLocker) GetReserved() xErrorCode { - return _OK + // Acquire the RESERVED lock. + return l.writeLock(_RESERVED_BYTE, 1) } func (l *vfsFileLocker) GetPending() xErrorCode { - return _OK + // Acquire the PENDING lock. + return l.writeLock(_PENDING_BYTE, 1) } func (l *vfsFileLocker) GetExclusive() xErrorCode { - return _OK + // Release the SHARED lock. + l.unlock(_SHARED_FIRST, _SHARED_SIZE) + + // Acquire the EXCLUSIVE lock. + rc := l.writeLock(_SHARED_FIRST, _SHARED_SIZE) + + // Reacquire the SHARED lock. + if rc != _OK { + l.readLock(_SHARED_FIRST, _SHARED_SIZE) + } + return rc } func (l *vfsFileLocker) Downgrade() xErrorCode { + // Release the SHARED lock. + l.unlock(_SHARED_FIRST, _SHARED_SIZE) + + // Reacquire the SHARED lock. + if rc := l.readLock(_SHARED_FIRST, _SHARED_SIZE); rc != _OK { + // This should never happen. + // We should always be able to reacquire the read lock. + return IOERR_RDLOCK + } + + // Release the PENDING and RESERVED locks. + l.unlock(_RESERVED_BYTE, 1) + l.unlock(_PENDING_BYTE, 1) return _OK } func (l *vfsFileLocker) Release() xErrorCode { + // Release all locks. + l.unlock(_SHARED_FIRST, _SHARED_SIZE) + l.unlock(_RESERVED_BYTE, 1) + l.unlock(_PENDING_BYTE, 1) return _OK } func (l *vfsFileLocker) CheckReserved() (bool, xErrorCode) { - return false, _OK + // Test the RESERVED lock. + rc := l.readLock(_RESERVED_BYTE, 1) + if rc == _OK { + l.unlock(_RESERVED_BYTE, 1) + } + return rc != _OK, _OK +} + +func (l *vfsFileLocker) unlock(start, len uint32) xErrorCode { + err := windows.UnlockFileEx(windows.Handle(l.file.Fd()), + 0, len, 0, &windows.Overlapped{Offset: start}) + if err != nil { + return IOERR_UNLOCK + } + return _OK +} + +func (l *vfsFileLocker) readLock(start, len uint32) xErrorCode { + return l.errorCode(windows.LockFileEx(windows.Handle(l.file.Fd()), + windows.LOCKFILE_FAIL_IMMEDIATELY, + 0, len, 0, &windows.Overlapped{Offset: start}), + IOERR_LOCK) +} + +func (l *vfsFileLocker) writeLock(start, len uint32) xErrorCode { + return l.errorCode(windows.LockFileEx(windows.Handle(l.file.Fd()), + windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK, + 0, len, 0, &windows.Overlapped{Offset: start}), + IOERR_LOCK) +} + +func (*vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode { + if err == nil { + return _OK + } + if errno, _ := err.(syscall.Errno); errno == windows.ERROR_INVALID_HANDLE { + return def + } + return xErrorCode(BUSY) }