diff --git a/.github/coverage.html b/.github/coverage.html
index 1ad4ddd..efe446a 100644
--- a/.github/coverage.html
+++ b/.github/coverage.html
@@ -67,13 +67,13 @@
-
+
-
+
-
+
-
+
@@ -784,6 +784,7 @@ import (
"math/rand"
"os"
"path/filepath"
+ "runtime"
"syscall"
"time"
@@ -907,7 +908,7 @@ func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32)
if err != nil {
return uint32(IOERR_DELETE)
}
- if syncDir != 0 {
+ if runtime.GOOS != "windows" && syncDir != 0 {
f, err := os.Open(filepath.Dir(path))
if err == nil {
err = f.Sync()
@@ -1025,7 +1026,7 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfs
file := vfsFilePtr{mod, pFile}.OSFile()
n, err := file.ReadAt(buf, int64(iOfst))
- if n == int(iAmt) {
+ if n == int(iAmt) {
return _OK
}
if n == 0 && err != io.EOF {
@@ -1110,10 +1111,10 @@ func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 {
- if of == nil {
+ if of == nil {
continue
}
- if os.SameFile(info, of.info) {
+ if os.SameFile(info, of.info) {
of.nref++
_ = file.Close()
return uint32(id)
@@ -1130,7 +1131,7 @@ func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 {
- if ptr == nil {
+ if ptr == nil {
vfsOpenFiles[id] = of
return uint32(id)
}
@@ -1147,7 +1148,7 @@ func vfsReleaseOpenFile(id uint32) error {
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[id]
- if of.nref--; of.nref > 0 {
+ if of.nref--; of.nref > 0 {
return nil
}
err := of.file.Close()
@@ -1294,10 +1295,10 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
fLock := of.LockState()
// 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 && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) {
return uint32(BUSY)
}
- if eLock == _EXCLUSIVE_LOCK && of.shared > 1 {
+ 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.
return uint32(BUSY)
@@ -1305,11 +1306,11 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
// 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 eLock == _SHARED_LOCK && (fLock == _SHARED_LOCK || fLock == _RESERVED_LOCK) {
if assert && !(cLock == _NO_LOCK && of.shared > 0) {
panic(assertErr + " [k7coz6]")
}
- ptr.SetLock(_SHARED_LOCK)
+ ptr.SetLock(_SHARED_LOCK)
of.shared++
return _OK
}
@@ -1363,7 +1364,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
cLock := ptr.Lock()
// If we don't have a more restrictive lock, do nothing.
- if cLock <= eLock {
+ if cLock <= eLock {
return _OK
}
@@ -1381,12 +1382,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
}
if eLock == _SHARED_LOCK {
if rc := of.DowngradeLock(); rc != _OK {
- // In theory, the downgrade to a SHARED cannot fail because another
- // process is holding an incompatible lock. If it does, this
- // indicates that the other process is not following the locking
- // protocol. If this happens, return IOERR_RDLOCK. Returning
- // BUSY would confuse the upper layer.
- return uint32(IOERR_RDLOCK)
+ return uint32(rc)
}
ptr.SetLock(_SHARED_LOCK)
return _OK
@@ -1399,14 +1395,14 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
// Decrement the shared lock counter. Release the file lock
// only when all connections have released the lock.
switch {
- case of.shared > 1:
+ case of.shared > 1:
ptr.SetLock(_NO_LOCK)
of.shared--
return _OK
case of.shared == 1:
if rc := of.Unlock(); rc != _OK {
- return uint32(IOERR_UNLOCK)
+ return uint32(rc)
}
ptr.SetLock(_NO_LOCK)
of.shared = 0
@@ -1417,7 +1413,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
}
}
-func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
+func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
@@ -1425,7 +1421,7 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
panic(assertErr + " [zarygt]")
}
- vfsOpenFilesMtx.Lock()
+ vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
@@ -1434,11 +1430,11 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
return uint32(IOERR_CHECKRESERVEDLOCK)
}
- var res uint32
- if locked {
+ var res uint32
+ if locked {
res = 1
}
- memory{mod}.writeUint32(pResOut, res)
+ memory{mod}.writeUint32(pResOut, res)
return _OK
}
@@ -1467,30 +1463,30 @@ func (l *vfsFileLocker) LockShared() xErrorCode {
}
// A PENDING lock is needed before acquiring a SHARED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _PENDING_BYTE,
Len: 1,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
// Acquire the SHARED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _SHARED_LOCK
// Relese the PENDING lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: _PENDING_BYTE,
Len: 1,
- }) {
+ }); err != nil {
return IOERR_UNLOCK
}
@@ -1503,12 +1499,12 @@ func (l *vfsFileLocker) LockReserved() xErrorCode {
}
// Acquire the RESERVED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _RESERVED_BYTE,
Len: 1,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _RESERVED_LOCK
return _OK
@@ -1520,12 +1516,12 @@ func (l *vfsFileLocker) LockPending() xErrorCode {
}
// Acquire the PENDING lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _PENDING_BYTE,
Len: 1,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _PENDING_LOCK
return _OK
@@ -1537,12 +1533,12 @@ func (l *vfsFileLocker) LockExclusive() xErrorCode
}
// Acquire the EXCLUSIVE lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _EXCLUSIVE_LOCK
return _OK
@@ -1554,21 +1550,26 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode
}
// Downgrade to a SHARED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
- }) {
+ }); err != nil {
+ // In theory, the downgrade to a SHARED cannot fail because another
+ // process is holding an incompatible lock. If it does, this
+ // indicates that the other process is not following the locking
+ // protocol. If this happens, return IOERR_RDLOCK. Returning
+ // BUSY would confuse the upper layer.
return IOERR_RDLOCK
}
l.state = _SHARED_LOCK
// Release the PENDING and RESERVED locks.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: _PENDING_BYTE,
Len: 2,
- }) {
+ }); err != nil {
return IOERR_UNLOCK
}
return _OK
@@ -1576,43 +1577,61 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode
func (l *vfsFileLocker) Unlock() xErrorCode {
// Release all locks.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
- }) {
+ }); err != nil {
return IOERR_UNLOCK
}
l.state = _NO_LOCK
return _OK
}
-func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
- if l.state >= _RESERVED_LOCK {
+func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
+ if l.state >= _RESERVED_LOCK {
return true, _OK
}
// Test all write locks.
lock := syscall.Flock_t{
Type: syscall.F_RDLCK,
}
- if !l.fcntlGetLock(&lock) {
+ if l.fcntlGetLock(&lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type == syscall.F_UNLCK, _OK
}
-func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) bool {
+func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error {
F_GETLK := syscall.F_GETLK
if runtime.GOOS == "linux" {
F_GETLK = 36 // F_OFD_GETLK
}
- return syscall.FcntlFlock(l.Fd(), F_GETLK, lock) == nil
+ return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)
}
-func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) bool {
+func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
F_SETLK := syscall.F_SETLK
if runtime.GOOS == "linux" {
F_SETLK = 37 // F_OFD_SETLK
}
- return syscall.FcntlFlock(l.Fd(), F_SETLK, lock) == nil
+ return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)
+}
+
+func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
+ if errno, ok := err.(syscall.Errno); ok {
+ switch errno {
+ case syscall.EACCES:
+ case syscall.EAGAIN:
+ case syscall.EBUSY:
+ case syscall.EINTR:
+ case syscall.ENOLCK:
+ case syscall.EDEADLK:
+ case syscall.ETIMEDOUT:
+ return xErrorCode(BUSY)
+ case syscall.EPERM:
+ return xErrorCode(PERM)
+ }
+ }
+ return def
}
diff --git a/.github/coverage.sh b/.github/coverage.sh
index fe8c52e..97d3ac7 100755
--- a/.github/coverage.sh
+++ b/.github/coverage.sh
@@ -5,15 +5,14 @@ SCRIPT_DIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
go test ./... -coverprofile "$SCRIPT_DIR/coverage.out"
go tool cover -html="$SCRIPT_DIR/coverage.out" -o "$SCRIPT_DIR/coverage.html"
-COVERAGE=$(go tool cover -func="$SCRIPT_DIR/coverage.out" | grep total: | grep -Eo '[0-9]+\.[0-9]+')
+COVERAGE=$(go tool cover -func="$SCRIPT_DIR/coverage.out" | tail -1 | grep -Eo '\d+\.\d')
-echo
echo "coverage: $COVERAGE% of statements"
COLOR=orange
-if (( $(echo "$COVERAGE <= 50" | bc -l) )) ; then
+if awk "BEGIN {exit !($COVERAGE <= 50)}"; then
COLOR=red
-elif (( $(echo "$COVERAGE > 80" | bc -l) )); then
+elif awk "BEGIN {exit !($COVERAGE > 80)}"; then
COLOR=green
fi
curl -s "https://img.shields.io/badge/coverage-$COVERAGE%25-$COLOR" > "$SCRIPT_DIR/coverage.svg"
diff --git a/.github/coverage.svg b/.github/coverage.svg
index f9c5b0f..ea2a384 100644
--- a/.github/coverage.svg
+++ b/.github/coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 4fc635a..2268976 100644
--- a/go.mod
+++ b/go.mod
@@ -6,3 +6,5 @@ require (
github.com/ncruces/julianday v0.1.3
github.com/tetratelabs/wazero v1.0.0-pre.7.0.20230117161130-15889085a5ca
)
+
+require golang.org/x/sync v0.1.0
diff --git a/go.sum b/go.sum
index d53e1ee..91d0ade 100644
--- a/go.sum
+++ b/go.sum
@@ -2,3 +2,5 @@ github.com/ncruces/julianday v0.1.3 h1:+JRqsbdGH8QlPWlB6i/pD8sENBZZ1qg24qBCAcYfG
github.com/ncruces/julianday v0.1.3/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.0.0-pre.7.0.20230117161130-15889085a5ca h1:cvbRnzLE99QnA6HT3yoqXjWEB2JMiyTs3GSLpQZg45A=
github.com/tetratelabs/wazero v1.0.0-pre.7.0.20230117161130-15889085a5ca/go.mod h1:u8wrFmpdrykiFK0DFPiFm5a4+0RzsdmXYVtijBKqUVo=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/sqlite3_test.go b/sqlite3_test.go
index 338cf80..fc455fb 100644
--- a/sqlite3_test.go
+++ b/sqlite3_test.go
@@ -5,43 +5,29 @@ import (
"os"
"path/filepath"
"testing"
+ "time"
+
+ "golang.org/x/sync/errgroup"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
)
-func TestOpen_memory(t *testing.T) {
- testOpen(t, ":memory:")
+func TestDB_memory(t *testing.T) {
+ testDB(t, ":memory:")
}
-func TestOpen_file(t *testing.T) {
+func TestDB_file(t *testing.T) {
dir, err := os.MkdirTemp("", "sqlite3-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
- testOpen(t, filepath.Join(dir, "test.db"))
+ testDB(t, filepath.Join(dir, "test.db"))
}
-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)
- }
-}
-
-func testOpen(t *testing.T, name string) {
+func testDB(t *testing.T, name string) {
db, err := sqlite3.Open(name)
if err != nil {
t.Fatal(err)
@@ -63,23 +49,22 @@ func testOpen(t *testing.T, name string) {
t.Fatal(err)
}
+ row := 0
ids := []int{0, 1, 2}
names := []string{"go", "zig", "whatever"}
-
- idx := 0
- for ; stmt.Step(); idx++ {
- if ids[idx] != stmt.ColumnInt(0) {
- t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[idx])
+ for ; stmt.Step(); row++ {
+ if ids[row] != stmt.ColumnInt(0) {
+ t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[row])
}
- if names[idx] != stmt.ColumnText(1) {
- t.Errorf("got %q, want %q", stmt.ColumnText(1), names[idx])
+ 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 idx != 3 {
- t.Errorf("got %d rows, want %d", idx, len(ids))
+ if row != 3 {
+ t.Errorf("got %d rows, want %d", row, len(ids))
}
err = stmt.Close()
@@ -92,3 +77,115 @@ func testOpen(t *testing.T, name string) {
t.Fatal(err)
}
}
+
+func TestDB_parallel(t *testing.T) {
+ dir, err := os.MkdirTemp("", "sqlite3-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ writer := func() error {
+ db, err := sqlite3.Open(filepath.Join(dir, "test.db"))
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ err = db.Exec(`
+ PRAGMA locking_mode = NORMAL;
+ PRAGMA busy_timeout = 1000;
+ `)
+ if err != nil {
+ return err
+ }
+
+ 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)
+ }
+
+ return db.Close()
+ }
+
+ reader := func() error {
+ db, err := sqlite3.Open(filepath.Join(dir, "test.db"))
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ err = db.Exec(`
+ PRAGMA locking_mode = NORMAL;
+ PRAGMA busy_timeout = 1000;
+ `)
+ if err != nil {
+ return err
+ }
+
+ stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
+ if err != nil {
+ return err
+ }
+
+ row := 0
+ for stmt.Step() {
+ row++
+ }
+ if err := stmt.Err(); err != nil {
+ return err
+ }
+ if row%3 != 0 {
+ t.Errorf("got %d rows, want multiple of 3", row)
+ }
+
+ err = stmt.Close()
+ if err != nil {
+ return err
+ }
+
+ return db.Close()
+ }
+
+ err = writer()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var group errgroup.Group
+ group.SetLimit(4)
+ for i := 0; i < 32; i++ {
+ if i&7 != 7 {
+ group.Go(reader)
+ } else {
+ group.Go(writer)
+ }
+ time.Sleep(time.Microsecond)
+ }
+ err = group.Wait()
+ if err != nil {
+ 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)
+ }
+}
diff --git a/vfs_lock.go b/vfs_lock.go
index 9aff705..0bcbcb0 100644
--- a/vfs_lock.go
+++ b/vfs_lock.go
@@ -193,12 +193,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
}
if eLock == _SHARED_LOCK {
if rc := of.DowngradeLock(); rc != _OK {
- // In theory, the downgrade to a SHARED cannot fail because another
- // process is holding an incompatible lock. If it does, this
- // indicates that the other process is not following the locking
- // protocol. If this happens, return IOERR_RDLOCK. Returning
- // BUSY would confuse the upper layer.
- return uint32(IOERR_RDLOCK)
+ return uint32(rc)
}
ptr.SetLock(_SHARED_LOCK)
return _OK
@@ -218,7 +213,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
case of.shared == 1:
if rc := of.Unlock(); rc != _OK {
- return uint32(IOERR_UNLOCK)
+ return uint32(rc)
}
ptr.SetLock(_NO_LOCK)
of.shared = 0
diff --git a/vfs_unix.go b/vfs_unix.go
index 905ca53..0c04663 100644
--- a/vfs_unix.go
+++ b/vfs_unix.go
@@ -22,30 +22,30 @@ func (l *vfsFileLocker) LockShared() xErrorCode {
}
// A PENDING lock is needed before acquiring a SHARED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _PENDING_BYTE,
Len: 1,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
// Acquire the SHARED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _SHARED_LOCK
// Relese the PENDING lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: _PENDING_BYTE,
Len: 1,
- }) {
+ }); err != nil {
return IOERR_UNLOCK
}
@@ -58,12 +58,12 @@ func (l *vfsFileLocker) LockReserved() xErrorCode {
}
// Acquire the RESERVED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _RESERVED_BYTE,
Len: 1,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _RESERVED_LOCK
return _OK
@@ -75,12 +75,12 @@ func (l *vfsFileLocker) LockPending() xErrorCode {
}
// Acquire the PENDING lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _PENDING_BYTE,
Len: 1,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _PENDING_LOCK
return _OK
@@ -92,12 +92,12 @@ func (l *vfsFileLocker) LockExclusive() xErrorCode {
}
// Acquire the EXCLUSIVE lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
- }) {
- return IOERR_LOCK
+ }); err != nil {
+ return l.errorCode(err, IOERR_LOCK)
}
l.state = _EXCLUSIVE_LOCK
return _OK
@@ -109,21 +109,26 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode {
}
// Downgrade to a SHARED lock.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: _SHARED_FIRST,
Len: _SHARED_SIZE,
- }) {
+ }); err != nil {
+ // In theory, the downgrade to a SHARED cannot fail because another
+ // process is holding an incompatible lock. If it does, this
+ // indicates that the other process is not following the locking
+ // protocol. If this happens, return IOERR_RDLOCK. Returning
+ // BUSY would confuse the upper layer.
return IOERR_RDLOCK
}
l.state = _SHARED_LOCK
// Release the PENDING and RESERVED locks.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: _PENDING_BYTE,
Len: 2,
- }) {
+ }); err != nil {
return IOERR_UNLOCK
}
return _OK
@@ -131,9 +136,9 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode {
func (l *vfsFileLocker) Unlock() xErrorCode {
// Release all locks.
- if !l.fcntlSetLock(&syscall.Flock_t{
+ if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
- }) {
+ }); err != nil {
return IOERR_UNLOCK
}
l.state = _NO_LOCK
@@ -148,24 +153,42 @@ func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
lock := syscall.Flock_t{
Type: syscall.F_RDLCK,
}
- if !l.fcntlGetLock(&lock) {
+ if l.fcntlGetLock(&lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type == syscall.F_UNLCK, _OK
}
-func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) bool {
+func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error {
F_GETLK := syscall.F_GETLK
if runtime.GOOS == "linux" {
F_GETLK = 36 // F_OFD_GETLK
}
- return syscall.FcntlFlock(l.Fd(), F_GETLK, lock) == nil
+ return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)
}
-func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) bool {
+func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
F_SETLK := syscall.F_SETLK
if runtime.GOOS == "linux" {
F_SETLK = 37 // F_OFD_SETLK
}
- return syscall.FcntlFlock(l.Fd(), F_SETLK, lock) == nil
+ return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)
+}
+
+func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
+ if errno, ok := err.(syscall.Errno); ok {
+ switch errno {
+ case syscall.EACCES:
+ case syscall.EAGAIN:
+ case syscall.EBUSY:
+ case syscall.EINTR:
+ case syscall.ENOLCK:
+ case syscall.EDEADLK:
+ case syscall.ETIMEDOUT:
+ return xErrorCode(BUSY)
+ case syscall.EPERM:
+ return xErrorCode(PERM)
+ }
+ }
+ return def
}