mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Unix locks.
This commit is contained in:
10
error.go
10
error.go
@@ -1,6 +1,7 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -42,5 +43,12 @@ const (
|
||||
noNulErr = errorString("sqlite3: missing NUL terminator")
|
||||
noGlobalErr = errorString("sqlite3: could not find global: ")
|
||||
noFuncErr = errorString("sqlite3: could not find function: ")
|
||||
assertErr = errorString("sqlite3: assertion failed")
|
||||
)
|
||||
|
||||
func assertErr() errorString {
|
||||
msg := "sqlite3: assertion failed"
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
|
||||
}
|
||||
return errorString(msg)
|
||||
}
|
||||
|
||||
3
init_test.go
Normal file
3
init_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package sqlite3_test
|
||||
|
||||
import _ "github.com/ncruces/go-sqlite3/embed"
|
||||
75
tests/db_test.go
Normal file
75
tests/db_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestDB_memory(t *testing.T) {
|
||||
testDB(t, ":memory:")
|
||||
}
|
||||
|
||||
func TestDB_file(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "sqlite3-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testDB(t, filepath.Join(dir, "test.db"))
|
||||
}
|
||||
|
||||
func testDB(t *testing.T, name string) {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO users(id, name) VALUES(0, 'go'), (1, 'zig'), (2, 'whatever')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
row := 0
|
||||
ids := []int{0, 1, 2}
|
||||
names := []string{"go", "zig", "whatever"}
|
||||
for ; stmt.Step(); row++ {
|
||||
if ids[row] != stmt.ColumnInt(0) {
|
||||
t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[row])
|
||||
}
|
||||
if names[row] != stmt.ColumnText(1) {
|
||||
t.Errorf("got %q, want %q", stmt.ColumnText(1), names[row])
|
||||
}
|
||||
}
|
||||
if err := stmt.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row != 3 {
|
||||
t.Errorf("got %d rows, want %d", row, len(ids))
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
25
tests/dir_test.go
Normal file
25
tests/dir_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
func TestDir(t *testing.T) {
|
||||
_, err := sqlite3.Open(".")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
var serr *sqlite3.Error
|
||||
if !errors.As(err, &serr) {
|
||||
t.Fatal("want sqlite3.Error")
|
||||
}
|
||||
if serr.Code != sqlite3.CANTOPEN {
|
||||
t.Error("want sqlite3.CANTOPEN")
|
||||
}
|
||||
if got := err.Error(); got != "sqlite3: unable to open database file" {
|
||||
t.Error("got message: ", got)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package sqlite3_test
|
||||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,72 +13,11 @@ import (
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestDB_memory(t *testing.T) {
|
||||
testDB(t, ":memory:")
|
||||
}
|
||||
|
||||
func TestDB_file(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "sqlite3-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testDB(t, filepath.Join(dir, "test.db"))
|
||||
}
|
||||
|
||||
func testDB(t *testing.T, name string) {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestParallel(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO users(id, name) VALUES(0, 'go'), (1, 'zig'), (2, 'whatever')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
row := 0
|
||||
ids := []int{0, 1, 2}
|
||||
names := []string{"go", "zig", "whatever"}
|
||||
for ; stmt.Step(); row++ {
|
||||
if ids[row] != stmt.ColumnInt(0) {
|
||||
t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[row])
|
||||
}
|
||||
if names[row] != stmt.ColumnText(1) {
|
||||
t.Errorf("got %q, want %q", stmt.ColumnText(1), names[row])
|
||||
}
|
||||
}
|
||||
if err := stmt.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row != 3 {
|
||||
t.Errorf("got %d rows, want %d", row, len(ids))
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDB_parallel(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "sqlite3-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -172,20 +111,3 @@ func TestDB_parallel(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpen_dir(t *testing.T) {
|
||||
_, err := sqlite3.Open(".")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
var serr *sqlite3.Error
|
||||
if !errors.As(err, &serr) {
|
||||
t.Fatal("want sqlite3.Error")
|
||||
}
|
||||
if serr.Code != sqlite3.CANTOPEN {
|
||||
t.Error("want sqlite3.CANTOPEN")
|
||||
}
|
||||
if got := err.Error(); got != "sqlite3: unable to open database file" {
|
||||
t.Error("got message: ", got)
|
||||
}
|
||||
}
|
||||
4
vfs.go
4
vfs.go
@@ -292,8 +292,8 @@ func vfsSync(ctx context.Context, mod api.Module, pFile, flags uint32) uint32 {
|
||||
}
|
||||
|
||||
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) uint32 {
|
||||
// This uses [file.Seek] because we don't care about the offset for reading/writing.
|
||||
// But consider using [file.Stat] instead (as other VFSes do).
|
||||
// This uses [os.File.Seek] because we don't care about the offset for reading/writing.
|
||||
// But consider using [os.File.Stat] instead (as other VFSes do).
|
||||
|
||||
file := vfsFilePtr{mod, pFile}.OSFile()
|
||||
off, err := file.Seek(0, io.SeekEnd)
|
||||
|
||||
26
vfs_files.go
26
vfs_files.go
@@ -8,12 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type vfsOpenFile struct {
|
||||
file *os.File
|
||||
info os.FileInfo
|
||||
nref int
|
||||
|
||||
shared int
|
||||
vfsLocker
|
||||
file *os.File
|
||||
info os.FileInfo
|
||||
nref int
|
||||
locker vfsFileLocker
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -38,11 +36,10 @@ func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 {
|
||||
}
|
||||
|
||||
of := &vfsOpenFile{
|
||||
file: file,
|
||||
info: info,
|
||||
nref: 1,
|
||||
|
||||
vfsLocker: &vfsFileLocker{file, _NO_LOCK},
|
||||
file: file,
|
||||
info: info,
|
||||
nref: 1,
|
||||
locker: vfsFileLocker{file: file},
|
||||
}
|
||||
|
||||
// Find an empty slot.
|
||||
@@ -84,6 +81,13 @@ func (p vfsFilePtr) OSFile() *os.File {
|
||||
return vfsOpenFiles[id].file
|
||||
}
|
||||
|
||||
func (p vfsFilePtr) Locker() *vfsFileLocker {
|
||||
id := p.ID()
|
||||
vfsOpenFilesMtx.Lock()
|
||||
defer vfsOpenFilesMtx.Unlock()
|
||||
return &vfsOpenFiles[id].locker
|
||||
}
|
||||
|
||||
func (p vfsFilePtr) ID() uint32 {
|
||||
return memory{p}.readUint32(p.ptr + ptrlen)
|
||||
}
|
||||
|
||||
157
vfs_lock.go
157
vfs_lock.go
@@ -3,6 +3,7 @@ package sqlite3
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
@@ -53,32 +54,18 @@ const (
|
||||
_SHARED_SIZE = 510
|
||||
)
|
||||
|
||||
type (
|
||||
vfsLockState uint32
|
||||
xErrorCode = ExtendedErrorCode
|
||||
)
|
||||
|
||||
type vfsLocker interface {
|
||||
LockState() vfsLockState
|
||||
|
||||
LockShared() xErrorCode // UNLOCKED -> SHARED
|
||||
LockReserved() xErrorCode // SHARED -> RESERVED
|
||||
LockPending() xErrorCode // SHARED|RESERVED -> PENDING
|
||||
LockExclusive() xErrorCode // PENDING -> EXCLUSIVE
|
||||
DowngradeLock() xErrorCode // SHARED <- EXCLUSIVE|PENDING|RESERVED
|
||||
Unlock() xErrorCode // UNLOCKED <- EXCLUSIVE|PENDING|RESERVED|SHARED
|
||||
|
||||
CheckReservedLock() (bool, xErrorCode)
|
||||
}
|
||||
type vfsLockState uint32
|
||||
|
||||
type vfsFileLocker struct {
|
||||
*os.File
|
||||
state vfsLockState
|
||||
sync.Mutex
|
||||
file *os.File
|
||||
state vfsLockState
|
||||
shared int
|
||||
}
|
||||
|
||||
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
|
||||
if assert && (eLock == _NO_LOCK || eLock == _PENDING_LOCK) {
|
||||
panic(assertErr + " [d4oxww]")
|
||||
if eLock != _SHARED_LOCK && eLock != _RESERVED_LOCK && eLock != _EXCLUSIVE_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
ptr := vfsFilePtr{mod, pFile}
|
||||
@@ -89,86 +76,89 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
|
||||
return _OK
|
||||
}
|
||||
|
||||
if assert {
|
||||
switch {
|
||||
case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
|
||||
// We never move from unlocked to anything higher than shared lock.
|
||||
panic(assertErr + " [pfa77m]")
|
||||
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
|
||||
// A shared lock is always held when a reserved lock is requested.
|
||||
panic(assertErr + " [5cfmsp]")
|
||||
}
|
||||
switch {
|
||||
case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
|
||||
// We never move from unlocked to anything higher than a shared lock.
|
||||
panic(assertErr())
|
||||
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
|
||||
// A shared lock is always held when a reserved lock is requested.
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
vfsOpenFilesMtx.Lock()
|
||||
defer vfsOpenFilesMtx.Unlock()
|
||||
of := vfsOpenFiles[ptr.ID()]
|
||||
fLock := of.LockState()
|
||||
fLock := ptr.Locker()
|
||||
fLock.Lock()
|
||||
defer fLock.Unlock()
|
||||
|
||||
// If some other connection has a lock that precludes the requested lock, return BUSY.
|
||||
if cLock != fLock && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) {
|
||||
if cLock != fLock.state && (eLock > _SHARED_LOCK || fLock.state >= _PENDING_LOCK) {
|
||||
return uint32(BUSY)
|
||||
}
|
||||
if eLock == _EXCLUSIVE_LOCK && of.shared > 1 {
|
||||
// We are trying for an exclusive lock but another connection in this
|
||||
// same process is still holding a shared lock.
|
||||
// We are trying for an exclusive lock but another connection is still holding a shared lock.
|
||||
if eLock == _EXCLUSIVE_LOCK && fLock.shared > 1 {
|
||||
return uint32(BUSY)
|
||||
}
|
||||
|
||||
// If a SHARED lock is requested, and some other connection has a SHARED or RESERVED lock,
|
||||
// then increment the reference count and return OK.
|
||||
if eLock == _SHARED_LOCK && (fLock == _SHARED_LOCK || fLock == _RESERVED_LOCK) {
|
||||
if assert && !(cLock == _NO_LOCK && of.shared > 0) {
|
||||
panic(assertErr + " [k7coz6]")
|
||||
if eLock == _SHARED_LOCK && (fLock.state == _SHARED_LOCK || fLock.state == _RESERVED_LOCK) {
|
||||
if cLock != _NO_LOCK || fLock.shared <= 0 {
|
||||
panic(assertErr())
|
||||
}
|
||||
ptr.SetLock(_SHARED_LOCK)
|
||||
of.shared++
|
||||
fLock.shared++
|
||||
return _OK
|
||||
}
|
||||
|
||||
// Get PENDING lock before acquiring an EXCLUSIVE lock.
|
||||
if eLock == _EXCLUSIVE_LOCK && cLock == _RESERVED_LOCK {
|
||||
if rc := fLock.GetPending(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_PENDING_LOCK)
|
||||
}
|
||||
|
||||
// If control gets to this point, then actually go ahead and make
|
||||
// operating system calls for the specified lock.
|
||||
switch eLock {
|
||||
case _SHARED_LOCK:
|
||||
if assert && !(fLock == _NO_LOCK && of.shared == 0) {
|
||||
panic(assertErr + " [jsyttq]")
|
||||
if !(fLock.state == _NO_LOCK && fLock.shared == 0) {
|
||||
panic(assertErr())
|
||||
}
|
||||
if rc := of.LockShared(); rc != _OK {
|
||||
if rc := fLock.GetShared(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
of.shared = 1
|
||||
ptr.SetLock(_SHARED_LOCK)
|
||||
fLock.shared = 1
|
||||
return _OK
|
||||
|
||||
case _RESERVED_LOCK:
|
||||
if rc := of.LockReserved(); rc != _OK {
|
||||
if !(fLock.state == _SHARED_LOCK && fLock.shared > 0) {
|
||||
panic(assertErr())
|
||||
}
|
||||
if rc := fLock.GetReserved(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_RESERVED_LOCK)
|
||||
return _OK
|
||||
|
||||
case _EXCLUSIVE_LOCK:
|
||||
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
|
||||
if cLock < _PENDING_LOCK {
|
||||
if rc := of.LockPending(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_PENDING_LOCK)
|
||||
if !(fLock.state != _NO_LOCK && fLock.shared > 0) {
|
||||
panic(assertErr())
|
||||
}
|
||||
if rc := of.LockExclusive(); rc != _OK {
|
||||
if rc := fLock.GetExclusive(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_EXCLUSIVE_LOCK)
|
||||
return _OK
|
||||
|
||||
default:
|
||||
panic(assertErr + " [56ng2l]")
|
||||
panic(assertErr())
|
||||
}
|
||||
}
|
||||
|
||||
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
|
||||
if assert && (eLock != _NO_LOCK && eLock != _SHARED_LOCK) {
|
||||
panic(assertErr + " [7i4jw3]")
|
||||
if eLock != _NO_LOCK && eLock != _SHARED_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
ptr := vfsFilePtr{mod, pFile}
|
||||
@@ -179,20 +169,19 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
return _OK
|
||||
}
|
||||
|
||||
vfsOpenFilesMtx.Lock()
|
||||
defer vfsOpenFilesMtx.Unlock()
|
||||
of := vfsOpenFiles[ptr.ID()]
|
||||
fLock := of.LockState()
|
||||
fLock := ptr.Locker()
|
||||
fLock.Lock()
|
||||
defer fLock.Unlock()
|
||||
|
||||
if assert && of.shared <= 0 {
|
||||
panic(assertErr + " [2bhkwg]")
|
||||
if fLock.shared <= 0 {
|
||||
panic(assertErr())
|
||||
}
|
||||
if cLock > _SHARED_LOCK {
|
||||
if assert && cLock != fLock {
|
||||
panic(assertErr + " [6pmjqf]")
|
||||
if cLock != fLock.state {
|
||||
panic(assertErr())
|
||||
}
|
||||
if eLock == _SHARED_LOCK {
|
||||
if rc := of.DowngradeLock(); rc != _OK {
|
||||
if rc := fLock.Downgrade(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_SHARED_LOCK)
|
||||
@@ -200,27 +189,25 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
}
|
||||
}
|
||||
|
||||
if assert && eLock != _NO_LOCK {
|
||||
panic(assertErr + " [gilo9p]")
|
||||
if eLock != _NO_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
// Decrement the shared lock counter. Release the file lock
|
||||
// only when all connections have released the lock.
|
||||
// Release the file lock only when all connections have released the lock.
|
||||
// Decrement the shared lock counter.
|
||||
switch {
|
||||
case of.shared > 1:
|
||||
ptr.SetLock(_NO_LOCK)
|
||||
of.shared--
|
||||
return _OK
|
||||
|
||||
case of.shared == 1:
|
||||
if rc := of.Unlock(); rc != _OK {
|
||||
case fLock.shared == 1:
|
||||
if rc := fLock.Release(); rc != _OK {
|
||||
return uint32(rc)
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case fLock.shared > 1:
|
||||
ptr.SetLock(_NO_LOCK)
|
||||
of.shared = 0
|
||||
fLock.shared--
|
||||
return _OK
|
||||
|
||||
default:
|
||||
panic(assertErr + " [50gw51]")
|
||||
panic(assertErr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,15 +215,15 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
|
||||
ptr := vfsFilePtr{mod, pFile}
|
||||
cLock := ptr.Lock()
|
||||
|
||||
if assert && cLock > _SHARED_LOCK {
|
||||
panic(assertErr + " [zarygt]")
|
||||
if cLock > _SHARED_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
vfsOpenFilesMtx.Lock()
|
||||
defer vfsOpenFilesMtx.Unlock()
|
||||
of := vfsOpenFiles[ptr.ID()]
|
||||
fLock := ptr.Locker()
|
||||
fLock.Lock()
|
||||
defer fLock.Unlock()
|
||||
|
||||
locked, rc := of.CheckReservedLock()
|
||||
locked, rc := fLock.CheckReserved()
|
||||
if rc != _OK {
|
||||
return uint32(IOERR_CHECKRESERVEDLOCK)
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ func Test_vfsLock(t *testing.T) {
|
||||
defer file2.Close()
|
||||
|
||||
vfsOpenFiles = append(vfsOpenFiles, &vfsOpenFile{
|
||||
file: file1,
|
||||
nref: 1,
|
||||
vfsLocker: &vfsFileLocker{file1, _NO_LOCK},
|
||||
file: file1,
|
||||
nref: 1,
|
||||
locker: vfsFileLocker{file: file1},
|
||||
}, &vfsOpenFile{
|
||||
file: file2,
|
||||
nref: 1,
|
||||
vfsLocker: &vfsFileLocker{file2, _NO_LOCK},
|
||||
file: file2,
|
||||
nref: 1,
|
||||
locker: vfsFileLocker{file: file2},
|
||||
})
|
||||
|
||||
mem := newMemory(128)
|
||||
@@ -65,7 +65,7 @@ func Test_vfsLock(t *testing.T) {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
|
||||
rc = vfsLock(context.TODO(), mem.mod, 16, _EXCLUSIVE_LOCK)
|
||||
rc = vfsLock(context.TODO(), mem.mod, 16, _RESERVED_LOCK)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
58
vfs_unix.go
58
vfs_unix.go
@@ -12,13 +12,9 @@ func deleteOnClose(f *os.File) {
|
||||
_ = os.Remove(f.Name())
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockState() vfsLockState {
|
||||
return l.state
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockShared() xErrorCode {
|
||||
if assert && !(l.state == _NO_LOCK) {
|
||||
panic(assertErr + " [wz9dcw]")
|
||||
func (l *vfsFileLocker) GetShared() ExtendedErrorCode {
|
||||
if l.state != _NO_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
// A PENDING lock is needed before acquiring a SHARED lock.
|
||||
@@ -52,9 +48,9 @@ func (l *vfsFileLocker) LockShared() xErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockReserved() xErrorCode {
|
||||
if assert && !(l.state == _SHARED_LOCK) {
|
||||
panic(assertErr + " [m9hcil]")
|
||||
func (l *vfsFileLocker) GetReserved() ExtendedErrorCode {
|
||||
if l.state != _SHARED_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
// Acquire the RESERVED lock.
|
||||
@@ -69,9 +65,9 @@ func (l *vfsFileLocker) LockReserved() xErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockPending() xErrorCode {
|
||||
if assert && !(l.state == _SHARED_LOCK || l.state == _RESERVED_LOCK) {
|
||||
panic(assertErr + " [wx8nk2]")
|
||||
func (l *vfsFileLocker) GetPending() ExtendedErrorCode {
|
||||
if l.state != _RESERVED_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
// Acquire the PENDING lock.
|
||||
@@ -86,9 +82,9 @@ func (l *vfsFileLocker) LockPending() xErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockExclusive() xErrorCode {
|
||||
if assert && !(l.state == _PENDING_LOCK) {
|
||||
panic(assertErr + " [84nbax]")
|
||||
func (l *vfsFileLocker) GetExclusive() ExtendedErrorCode {
|
||||
if l.state != _SHARED_LOCK && l.state != _PENDING_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
@@ -103,9 +99,9 @@ func (l *vfsFileLocker) LockExclusive() xErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) DowngradeLock() xErrorCode {
|
||||
if assert && !(l.state > _SHARED_LOCK) {
|
||||
panic(assertErr + " [je31i3]")
|
||||
func (l *vfsFileLocker) Downgrade() ExtendedErrorCode {
|
||||
if l.state <= _SHARED_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
// Downgrade to a SHARED lock.
|
||||
@@ -134,7 +130,11 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) Unlock() xErrorCode {
|
||||
func (l *vfsFileLocker) Release() ExtendedErrorCode {
|
||||
if l.state <= _NO_LOCK {
|
||||
panic(assertErr())
|
||||
}
|
||||
|
||||
// Release all locks.
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
@@ -145,13 +145,15 @@ func (l *vfsFileLocker) Unlock() xErrorCode {
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
|
||||
func (l *vfsFileLocker) CheckReserved() (bool, ExtendedErrorCode) {
|
||||
if l.state >= _RESERVED_LOCK {
|
||||
return true, _OK
|
||||
}
|
||||
// Test all write locks.
|
||||
// Test the RESERVED lock.
|
||||
lock := syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _RESERVED_BYTE,
|
||||
Len: 1,
|
||||
}
|
||||
if l.fcntlGetLock(&lock) != nil {
|
||||
return false, IOERR_CHECKRESERVEDLOCK
|
||||
@@ -169,7 +171,7 @@ func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error {
|
||||
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
|
||||
F_GETLK = 92 // F_OFD_GETLK
|
||||
}
|
||||
return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)
|
||||
return syscall.FcntlFlock(l.file.Fd(), F_GETLK, lock)
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
|
||||
@@ -182,10 +184,10 @@ func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
|
||||
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
|
||||
F_SETLK = 90 // F_OFD_SETLK
|
||||
}
|
||||
return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)
|
||||
return syscall.FcntlFlock(l.file.Fd(), F_SETLK, lock)
|
||||
}
|
||||
|
||||
func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
|
||||
func (*vfsFileLocker) errorCode(err error, def ExtendedErrorCode) ExtendedErrorCode {
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
switch errno {
|
||||
case syscall.EACCES:
|
||||
@@ -195,9 +197,9 @@ func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
|
||||
case syscall.ENOLCK:
|
||||
case syscall.EDEADLK:
|
||||
case syscall.ETIMEDOUT:
|
||||
return xErrorCode(BUSY)
|
||||
return ExtendedErrorCode(BUSY)
|
||||
case syscall.EPERM:
|
||||
return xErrorCode(PERM)
|
||||
return ExtendedErrorCode(PERM)
|
||||
}
|
||||
}
|
||||
return def
|
||||
|
||||
@@ -4,41 +4,37 @@ import "os"
|
||||
|
||||
func deleteOnClose(f *os.File) {}
|
||||
|
||||
func (l *vfsFileLocker) LockState() vfsLockState {
|
||||
return l.state
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockShared() xErrorCode {
|
||||
func (l *vfsFileLocker) GetShared() ExtendedErrorCode {
|
||||
l.state = _SHARED_LOCK
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockReserved() xErrorCode {
|
||||
func (l *vfsFileLocker) GetReserved() ExtendedErrorCode {
|
||||
l.state = _RESERVED_LOCK
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockPending() xErrorCode {
|
||||
func (l *vfsFileLocker) GetPending() ExtendedErrorCode {
|
||||
l.state = _PENDING_LOCK
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) LockExclusive() xErrorCode {
|
||||
func (l *vfsFileLocker) GetExclusive() ExtendedErrorCode {
|
||||
l.state = _EXCLUSIVE_LOCK
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) DowngradeLock() xErrorCode {
|
||||
func (l *vfsFileLocker) Downgrade() ExtendedErrorCode {
|
||||
l.state = _SHARED_LOCK
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) Unlock() xErrorCode {
|
||||
func (l *vfsFileLocker) Release() ExtendedErrorCode {
|
||||
l.state = _NO_LOCK
|
||||
return _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
|
||||
func (l *vfsFileLocker) CheckReserved() (bool, ExtendedErrorCode) {
|
||||
if l.state >= _RESERVED_LOCK {
|
||||
return true, _OK
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user