diff --git a/.github/workflows/libc.yml b/.github/workflows/libc.yml index b4317e17..f89a7053 100644 --- a/.github/workflows/libc.yml +++ b/.github/workflows/libc.yml @@ -10,7 +10,7 @@ jobs: test: strategy: matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15] + os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, macos-15-intel] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ebcb2e50..ef3caced 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -224,7 +224,7 @@ jobs: run: go test -v ./... test-macintel: - runs-on: macos-13 + runs-on: macos-15-intel needs: test steps: diff --git a/error.go b/error.go index 3dfea7b5..8d0419c5 100644 --- a/error.go +++ b/error.go @@ -40,6 +40,10 @@ func (e *Error) Error() string { b.WriteString(": ") b.WriteString(e.msg) } + if e.sys != nil { + b.WriteString(": ") + b.WriteString(e.sys.Error()) + } return b.String() } diff --git a/sqlite.go b/sqlite.go index 67f961cb..52532e07 100644 --- a/sqlite.go +++ b/sqlite.go @@ -132,7 +132,7 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error { msg = strings.TrimPrefix(msg, "sqlite3: ") msg = strings.TrimPrefix(msg, util.ErrorCodeString(rc)[len("sqlite3: "):]) msg = strings.TrimPrefix(msg, ": ") - if msg == "not an error" { + if msg == "" || msg == "not an error" { msg = "" } } diff --git a/vfs/api.go b/vfs/api.go index 46a716e6..ef0623e5 100644 --- a/vfs/api.go +++ b/vfs/api.go @@ -10,7 +10,8 @@ import ( // A VFS defines the interface between the SQLite core and the underlying operating system. // -// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite. +// Use [SystemError], sqlite3.ErrorCode, or sqlite3.ExtendedErrorCode +// to return specific error codes to SQLite. // // https://sqlite.org/c3ref/vfs.html type VFS interface { @@ -31,8 +32,9 @@ type VFSFilename interface { // A File represents an open file in the OS interface layer. // -// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite. -// In particular, sqlite3.BUSY is necessary to correctly implement lock methods. +// Use [SystemError], sqlite3.ErrorCode, or sqlite3.ExtendedErrorCode +// to return specific error codes to SQLite. +// In particular, sqlite3.BUSY is needed to correctly implement lock methods. // // https://sqlite.org/c3ref/io_methods.html type File interface { diff --git a/vfs/const.go b/vfs/const.go index 87e19d95..ffb84dd7 100644 --- a/vfs/const.go +++ b/vfs/const.go @@ -24,7 +24,7 @@ func (e _ErrorCode) Error() string { } const ( - _OK _ErrorCode = util.OK + _OK = util.OK _ERROR _ErrorCode = util.ERROR _PERM _ErrorCode = util.PERM _BUSY _ErrorCode = util.BUSY diff --git a/vfs/file.go b/vfs/file.go index 518f7dc7..1ae6cdd9 100644 --- a/vfs/file.go +++ b/vfs/file.go @@ -48,7 +48,7 @@ func (vfsOS) Delete(path string, syncDir bool) error { if isUnix && syncDir { f, err := os.Open(filepath.Dir(path)) if err != nil { - return _OK + return nil } defer f.Close() err = osSync(f, 0, SYNC_FULL) diff --git a/vfs/lock.go b/vfs/lock.go index 253057ae..84c6e8da 100644 --- a/vfs/lock.go +++ b/vfs/lock.go @@ -51,8 +51,8 @@ func (f *vfsFile) Lock(lock LockLevel) error { if f.lock != LOCK_NONE { panic(util.AssertErr()) } - if rc := osGetSharedLock(f.File); rc != _OK { - return rc + if err := osGetSharedLock(f.File); err != nil { + return err } f.lock = LOCK_SHARED return nil @@ -62,8 +62,8 @@ func (f *vfsFile) Lock(lock LockLevel) error { if f.lock != LOCK_SHARED { panic(util.AssertErr()) } - if rc := osGetReservedLock(f.File); rc != _OK { - return rc + if err := osGetReservedLock(f.File); err != nil { + return err } f.lock = LOCK_RESERVED return nil @@ -73,8 +73,8 @@ func (f *vfsFile) Lock(lock LockLevel) error { if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE { panic(util.AssertErr()) } - if rc := osGetExclusiveLock(f.File, &f.lock); rc != _OK { - return rc + if err := osGetExclusiveLock(f.File, &f.lock); err != nil { + return err } f.lock = LOCK_EXCLUSIVE return nil @@ -101,14 +101,14 @@ func (f *vfsFile) Unlock(lock LockLevel) error { switch lock { case LOCK_SHARED: - rc := osDowngradeLock(f.File, f.lock) + err := osDowngradeLock(f.File, f.lock) f.lock = LOCK_SHARED - return rc + return err case LOCK_NONE: - rc := osReleaseLock(f.File, f.lock) + err := osReleaseLock(f.File, f.lock) f.lock = LOCK_NONE - return rc + return err default: panic(util.AssertErr()) diff --git a/vfs/os_bsd.go b/vfs/os_bsd.go index 4542f8e7..a70579d2 100644 --- a/vfs/os_bsd.go +++ b/vfs/os_bsd.go @@ -8,13 +8,13 @@ import ( "golang.org/x/sys/unix" ) -func osGetSharedLock(file *os.File) _ErrorCode { +func osGetSharedLock(file *os.File) error { return osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) } -func osGetReservedLock(file *os.File) _ErrorCode { - rc := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) - if rc == _BUSY { +func osGetReservedLock(file *os.File) error { + err := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) + if err == _BUSY { // The documentation states that a lock is upgraded by // releasing the previous lock, then acquiring the new lock. // Going over the source code of various BSDs, though, @@ -26,19 +26,19 @@ func osGetReservedLock(file *os.File) _ErrorCode { // and invoke the busy handler if appropriate. return _BUSY_SNAPSHOT } - return rc + return err } -func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { +func osGetExclusiveLock(file *os.File, state *LockLevel) error { if *state >= LOCK_RESERVED { - return _OK + return nil } return osGetReservedLock(file) } -func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode { - rc := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) - if rc == _BUSY { +func osDowngradeLock(file *os.File, _ LockLevel) error { + err := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) + if err == _BUSY { // The documentation states that a lock is downgraded by // releasing the previous lock then acquiring the new lock. // Going over the source code of various BSDs, though, @@ -46,44 +46,44 @@ func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode { // Return IOERR_RDLOCK, as BUSY would cause an assert to fail. return _IOERR_RDLOCK } - return _OK + return err } -func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode { +func osReleaseLock(file *os.File, _ LockLevel) error { for { err := unix.Flock(int(file.Fd()), unix.LOCK_UN) if err == nil { - return _OK + return nil } if err != unix.EINTR { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } } } -func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { +func osCheckReservedLock(file *os.File) (bool, error) { // Test the RESERVED lock with fcntl(F_GETLK). // This only works on systems where fcntl and flock are compatible. // However, SQLite only calls this while holding a shared lock, // so the difference is immaterial. - lock, rc := osTestLock(file, _RESERVED_BYTE, 1) - return lock == unix.F_WRLCK, rc + lock, err := osTestLock(file, _RESERVED_BYTE, 1, _IOERR_CHECKRESERVEDLOCK) + return lock == unix.F_WRLCK, err } -func osFlock(file *os.File, how int, def _ErrorCode) _ErrorCode { +func osFlock(file *os.File, how int, def _ErrorCode) error { err := unix.Flock(int(file.Fd()), how) return osLockErrorCode(err, def) } -func osReadLock(file *os.File, start, len int64) _ErrorCode { +func osReadLock(file *os.File, start, len int64) error { return osLock(file, unix.F_RDLCK, start, len, _IOERR_RDLOCK) } -func osWriteLock(file *os.File, start, len int64) _ErrorCode { +func osWriteLock(file *os.File, start, len int64) error { return osLock(file, unix.F_WRLCK, start, len, _IOERR_LOCK) } -func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCode { +func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) error { err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{ Type: typ, Start: start, @@ -92,7 +92,7 @@ func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCo return osLockErrorCode(err, def) } -func osUnlock(file *os.File, start, len int64) _ErrorCode { +func osUnlock(file *os.File, start, len int64) error { lock := unix.Flock_t{ Type: unix.F_UNLCK, Start: start, @@ -101,10 +101,10 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode { for { err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &lock) if err == nil { - return _OK + return nil } if err != unix.EINTR { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } } } diff --git a/vfs/os_darwin.go b/vfs/os_darwin.go index 9bb8b559..6748c299 100644 --- a/vfs/os_darwin.go +++ b/vfs/os_darwin.go @@ -75,15 +75,15 @@ func osAllocate(file *os.File, size int64) error { return file.Truncate(size) } -func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { +func osReadLock(file *os.File, start, len int64, timeout time.Duration) error { return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) } -func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { +func osWriteLock(file *os.File, start, len int64, timeout time.Duration) error { return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) } -func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { +func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) error { lock := &flocktimeout_t{fl: unix.Flock_t{ Type: typ, Start: start, @@ -103,7 +103,7 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d return osLockErrorCode(err, def) } -func osUnlock(file *os.File, start, len int64) _ErrorCode { +func osUnlock(file *os.File, start, len int64) error { lock := unix.Flock_t{ Type: unix.F_UNLCK, Start: start, @@ -112,10 +112,10 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode { for { err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock) if err == nil { - return _OK + return nil } if err != unix.EINTR { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } } } diff --git a/vfs/os_dotlk.go b/vfs/os_dotlk.go index 7a9c3889..b64e8ec0 100644 --- a/vfs/os_dotlk.go +++ b/vfs/os_dotlk.go @@ -23,7 +23,7 @@ type vfsDotLocker struct { reserved *os.File // +checklocks:vfsDotLocksMtx } -func osGetSharedLock(file *os.File) _ErrorCode { +func osGetSharedLock(file *os.File) error { vfsDotLocksMtx.Lock() defer vfsDotLocksMtx.Unlock() @@ -34,7 +34,7 @@ func osGetSharedLock(file *os.File) _ErrorCode { if errors.Is(err, fs.ErrExist) { return _BUSY // Another process has the lock. } - return _IOERR_LOCK + return sysError{err, _IOERR_LOCK} } locker = &vfsDotLocker{} vfsDotLocks[name] = locker @@ -44,10 +44,10 @@ func osGetSharedLock(file *os.File) _ErrorCode { return _BUSY } locker.shared++ - return _OK + return nil } -func osGetReservedLock(file *os.File) _ErrorCode { +func osGetReservedLock(file *os.File) error { vfsDotLocksMtx.Lock() defer vfsDotLocksMtx.Unlock() @@ -61,10 +61,10 @@ func osGetReservedLock(file *os.File) _ErrorCode { return _BUSY } locker.reserved = file - return _OK + return nil } -func osGetExclusiveLock(file *os.File, _ *LockLevel) _ErrorCode { +func osGetExclusiveLock(file *os.File, _ *LockLevel) error { vfsDotLocksMtx.Lock() defer vfsDotLocksMtx.Unlock() @@ -81,10 +81,10 @@ func osGetExclusiveLock(file *os.File, _ *LockLevel) _ErrorCode { if locker.shared > 1 { return _BUSY } - return _OK + return nil } -func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode { +func osDowngradeLock(file *os.File, _ LockLevel) error { vfsDotLocksMtx.Lock() defer vfsDotLocksMtx.Unlock() @@ -100,10 +100,10 @@ func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode { if locker.pending == file { locker.pending = nil } - return _OK + return nil } -func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { +func osReleaseLock(file *os.File, state LockLevel) error { vfsDotLocksMtx.Lock() defer vfsDotLocksMtx.Unlock() @@ -115,7 +115,7 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { if locker.shared == 1 { if err := dotlk.Unlock(name + ".lock"); err != nil { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } delete(vfsDotLocks, name) } @@ -127,17 +127,14 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { locker.pending = nil } locker.shared-- - return _OK + return nil } -func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { +func osCheckReservedLock(file *os.File) (bool, error) { vfsDotLocksMtx.Lock() defer vfsDotLocksMtx.Unlock() name := file.Name() locker := vfsDotLocks[name] - if locker == nil { - return false, _OK - } - return locker.reserved != nil, _OK + return locker != nil && locker.reserved != nil, nil } diff --git a/vfs/os_linux.go b/vfs/os_linux.go index 893f1512..1f7705ca 100644 --- a/vfs/os_linux.go +++ b/vfs/os_linux.go @@ -44,15 +44,15 @@ func osAllocate(file *os.File, size int64) error { } -func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { +func osReadLock(file *os.File, start, len int64, timeout time.Duration) error { return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) } -func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { +func osWriteLock(file *os.File, start, len int64, timeout time.Duration) error { return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) } -func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { +func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) error { lock := unix.Flock_t{ Type: typ, Start: start, @@ -68,7 +68,7 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d return osLockErrorCode(err, def) } -func osUnlock(file *os.File, start, len int64) _ErrorCode { +func osUnlock(file *os.File, start, len int64) error { lock := unix.Flock_t{ Type: unix.F_UNLCK, Start: start, @@ -77,10 +77,10 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode { for { err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) if err == nil { - return _OK + return nil } if err != unix.EINTR { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } } } diff --git a/vfs/os_ofd.go b/vfs/os_ofd.go index d93050e8..e917e12d 100644 --- a/vfs/os_ofd.go +++ b/vfs/os_ofd.go @@ -9,25 +9,25 @@ import ( "golang.org/x/sys/unix" ) -func osGetSharedLock(file *os.File) _ErrorCode { +func osGetSharedLock(file *os.File) error { // Test the PENDING lock before acquiring a new SHARED lock. - if lock, _ := osTestLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK { + if lock, _ := osTestLock(file, _PENDING_BYTE, 1, _IOERR); lock == unix.F_WRLCK { return _BUSY } // Acquire the SHARED lock. return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) } -func osGetReservedLock(file *os.File) _ErrorCode { +func osGetReservedLock(file *os.File) error { // Acquire the RESERVED lock. return osWriteLock(file, _RESERVED_BYTE, 1, 0) } -func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { +func osGetExclusiveLock(file *os.File, state *LockLevel) error { if *state == LOCK_RESERVED { // A PENDING lock is needed before acquiring an EXCLUSIVE lock. - if rc := osWriteLock(file, _PENDING_BYTE, 1, -1); rc != _OK { - return rc + if err := osWriteLock(file, _PENDING_BYTE, 1, -1); err != nil { + return err } *state = LOCK_PENDING } @@ -35,10 +35,10 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond) } -func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { +func osDowngradeLock(file *os.File, state LockLevel) error { if state >= LOCK_EXCLUSIVE { // Downgrade to a SHARED lock. - if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { + if err := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); err != nil { // notest // this should never happen return _IOERR_RDLOCK } @@ -47,13 +47,13 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { return osUnlock(file, _PENDING_BYTE, 2) } -func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode { +func osReleaseLock(file *os.File, _ LockLevel) error { // Release all locks. return osUnlock(file, 0, 0) } -func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { +func osCheckReservedLock(file *os.File) (bool, error) { // Test the RESERVED lock. - lock, rc := osTestLock(file, _RESERVED_BYTE, 1) - return lock == unix.F_WRLCK, rc + lock, err := osTestLock(file, _RESERVED_BYTE, 1, _IOERR_CHECKRESERVEDLOCK) + return lock == unix.F_WRLCK, err } diff --git a/vfs/os_unix.go b/vfs/os_unix.go index 8aa07e47..69fdc8fb 100644 --- a/vfs/os_unix.go +++ b/vfs/os_unix.go @@ -59,7 +59,7 @@ func osSetMode(file *os.File, modeof string) error { return nil } -func osTestLock(file *os.File, start, len int64) (int16, _ErrorCode) { +func osTestLock(file *os.File, start, len int64, def _ErrorCode) (int16, error) { lock := unix.Flock_t{ Type: unix.F_WRLCK, Start: start, @@ -68,17 +68,17 @@ func osTestLock(file *os.File, start, len int64) (int16, _ErrorCode) { for { err := unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) if err == nil { - return lock.Type, _OK + return lock.Type, nil } if err != unix.EINTR { - return 0, _IOERR_CHECKRESERVEDLOCK + return 0, sysError{err, def} } } } -func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { +func osLockErrorCode(err error, def _ErrorCode) error { if err == nil { - return _OK + return nil } if errno, ok := err.(unix.Errno); ok { switch errno { @@ -92,12 +92,12 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { unix.ETIMEDOUT: return _BUSY case unix.EPERM: - return _PERM + return sysError{err, _PERM} } // notest // usually EWOULDBLOCK == EAGAIN if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN { return _BUSY } } - return def + return sysError{err, def} } diff --git a/vfs/os_windows.go b/vfs/os_windows.go index 19ad7a2e..eb843e39 100644 --- a/vfs/os_windows.go +++ b/vfs/os_windows.go @@ -26,25 +26,25 @@ func osWriteAt(file *os.File, p []byte, off int64) (int, error) { return n, err } -func osGetSharedLock(file *os.File) _ErrorCode { +func osGetSharedLock(file *os.File) error { // Acquire the PENDING lock temporarily before acquiring a new SHARED lock. - rc := osReadLock(file, _PENDING_BYTE, 1, 0) - if rc == _OK { + err := osReadLock(file, _PENDING_BYTE, 1, 0) + if err == nil { // Acquire the SHARED lock. - rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) + err = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) // Release the PENDING lock. osUnlock(file, _PENDING_BYTE, 1) } - return rc + return err } -func osGetReservedLock(file *os.File) _ErrorCode { +func osGetReservedLock(file *os.File) error { // Acquire the RESERVED lock. return osWriteLock(file, _RESERVED_BYTE, 1, 0) } -func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { +func osGetExclusiveLock(file *os.File, state *LockLevel) error { // A PENDING lock is needed before releasing the SHARED lock. if *state < LOCK_PENDING { // If we were RESERVED, we can block indefinitely. @@ -52,8 +52,8 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { if *state == LOCK_RESERVED { timeout = -1 } - if rc := osWriteLock(file, _PENDING_BYTE, 1, timeout); rc != _OK { - return rc + if err := osWriteLock(file, _PENDING_BYTE, 1, timeout); err != nil { + return err } *state = LOCK_PENDING } @@ -63,25 +63,25 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { // Acquire the EXCLUSIVE lock. // Can't wait here, because the file is not OVERLAPPED. - rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) + err := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) - if rc != _OK { + if err != nil { // Reacquire the SHARED lock. - if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { + if err := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); err != nil { // notest // this should never happen return _IOERR_RDLOCK } } - return rc + return err } -func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { +func osDowngradeLock(file *os.File, state LockLevel) error { if state >= LOCK_EXCLUSIVE { // Release the EXCLUSIVE lock while holding the PENDING lock. osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) // Reacquire the SHARED lock. - if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { + if err := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); err != nil { // notest // this should never happen return _IOERR_RDLOCK } @@ -94,10 +94,10 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { if state >= LOCK_PENDING { osUnlock(file, _PENDING_BYTE, 1) } - return _OK + return nil } -func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { +func osReleaseLock(file *os.File, state LockLevel) error { // Release all locks, PENDING must be last. if state >= LOCK_RESERVED { osUnlock(file, _RESERVED_BYTE, 1) @@ -108,31 +108,32 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { if state >= LOCK_PENDING { osUnlock(file, _PENDING_BYTE, 1) } - return _OK + return nil } -func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { +func osCheckReservedLock(file *os.File) (bool, error) { // Test the RESERVED lock. - rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK) - if rc == _BUSY { - return true, _OK + err := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK) + if err == _BUSY { + return true, nil } - if rc == _OK { + if err == nil { // Release the RESERVED lock. osUnlock(file, _RESERVED_BYTE, 1) + return false, nil } - return false, rc + return false, err } -func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { +func osReadLock(file *os.File, start, len uint32, timeout time.Duration) error { return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK) } -func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { +func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) error { return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK) } -func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode { +func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) error { var err error switch { default: @@ -143,16 +144,16 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def return osLockErrorCode(err, def) } -func osUnlock(file *os.File, start, len uint32) _ErrorCode { +func osUnlock(file *os.File, start, len uint32) error { err := windows.UnlockFileEx(windows.Handle(file.Fd()), 0, len, 0, &windows.Overlapped{Offset: start}) if err == windows.ERROR_NOT_LOCKED { - return _OK + return nil } if err != nil { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } - return _OK + return nil } func osLockEx(file *os.File, flags, start, len uint32) error { @@ -160,9 +161,9 @@ func osLockEx(file *os.File, flags, start, len uint32) error { 0, len, 0, &windows.Overlapped{Offset: start}) } -func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { +func osLockErrorCode(err error, def _ErrorCode) error { if err == nil { - return _OK + return nil } if errno, ok := err.(windows.Errno); ok { // https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63 @@ -175,5 +176,5 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { return _BUSY } } - return def + return sysError{err, def} } diff --git a/vfs/shm_bsd.go b/vfs/shm_bsd.go index 4545517f..ebf8418c 100644 --- a/vfs/shm_bsd.go +++ b/vfs/shm_bsd.go @@ -3,6 +3,7 @@ package vfs import ( + "cmp" "context" "errors" "io" @@ -68,9 +69,9 @@ func (s *vfsShm) Close() error { panic(util.AssertErr()) } -func (s *vfsShm) shmOpen() (rc _ErrorCode) { +func (s *vfsShm) shmOpen() (err error) { if s.vfsShmParent != nil { - return _OK + return nil } vfsShmListMtx.Lock() @@ -80,7 +81,7 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) { // Closing it would release all POSIX locks on it. fi, err := os.Stat(s.path) if err != nil && !errors.Is(err, fs.ErrNotExist) { - return _IOERR_FSTAT + return sysError{err, _IOERR_FSTAT} } // Find a shared file, increase the reference count. @@ -88,7 +89,7 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) { if g != nil && os.SameFile(fi, g.info) { s.vfsShmParent = g g.refs++ - return _OK + return nil } } @@ -96,34 +97,34 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) { f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666) if err != nil { - return _CANTOPEN + return sysError{err, _CANTOPEN} } defer func() { - if rc != _OK { + if err != nil { f.Close() } }() // Dead man's switch. - if lock, rc := osTestLock(f, _SHM_DMS, 1); rc != _OK { - return _IOERR_LOCK + if lock, err := osTestLock(f, _SHM_DMS, 1, _IOERR_LOCK); err != nil { + return err } else if lock == unix.F_WRLCK { return _BUSY } else if lock == unix.F_UNLCK { - if rc := osWriteLock(f, _SHM_DMS, 1); rc != _OK { - return rc + if err := osWriteLock(f, _SHM_DMS, 1); err != nil { + return err } if err := f.Truncate(0); err != nil { - return _IOERR_SHMOPEN + return sysError{err, _IOERR_SHMOPEN} } } - if rc := osReadLock(f, _SHM_DMS, 1); rc != _OK { - return rc + if err := osReadLock(f, _SHM_DMS, 1); err != nil { + return err } fi, err = f.Stat() if err != nil { - return _IOERR_FSTAT + return sysError{err, _IOERR_FSTAT} } // Add the new shared file. @@ -134,11 +135,11 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) { for i, g := range vfsShmList { if g == nil { vfsShmList[i] = s.vfsShmParent - return _OK + return nil } } vfsShmList = append(vfsShmList, s.vfsShmParent) - return _OK + return nil } func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) { @@ -147,8 +148,8 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext return 0, _IOERR_SHMMAP } - if rc := s.shmOpen(); rc != _OK { - return 0, rc + if err := s.shmOpen(); err != nil { + return 0, err } // Check if file is big enough. @@ -182,9 +183,9 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { defer s.Unlock() // Check if we can obtain/release locks locally. - rc := s.shmMemLock(offset, n, flags) - if rc != _OK { - return rc + err := s.shmMemLock(offset, n, flags) + if err != nil { + return err } // Obtain/release the appropriate file locks. @@ -196,36 +197,38 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { for i := begin; i < end; i++ { if s.vfsShmParent.lock[i] != 0 { if i > begin { - rc |= osUnlock(s.File, _SHM_BASE+int64(begin), int64(i-begin)) + err = cmp.Or(err, + osUnlock(s.File, _SHM_BASE+int64(begin), int64(i-begin))) } begin = i + 1 } } if end > begin { - rc |= osUnlock(s.File, _SHM_BASE+int64(begin), int64(end-begin)) + err = cmp.Or(err, + osUnlock(s.File, _SHM_BASE+int64(begin), int64(end-begin))) } - return rc + return err case flags&_SHM_SHARED != 0: // Acquiring a new shared lock on the file is only necessary // if there was a new shared lock in the range. for i := offset; i < offset+n; i++ { if s.vfsShmParent.lock[i] == 1 { - rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n)) + err = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n)) break } } case flags&_SHM_EXCLUSIVE != 0: // Acquiring an exclusive lock on the file is always necessary. - rc = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n)) + err = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n)) default: panic(util.AssertErr()) } - // Release the local locks we had acquired. - if rc != _OK { + if err != nil { + // Release the local locks we had acquired. s.shmMemLock(offset, n, flags^(_SHM_UNLOCK|_SHM_LOCK)) } - return rc + return err } func (s *vfsShm) shmUnmap(delete bool) { diff --git a/vfs/shm_copy.go b/vfs/shm_copy.go index 4e20bcb5..8e40aafb 100644 --- a/vfs/shm_copy.go +++ b/vfs/shm_copy.go @@ -32,7 +32,7 @@ const ( // https://sqlite.org/walformat.html#the_wal_index_file_format func (s *vfsShm) shmAcquire(errp *error) { - if errp != nil && *errp != _OK { + if errp != nil && *errp != nil { return } if len(s.ptrs) == 0 || shmEqual(s.shadow[0][:], s.shared[0][:]) { diff --git a/vfs/shm_dotlk.go b/vfs/shm_dotlk.go index 4dbfb3a5..8f2bc6ce 100644 --- a/vfs/shm_dotlk.go +++ b/vfs/shm_dotlk.go @@ -66,9 +66,9 @@ func (s *vfsShm) Close() error { return nil } -func (s *vfsShm) shmOpen() _ErrorCode { +func (s *vfsShm) shmOpen() error { if s.vfsShmParent != nil { - return _OK + return nil } vfsShmListMtx.Lock() @@ -78,7 +78,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { if g, ok := vfsShmList[s.path]; ok { s.vfsShmParent = g g.refs++ - return _OK + return nil } // Dead man's switch. @@ -87,13 +87,13 @@ func (s *vfsShm) shmOpen() _ErrorCode { return _BUSY } if err != nil { - return _IOERR_LOCK + return sysError{err, _IOERR_LOCK} } // Add the new shared buffer. s.vfsShmParent = &vfsShmParent{} vfsShmList[s.path] = s.vfsShmParent - return _OK + return nil } func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) { @@ -105,8 +105,8 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext s.free = mod.ExportedFunction("sqlite3_free") s.alloc = mod.ExportedFunction("sqlite3_malloc64") } - if rc := s.shmOpen(); rc != _OK { - return 0, rc + if err := s.shmOpen(); err != nil { + return 0, err } s.Lock() diff --git a/vfs/shm_memlk.go b/vfs/shm_memlk.go index 5c8071eb..d23c26c3 100644 --- a/vfs/shm_memlk.go +++ b/vfs/shm_memlk.go @@ -5,7 +5,7 @@ package vfs import "github.com/ncruces/go-sqlite3/internal/util" // +checklocks:s.Mutex -func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode { +func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) error { switch { case flags&_SHM_UNLOCK != 0: for i := offset; i < offset+n; i++ { @@ -48,6 +48,5 @@ func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode { default: panic(util.AssertErr()) } - - return _OK + return nil } diff --git a/vfs/shm_ofd.go b/vfs/shm_ofd.go index d7a37811..de31e59d 100644 --- a/vfs/shm_ofd.go +++ b/vfs/shm_ofd.go @@ -27,7 +27,10 @@ type vfsShm struct { var _ blockingSharedMemory = &vfsShm{} -func (s *vfsShm) shmOpen() _ErrorCode { +func (s *vfsShm) shmOpen() error { + if s.fileLock { + return nil + } if s.File == nil { f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666) @@ -37,17 +40,15 @@ func (s *vfsShm) shmOpen() _ErrorCode { s.readOnly = true } if err != nil { - return _CANTOPEN + return sysError{err, _CANTOPEN} } + s.fileLock = false s.File = f } - if s.fileLock { - return _OK - } // Dead man's switch. - if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK { - return _IOERR_LOCK + if lock, err := osTestLock(s.File, _SHM_DMS, 1, _IOERR_LOCK); err != nil { + return err } else if lock == unix.F_WRLCK { return _BUSY } else if lock == unix.F_UNLCK { @@ -61,16 +62,16 @@ func (s *vfsShm) shmOpen() _ErrorCode { // but only downgrade it to a shared lock. // So no point in blocking here. // The call below to obtain the shared DMS lock may use a blocking lock. - if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK { - return rc + if err := osWriteLock(s.File, _SHM_DMS, 1, 0); err != nil { + return err } if err := s.Truncate(0); err != nil { - return _IOERR_SHMOPEN + return sysError{err, _IOERR_SHMOPEN} } } - rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) - s.fileLock = rc == _OK - return rc + err := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) + s.fileLock = err == nil + return err } func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) { @@ -79,8 +80,8 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext return 0, _IOERR_SHMMAP } - if rc := s.shmOpen(); rc != _OK { - return 0, rc + if err := s.shmOpen(); err != nil { + return 0, err } // Check if file is big enough. @@ -170,6 +171,7 @@ func (s *vfsShm) shmUnmap(delete bool) { } s.Close() s.File = nil + s.fileLock = false } func (s *vfsShm) shmBarrier() { diff --git a/vfs/shm_windows.go b/vfs/shm_windows.go index bf542066..ad3e153a 100644 --- a/vfs/shm_windows.go +++ b/vfs/shm_windows.go @@ -40,29 +40,30 @@ func (s *vfsShm) Close() error { return s.File.Close() } -func (s *vfsShm) shmOpen() _ErrorCode { +func (s *vfsShm) shmOpen() error { + if s.fileLock { + return nil + } if s.File == nil { f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666) if err != nil { - return _CANTOPEN + return sysError{err, _CANTOPEN} } + s.fileLock = false s.File = f } - if s.fileLock { - return _OK - } // Dead man's switch. - if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc == _OK { + if osWriteLock(s.File, _SHM_DMS, 1, 0) == nil { err := s.Truncate(0) osUnlock(s.File, _SHM_DMS, 1) if err != nil { - return _IOERR_SHMOPEN + return sysError{err, _IOERR_SHMOPEN} } } - rc := osReadLock(s.File, _SHM_DMS, 1, 0) - s.fileLock = rc == _OK - return rc + err := osReadLock(s.File, _SHM_DMS, 1, 0) + s.fileLock = err == nil + return err } func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ ptr_t, err error) { @@ -75,8 +76,8 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext s.free = mod.ExportedFunction("sqlite3_free") s.alloc = mod.ExportedFunction("sqlite3_malloc64") } - if rc := s.shmOpen(); rc != _OK { - return 0, rc + if err := s.shmOpen(); err != nil { + return 0, err } defer s.shmAcquire(&err) @@ -172,6 +173,7 @@ func (s *vfsShm) shmUnmap(delete bool) { // Close the file. s.Close() s.File = nil + s.fileLock = false if delete { os.Remove(s.path) } diff --git a/vfs/vfs.go b/vfs/vfs.go index 49ca25b1..569346fb 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -477,6 +477,8 @@ func vfsErrorCode(ctx context.Context, err error, code _ErrorCode) _ErrorCode { return code } +// SystemError tags an error with a given +// sqlite3.ErrorCode or sqlite3.ExtendedErrorCode. func SystemError[T interface{ ~uint8 | ~uint16 }](err error, code T) error { if err == nil { return nil