mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-21 19:39:43 +01:00
optionally protect against multiple starts with flock (#1873)
* optionally protect against multiple starts with flock Fixes #1823 * use traditional .lock extension * move config key to top level
This commit is contained in:
parent
e112a78b9b
commit
ed75533cb1
@ -746,6 +746,12 @@ debug:
|
||||
# set to `null`, "", leave blank, or omit to disable
|
||||
# pprof-listener: "localhost:6060"
|
||||
|
||||
# lock file preventing multiple instances of Ergo from accidentally being
|
||||
# started at once. comment out or set to the empty string ("") to disable.
|
||||
# this path is relative to the working directory; you may want to use an
|
||||
# absolute path instead:
|
||||
lock-file: "ircd.lock"
|
||||
|
||||
# datastore configuration
|
||||
datastore:
|
||||
# path to the datastore
|
||||
|
2
go.mod
2
go.mod
@ -25,6 +25,8 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require github.com/gofrs/flock v0.8.1
|
||||
|
||||
require (
|
||||
github.com/tidwall/btree v0.6.1 // indirect
|
||||
github.com/tidwall/gjson v1.10.2 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -21,6 +21,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
|
||||
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -616,6 +616,8 @@ type Config struct {
|
||||
|
||||
languageManager *languages.Manager
|
||||
|
||||
LockFile string `yaml:"lock-file"`
|
||||
|
||||
Datastore struct {
|
||||
Path string
|
||||
AutoUpgrade bool
|
||||
|
24
irc/flock/flock.go
Normal file
24
irc/flock/flock.go
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build !plan9
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
)
|
||||
|
||||
var (
|
||||
CouldntAcquire = errors.New("Couldn't acquire flock (is another Ergo running?)")
|
||||
)
|
||||
|
||||
func TryAcquireFlock(path string) (fl Flocker, err error) {
|
||||
f := flock.New(path)
|
||||
success, err := f.TryLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !success {
|
||||
return nil, CouldntAcquire
|
||||
}
|
||||
return f, nil
|
||||
}
|
14
irc/flock/flock_iface.go
Normal file
14
irc/flock/flock_iface.go
Normal file
@ -0,0 +1,14 @@
|
||||
package flock
|
||||
|
||||
// documentation for github.com/gofrs/flock incorrectly claims that
|
||||
// Flock implements sync.Locker; it does not because the Unlock method
|
||||
// has a return type (err).
|
||||
type Flocker interface {
|
||||
Unlock() error
|
||||
}
|
||||
|
||||
type noopFlocker struct{}
|
||||
|
||||
func (n *noopFlocker) Unlock() error {
|
||||
return nil
|
||||
}
|
7
irc/flock/flock_plan9.go
Normal file
7
irc/flock/flock_plan9.go
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build plan9
|
||||
|
||||
package flock
|
||||
|
||||
func TryAcquireFlock(path string) (fl Flocker, err error) {
|
||||
return &noopFlocker{}, nil
|
||||
}
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/ergochat/ergo/irc/caps"
|
||||
"github.com/ergochat/ergo/irc/connection_limits"
|
||||
"github.com/ergochat/ergo/irc/flatip"
|
||||
"github.com/ergochat/ergo/irc/flock"
|
||||
"github.com/ergochat/ergo/irc/history"
|
||||
"github.com/ergochat/ergo/irc/logger"
|
||||
"github.com/ergochat/ergo/irc/modes"
|
||||
@ -88,6 +89,7 @@ type Server struct {
|
||||
whoWas WhoWasList
|
||||
stats Stats
|
||||
semaphores ServerSemaphores
|
||||
flock flock.Flocker
|
||||
defcon uint32
|
||||
}
|
||||
|
||||
@ -585,6 +587,19 @@ func (server *Server) applyConfig(config *Config) (err error) {
|
||||
|
||||
server.logger.Info("server", "Using config file", server.configFilename)
|
||||
|
||||
if initial {
|
||||
if config.LockFile != "" {
|
||||
server.flock, err = flock.TryAcquireFlock(config.LockFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to acquire flock on %s: %w",
|
||||
config.LockFile, err)
|
||||
}
|
||||
}
|
||||
// the lock is never released until quit; we need to save a pointer
|
||||
// to the (*flock.Flock) object so it doesn't get GC'ed, which would
|
||||
// close the file and surrender the lock
|
||||
}
|
||||
|
||||
// first, reload config sections for functionality implemented in subpackages:
|
||||
wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO()
|
||||
err = server.logger.ApplyConfig(config.Logging)
|
||||
|
@ -718,6 +718,12 @@ debug:
|
||||
# set to `null`, "", leave blank, or omit to disable
|
||||
# pprof-listener: "localhost:6060"
|
||||
|
||||
# lock file preventing multiple instances of Ergo from accidentally being
|
||||
# started at once. comment out or set to the empty string ("") to disable.
|
||||
# this path is relative to the working directory; you may want to use an
|
||||
# absolute path instead:
|
||||
lock-file: "ircd.lock"
|
||||
|
||||
# datastore configuration
|
||||
datastore:
|
||||
# path to the datastore
|
||||
|
24
vendor/github.com/gofrs/flock/.gitignore
generated
vendored
Normal file
24
vendor/github.com/gofrs/flock/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
10
vendor/github.com/gofrs/flock/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/gofrs/flock/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
script: go test -v -check.vv -race ./...
|
||||
sudo: false
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
27
vendor/github.com/gofrs/flock/LICENSE
generated
vendored
Normal file
27
vendor/github.com/gofrs/flock/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2015-2020, Tim Heckman
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of gofrs nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
41
vendor/github.com/gofrs/flock/README.md
generated
vendored
Normal file
41
vendor/github.com/gofrs/flock/README.md
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# flock
|
||||
[![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock)
|
||||
[![GoDoc](https://img.shields.io/badge/godoc-flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock)
|
||||
[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock)
|
||||
|
||||
`flock` implements a thread-safe sync.Locker interface for file locking. It also
|
||||
includes a non-blocking TryLock() function to allow locking without blocking execution.
|
||||
|
||||
## License
|
||||
`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details.
|
||||
|
||||
## Go Compatibility
|
||||
This package makes use of the `context` package that was introduced in Go 1.7. As such, this
|
||||
package has an implicit dependency on Go 1.7+.
|
||||
|
||||
## Installation
|
||||
```
|
||||
go get -u github.com/gofrs/flock
|
||||
```
|
||||
|
||||
## Usage
|
||||
```Go
|
||||
import "github.com/gofrs/flock"
|
||||
|
||||
fileLock := flock.New("/var/lock/go-lock.lock")
|
||||
|
||||
locked, err := fileLock.TryLock()
|
||||
|
||||
if err != nil {
|
||||
// handle locking error
|
||||
}
|
||||
|
||||
if locked {
|
||||
// do work
|
||||
fileLock.Unlock()
|
||||
}
|
||||
```
|
||||
|
||||
For more detailed usage information take a look at the package API docs on
|
||||
[GoDoc](https://godoc.org/github.com/gofrs/flock).
|
25
vendor/github.com/gofrs/flock/appveyor.yml
generated
vendored
Normal file
25
vendor/github.com/gofrs/flock/appveyor.yml
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
version: '{build}'
|
||||
|
||||
build: false
|
||||
deploy: false
|
||||
|
||||
clone_folder: 'c:\gopath\src\github.com\gofrs\flock'
|
||||
|
||||
environment:
|
||||
GOPATH: 'c:\gopath'
|
||||
GOVERSION: '1.15'
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- rmdir c:\go /s /q
|
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi
|
||||
- msiexec /i go%GOVERSION%.windows-amd64.msi /q
|
||||
- set Path=c:\go\bin;c:\gopath\bin;%Path%
|
||||
- go version
|
||||
- go env
|
||||
|
||||
test_script:
|
||||
- go get -t ./...
|
||||
- go test -race -v ./...
|
144
vendor/github.com/gofrs/flock/flock.go
generated
vendored
Normal file
144
vendor/github.com/gofrs/flock/flock.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package flock implements a thread-safe interface for file locking.
|
||||
// It also includes a non-blocking TryLock() function to allow locking
|
||||
// without blocking execution.
|
||||
//
|
||||
// Package flock is released under the BSD 3-Clause License. See the LICENSE file
|
||||
// for more details.
|
||||
//
|
||||
// While using this library, remember that the locking behaviors are not
|
||||
// guaranteed to be the same on each platform. For example, some UNIX-like
|
||||
// operating systems will transparently convert a shared lock to an exclusive
|
||||
// lock. If you Unlock() the flock from a location where you believe that you
|
||||
// have the shared lock, you may accidentally drop the exclusive lock.
|
||||
package flock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Flock is the struct type to handle file locking. All fields are unexported,
|
||||
// with access to some of the fields provided by getter methods (Path() and Locked()).
|
||||
type Flock struct {
|
||||
path string
|
||||
m sync.RWMutex
|
||||
fh *os.File
|
||||
l bool
|
||||
r bool
|
||||
}
|
||||
|
||||
// New returns a new instance of *Flock. The only parameter
|
||||
// it takes is the path to the desired lockfile.
|
||||
func New(path string) *Flock {
|
||||
return &Flock{path: path}
|
||||
}
|
||||
|
||||
// NewFlock returns a new instance of *Flock. The only parameter
|
||||
// it takes is the path to the desired lockfile.
|
||||
//
|
||||
// Deprecated: Use New instead.
|
||||
func NewFlock(path string) *Flock {
|
||||
return New(path)
|
||||
}
|
||||
|
||||
// Close is equivalent to calling Unlock.
|
||||
//
|
||||
// This will release the lock and close the underlying file descriptor.
|
||||
// It will not remove the file from disk, that's up to your application.
|
||||
func (f *Flock) Close() error {
|
||||
return f.Unlock()
|
||||
}
|
||||
|
||||
// Path returns the path as provided in NewFlock().
|
||||
func (f *Flock) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// Locked returns the lock state (locked: true, unlocked: false).
|
||||
//
|
||||
// Warning: by the time you use the returned value, the state may have changed.
|
||||
func (f *Flock) Locked() bool {
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
return f.l
|
||||
}
|
||||
|
||||
// RLocked returns the read lock state (locked: true, unlocked: false).
|
||||
//
|
||||
// Warning: by the time you use the returned value, the state may have changed.
|
||||
func (f *Flock) RLocked() bool {
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
return f.r
|
||||
}
|
||||
|
||||
func (f *Flock) String() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// TryLockContext repeatedly tries to take an exclusive lock until one of the
|
||||
// conditions is met: TryLock succeeds, TryLock fails with error, or Context
|
||||
// Done channel is closed.
|
||||
func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
|
||||
return tryCtx(ctx, f.TryLock, retryDelay)
|
||||
}
|
||||
|
||||
// TryRLockContext repeatedly tries to take a shared lock until one of the
|
||||
// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context
|
||||
// Done channel is closed.
|
||||
func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
|
||||
return tryCtx(ctx, f.TryRLock, retryDelay)
|
||||
}
|
||||
|
||||
func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) {
|
||||
if ctx.Err() != nil {
|
||||
return false, ctx.Err()
|
||||
}
|
||||
for {
|
||||
if ok, err := fn(); ok || err != nil {
|
||||
return ok, err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
case <-time.After(retryDelay):
|
||||
// try again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flock) setFh() error {
|
||||
// open a new os.File instance
|
||||
// create it if it doesn't exist, and open the file read-only.
|
||||
flags := os.O_CREATE
|
||||
if runtime.GOOS == "aix" {
|
||||
// AIX cannot preform write-lock (ie exclusive) on a
|
||||
// read-only file.
|
||||
flags |= os.O_RDWR
|
||||
} else {
|
||||
flags |= os.O_RDONLY
|
||||
}
|
||||
fh, err := os.OpenFile(f.path, flags, os.FileMode(0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the filehandle on the struct
|
||||
f.fh = fh
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure the file handle is closed if no lock is held
|
||||
func (f *Flock) ensureFhState() {
|
||||
if !f.l && !f.r && f.fh != nil {
|
||||
f.fh.Close()
|
||||
f.fh = nil
|
||||
}
|
||||
}
|
281
vendor/github.com/gofrs/flock/flock_aix.go
generated
vendored
Normal file
281
vendor/github.com/gofrs/flock/flock_aix.go
generated
vendored
Normal file
@ -0,0 +1,281 @@
|
||||
// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is
|
||||
// governed by the BSD 3-Clause license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This code implements the filelock API using POSIX 'fcntl' locks, which attach
|
||||
// to an (inode, process) pair rather than a file descriptor. To avoid unlocking
|
||||
// files prematurely when the same file is opened through different descriptors,
|
||||
// we allow only one read-lock at a time.
|
||||
//
|
||||
// This code is adapted from the Go package:
|
||||
// cmd/go/internal/lockedfile/internal/filelock
|
||||
|
||||
//+build aix
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type lockType int16
|
||||
|
||||
const (
|
||||
readLock lockType = unix.F_RDLCK
|
||||
writeLock lockType = unix.F_WRLCK
|
||||
)
|
||||
|
||||
type cmdType int
|
||||
|
||||
const (
|
||||
tryLock cmdType = unix.F_SETLK
|
||||
waitLock cmdType = unix.F_SETLKW
|
||||
)
|
||||
|
||||
type inode = uint64
|
||||
|
||||
type inodeLock struct {
|
||||
owner *Flock
|
||||
queue []<-chan *Flock
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
inodes = map[*Flock]inode{}
|
||||
locks = map[inode]inodeLock{}
|
||||
)
|
||||
|
||||
// Lock is a blocking call to try and take an exclusive file lock. It will wait
|
||||
// until it is able to obtain the exclusive file lock. It's recommended that
|
||||
// TryLock() be used over this function. This function may block the ability to
|
||||
// query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already exclusive-locked, this function short-circuits and returns
|
||||
// immediately assuming it can take the mutex lock.
|
||||
//
|
||||
// If the *Flock has a shared lock (RLock), this may transparently replace the
|
||||
// shared lock with an exclusive lock on some UNIX-like operating systems. Be
|
||||
// careful when using exclusive locks in conjunction with shared locks
|
||||
// (RLock()), because calling Unlock() may accidentally release the exclusive
|
||||
// lock that was once a shared lock.
|
||||
func (f *Flock) Lock() error {
|
||||
return f.lock(&f.l, writeLock)
|
||||
}
|
||||
|
||||
// RLock is a blocking call to try and take a shared file lock. It will wait
|
||||
// until it is able to obtain the shared file lock. It's recommended that
|
||||
// TryRLock() be used over this function. This function may block the ability to
|
||||
// query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already shared-locked, this function short-circuits and returns
|
||||
// immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) RLock() error {
|
||||
return f.lock(&f.r, readLock)
|
||||
}
|
||||
|
||||
func (f *Flock) lock(locked *bool, flag lockType) error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
if _, err := f.doLock(waitLock, flag, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*locked = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) {
|
||||
// POSIX locks apply per inode and process, and the lock for an inode is
|
||||
// released when *any* descriptor for that inode is closed. So we need to
|
||||
// synchronize access to each inode internally, and must serialize lock and
|
||||
// unlock calls that refer to the same inode through different descriptors.
|
||||
fi, err := f.fh.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ino := inode(fi.Sys().(*syscall.Stat_t).Ino)
|
||||
|
||||
mu.Lock()
|
||||
if i, dup := inodes[f]; dup && i != ino {
|
||||
mu.Unlock()
|
||||
return false, &os.PathError{
|
||||
Path: f.Path(),
|
||||
Err: errors.New("inode for file changed since last Lock or RLock"),
|
||||
}
|
||||
}
|
||||
|
||||
inodes[f] = ino
|
||||
|
||||
var wait chan *Flock
|
||||
l := locks[ino]
|
||||
if l.owner == f {
|
||||
// This file already owns the lock, but the call may change its lock type.
|
||||
} else if l.owner == nil {
|
||||
// No owner: it's ours now.
|
||||
l.owner = f
|
||||
} else if !blocking {
|
||||
// Already owned: cannot take the lock.
|
||||
mu.Unlock()
|
||||
return false, nil
|
||||
} else {
|
||||
// Already owned: add a channel to wait on.
|
||||
wait = make(chan *Flock)
|
||||
l.queue = append(l.queue, wait)
|
||||
}
|
||||
locks[ino] = l
|
||||
mu.Unlock()
|
||||
|
||||
if wait != nil {
|
||||
wait <- f
|
||||
}
|
||||
|
||||
err = setlkw(f.fh.Fd(), cmd, lt)
|
||||
|
||||
if err != nil {
|
||||
f.doUnlock()
|
||||
if cmd == tryLock && err == unix.EACCES {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *Flock) Unlock() error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
// if we aren't locked or if the lockfile instance is nil
|
||||
// just return a nil error because we are unlocked
|
||||
if (!f.l && !f.r) || f.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := f.doUnlock(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.fh.Close()
|
||||
|
||||
f.l = false
|
||||
f.r = false
|
||||
f.fh = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Flock) doUnlock() (err error) {
|
||||
var owner *Flock
|
||||
mu.Lock()
|
||||
ino, ok := inodes[f]
|
||||
if ok {
|
||||
owner = locks[ino].owner
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
if owner == f {
|
||||
err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
l := locks[ino]
|
||||
if len(l.queue) == 0 {
|
||||
// No waiters: remove the map entry.
|
||||
delete(locks, ino)
|
||||
} else {
|
||||
// The first waiter is sending us their file now.
|
||||
// Receive it and update the queue.
|
||||
l.owner = <-l.queue[0]
|
||||
l.queue = l.queue[1:]
|
||||
locks[ino] = l
|
||||
}
|
||||
delete(inodes, f)
|
||||
mu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// TryLock is the preferred function for taking an exclusive file lock. This
|
||||
// function takes an RW-mutex lock before it tries to lock the file, so there is
|
||||
// the possibility that this function may block for a short time if another
|
||||
// goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking. If we are unable to get the exclusive
|
||||
// file lock, the function will return false instead of waiting for the lock. If
|
||||
// we get the lock, we also set the *Flock instance as being exclusive-locked.
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return f.try(&f.l, writeLock)
|
||||
}
|
||||
|
||||
// TryRLock is the preferred function for taking a shared file lock. This
|
||||
// function takes an RW-mutex lock before it tries to lock the file, so there is
|
||||
// the possibility that this function may block for a short time if another
|
||||
// goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking. If we are unable to get the shared file
|
||||
// lock, the function will return false instead of waiting for the lock. If we
|
||||
// get the lock, we also set the *Flock instance as being share-locked.
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return f.try(&f.r, readLock)
|
||||
}
|
||||
|
||||
func (f *Flock) try(locked *bool, flag lockType) (bool, error) {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
haslock, err := f.doLock(tryLock, flag, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
*locked = haslock
|
||||
return haslock, nil
|
||||
}
|
||||
|
||||
// setlkw calls FcntlFlock with cmd for the entire file indicated by fd.
|
||||
func setlkw(fd uintptr, cmd cmdType, lt lockType) error {
|
||||
for {
|
||||
err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{
|
||||
Type: int16(lt),
|
||||
Whence: io.SeekStart,
|
||||
Start: 0,
|
||||
Len: 0, // All bytes.
|
||||
})
|
||||
if err != unix.EINTR {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
197
vendor/github.com/gofrs/flock/flock_unix.go
generated
vendored
Normal file
197
vendor/github.com/gofrs/flock/flock_unix.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !aix,!windows
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Lock is a blocking call to try and take an exclusive file lock. It will wait
|
||||
// until it is able to obtain the exclusive file lock. It's recommended that
|
||||
// TryLock() be used over this function. This function may block the ability to
|
||||
// query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already exclusive-locked, this function short-circuits and returns
|
||||
// immediately assuming it can take the mutex lock.
|
||||
//
|
||||
// If the *Flock has a shared lock (RLock), this may transparently replace the
|
||||
// shared lock with an exclusive lock on some UNIX-like operating systems. Be
|
||||
// careful when using exclusive locks in conjunction with shared locks
|
||||
// (RLock()), because calling Unlock() may accidentally release the exclusive
|
||||
// lock that was once a shared lock.
|
||||
func (f *Flock) Lock() error {
|
||||
return f.lock(&f.l, syscall.LOCK_EX)
|
||||
}
|
||||
|
||||
// RLock is a blocking call to try and take a shared file lock. It will wait
|
||||
// until it is able to obtain the shared file lock. It's recommended that
|
||||
// TryRLock() be used over this function. This function may block the ability to
|
||||
// query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already shared-locked, this function short-circuits and returns
|
||||
// immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) RLock() error {
|
||||
return f.lock(&f.r, syscall.LOCK_SH)
|
||||
}
|
||||
|
||||
func (f *Flock) lock(locked *bool, flag int) error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil {
|
||||
shouldRetry, reopenErr := f.reopenFDOnError(err)
|
||||
if reopenErr != nil {
|
||||
return reopenErr
|
||||
}
|
||||
|
||||
if !shouldRetry {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*locked = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
|
||||
// while it is running the Locked() and RLocked() functions will be blocked.
|
||||
//
|
||||
// This function short-circuits if we are unlocked already. If not, it calls
|
||||
// syscall.LOCK_UN on the file and closes the file descriptor. It does not
|
||||
// remove the file from disk. It's up to your application to do.
|
||||
//
|
||||
// Please note, if your shared lock became an exclusive lock this may
|
||||
// unintentionally drop the exclusive lock if called by the consumer that
|
||||
// believes they have a shared lock. Please see Lock() for more details.
|
||||
func (f *Flock) Unlock() error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
// if we aren't locked or if the lockfile instance is nil
|
||||
// just return a nil error because we are unlocked
|
||||
if (!f.l && !f.r) || f.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mark the file as unlocked
|
||||
if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.fh.Close()
|
||||
|
||||
f.l = false
|
||||
f.r = false
|
||||
f.fh = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLock is the preferred function for taking an exclusive file lock. This
|
||||
// function takes an RW-mutex lock before it tries to lock the file, so there is
|
||||
// the possibility that this function may block for a short time if another
|
||||
// goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking. If we are unable to get the exclusive
|
||||
// file lock, the function will return false instead of waiting for the lock. If
|
||||
// we get the lock, we also set the *Flock instance as being exclusive-locked.
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return f.try(&f.l, syscall.LOCK_EX)
|
||||
}
|
||||
|
||||
// TryRLock is the preferred function for taking a shared file lock. This
|
||||
// function takes an RW-mutex lock before it tries to lock the file, so there is
|
||||
// the possibility that this function may block for a short time if another
|
||||
// goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking. If we are unable to get the shared file
|
||||
// lock, the function will return false instead of waiting for the lock. If we
|
||||
// get the lock, we also set the *Flock instance as being share-locked.
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return f.try(&f.r, syscall.LOCK_SH)
|
||||
}
|
||||
|
||||
func (f *Flock) try(locked *bool, flag int) (bool, error) {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
var retried bool
|
||||
retry:
|
||||
err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB)
|
||||
|
||||
switch err {
|
||||
case syscall.EWOULDBLOCK:
|
||||
return false, nil
|
||||
case nil:
|
||||
*locked = true
|
||||
return true, nil
|
||||
}
|
||||
if !retried {
|
||||
if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil {
|
||||
return false, reopenErr
|
||||
} else if shouldRetry {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// reopenFDOnError determines whether we should reopen the file handle
|
||||
// in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c:
|
||||
// Since Linux 3.4 (commit 55725513)
|
||||
// Probably NFSv4 where flock() is emulated by fcntl().
|
||||
func (f *Flock) reopenFDOnError(err error) (bool, error) {
|
||||
if err != syscall.EIO && err != syscall.EBADF {
|
||||
return false, nil
|
||||
}
|
||||
if st, err := f.fh.Stat(); err == nil {
|
||||
// if the file is able to be read and written
|
||||
if st.Mode()&0600 == 0600 {
|
||||
f.fh.Close()
|
||||
f.fh = nil
|
||||
|
||||
// reopen in read-write mode and set the filehandle
|
||||
fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
f.fh = fh
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
76
vendor/github.com/gofrs/flock/flock_winapi.go
generated
vendored
Normal file
76
vendor/github.com/gofrs/flock/flock_winapi.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32, _ = syscall.LoadLibrary("kernel32.dll")
|
||||
procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx")
|
||||
procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx")
|
||||
)
|
||||
|
||||
const (
|
||||
winLockfileFailImmediately = 0x00000001
|
||||
winLockfileExclusiveLock = 0x00000002
|
||||
winLockfileSharedLock = 0x00000000
|
||||
)
|
||||
|
||||
// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows
|
||||
// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as:
|
||||
//
|
||||
// > The function requests an exclusive lock. Otherwise, it requests a shared
|
||||
// > lock.
|
||||
//
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
||||
|
||||
func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) {
|
||||
r1, _, errNo := syscall.Syscall6(
|
||||
uintptr(procLockFileEx),
|
||||
6,
|
||||
uintptr(handle),
|
||||
uintptr(flags),
|
||||
uintptr(reserved),
|
||||
uintptr(numberOfBytesToLockLow),
|
||||
uintptr(numberOfBytesToLockHigh),
|
||||
uintptr(unsafe.Pointer(offset)))
|
||||
|
||||
if r1 != 1 {
|
||||
if errNo == 0 {
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
|
||||
return false, errNo
|
||||
}
|
||||
|
||||
return true, 0
|
||||
}
|
||||
|
||||
func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) {
|
||||
r1, _, errNo := syscall.Syscall6(
|
||||
uintptr(procUnlockFileEx),
|
||||
5,
|
||||
uintptr(handle),
|
||||
uintptr(reserved),
|
||||
uintptr(numberOfBytesToLockLow),
|
||||
uintptr(numberOfBytesToLockHigh),
|
||||
uintptr(unsafe.Pointer(offset)),
|
||||
0)
|
||||
|
||||
if r1 != 1 {
|
||||
if errNo == 0 {
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
|
||||
return false, errNo
|
||||
}
|
||||
|
||||
return true, 0
|
||||
}
|
142
vendor/github.com/gofrs/flock/flock_windows.go
generated
vendored
Normal file
142
vendor/github.com/gofrs/flock/flock_windows.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ErrorLockViolation is the error code returned from the Windows syscall when a
|
||||
// lock would block and you ask to fail immediately.
|
||||
const ErrorLockViolation syscall.Errno = 0x21 // 33
|
||||
|
||||
// Lock is a blocking call to try and take an exclusive file lock. It will wait
|
||||
// until it is able to obtain the exclusive file lock. It's recommended that
|
||||
// TryLock() be used over this function. This function may block the ability to
|
||||
// query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already locked, this function short-circuits and returns
|
||||
// immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) Lock() error {
|
||||
return f.lock(&f.l, winLockfileExclusiveLock)
|
||||
}
|
||||
|
||||
// RLock is a blocking call to try and take a shared file lock. It will wait
|
||||
// until it is able to obtain the shared file lock. It's recommended that
|
||||
// TryRLock() be used over this function. This function may block the ability to
|
||||
// query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already locked, this function short-circuits and returns
|
||||
// immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) RLock() error {
|
||||
return f.lock(&f.r, winLockfileSharedLock)
|
||||
}
|
||||
|
||||
func (f *Flock) lock(locked *bool, flag uint32) error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 {
|
||||
return errNo
|
||||
}
|
||||
|
||||
*locked = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
|
||||
// while it is running the Locked() and RLocked() functions will be blocked.
|
||||
//
|
||||
// This function short-circuits if we are unlocked already. If not, it calls
|
||||
// UnlockFileEx() on the file and closes the file descriptor. It does not remove
|
||||
// the file from disk. It's up to your application to do.
|
||||
func (f *Flock) Unlock() error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
// if we aren't locked or if the lockfile instance is nil
|
||||
// just return a nil error because we are unlocked
|
||||
if (!f.l && !f.r) || f.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mark the file as unlocked
|
||||
if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 {
|
||||
return errNo
|
||||
}
|
||||
|
||||
f.fh.Close()
|
||||
|
||||
f.l = false
|
||||
f.r = false
|
||||
f.fh = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLock is the preferred function for taking an exclusive file lock. This
|
||||
// function does take a RW-mutex lock before it tries to lock the file, so there
|
||||
// is the possibility that this function may block for a short time if another
|
||||
// goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking. If we are unable to get the exclusive
|
||||
// file lock, the function will return false instead of waiting for the lock. If
|
||||
// we get the lock, we also set the *Flock instance as being exclusive-locked.
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return f.try(&f.l, winLockfileExclusiveLock)
|
||||
}
|
||||
|
||||
// TryRLock is the preferred function for taking a shared file lock. This
|
||||
// function does take a RW-mutex lock before it tries to lock the file, so there
|
||||
// is the possibility that this function may block for a short time if another
|
||||
// goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking. If we are unable to get the shared file
|
||||
// lock, the function will return false instead of waiting for the lock. If we
|
||||
// get the lock, we also set the *Flock instance as being shared-locked.
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return f.try(&f.r, winLockfileSharedLock)
|
||||
}
|
||||
|
||||
func (f *Flock) try(locked *bool, flag uint32) (bool, error) {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
_, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{})
|
||||
|
||||
if errNo > 0 {
|
||||
if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errNo
|
||||
}
|
||||
|
||||
*locked = true
|
||||
|
||||
return true, nil
|
||||
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@ -27,6 +27,9 @@ github.com/ergochat/irc-go/ircutils
|
||||
github.com/go-sql-driver/mysql
|
||||
# github.com/go-test/deep v1.0.6
|
||||
## explicit; go 1.13
|
||||
# github.com/gofrs/flock v0.8.1
|
||||
## explicit
|
||||
github.com/gofrs/flock
|
||||
# github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
## explicit
|
||||
github.com/golang-jwt/jwt
|
||||
|
Loading…
Reference in New Issue
Block a user