lock.go

  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}