mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
241 lines
6.2 KiB
Go
241 lines
6.2 KiB
Go
package sqlite3vfs
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// osOpenFile is a simplified copy of [os.openFileNolog]
|
|
// that uses syscall.FILE_SHARE_DELETE.
|
|
// https://go.dev/src/os/file_windows.go
|
|
//
|
|
// See: https://go.dev/issue/32088
|
|
func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
|
if name == "" {
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
|
}
|
|
r, e := syscallOpen(name, flag, uint32(perm.Perm()))
|
|
if e != nil {
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
|
}
|
|
return os.NewFile(uintptr(r), name), nil
|
|
}
|
|
|
|
func osAccess(path string, flags AccessFlag) error {
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if flags == ACCESS_EXISTS {
|
|
return nil
|
|
}
|
|
|
|
var want fs.FileMode = windows.S_IRUSR
|
|
if flags == ACCESS_READWRITE {
|
|
want |= windows.S_IWUSR
|
|
}
|
|
if fi.IsDir() {
|
|
want |= windows.S_IXUSR
|
|
}
|
|
if fi.Mode()&want != want {
|
|
return fs.ErrPermission
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func osGetSharedLock(file *os.File, timeout time.Duration) _ErrorCode {
|
|
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
|
|
rc := osReadLock(file, _PENDING_BYTE, 1, timeout)
|
|
|
|
if rc == _OK {
|
|
// Acquire the SHARED lock.
|
|
rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
|
|
|
// Release the PENDING lock.
|
|
osUnlock(file, _PENDING_BYTE, 1)
|
|
}
|
|
return rc
|
|
}
|
|
|
|
func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
|
|
if timeout == 0 {
|
|
timeout = time.Millisecond
|
|
}
|
|
|
|
// Release the SHARED lock.
|
|
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
|
|
|
// Acquire the EXCLUSIVE lock.
|
|
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
|
|
|
if rc != _OK {
|
|
// Reacquire the SHARED lock.
|
|
osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
|
}
|
|
return rc
|
|
}
|
|
|
|
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
|
if state >= LOCK_EXCLUSIVE {
|
|
// Release the SHARED lock.
|
|
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
|
|
|
// Reacquire the SHARED lock.
|
|
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); 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.
|
|
if state >= LOCK_RESERVED {
|
|
osUnlock(file, _RESERVED_BYTE, 1)
|
|
}
|
|
if state >= LOCK_PENDING {
|
|
osUnlock(file, _PENDING_BYTE, 1)
|
|
}
|
|
return _OK
|
|
}
|
|
|
|
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
|
// Release all locks.
|
|
if state >= LOCK_RESERVED {
|
|
osUnlock(file, _RESERVED_BYTE, 1)
|
|
}
|
|
if state >= LOCK_SHARED {
|
|
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
|
}
|
|
if state >= LOCK_PENDING {
|
|
osUnlock(file, _PENDING_BYTE, 1)
|
|
}
|
|
return _OK
|
|
}
|
|
|
|
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
|
|
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
|
|
0, len, 0, &windows.Overlapped{Offset: start})
|
|
if err == windows.ERROR_NOT_LOCKED {
|
|
return _OK
|
|
}
|
|
if err != nil {
|
|
return _IOERR_UNLOCK
|
|
}
|
|
return _OK
|
|
}
|
|
|
|
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
|
var err error
|
|
for {
|
|
err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
|
|
0, len, 0, &windows.Overlapped{Offset: start})
|
|
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
|
|
break
|
|
}
|
|
if timeout < time.Millisecond {
|
|
break
|
|
}
|
|
timeout -= time.Millisecond
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
return osLockErrorCode(err, def)
|
|
}
|
|
|
|
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
|
return osLock(file,
|
|
windows.LOCKFILE_FAIL_IMMEDIATELY,
|
|
start, len, timeout, _IOERR_RDLOCK)
|
|
}
|
|
|
|
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
|
return osLock(file,
|
|
windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK,
|
|
start, len, timeout, _IOERR_LOCK)
|
|
}
|
|
|
|
func osCheckLock(file *os.File, start, len uint32) (bool, _ErrorCode) {
|
|
rc := osLock(file,
|
|
windows.LOCKFILE_FAIL_IMMEDIATELY,
|
|
start, len, 0, _IOERR_CHECKRESERVEDLOCK)
|
|
if rc == _BUSY {
|
|
return true, _OK
|
|
}
|
|
if rc == _OK {
|
|
osUnlock(file, start, len)
|
|
}
|
|
return false, rc
|
|
}
|
|
|
|
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
|
if err == nil {
|
|
return _OK
|
|
}
|
|
if errno, ok := err.(windows.Errno); ok {
|
|
// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
|
|
switch errno {
|
|
case
|
|
windows.ERROR_LOCK_VIOLATION,
|
|
windows.ERROR_IO_PENDING,
|
|
windows.ERROR_OPERATION_ABORTED:
|
|
return _BUSY
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
// syscallOpen is a simplified copy of [syscall.Open]
|
|
// that uses syscall.FILE_SHARE_DELETE.
|
|
// https://go.dev/src/syscall/syscall_windows.go
|
|
func syscallOpen(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
|
|
if len(path) == 0 {
|
|
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
|
|
}
|
|
pathp, err := syscall.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return syscall.InvalidHandle, err
|
|
}
|
|
var access uint32
|
|
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
|
|
case syscall.O_RDONLY:
|
|
access = syscall.GENERIC_READ
|
|
case syscall.O_WRONLY:
|
|
access = syscall.GENERIC_WRITE
|
|
case syscall.O_RDWR:
|
|
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
|
|
}
|
|
if mode&syscall.O_CREAT != 0 {
|
|
access |= syscall.GENERIC_WRITE
|
|
}
|
|
if mode&syscall.O_APPEND != 0 {
|
|
access &^= syscall.GENERIC_WRITE
|
|
access |= syscall.FILE_APPEND_DATA
|
|
}
|
|
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
|
|
var createmode uint32
|
|
switch {
|
|
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
|
|
createmode = syscall.CREATE_NEW
|
|
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
|
|
createmode = syscall.CREATE_ALWAYS
|
|
case mode&syscall.O_CREAT == syscall.O_CREAT:
|
|
createmode = syscall.OPEN_ALWAYS
|
|
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
|
|
createmode = syscall.TRUNCATE_EXISTING
|
|
default:
|
|
createmode = syscall.OPEN_EXISTING
|
|
}
|
|
var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL
|
|
if perm&syscall.S_IWRITE == 0 {
|
|
attrs = syscall.FILE_ATTRIBUTE_READONLY
|
|
}
|
|
if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ {
|
|
// Necessary for opening directory handles.
|
|
attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS
|
|
}
|
|
return syscall.CreateFile(pathp, access, sharemode, nil, createmode, attrs, 0)
|
|
}
|