1//go:build linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock || sqlite3_dotlk
2
3package vfs
4
5import "github.com/ncruces/go-sqlite3/internal/util"
6
7// SupportsFileLocking is false on platforms that do not support file locking.
8// To open a database file on those platforms,
9// you need to use the [nolock] or [immutable] URI parameters.
10//
11// [nolock]: https://sqlite.org/uri.html#urinolock
12// [immutable]: https://sqlite.org/uri.html#uriimmutable
13const SupportsFileLocking = true
14
15const (
16 _PENDING_BYTE = 0x40000000
17 _RESERVED_BYTE = (_PENDING_BYTE + 1)
18 _SHARED_FIRST = (_PENDING_BYTE + 2)
19 _SHARED_SIZE = 510
20)
21
22func (f *vfsFile) Lock(lock LockLevel) error {
23 switch {
24 case lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE:
25 // Argument check. SQLite never explicitly requests a pending lock.
26 panic(util.AssertErr())
27 case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE:
28 // Connection state check.
29 panic(util.AssertErr())
30 case f.lock == LOCK_NONE && lock > LOCK_SHARED:
31 // We never move from unlocked to anything higher than a shared lock.
32 panic(util.AssertErr())
33 case f.lock != LOCK_SHARED && lock == LOCK_RESERVED:
34 // A shared lock is always held when a reserved lock is requested.
35 panic(util.AssertErr())
36 }
37
38 // If we already have an equal or more restrictive lock, do nothing.
39 if f.lock >= lock {
40 return nil
41 }
42
43 // Do not allow any kind of write-lock on a read-only database.
44 if f.readOnly && lock >= LOCK_RESERVED {
45 return _IOERR_LOCK
46 }
47
48 switch lock {
49 case LOCK_SHARED:
50 // Must be unlocked to get SHARED.
51 if f.lock != LOCK_NONE {
52 panic(util.AssertErr())
53 }
54 if rc := osGetSharedLock(f.File); rc != _OK {
55 return rc
56 }
57 f.lock = LOCK_SHARED
58 return nil
59
60 case LOCK_RESERVED:
61 // Must be SHARED to get RESERVED.
62 if f.lock != LOCK_SHARED {
63 panic(util.AssertErr())
64 }
65 if rc := osGetReservedLock(f.File); rc != _OK {
66 return rc
67 }
68 f.lock = LOCK_RESERVED
69 return nil
70
71 case LOCK_EXCLUSIVE:
72 // Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
73 if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
74 panic(util.AssertErr())
75 }
76 if rc := osGetExclusiveLock(f.File, &f.lock); rc != _OK {
77 return rc
78 }
79 f.lock = LOCK_EXCLUSIVE
80 return nil
81
82 default:
83 panic(util.AssertErr())
84 }
85}
86
87func (f *vfsFile) Unlock(lock LockLevel) error {
88 switch {
89 case lock != LOCK_NONE && lock != LOCK_SHARED:
90 // Argument check.
91 panic(util.AssertErr())
92 case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE:
93 // Connection state check.
94 panic(util.AssertErr())
95 }
96
97 // If we don't have a more restrictive lock, do nothing.
98 if f.lock <= lock {
99 return nil
100 }
101
102 switch lock {
103 case LOCK_SHARED:
104 rc := osDowngradeLock(f.File, f.lock)
105 f.lock = LOCK_SHARED
106 return rc
107
108 case LOCK_NONE:
109 rc := osReleaseLock(f.File, f.lock)
110 f.lock = LOCK_NONE
111 return rc
112
113 default:
114 panic(util.AssertErr())
115 }
116}
117
118func (f *vfsFile) CheckReservedLock() (bool, error) {
119 // Connection state check.
120 if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE {
121 panic(util.AssertErr())
122 }
123
124 if f.lock >= LOCK_RESERVED {
125 return true, nil
126 }
127 return osCheckReservedLock(f.File)
128}