diff --git a/README.md b/README.md index 8a4f397..f113fc4 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,14 @@ disable connection pooling by calling #### Open File Description Locks -On Unix, this module uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html) -to synchronize access to database files. - POSIX advisory locks, which SQLite uses, are [broken by design](https://www.sqlite.org/src/artifact/90c4fa?ln=1073-1161). -OFD locks are fully compatible with process-associated POSIX advisory locks, -and are supported on Linux and macOS. -As a work around for other Unixes, you can use [`nolock=1`](https://www.sqlite.org/uri.html). + +On Linux and macOS, this module uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html) +to synchronize access to database files. +OFD locks are fully compatible with process-associated POSIX advisory locks. + +On other Unixes, this module uses [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2). +BSD locks may _not_ be compatible with process-associated POSIX advisory locks. #### Testing diff --git a/error.go b/error.go index 4dd3c14..0fa5bb5 100644 --- a/error.go +++ b/error.go @@ -202,7 +202,6 @@ const ( noFuncErr = errorString("sqlite3: could not find function: ") binaryErr = errorString("sqlite3: no SQLite binary embed/set/loaded") timeErr = errorString("sqlite3: invalid time value") - notImplErr = errorString("sqlite3: not implemented") whenceErr = errorString("sqlite3: invalid whence") offsetErr = errorString("sqlite3: invalid offset") ) diff --git a/vfs.go b/vfs.go index 434c2a8..e40acfb 100644 --- a/vfs.go +++ b/vfs.go @@ -338,9 +338,6 @@ 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 == notImplErr { - return uint32(NOTFOUND) - } if err != nil { return uint32(IOERR_TRUNCATE) } diff --git a/vfs_os_darwin.go b/vfs_os_darwin.go index 84b2459..2362e57 100644 --- a/vfs_os_darwin.go +++ b/vfs_os_darwin.go @@ -1,12 +1,26 @@ package sqlite3 import ( + "io" "os" "time" "golang.org/x/sys/unix" ) +const ( + // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h + _F_OFD_SETLK = 90 + _F_OFD_SETLKW = 91 + _F_OFD_GETLK = 92 + _F_OFD_SETLKWTIMEOUT = 93 +) + +type flocktimeout_t struct { + fl unix.Flock_t + timeout unix.Timespec +} + func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { if !fullsync { return unix.Fsync(int(file.Fd())) @@ -15,6 +29,14 @@ func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { } func (vfsOSMethods) Allocate(file *os.File, size int64) error { + off, err := file.Seek(0, io.SeekEnd) + if err != nil { + return err + } + if size <= off { + return nil + } + // https://stackoverflow.com/a/11497568/867786 store := unix.Fstore_t{ Flags: unix.F_ALLOCATECONTIG, @@ -24,37 +46,67 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error { } // Try to get a continous chunk of disk space. - err := unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) + err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) if err != nil { // OK, perhaps we are too fragmented, allocate non-continuous. store.Flags = unix.F_ALLOCATEALL - return unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) + unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) } - return nil + return file.Truncate(size) } -func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error { - const F_OFD_GETLK = 92 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h - return unix.FcntlFlock(file.Fd(), F_OFD_GETLK, lock) +func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode { + err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{ + Type: unix.F_UNLCK, + Start: start, + Len: len, + }) + if err != nil { + return IOERR_UNLOCK + } + return _OK } -func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error { - const F_OFD_SETLK = 90 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h - return unix.FcntlFlock(file.Fd(), F_OFD_SETLK, &lock) -} - -func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error { +func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + lock := flocktimeout_t{fl: unix.Flock_t{ + Type: unix.F_RDLCK, + Start: start, + Len: len, + }} + var err error if timeout == 0 { - return vfsOS.fcntlSetLock(file, lock) + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl) + } else { + lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond)) + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl) } - - const F_OFD_SETLKWTIMEOUT = 93 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h - flocktimeout := &struct { - unix.Flock_t - unix.Timespec - }{ - Flock_t: lock, - Timespec: unix.NsecToTimespec(int64(timeout / time.Nanosecond)), - } - return unix.FcntlFlock(file.Fd(), F_OFD_SETLKWTIMEOUT, &flocktimeout.Flock_t) + return vfsOS.lockErrorCode(err, IOERR_RDLOCK) +} + +func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + lock := flocktimeout_t{fl: unix.Flock_t{ + Type: unix.F_WRLCK, + Start: start, + Len: len, + }} + var err error + if timeout == 0 { + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl) + } else { + lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond)) + err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl) + } + return vfsOS.lockErrorCode(err, 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(), _F_OFD_GETLK, &lock) != nil { + return false, IOERR_CHECKRESERVEDLOCK + } + return lock.Type != unix.F_UNLCK, _OK } diff --git a/vfs_os_linux.go b/vfs_os_linux.go index 78938e3..e19ab74 100644 --- a/vfs_os_linux.go +++ b/vfs_os_linux.go @@ -9,7 +9,6 @@ import ( func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { if dataonly { - //lint:ignore SA1019 OK on linux _, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0) if err != 0 { return err @@ -26,24 +25,68 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error { return unix.Fallocate(int(file.Fd()), 0, 0, size) } -func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error { - return unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, lock) +func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode { + err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ + Type: unix.F_UNLCK, + Start: start, + Len: len, + }) + if err != nil { + return IOERR_UNLOCK + } + return _OK } -func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error { - return unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) -} - -func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error { +func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + lock := unix.Flock_t{ + Type: unix.F_RDLCK, + Start: start, + Len: len, + } + var err error for { - err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) if errno, _ := err.(unix.Errno); errno != unix.EAGAIN { - return err + break } if timeout < time.Millisecond { - return err + break } timeout -= time.Millisecond time.Sleep(time.Millisecond) } + return vfsOS.lockErrorCode(err, IOERR_RDLOCK) +} + +func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + lock := unix.Flock_t{ + Type: unix.F_WRLCK, + Start: start, + Len: len, + } + var err error + for { + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) + 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, IOERR_RDLOCK) +} + +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_OFD_GETLK, &lock) != nil { + return false, IOERR_CHECKRESERVEDLOCK + } + return lock.Type != unix.F_UNLCK, _OK } diff --git a/vfs_os_other.go b/vfs_os_other.go new file mode 100644 index 0000000..c00f530 --- /dev/null +++ b/vfs_os_other.go @@ -0,0 +1,80 @@ +//go:build !windows && !linux && !darwin + +package sqlite3 + +import ( + "io" + "os" + "time" + + "golang.org/x/sys/unix" +) + +func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { + return file.Sync() +} + +func (vfsOSMethods) Allocate(file *os.File, size int64) error { + off, err := file.Seek(0, io.SeekEnd) + if err != nil { + return err + } + if size <= off { + return nil + } + return file.Truncate(size) +} + +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) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + var err error + for { + err = unix.Flock(int(file.Fd()), unix.LOCK_SH|unix.LOCK_NB) + 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, IOERR_RDLOCK) +} + +func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + var err error + for { + err = unix.Flock(int(file.Fd()), unix.LOCK_EX|unix.LOCK_NB) + 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, IOERR_RDLOCK) +} + +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 +} diff --git a/vfs_os_posix.go b/vfs_os_posix.go deleted file mode 100644 index c3deac9..0000000 --- a/vfs_os_posix.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build !windows && !linux && !darwin - -package sqlite3 - -import ( - "os" - "time" - - "golang.org/x/sys/unix" -) - -func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error { - return file.Sync() -} - -func (vfsOSMethods) Allocate(file *os.File, size int64) error { - return notImplErr -} - -func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error { - return notImplErr -} - -func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error { - return notImplErr -} - -func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error { - return notImplErr -} diff --git a/vfs_os_unix.go b/vfs_os_unix.go index 8d9aba9..4f76194 100644 --- a/vfs_os_unix.go +++ b/vfs_os_unix.go @@ -55,47 +55,6 @@ func (vfsOSMethods) ReleaseLock(file *os.File, _ vfsLockState) xErrorCode { return vfsOS.unlock(file, 0, 0) } -func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode { - err := vfsOS.fcntlSetLock(file, unix.Flock_t{ - Type: unix.F_UNLCK, - Start: start, - Len: len, - }) - if err != nil { - return IOERR_UNLOCK - } - return _OK -} - -func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { - return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{ - Type: unix.F_RDLCK, - Start: start, - Len: len, - }, timeout), IOERR_RDLOCK) -} - -func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { - // TODO: implement timeouts. - return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{ - Type: unix.F_WRLCK, - Start: start, - Len: len, - }, 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 vfsOS.fcntlGetLock(file, &lock) != nil { - return false, IOERR_CHECKRESERVEDLOCK - } - return lock.Type != unix.F_UNLCK, _OK -} - func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode { if err == nil { return _OK diff --git a/vfs_os_windows.go b/vfs_os_windows.go index a2be3ce..0699d13 100644 --- a/vfs_os_windows.go +++ b/vfs_os_windows.go @@ -57,10 +57,10 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error { if err != nil { return err } - if size > off { - return file.Truncate(size) + if size <= off { + return nil } - return nil + return file.Truncate(size) } func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode {