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 @@ -coverage: 66.6%coverage66.6% \ No newline at end of file +coverage: 71.4%coverage71.4% \ 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 }