Files
sqlite3/vfs/os_windows.go

225 lines
6.0 KiB
Go
Raw Normal View History

2023-06-01 18:11:37 +01:00
package vfs
2023-01-25 14:59:02 +00:00
2023-02-08 15:58:36 +00:00
import (
2023-03-09 01:59:46 +00:00
"io/fs"
2023-02-08 15:58:36 +00:00
"os"
"syscall"
2023-03-18 01:13:31 +00:00
"time"
2023-02-08 15:58:36 +00:00
"golang.org/x/sys/windows"
)
2023-01-26 00:05:52 +00:00
2023-03-29 15:01:25 +01:00
// osOpenFile is a simplified copy of [os.openFileNolog]
2023-03-09 16:10:10 +00:00
// that uses syscall.FILE_SHARE_DELETE.
// https://go.dev/src/os/file_windows.go
//
// See: https://go.dev/issue/32088
2023-03-29 15:01:25 +01:00
func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
2023-03-09 16:10:10 +00:00
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
}
2023-03-29 15:01:25 +01:00
func osGetSharedLock(file *os.File, timeout time.Duration) _ErrorCode {
2023-03-23 12:40:55 +00:00
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
2023-03-29 15:01:25 +01:00
rc := osReadLock(file, _PENDING_BYTE, 1, timeout)
2023-03-23 12:40:55 +00:00
if rc == _OK {
// Acquire the SHARED lock.
2023-03-29 15:01:25 +01:00
rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
2023-03-23 12:40:55 +00:00
// Release the PENDING lock.
2023-03-29 15:01:25 +01:00
osUnlock(file, _PENDING_BYTE, 1)
2023-03-23 12:40:55 +00:00
}
return rc
}
2023-03-29 15:01:25 +01:00
func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
2023-03-18 01:13:31 +00:00
if timeout == 0 {
timeout = time.Millisecond
}
2023-02-08 15:58:36 +00:00
// Release the SHARED lock.
2023-03-29 15:01:25 +01:00
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
2023-02-08 15:58:36 +00:00
// Acquire the EXCLUSIVE lock.
2023-03-29 15:01:25 +01:00
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
2023-02-08 15:58:36 +00:00
if rc != _OK {
2023-03-23 12:40:55 +00:00
// Reacquire the SHARED lock.
2023-03-29 15:01:25 +01:00
osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
2023-02-08 15:58:36 +00:00
}
return rc
2023-01-26 00:05:52 +00:00
}
2023-05-19 03:04:07 +01:00
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
2023-02-21 12:51:52 +00:00
// Release the SHARED lock.
2023-03-29 15:01:25 +01:00
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
2023-02-08 15:58:36 +00:00
2023-02-21 12:51:52 +00:00
// Reacquire the SHARED lock.
2023-03-29 15:01:25 +01:00
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
2023-02-21 12:51:52 +00:00
// This should never happen.
// We should always be able to reacquire the read lock.
2023-03-29 15:01:25 +01:00
return _IOERR_RDLOCK
2023-02-21 12:51:52 +00:00
}
2023-02-08 15:58:36 +00:00
}
// Release the PENDING and RESERVED locks.
2023-05-19 03:04:07 +01:00
if state >= LOCK_RESERVED {
2023-03-29 15:01:25 +01:00
osUnlock(file, _RESERVED_BYTE, 1)
2023-02-21 12:51:52 +00:00
}
2023-05-19 03:04:07 +01:00
if state >= LOCK_PENDING {
2023-03-29 15:01:25 +01:00
osUnlock(file, _PENDING_BYTE, 1)
2023-02-21 12:51:52 +00:00
}
2023-01-26 00:05:52 +00:00
return _OK
}
2023-05-19 03:04:07 +01:00
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
2023-02-08 15:58:36 +00:00
// Release all locks.
2023-05-19 03:04:07 +01:00
if state >= LOCK_RESERVED {
2023-03-29 15:01:25 +01:00
osUnlock(file, _RESERVED_BYTE, 1)
2023-02-21 12:51:52 +00:00
}
2023-05-19 03:04:07 +01:00
if state >= LOCK_SHARED {
2023-03-29 15:01:25 +01:00
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
2023-02-21 12:51:52 +00:00
}
2023-05-19 03:04:07 +01:00
if state >= LOCK_PENDING {
2023-03-29 15:01:25 +01:00
osUnlock(file, _PENDING_BYTE, 1)
2023-02-21 12:51:52 +00:00
}
2023-01-26 00:05:52 +00:00
return _OK
}
2023-03-29 15:01:25 +01:00
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
2023-02-23 13:29:51 +00:00
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
2023-02-08 15:58:36 +00:00
0, len, 0, &windows.Overlapped{Offset: start})
2023-03-08 20:10:46 +00:00
if err == windows.ERROR_NOT_LOCKED {
return _OK
}
2023-02-08 15:58:36 +00:00
if err != nil {
2023-03-29 15:01:25 +01:00
return _IOERR_UNLOCK
2023-02-08 15:58:36 +00:00
}
return _OK
}
2023-03-29 15:01:25 +01:00
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
2023-08-20 03:07:53 +01:00
before := time.Now()
2023-03-23 01:07:38 +00:00
var err error
2023-03-18 01:13:31 +00:00
for {
2023-03-23 01:07:38 +00:00
err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
2023-03-18 01:13:31 +00:00
0, len, 0, &windows.Overlapped{Offset: start})
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
2023-03-23 01:07:38 +00:00
break
2023-03-18 01:13:31 +00:00
}
2023-08-20 03:07:53 +01:00
if timeout <= 0 || timeout < time.Since(before) {
break
}
if err := windows.TimeBeginPeriod(1); err != nil {
2023-03-23 01:07:38 +00:00
break
2023-03-18 01:13:31 +00:00
}
time.Sleep(time.Millisecond)
2023-08-20 03:07:53 +01:00
if err := windows.TimeEndPeriod(1); err != nil {
break
}
2023-03-18 01:13:31 +00:00
}
2023-03-29 15:01:25 +01:00
return osLockErrorCode(err, def)
2023-03-18 01:13:31 +00:00
}
2023-03-29 15:01:25 +01:00
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return osLock(file,
2023-02-08 15:58:36 +00:00
windows.LOCKFILE_FAIL_IMMEDIATELY,
2023-03-29 15:01:25 +01:00
start, len, timeout, _IOERR_RDLOCK)
2023-02-08 15:58:36 +00:00
}
2023-03-29 15:01:25 +01:00
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return osLock(file,
2023-02-08 15:58:36 +00:00
windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK,
2023-03-29 15:01:25 +01:00
start, len, timeout, _IOERR_LOCK)
2023-02-08 15:58:36 +00:00
}
2023-03-29 15:01:25 +01:00
func osCheckLock(file *os.File, start, len uint32) (bool, _ErrorCode) {
rc := osLock(file,
2023-03-16 02:52:22 +00:00
windows.LOCKFILE_FAIL_IMMEDIATELY,
2023-03-29 15:01:25 +01:00
start, len, 0, _IOERR_CHECKRESERVEDLOCK)
if rc == _BUSY {
2023-03-16 02:52:22 +00:00
return true, _OK
}
2023-02-23 13:29:51 +00:00
if rc == _OK {
2023-03-29 15:01:25 +01:00
osUnlock(file, start, len)
2023-02-23 13:29:51 +00:00
}
2023-03-16 02:52:22 +00:00
return false, rc
2023-02-23 13:29:51 +00:00
}
2023-03-29 15:01:25 +01:00
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
2023-02-08 15:58:36 +00:00
if err == nil {
return _OK
}
2023-03-17 17:13:03 +00:00
if errno, ok := err.(windows.Errno); ok {
2023-03-08 20:10:46 +00:00
// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
switch errno {
case
windows.ERROR_LOCK_VIOLATION,
2023-03-16 02:52:22 +00:00
windows.ERROR_IO_PENDING,
windows.ERROR_OPERATION_ABORTED:
2023-03-29 15:01:25 +01:00
return _BUSY
2023-03-08 20:10:46 +00:00
}
2023-02-08 15:58:36 +00:00
}
2023-03-08 20:10:46 +00:00
return def
2023-01-26 00:05:52 +00:00
}
2023-03-09 16:10:10 +00:00
// 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)
}